initial project code commit
This commit is contained in:
BIN
artpipeline/.DS_Store
vendored
Normal file
BIN
artpipeline/.DS_Store
vendored
Normal file
Binary file not shown.
6
artpipeline/pyproject.toml
Normal file
6
artpipeline/pyproject.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
17
artpipeline/setup.py
Normal file
17
artpipeline/setup.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from setuptools import setup
|
||||
|
||||
exec(open('src/artpipeline/version.py').read())
|
||||
|
||||
setup(
|
||||
name="artpipeline",
|
||||
version=__version__,
|
||||
author="M.Pavlinsky SRG/ART-XC software team",
|
||||
description="data reduction pipeline",
|
||||
package_dir={"": "src"},
|
||||
packages=["artpipeline"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"artpipeline = artpipeline.pipeline:main",
|
||||
]
|
||||
},
|
||||
)
|
||||
BIN
artpipeline/src/.DS_Store
vendored
Normal file
BIN
artpipeline/src/.DS_Store
vendored
Normal file
Binary file not shown.
325
artpipeline/src/artpipeline/caldb.py
Normal file
325
artpipeline/src/artpipeline/caldb.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime as dt
|
||||
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
import polars as pl
|
||||
|
||||
from astropy.io import fits
|
||||
# from astropy.table import Table
|
||||
|
||||
from arttools.datatable import DataTable, FitsAdapter
|
||||
|
||||
|
||||
# if 'ARTCALDB' in os.environ:
|
||||
# ARTCALDBPATH = os.getenv('ARTCALDB')
|
||||
# else:
|
||||
# print('error: ARTCALDB environment variable not found')
|
||||
# sys.exit(-1)
|
||||
|
||||
|
||||
class CaldbInfo(object):
|
||||
|
||||
def __init__(self, caldbdir):
|
||||
self._caldbpath = caldbdir
|
||||
self._indexfile = 'caldb.indx'
|
||||
self._caldbindex = os.path.join(self._caldbpath, self._indexfile)
|
||||
|
||||
with fits.open(self._caldbindex) as caldbfile:
|
||||
self._version = caldbfile['CIF'].header['ARTCALDB']
|
||||
# self._index = Table(caldbfile['CIF'].data).to_pandas()
|
||||
|
||||
self._index = DataTable().adapter(FitsAdapter(self._caldbindex, 'CIF'))
|
||||
self._index.read()
|
||||
|
||||
def get(self):
|
||||
return {
|
||||
'CALDBDIR': self._caldbpath,
|
||||
'CALDBVER': self._version
|
||||
}
|
||||
|
||||
|
||||
class Caldb(object):
|
||||
|
||||
def __init__(self, caldbdir, instname, silent=True):
|
||||
|
||||
self._instname = instname
|
||||
self._silent = silent
|
||||
self._version = None
|
||||
self._index = None
|
||||
|
||||
# --- read version, index
|
||||
self._caldbpath = caldbdir
|
||||
self._indexfile = 'caldb.indx'
|
||||
self._caldbindex = os.path.join(self._caldbpath, self._indexfile)
|
||||
|
||||
with fits.open(self._caldbindex) as caldbfile:
|
||||
self._version = caldbfile['CIF'].header['ARTCALDB']
|
||||
# self._index = Table(caldbfile['CIF'].data).to_pandas()
|
||||
|
||||
self._index = DataTable().adapter(FitsAdapter(self._caldbindex, 'CIF'))
|
||||
self._index.read()
|
||||
|
||||
# print('CALDB: version {} [{}]'.format(self._version, self._caldbfilespath))
|
||||
|
||||
@property
|
||||
def silent(self):
|
||||
return self._silent
|
||||
|
||||
def cif(self, instname, calcname):
|
||||
iname = instname; cname = calcname
|
||||
|
||||
if not self._silent:
|
||||
print(self._silent)
|
||||
print('CALDB: select: {}'.format(
|
||||
'INSTRUME==\'{}\' and CAL_CNAM==\'{}\''.format(iname, cname)))
|
||||
|
||||
# iname = iname.ljust(10, ' ')
|
||||
# cname = cname.ljust(20, ' ')
|
||||
|
||||
# return self._index.query(
|
||||
# 'INSTRUME==\'{}\' and CAL_CNAM==\'{}\''.format(iname, cname))
|
||||
|
||||
iname = iname.ljust(10, ' ')
|
||||
cname = cname.ljust(20, ' ')
|
||||
|
||||
return self._index.frame.filter(
|
||||
pl.col('INSTRUME') == iname,
|
||||
pl.col('CAL_CNAM') == cname
|
||||
)
|
||||
|
||||
def file(self, calcname):
|
||||
return self.instfile(self._instname, calcname)
|
||||
|
||||
def instfile(self, instname, calcname):
|
||||
selected = self.cif(instname, calcname)
|
||||
for cdbpath, cdbfile in zip(selected['CAL_DIR'], selected['CAL_FILE']):
|
||||
cdbpath = cdbpath.strip()
|
||||
cdbfile = cdbfile.strip()
|
||||
|
||||
if not self._silent:
|
||||
print('CALDB: product: {}'.format(cdbfile))
|
||||
|
||||
p = Path(cdbpath)
|
||||
p = Path(*p.parts[2:]) # chop off data/artxc_caldb
|
||||
|
||||
cdbpath = p
|
||||
|
||||
# return first record only, @fixme
|
||||
return os.path.join(cdbpath, cdbfile)
|
||||
|
||||
return None
|
||||
|
||||
def path(self, folder='bcf'):
|
||||
return os.path.join(self._caldbpath, folder)
|
||||
|
||||
def instname(self):
|
||||
return self._instname
|
||||
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
|
||||
class CaldbTelescope(object):
|
||||
|
||||
def __init__(self, caldb):
|
||||
self._caldb = caldb
|
||||
self._silent = self._caldb.silent
|
||||
self._oaxis_file \
|
||||
= os.path.join(self._caldb.path(), self._caldb.file('OPTAXIS'))
|
||||
|
||||
with fits.open(self._oaxis_file) as hdul:
|
||||
self._oaxis = {
|
||||
'X': hdul[1].data['X'][0],
|
||||
'Y': hdul[1].data['Y'][0]
|
||||
}
|
||||
|
||||
if not self._silent:
|
||||
print('CALDB: selected OPTAXIS: X={} Y={}'.format(self._oaxis['X'], self._oaxis['Y']))
|
||||
|
||||
def oaxis(self):
|
||||
return self._oaxis
|
||||
|
||||
def info(self):
|
||||
meta = {
|
||||
'OPTAXIS': self._oaxis
|
||||
}
|
||||
|
||||
|
||||
# class CaldbEnergy(object):
|
||||
|
||||
# def __init__(self, instname):
|
||||
# self._caldb = Caldb(instname)
|
||||
|
||||
# self._thresh_file \
|
||||
# = self._caldb.file('THRESHOL')
|
||||
# self._escale_file \
|
||||
# = self._caldb.file('ESCALE' )
|
||||
|
||||
# #todo: check files is not None
|
||||
|
||||
# self._thresh = fits.open(os.path.join(self._caldb.path(), self._thresh_file))
|
||||
# self._escale = fits.open(os.path.join(self._caldb.path(), self._escale_file))
|
||||
|
||||
# def bot(self):
|
||||
# return self._thresh['BOT'].data
|
||||
|
||||
# def top(self):
|
||||
# return self._thresh['TOP'].data
|
||||
|
||||
# def scale(self):
|
||||
# # escale_data = self._escale['ESCALE'].data
|
||||
# escale_data = self._escale['E_COEFF'].data
|
||||
# obt_data = escale_data['OBT']
|
||||
# c0_data = escale_data['C0' ]
|
||||
# c1_data = escale_data['C1' ]
|
||||
|
||||
# scale_fn = interp1d(obt_data, c0_data, bounds_error=False, fill_value=tuple(c0_data[[0, -1]]))
|
||||
# shift_fn = interp1d(obt_data, c1_data, bounds_error=False, fill_value=tuple(c1_data[[0, -1]]))
|
||||
|
||||
# # print last time (obt_data)
|
||||
# # save it to header
|
||||
|
||||
# return {
|
||||
# 'C0': scale_fn,
|
||||
# 'C1': shift_fn}
|
||||
|
||||
# def version(self):
|
||||
# return self._caldb.version()
|
||||
|
||||
# def info(self):
|
||||
# meta = {
|
||||
# 'THRESHOL': self._thresh_file,
|
||||
# 'ESCALE' : self._escale_file,
|
||||
# 'CALDBVER': self.version()
|
||||
# }
|
||||
# return meta
|
||||
|
||||
class CaldbEnergy(object):
|
||||
|
||||
def __init__(self, caldbdir, instname):
|
||||
self._caldb = Caldb(caldbdir, instname)
|
||||
|
||||
self._chen_file \
|
||||
= self._caldb.file('CHENERGY')
|
||||
|
||||
self._chen = fits.open(os.path.join(self._caldb.path(), self._chen_file))
|
||||
|
||||
def bot(self):
|
||||
return self._chen['BOT'].data
|
||||
|
||||
def top(self):
|
||||
return self._chen['TOP'].data
|
||||
|
||||
def version(self):
|
||||
return self._caldb.version()
|
||||
|
||||
def info(self):
|
||||
meta = {
|
||||
'CHENERGY': self._chen_file,
|
||||
'CALDBVER': self.version()
|
||||
}
|
||||
return meta
|
||||
|
||||
|
||||
class CaldbMask(object):
|
||||
|
||||
def __init__(self, caldbdir, instname):
|
||||
self._caldb = Caldb(caldbdir, instname)
|
||||
|
||||
self._dmask_file \
|
||||
= os.path.join(self._caldb.path(), self._caldb.file('DETMASK'))
|
||||
self._dmask = np.copy(fits.getdata(self._dmask_file, 1)).astype(bool).T
|
||||
|
||||
# self._dmask = fits.open(os.path.join(self._caldb.path(), self._dmask_file))
|
||||
|
||||
def get(self):
|
||||
return self._dmask
|
||||
|
||||
def version(self):
|
||||
return self._caldb.version()
|
||||
|
||||
def info(self):
|
||||
meta = {
|
||||
'DETMASK': self._dmask_file,
|
||||
'CALDBVER': self.version()
|
||||
}
|
||||
return meta
|
||||
|
||||
|
||||
class CaldbOrientation(object):
|
||||
|
||||
_INST_QUAT = {
|
||||
'GYRO': [ 0. , 0. , 0. , 1. ],
|
||||
'BOKZ': [ 0. , -0.707106781186548, 0. , 0.707106781186548],
|
||||
'SED1': [ 0.183012701892219, 0.683012701892219, -0.183012701892219, 0.683012701892219],
|
||||
'SED2': [-0.183012701892219, 0.683012701892219, 0.183012701892219, 0.683012701892219]}
|
||||
|
||||
def __init__(self, caldbdir, instname):
|
||||
self._delay_file = None
|
||||
self._bs_file = None
|
||||
self._caldb = Caldb(caldbdir, instname)
|
||||
self._delay = self.read_time_delay()
|
||||
self._instquat = self.read_instquat ()
|
||||
self._boresight = self.read_boresight ()
|
||||
|
||||
def read_time_delay(self):
|
||||
delay_file = self._caldb.file('{instname}LAG'.format(instname=self._caldb.instname()))
|
||||
delay_hdul = fits.open(os.path.join(self._caldb.path(), delay_file))
|
||||
delay = delay_hdul[1].data['DELAY'][0]
|
||||
|
||||
self._delay_file = delay_file
|
||||
|
||||
# print('CALDB: select {} DELAY={}'.format(self._caldb.instname(), delay))
|
||||
|
||||
return delay
|
||||
|
||||
def read_instquat(self):
|
||||
return self._INST_QUAT[self._caldb.instname()]
|
||||
|
||||
def read_boresight(self, instname=None):
|
||||
if instname is None:
|
||||
instname = self._caldb.instname()
|
||||
|
||||
bs_file = self._caldb.instfile(instname, 'BORESIGH')
|
||||
bs_hdul = fits.open(os.path.join(self._caldb.path(), bs_file))
|
||||
|
||||
self._bs_file = bs_file
|
||||
|
||||
return bs_hdul[1].data[0]
|
||||
|
||||
def timeshift(self):
|
||||
return self._delay
|
||||
|
||||
def instquat(self):
|
||||
return self._instquat
|
||||
|
||||
def boresight(self):
|
||||
return self._boresight
|
||||
|
||||
def version(self):
|
||||
return self._caldb.version()
|
||||
|
||||
def info(self):
|
||||
meta = {
|
||||
'INSTRUME': self._caldb.instname(),
|
||||
'TIMELAG' : self._delay_file,
|
||||
'BORESIGH': self._bs_file
|
||||
}
|
||||
return meta
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
152
artpipeline/src/artpipeline/energy.old.py
Normal file
152
artpipeline/src/artpipeline/energy.old.py
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
from .time import Mission
|
||||
|
||||
|
||||
class Energy(object):
|
||||
|
||||
def __init__(self, caldb_energy, seed=None):
|
||||
self._caldb_energy = caldb_energy
|
||||
self._seed = seed
|
||||
|
||||
self.GRADES = np.ones(256) * (-1)
|
||||
self.GRADES[18] = 0 # single central events
|
||||
self.GRADES[[50, 26, 22, 19, 54, 51, 30, 27]] = [1, 2, 3, 4, 5, 6, 7, 8] # double events
|
||||
self.GRADES[[58, 62, 59, 23, 55, 31, 63]] = [9, 10, 11, 12, 13, 14, 15] # triple events
|
||||
|
||||
def calc(self, evtdata, hkdata):
|
||||
time = evtdata['time']
|
||||
tstart = Mission(time[0]).to_datetime()
|
||||
escale = self._caldb_energy.scale()
|
||||
ebot = self._caldb_energy.bot()
|
||||
etop = self._caldb_energy.top()
|
||||
|
||||
tempdata = hkdata['td1']
|
||||
temperature = interp1d(
|
||||
hkdata['time'], tempdata, bounds_error=False, kind='linear',
|
||||
fill_value=(tempdata[0], tempdata[-1]))(evtdata['time'])
|
||||
|
||||
if self._seed is not None:
|
||||
sseq = np.random.SeedSequence(self._seed)
|
||||
else:
|
||||
sseq = np.random.SeedSequence()
|
||||
|
||||
rng = np.random.default_rng(sseq)
|
||||
seed = sseq.entropy
|
||||
|
||||
energybot, sigmabot, maskbot \
|
||||
= self._calc(
|
||||
evtdata,
|
||||
('raw_x' ,
|
||||
'pha_bot_sub1',
|
||||
'pha_bot' ,
|
||||
'pha_bot_add1'),
|
||||
temperature, ebot, rng)
|
||||
energytop, sigmatop, masktop \
|
||||
= self._calc(
|
||||
evtdata,
|
||||
('raw_y' ,
|
||||
'pha_top_sub1',
|
||||
'pha_top' ,
|
||||
'pha_top_add1'),
|
||||
temperature, etop, rng)
|
||||
|
||||
energy = np.zeros(evtdata.size, np.double)
|
||||
mask = np.zeros((8, evtdata.size), bool)
|
||||
|
||||
mask[2:5,:] = masktop
|
||||
mask[5:8,:] = maskbot
|
||||
|
||||
emask = np.any(mask[2:,:], axis=0)
|
||||
|
||||
energybot = np.sum(energybot * maskbot , axis=0)
|
||||
sigmabot2 = np.sum(np.power(sigmabot, 2.) * maskbot, axis=0)
|
||||
|
||||
energytop = np.sum(energytop * masktop , axis=0)
|
||||
sigmatop2 = np.sum(np.power(sigmatop, 2.) * masktop, axis=0)
|
||||
|
||||
energy[emask] \
|
||||
= ((energybot * sigmatop2 + energytop * sigmabot2) / (sigmabot2 + sigmatop2))[emask]
|
||||
energy[emask] = self._scale(evtdata['time'][emask], energy[emask], escale)
|
||||
|
||||
# energy = self._scale(energy, escale)
|
||||
pi = self._en2pi(energy, emask )
|
||||
grade = self._grade(mask )
|
||||
|
||||
return energy, pi, grade, temperature, energybot, energytop, seed
|
||||
|
||||
def _calc(self, evtdata, coln, temperature, energycal, rng):
|
||||
strip, mask \
|
||||
= self._eventindex(evtdata[coln[0]])
|
||||
pha = np.array([evtdata[coln[1]], evtdata[coln[2]], evtdata[coln[3]]])
|
||||
|
||||
e1 = self._pha2en(pha , strip, temperature, energycal)
|
||||
e2 = self._pha2en(pha + 1., strip, temperature, energycal)
|
||||
|
||||
# energy = np.random.uniform(e1, e2)
|
||||
energy = rng.uniform(e1, e2)
|
||||
|
||||
sigma = energycal['fwhm_0'][strip] + energycal['fwhm_1'][strip] * temperature
|
||||
mask = np.logical_and(mask, energy > energycal['threshold'][strip])
|
||||
|
||||
return energy, sigma, mask
|
||||
|
||||
def _pha2en(self, pha, strip, temperature, energycal):
|
||||
''' Energy = c00 + c01*T + (c10 + c11*T)*PHA + (c20 + c21*T)*PHA**2 '''
|
||||
pha2 = np.power(pha, 2.)
|
||||
energy = \
|
||||
energycal["c0_0"][strip] + energycal["c0_1"][strip] * temperature + \
|
||||
(energycal["c1_0"][strip] + energycal["c1_1"][strip] * temperature) * pha + \
|
||||
(energycal["c2_0"][strip] + energycal["c2_1"][strip] * temperature) * pha2
|
||||
|
||||
return energy
|
||||
|
||||
def _scale(self, times, energy, escale):
|
||||
''' Еnergy = C0 + C1*Еnergy + С2*Еnergy**2 '''
|
||||
# return escale['C0'] + escale['C1'] * energy + escale['C2'] * np.power(energy, 2.)
|
||||
|
||||
C0 = escale['C0'](times)
|
||||
C1 = escale['C1'](times)
|
||||
|
||||
return energy * C0 + C1
|
||||
|
||||
|
||||
def _en2pi(self, energy, mask):
|
||||
PHA2PI = 10 # channels are in units of 100 eV
|
||||
|
||||
pi = np.int16(energy * PHA2PI) - 1
|
||||
pi = np.maximum(pi, 0)
|
||||
pi[~mask] = 0
|
||||
|
||||
return pi
|
||||
|
||||
def _eventindex(self, strip):
|
||||
coord = np.empty((3, strip.size), int) #(strip, (3, 1))
|
||||
coord[0, :] = strip - 1
|
||||
coord[1, :] = strip
|
||||
coord[2, :] = strip + 1
|
||||
mask = (coord >= 0) & (coord <= 47)
|
||||
coord[np.logical_not(mask)] = -1
|
||||
return coord, mask
|
||||
|
||||
def _grade(self, mask):
|
||||
grademask = np.packbits(mask, axis=0)[0]
|
||||
return self.GRADES[grademask].astype(int)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
206
artpipeline/src/artpipeline/energy.py
Executable file
206
artpipeline/src/artpipeline/energy.py
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
# from .time import Mission
|
||||
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
np.seterr(all="ignore")
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
|
||||
class Energy(object):
|
||||
|
||||
def __init__(self, caldb, seed=None):
|
||||
self._caldb = caldb
|
||||
self._seed = seed
|
||||
|
||||
self.MJDCDB = Time(58677.6464931, format='mjd')
|
||||
self.MJDREF = Time(51543.8750000, format='mjd') #corresponds to date 01.01.2000 00:00:00 (UTC+3)
|
||||
self.TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) #UTC time zone
|
||||
self.TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) #UTC+3 (Moscow) time zone
|
||||
|
||||
self.GRADES = np.ones(256) * (-1)
|
||||
self.GRADES[18] = 0 # single central events
|
||||
self.GRADES[[50, 26, 22, 19, 54, 51, 30, 27]] = [1, 2, 3, 4, 5, 6, 7, 8] # double events
|
||||
self.GRADES[[58, 62, 59, 23, 55, 31, 63]] = [9, 10, 11, 12, 13, 14, 15] # triple events
|
||||
|
||||
def _calc_mjdtime(self, missiontime):
|
||||
return self.MJDREF + Time(missiontime / erfa.DAYSEC, format='mjd').mjd
|
||||
|
||||
def _calc_dt(self, missiontime):
|
||||
mjdt = self._calc_mjdtime(missiontime)
|
||||
return (mjdt - self.MJDCDB).jd
|
||||
|
||||
def _eventindex(self, strip):
|
||||
# coord = np.empty((3, strip.size), int) #(strip, (3, 1))
|
||||
coord = np.empty((3, strip.count()), int) #(strip, (3, 1))
|
||||
coord[0, :] = strip - 1
|
||||
coord[1, :] = strip
|
||||
coord[2, :] = strip + 1
|
||||
mask = (coord >= 0) & (coord <= 47)
|
||||
coord[np.logical_not(mask)] = -1
|
||||
return coord, mask
|
||||
|
||||
def _calc(self, evtdata, coln, dt, dt2, random, energycal):
|
||||
data_size = dt.size
|
||||
strip, mask_strip \
|
||||
= self._eventindex(evtdata.column(coln[0]))
|
||||
pha0 = np.array([
|
||||
evtdata.column(coln[1]),
|
||||
evtdata.column(coln[2]),
|
||||
evtdata.column(coln[3])
|
||||
], dtype=np.uint32)
|
||||
|
||||
C0 = energycal['C0_0'][strip] + energycal['C0_1'][strip] * dt + energycal['C0_2'][strip] * dt2
|
||||
C1 = energycal['C1_0'][strip] + energycal['C1_1'][strip] * dt + energycal['C1_2'][strip] * dt2
|
||||
C2 = energycal['C2_0'][strip] + energycal['C2_1'][strip] * dt + energycal['C2_2'][strip] * dt2
|
||||
C3 = energycal['C3_0'][strip] + energycal['C3_1'][strip] * dt + energycal['C3_2'][strip] * dt2
|
||||
C4 = energycal['C4_0'][strip] + energycal['C4_1'][strip] * dt + energycal['C4_2'][strip] * dt2
|
||||
|
||||
if random is not None:
|
||||
pha_rand = random.uniform(0, 1, pha0.shape)
|
||||
else:
|
||||
pha_rand = np.zeros(pha0.shape)
|
||||
|
||||
pha = pha0 + pha_rand
|
||||
|
||||
energy3 = C0 + C1 * pha + C2 * np.power(pha, 2)
|
||||
|
||||
kofA = [0.9, 1.15]
|
||||
dayA = [223., 1480.]
|
||||
|
||||
C1_thr = (kofA[0] - kofA[1]) / (dayA[0] - dayA[1])
|
||||
C0_thr = ((kofA[0] + kofA[1]) - (dayA[0] + dayA[1]) * C1_thr) / 2.
|
||||
|
||||
kof = C0_thr + C1_thr * dt
|
||||
|
||||
mask_threshold = energy3 > energycal['THRESHOLD'][strip] * kof
|
||||
mask_threshold[1] = np.full_like(mask_threshold[1], True)
|
||||
|
||||
mask3 = np.logical_and(mask_strip, mask_threshold)
|
||||
|
||||
energy3[~mask3] = 0
|
||||
energy = np.sum(energy3, axis=0)
|
||||
|
||||
# energy = np.sum(energy3 * mask3, axis=0)
|
||||
|
||||
fwhm = (C3[1] + C4[1] * np.sqrt(energy))
|
||||
|
||||
return energy, fwhm, mask3
|
||||
|
||||
def _grade(self, mask):
|
||||
grademask = np.packbits(mask, axis=0)[0]
|
||||
|
||||
return self.GRADES[grademask].astype(int)
|
||||
|
||||
def calc(self, teln, evtdata, random=None):
|
||||
dt = self._calc_dt(evtdata.column('TIME'))
|
||||
dt2 = np.power(dt, 2)
|
||||
|
||||
energybot, fwhmbot, maskbot3 =\
|
||||
self._calc(
|
||||
evtdata,
|
||||
('RAW_X' ,
|
||||
'PHA_BOT_SUB1',
|
||||
'PHA_BOT' ,
|
||||
'PHA_BOT_ADD1'),
|
||||
dt, dt2, random, self._caldb.bot()
|
||||
)
|
||||
fwhmbot_2 = np.power(fwhmbot, 2.)
|
||||
|
||||
energytop, fwhmtop, masktop3 =\
|
||||
self._calc(
|
||||
evtdata,
|
||||
('RAW_Y' ,
|
||||
'PHA_TOP_SUB1',
|
||||
'PHA_TOP' ,
|
||||
'PHA_TOP_ADD1'),
|
||||
dt, dt2, random, self._caldb.top()
|
||||
)
|
||||
fwhmtop_2 = np.power(fwhmtop, 2.)
|
||||
|
||||
# -- calculate mask
|
||||
mask = np.zeros((8, evtdata.size), bool)
|
||||
mask[2:5,:] = masktop3
|
||||
mask[5:8,:] = maskbot3
|
||||
|
||||
# energy = np.zeros(dt.size, float)
|
||||
energy =\
|
||||
((energybot * fwhmtop_2 + energytop * fwhmbot_2) / (fwhmbot_2 + fwhmtop_2))
|
||||
|
||||
grades = self._grade(mask)
|
||||
|
||||
return energy, energybot, energytop, grades
|
||||
|
||||
|
||||
class Flag(object):
|
||||
|
||||
def __init__(self, caldb):
|
||||
self._caldb = caldb
|
||||
|
||||
def _mask_events(self, evtdata, tstart, tstop):
|
||||
times = evtdata.column('TIME')
|
||||
# try:
|
||||
# tstart = evtdata.tstart
|
||||
# tstop = evtdata.tstop
|
||||
# except KeyError:
|
||||
# tstart = times[ 0]
|
||||
# tstop = times[-1]
|
||||
# print('WARNING: TSTART or TSTOP keywords not found')
|
||||
# print('WARNING: will use event times TSTART = {}, TSTOP = {}'.format(tstart, tstop))
|
||||
|
||||
return (times > tstart) & (times < tstop)
|
||||
|
||||
def _mask_shadow(self, evtdata):
|
||||
evtrawx = evtdata.column('RAW_X')
|
||||
evtrawy = evtdata.column('RAW_Y')
|
||||
|
||||
shadowmask = self._caldb.get()
|
||||
|
||||
return np.logical_not(shadowmask[evtrawx, evtrawy])
|
||||
|
||||
def _mask_edges(self, evtdata):
|
||||
evtrawx = evtdata.column('RAW_X')
|
||||
evtrawy = evtdata.column('RAW_Y')
|
||||
|
||||
return np.any([
|
||||
evtrawx == 0, evtrawx == 47,
|
||||
evtrawy == 0, evtrawy == 47], axis=0)
|
||||
|
||||
def calc(self, evtdata, tstart, tstop):
|
||||
flag = np.ones(evtdata.size, dtype=np.uint8)
|
||||
|
||||
eventsmask = self._mask_events(evtdata, tstart, tstop)
|
||||
shadowmask = self._mask_shadow(evtdata)
|
||||
edgesmask = self._mask_edges (evtdata)
|
||||
|
||||
flag[eventsmask] = 0
|
||||
flag[shadowmask] = 2
|
||||
flag[edgesmask ] = 3
|
||||
|
||||
return flag
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
176
artpipeline/src/artpipeline/events.py
Executable file
176
artpipeline/src/artpipeline/events.py
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.gti import Gti
|
||||
from arttools.datatable import DataTable, FitsAdapter
|
||||
|
||||
from artpipeline.version import __version__
|
||||
|
||||
|
||||
class Events(object):
|
||||
|
||||
def __init__(self, srcfile):
|
||||
super().__init__()
|
||||
|
||||
self._srcfile = srcfile
|
||||
|
||||
self._telname = 0
|
||||
self._teln = 0
|
||||
|
||||
self._tstart = 0
|
||||
self._tstop = 0
|
||||
|
||||
self._events = DataTable()
|
||||
self._hk = DataTable()
|
||||
self._gti = Gti()
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._events.size
|
||||
|
||||
@property
|
||||
def teln(self): return self._teln
|
||||
|
||||
@property
|
||||
def telname(self): return self._telname
|
||||
|
||||
@property
|
||||
def tstart(self): return self._tstart
|
||||
|
||||
@property
|
||||
def tstop(self): return self._tstop
|
||||
|
||||
@property
|
||||
def events(self): return self._events
|
||||
|
||||
@property
|
||||
def hk(self): return self._hk
|
||||
|
||||
@property
|
||||
def gti(self): return self._gti
|
||||
|
||||
def _read_header(self):
|
||||
with fits.open(self._srcfile) as hdul:
|
||||
self._teln = hdul['events'].header['teln' ]
|
||||
self._telname = hdul['events'].header['instrume']
|
||||
self._tstart = hdul['events'].header['tstart' ]
|
||||
self._tstop = hdul['events'].header['tstop' ]
|
||||
|
||||
def read(self):
|
||||
self._read_header()
|
||||
|
||||
self._events = DataTable().adapter(FitsAdapter(self._srcfile, 'events')).read()
|
||||
self._hk = DataTable().adapter(FitsAdapter(self._srcfile, 'hk' )).read()
|
||||
self._gti = Gti.from_hduname(self._srcfile, 'stdgti')
|
||||
|
||||
return self
|
||||
|
||||
def write_cl(self, dstfile, cdbinfo):
|
||||
|
||||
srchdul = fits.open(self._srcfile)
|
||||
|
||||
# --- primary HDU ---
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
# --- EVENTS HDU ---
|
||||
a00 = np.array(self._events.column('TIME' ))
|
||||
a01 = np.array(self._events.column('TIME_I' ))
|
||||
a02 = np.array(self._events.column('TIME_F' ))
|
||||
a03 = np.array(self._events.column('TIME_CORR' ))
|
||||
a04 = np.array(self._events.column('ENERGY' ))
|
||||
a05 = np.array(self._events.column('ENERGY_TOP' ))
|
||||
a06 = np.array(self._events.column('ENERGY_BOT' ))
|
||||
a07 = np.array(self._events.column('GRADE' ))
|
||||
a08 = np.array(self._events.column('FLAG' ))
|
||||
a09 = np.array(self._events.column('RA' ))
|
||||
a10 = np.array(self._events.column('DEC' ))
|
||||
a11 = np.array(self._events.column('RAW_X' ))
|
||||
a12 = np.array(self._events.column('RAW_Y' ))
|
||||
a13 = np.array(self._events.column('PHA_BOT' ))
|
||||
a14 = np.array(self._events.column('PHA_BOT_ADD1'))
|
||||
a15 = np.array(self._events.column('PHA_BOT_SUB1'))
|
||||
a16 = np.array(self._events.column('PHA_TOP' ))
|
||||
a17 = np.array(self._events.column('PHA_TOP_ADD1'))
|
||||
a18 = np.array(self._events.column('PHA_TOP_SUB1'))
|
||||
a19 = np.array(self._events.column('TRIGGER' ))
|
||||
|
||||
c00 = fits.Column(name='TIME' , format='1D', unit='sec', array=a00 )
|
||||
c01 = fits.Column(name='TIME_I' , format='1J', unit='sec', array=a01, bzero=2147483648)
|
||||
c02 = fits.Column(name='TIME_F' , format='1D', unit='sec', array=a02 )
|
||||
c03 = fits.Column(name='TIME_CORR' , format='1D', unit='sec', array=a03 )
|
||||
c04 = fits.Column(name='ENERGY' , format='1D', unit='keV', array=a04 )
|
||||
c05 = fits.Column(name='ENERGY_TOP' , format='1D', unit='keV', array=a05 )
|
||||
c06 = fits.Column(name='ENERGY_BOT' , format='1D', unit='keV', array=a06 )
|
||||
c07 = fits.Column(name='GRADE' , format='1I', unit='' , array=a07 )
|
||||
c08 = fits.Column(name='FLAG' , format='1I', unit='' , array=a08 )
|
||||
c09 = fits.Column(name='RA' , format='1D', unit='deg', array=a09 )
|
||||
c10 = fits.Column(name='DEC' , format='1D', unit='deg', array=a10 )
|
||||
c11 = fits.Column(name='RAW_X' , format='1I', unit='pix', array=a11 )
|
||||
c12 = fits.Column(name='RAW_Y' , format='1I', unit='pix', array=a12 )
|
||||
c13 = fits.Column(name='PHA_BOT' , format='1I', unit='' , array=a13 )
|
||||
c14 = fits.Column(name='PHA_BOT_ADD1', format='1I', unit='' , array=a14 )
|
||||
c15 = fits.Column(name='PHA_BOT_SUB1', format='1I', unit='' , array=a15 )
|
||||
c16 = fits.Column(name='PHA_TOP' , format='1I', unit='' , array=a16 )
|
||||
c17 = fits.Column(name='PHA_TOP_ADD1', format='1I', unit='' , array=a17 )
|
||||
c18 = fits.Column(name='PHA_TOP_SUB1', format='1I', unit='' , array=a18 )
|
||||
c19 = fits.Column(name='TRIGGER' , format='1I', unit='' , array=a19 )
|
||||
|
||||
cols = fits.ColDefs([
|
||||
c00, c01, c02, c03, c04, c05, c06, c07, c08, c09,
|
||||
c10, c11, c12, c13, c14, c15, c16, c17, c18, c19
|
||||
])
|
||||
|
||||
evthdu = fits.BinTableHDU.from_columns(cols)
|
||||
evthdu.name = 'EVENTS'
|
||||
|
||||
creator = 'artpipeline v.{}'.format(__version__)
|
||||
|
||||
evthdu.header['creator' ] = (creator , 'program and version that created this file' )
|
||||
evthdu.header['origin' ] = ('IKI RAS' , 'institution that created this file' )
|
||||
evthdu.header['telescop'] = ('SRG/ART-XC', 'mission name' )
|
||||
|
||||
# copy header keys
|
||||
COPYKEYS = [
|
||||
'instrume', 'teln', 'detn', 'urdn',
|
||||
'mjdref', 'artday', 'tstart', 'tstop',
|
||||
'date', 'date-obs', 'time-obs', 'date-end', 'time-end',
|
||||
'maxticks', 'timedel', 'timeunit', 'timeref', 'tassign'
|
||||
]
|
||||
|
||||
for key in COPYKEYS:
|
||||
try:
|
||||
evthdu.header[key] = srchdul['EVENTS'].header[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
# add caldb key
|
||||
for key in cdbinfo:
|
||||
evthdu.header[key] = cdbinfo[key]
|
||||
|
||||
# --- make file ---
|
||||
# hdulist = fits.HDUList([hdu, evthdu, srchdul['']])
|
||||
hdulist = fits.HDUList([hdu, evthdu, srchdul['KVEA'], srchdul['HK'], srchdul['STDGTI']])
|
||||
|
||||
hdulist.writeto(dstfile, overwrite=True, checksum=True)
|
||||
|
||||
|
||||
def setcol(self, name, arr):
|
||||
self._events.setcol(name, arr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
302
artpipeline/src/artpipeline/orientation.py
Normal file
302
artpipeline/src/artpipeline/orientation.py
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Slerp
|
||||
from scipy.spatial.transform import Rotation
|
||||
|
||||
from astropy.io import fits
|
||||
from astropy.nddata import bitmask
|
||||
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
|
||||
class AttitudeStatus(object):
|
||||
|
||||
INTERP = 1
|
||||
SCIREADY = 2
|
||||
PRECSTAB = 4
|
||||
SED1_ON = 8
|
||||
SED2_ON = 16
|
||||
BOKZ_ON = 32
|
||||
SED1_MEAS = 64
|
||||
SED2_MEAS = 128
|
||||
BOKZ_MEAS = 256
|
||||
|
||||
flags = np.array([
|
||||
INTERP ,
|
||||
SCIREADY ,
|
||||
PRECSTAB ,
|
||||
SED1_ON ,
|
||||
SED2_ON ,
|
||||
BOKZ_ON ,
|
||||
SED1_MEAS,
|
||||
SED2_MEAS,
|
||||
BOKZ_MEAS
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def flags_except(cls, flags):
|
||||
indices = np.searchsorted(cls.flags, flags)
|
||||
return np.delete(cls.flags, indices)
|
||||
|
||||
@classmethod
|
||||
def pack(cls, arr):
|
||||
bits = [cls.flags[x.astype(bool)] for x in arr.T]
|
||||
return [bitmask.interpret_bit_flags(x) for x in bits]
|
||||
|
||||
@classmethod
|
||||
def flagval(cls, val, flags):
|
||||
return bitmask.bitfield_to_boolean_mask(val, ignore_flags=flags)
|
||||
|
||||
@classmethod
|
||||
def extract(cls, arr, flags):
|
||||
return np.array([AttitudeStatus.flagval(x, flags) for x in arr])
|
||||
|
||||
@staticmethod
|
||||
def tostr(val, fmt='{0:016b}'):
|
||||
return fmt.format(val)
|
||||
|
||||
|
||||
class Orientation(object):
|
||||
|
||||
UNITY_QUAT = np.array([0., 0., 0., 1.])
|
||||
|
||||
VSC = {
|
||||
'X': np.array([1., 0., 0.]),
|
||||
'Y': np.array([0., 1., 0.]),
|
||||
'Z': np.array([0., 0., 1.])}
|
||||
|
||||
OPTICAL_AXIS = VSC['X']
|
||||
NORTH = VSC['Z']
|
||||
|
||||
RELCORR_GTI = [
|
||||
[624390347, 624399808],
|
||||
[624410643, 630954575]]
|
||||
|
||||
def __init__(self, caldb):
|
||||
self._caldb = caldb
|
||||
|
||||
def calc_events_coords(self, oridata, evtdata, telname, random=None):
|
||||
oritime = np.array(oridata['time'])
|
||||
evttime = np.array(evtdata.column('TIME' ))
|
||||
rawx0 = np.array(evtdata.column('RAW_X'))
|
||||
rawy0 = np.array(evtdata.column('RAW_Y'))
|
||||
|
||||
mask = np.logical_and(evttime > oritime[0], evttime < oritime[-1])
|
||||
|
||||
if random is not None:
|
||||
rawx_rand = random.uniform(-0.5, 0.5, rawx0.shape)
|
||||
rawy_rand = random.uniform(-0.5, 0.5, rawy0.shape)
|
||||
else:
|
||||
rawx_rand = 0
|
||||
rawy_rand = 0
|
||||
|
||||
rawx = rawx0 + rawx_rand
|
||||
rawy = rawy0 + rawy_rand
|
||||
|
||||
orient = Slerp(oridata['time'], oridata['quat'])
|
||||
telquat = Rotation(self._caldb.read_boresight(telname))
|
||||
|
||||
evtquat = np.zeros_like(evttime)
|
||||
ra = np.zeros_like(evttime)
|
||||
dec = np.zeros_like(evttime)
|
||||
|
||||
evtquat = orient(evttime[mask]) * telquat
|
||||
|
||||
ra, dec = self.rawxy_to_radec(evtquat, rawx[mask], rawy[mask])
|
||||
|
||||
ra_deg = np.zeros_like(evttime)
|
||||
dec_deg = np.zeros_like(evttime)
|
||||
|
||||
ra_deg[mask] = np.rad2deg(ra)
|
||||
dec_deg[mask] = np.rad2deg(dec)
|
||||
|
||||
return ra_deg, dec_deg, mask
|
||||
|
||||
def calc_attitudes(self, oridata, instname=None):
|
||||
instquat = self.instquat(instname)
|
||||
quatdata = oridata['quat_sc']
|
||||
if instname != None:
|
||||
quatdata = oridata['quat']
|
||||
|
||||
orient = Slerp (oridata['time'], quatdata)
|
||||
attquat = orient(oridata['time']) * Rotation(instquat)
|
||||
|
||||
ra, dec, roll \
|
||||
= self.calc_radecroll(attquat)
|
||||
|
||||
return np.rad2deg(ra), np.rad2deg(dec), np.rad2deg(roll)
|
||||
|
||||
def calc_rawxy(self, oritime, oriquat, evttime, ra, dec, telname):
|
||||
ra = np.deg2rad(ra )
|
||||
dec = np.deg2rad(dec)
|
||||
|
||||
orient = Slerp(oritime, oriquat)
|
||||
telquat = Rotation(self._caldb.read_boresight(telname))
|
||||
evtquat = orient(evttime) * telquat
|
||||
|
||||
rawx, rawy \
|
||||
= self.radec_to_rawxy(evtquat, ra, dec)
|
||||
|
||||
return rawx, rawy
|
||||
|
||||
def calc_radec(self, oritime, oriquat, evttime, evtrawx, evtrawy, telname):
|
||||
orient = Slerp(oritime, oriquat)
|
||||
telquat = Rotation(self._caldb.read_boresight(telname))
|
||||
|
||||
evtquat = orient(evttime) * telquat
|
||||
|
||||
ra, dec = self.rawxy2radec(evtquat, evtrawx, evtrawy)
|
||||
|
||||
return np.rad2deg(ra), np.rad2deg(dec)
|
||||
|
||||
def calc_radecroll(self, quat):
|
||||
oaxis = self.vec_normalize(quat.apply(self.OPTICAL_AXIS))
|
||||
|
||||
ra, dec \
|
||||
= self.vec_to_pol(oaxis)
|
||||
|
||||
YZ = self.vec_normalize(np.cross(oaxis, self.NORTH))
|
||||
roll = np.arctan2(
|
||||
np.sum(YZ * quat.apply(self.VSC['Z']), axis=1),
|
||||
np.sum(YZ * quat.apply(self.VSC['Y']), axis=1))
|
||||
|
||||
return ra, dec, roll
|
||||
|
||||
def instquat(self, instname=None):
|
||||
if instname == None:
|
||||
return self.UNITY_QUAT
|
||||
|
||||
if instname != 'GYRO':
|
||||
return self._caldb.read_boresight(instname)
|
||||
|
||||
return self.UNITY_QUAT
|
||||
|
||||
def read(self, orifile):
|
||||
times, quats, state = self._read_gyro(orifile)
|
||||
|
||||
quat = Rotation(quats) * Rotation(self._caldb.instquat()) * Rotation(self._caldb.boresight())
|
||||
quat_sc = Rotation(quats) * Rotation(self._caldb.instquat())
|
||||
|
||||
return {
|
||||
'size' : times.size,
|
||||
'time' : times ,
|
||||
'quat' : quat ,
|
||||
'quat_sc': quat_sc ,
|
||||
'state' : state }
|
||||
|
||||
def _read_gyro(self, orifile):
|
||||
ori_hdul = fits.open(orifile)
|
||||
data_size = ori_hdul[1].data.size
|
||||
times = ori_hdul[1].data['TIME' ]
|
||||
q0 = ori_hdul[1].data['QORT_1']
|
||||
q1 = ori_hdul[1].data['QORT_2']
|
||||
q2 = ori_hdul[1].data['QORT_3']
|
||||
q3 = ori_hdul[1].data['QORT_0']
|
||||
|
||||
times -= self._caldb.timeshift()
|
||||
quats = np.array([q0, q1, q2, q3]).T
|
||||
|
||||
state = np.array([
|
||||
np.zeros(data_size, dtype=int),
|
||||
ori_hdul[1].data['ST_SCIREADY' ],
|
||||
ori_hdul[1].data['ST_PRECSTAB' ],
|
||||
ori_hdul[1].data['ST_SED1_ON' ],
|
||||
ori_hdul[1].data['ST_SED2_ON' ],
|
||||
ori_hdul[1].data['ST_BOKZ_ON' ],
|
||||
ori_hdul[1].data['ST_SED1_MEAS' ],
|
||||
ori_hdul[1].data['ST_SED2_MEAS' ],
|
||||
ori_hdul[1].data['ST_BOKZ_MEAS' ]
|
||||
])
|
||||
|
||||
return times, quats, state
|
||||
|
||||
def vec_normalize(self, vec):
|
||||
norm = np.sqrt(np.sum(vec**2, axis=-1))[..., np.newaxis]
|
||||
|
||||
return vec / norm
|
||||
|
||||
def rawxy_to_offset(self, rawx, rawy):
|
||||
xoffset = (rawx - Teldef.centeroffset) * Teldef.d
|
||||
yoffset = (rawy - Teldef.centeroffset) * Teldef.d
|
||||
|
||||
return xoffset, yoffset
|
||||
|
||||
def offset_to_rawxy(self, xoffset, yoffset):
|
||||
x = xoffset / Teldef.d + Teldef.centeroffset + 0.5
|
||||
y = yoffset / Teldef.d + Teldef.centeroffset + 0.5
|
||||
|
||||
# return x.astype(np.int) - (x < 0), y.astype(np.int) - (y < 0)
|
||||
# return x.astype(np.int), y.astype(np.int)
|
||||
return x.astype(int), y.astype(int)
|
||||
|
||||
def offset_to_vec(self, x, y):
|
||||
vec = np.empty(shape=x.shape+(3,), dtype=np.double)
|
||||
vec[..., 0] = Teldef.F
|
||||
vec[..., 1] = x
|
||||
vec[..., 2] = -y
|
||||
|
||||
return vec
|
||||
|
||||
def vec_to_offset(self, vec):
|
||||
x = vec[..., 0]
|
||||
y = vec[..., 1]
|
||||
z = vec[..., 2]
|
||||
|
||||
xoffset = y * Teldef.F / x
|
||||
yoffset = -z * Teldef.F / x
|
||||
|
||||
return xoffset, yoffset
|
||||
|
||||
def vec_to_pol(self, vec):
|
||||
x = vec[..., 0]
|
||||
y = vec[..., 1]
|
||||
z = vec[..., 2]
|
||||
h = np.hypot(x, y)
|
||||
|
||||
phi = np.arctan2(y, x) % (2. * np.pi)
|
||||
theta = np.arctan2(z, h)
|
||||
|
||||
return phi, theta
|
||||
|
||||
def pol_to_vec(self, phi, theta):
|
||||
vec = np.empty(shape=phi.shape+(3,), dtype=np.double)
|
||||
|
||||
vec[..., 0] = np.cos(theta) * np.cos(phi)
|
||||
vec[..., 1] = np.cos(theta) * np.sin(phi)
|
||||
vec[..., 2] = np.sin(theta)
|
||||
|
||||
return vec
|
||||
|
||||
def rawxy_to_radec(self, quat, rawx, rawy):
|
||||
xoffset, yoffset \
|
||||
= self.rawxy_to_offset(rawx, rawy)
|
||||
xyvec = self.offset_to_vec(xoffset, yoffset)
|
||||
ra, dec = self.vec_to_pol(quat.apply(xyvec))
|
||||
|
||||
return ra, dec
|
||||
|
||||
def radec_to_rawxy(self, quat, ra, dec):
|
||||
skyvec = self.pol_to_vec(ra, dec)
|
||||
detvec = quat.apply(skyvec, inverse=True)
|
||||
xoffset, yoffset \
|
||||
= self.vec_to_offset(detvec)
|
||||
rawx, rawy \
|
||||
= self.offset_to_rawxy(xoffset, yoffset)
|
||||
|
||||
return rawx, rawy
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
518
artpipeline/src/artpipeline/pipeline.py
Executable file
518
artpipeline/src/artpipeline/pipeline.py
Executable file
@@ -0,0 +1,518 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
import datetime
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) # UTC time zone
|
||||
TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) # UTC+3 (Moscow) time zone
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arttools.cmd import Command, ExecutionResult
|
||||
from arttools.task import (
|
||||
Task, TaskError, TaskArgs, TaskCmds
|
||||
)
|
||||
from arttools.errors import Error
|
||||
from arttools.fstools import Dir
|
||||
from arttools.argparse import KvAction
|
||||
from arttools.telescope import Teldef
|
||||
from arttools.random import Random
|
||||
from arttools.arttime import ArtDay, MissionTime
|
||||
|
||||
from artpipeline.version import __version__
|
||||
|
||||
from artpipeline.caldb import CaldbInfo
|
||||
from artpipeline.caldb import CaldbMask
|
||||
from artpipeline.caldb import CaldbEnergy
|
||||
from artpipeline.caldb import CaldbOrientation
|
||||
|
||||
from artpipeline.events import Events
|
||||
from artpipeline.energy import Flag
|
||||
from artpipeline.energy import Energy
|
||||
from artpipeline.skyaxis import SkyAxis
|
||||
from artpipeline.skycoord import SkyCoord
|
||||
|
||||
|
||||
class Args(TaskArgs):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def read_env(self):
|
||||
if not 'ARTCALDB' in os.environ:
|
||||
raise TaskError('ARTCALDB not found (environment variable not set?)')
|
||||
|
||||
self.caldbdir = os.environ['ARTCALDB']
|
||||
|
||||
def read_cfg(self, cfgfile):
|
||||
pass
|
||||
|
||||
def read_args(self):
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument(
|
||||
'--stem',
|
||||
help='newfile creation stem',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--stem-in',
|
||||
type=str,
|
||||
help='input files stem',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'srcdir',
|
||||
help='input directory',
|
||||
action=KvAction
|
||||
)
|
||||
argparser.add_argument(
|
||||
'tmpdir',
|
||||
help='temporary directory',
|
||||
action=KvAction
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--dstdir',
|
||||
help='output directory',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
# argparser.add_argument(
|
||||
# '--user-gti',
|
||||
# type=str,
|
||||
# help='user gti file',
|
||||
# action=KvAction,
|
||||
# default=None
|
||||
# )
|
||||
argparser.add_argument(
|
||||
'--seed',
|
||||
type=str,
|
||||
help='random generator seed',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--no-random',
|
||||
help='turn randomization off',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--plain-energy',
|
||||
help='calculate energy on a plain list of files',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--L0',
|
||||
help='process L0 files',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
args = argparser.parse_args()
|
||||
|
||||
self.stem = args.stem
|
||||
self.stem_in = args.stem_in
|
||||
self.srcdir = args.srcdir
|
||||
self.dstdir = args.dstdir
|
||||
self.tmpdir = os.path.join(args.tmpdir, str(self.pid))
|
||||
self.tmpdst = os.path.join(self.tmpdir, 'dst')
|
||||
# self.usergti = args.usergti
|
||||
self.seed = args.seed
|
||||
self.no_random = args.no_random
|
||||
self.plain_energy = args.plain_energy
|
||||
self.process_l0 = args.L0
|
||||
|
||||
if self.dstdir is None:
|
||||
self.dstdir = self.srcdir
|
||||
|
||||
# if self.seed is not None:
|
||||
# self.seed = int(self.seed)
|
||||
|
||||
def print(self):
|
||||
print('Input parameters:')
|
||||
print(' stem = {}'.format(self.stem ))
|
||||
print(' stem-in = {}'.format(self.stem_in ))
|
||||
print(' srcdir = {}'.format(self.srcdir ))
|
||||
print(' dstdir = {}'.format(self.dstdir ))
|
||||
print(' tmpdir = {}'.format(self.tmpdir ))
|
||||
# print(' user-gti = {}'.format(self.usergti ))
|
||||
print(' seed = {}'.format(self.seed ))
|
||||
print(' no random = {}'.format(self.no_random ))
|
||||
print(' plain energy = {}'.format(self.plain_energy))
|
||||
print(' peocess L0 = {}'.format(self.process_l0 ))
|
||||
print()
|
||||
|
||||
caldbinfo = CaldbInfo(self.caldbdir).get()
|
||||
|
||||
print('CALDB version {} [{}]'.format(caldbinfo['CALDBVER'], caldbinfo['CALDBDIR']))
|
||||
print()
|
||||
|
||||
def check(self):
|
||||
if not Dir(self.srcdir).exists():
|
||||
raise TaskError('error: srcdir not found')
|
||||
Dir(self.dstdir).check()
|
||||
Dir(self.tmpdir).check(clean=True)
|
||||
Dir(self.tmpdst).check(clean=True)
|
||||
|
||||
|
||||
class Pipeline(Task):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
print('*** artpipeline v.{}'.format(__version__))
|
||||
|
||||
self._name = 'artpipeline v.{}'.format(__version__)
|
||||
|
||||
self._args = Args()
|
||||
|
||||
self._orifile = None
|
||||
|
||||
def initialize(self):
|
||||
self._args.read()
|
||||
self._args.print()
|
||||
self._args.check()
|
||||
|
||||
if not self._args.no_random:
|
||||
self._random = Random(self._args.seed)
|
||||
else:
|
||||
self._random = None
|
||||
|
||||
def finalize(self):
|
||||
Dir(self._args.tmpdir).delete()
|
||||
|
||||
def runmain(self):
|
||||
layout = self._layout()
|
||||
|
||||
self._make_orient(layout)
|
||||
self._make_attitude(layout)
|
||||
self._process_events(layout)
|
||||
|
||||
print('*** artpipeline finished')
|
||||
|
||||
def _get_stem(self, fname, sep):
|
||||
# get filenme from path
|
||||
fn = os.path.basename(fname)
|
||||
|
||||
#find teln in fname
|
||||
fi = fn.find(sep)
|
||||
|
||||
return fn[:fi]
|
||||
|
||||
def _list_files(self, srcdir, stem_in=None):
|
||||
allfiles = [fi for fi in os.listdir(srcdir)]
|
||||
allfiles.sort()
|
||||
|
||||
if stem_in==None:
|
||||
return allfiles
|
||||
|
||||
return [fi for fi in allfiles if fi.startswith(stem_in)]
|
||||
|
||||
def _plain_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'plain'
|
||||
layout['post'] = ''
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick events
|
||||
srcdir = Dir(self._args.srcdir)
|
||||
eventfiles = self._list_files(srcdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
telfiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
telfiles.append((srcdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = telfiles
|
||||
|
||||
return layout
|
||||
|
||||
def _L0_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'L0'
|
||||
layout['post'] = '_urd.L2'
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick L0 events
|
||||
ufdir = Dir(self._args.srcdir)
|
||||
eventfiles = self._list_files(ufdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
uffiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
uffiles.append((ufdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = uffiles
|
||||
|
||||
# pick orientation
|
||||
orientation = {}
|
||||
|
||||
auxdir = Dir(self._args.srcdir)
|
||||
auxfiles = self._list_files(auxdir.get(), self._args.stem_in)
|
||||
|
||||
for otype in ['gyro', 'sed1', 'sed2', 'bokz']:
|
||||
for auxf in auxfiles:
|
||||
if auxf.endswith('_{}.L0.fits'.format(otype)):
|
||||
ofile = auxdir / auxf
|
||||
|
||||
if ofile.exists():
|
||||
orientation[otype] = ofile.get()
|
||||
|
||||
layout['orientation'] = orientation
|
||||
|
||||
return layout
|
||||
|
||||
def _science_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'science'
|
||||
layout['post'] = '_cl'
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick uf events
|
||||
ufdir = Dir(self._args.srcdir) / 'event_uf'
|
||||
eventfiles = self._list_files(ufdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
uffiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
uffiles.append((ufdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = uffiles
|
||||
|
||||
# pick orientation
|
||||
orientation = {}
|
||||
|
||||
auxdir = Dir(self._args.srcdir) / 'auxiliary'
|
||||
auxfiles = self._list_files(auxdir.get(), self._args.stem_in)
|
||||
|
||||
for otype in ['gyro', 'sed1', 'sed2', 'bokz']:
|
||||
for auxf in auxfiles:
|
||||
if auxf.endswith('_{}.fits'.format(otype)):
|
||||
ofile = auxdir / auxf
|
||||
|
||||
if ofile.exists():
|
||||
orientation[otype] = ofile.get()
|
||||
|
||||
layout['orientation'] = orientation
|
||||
|
||||
return layout
|
||||
|
||||
def _layout(self):
|
||||
if self._args.process_l0:
|
||||
return self._L0_layout()
|
||||
|
||||
if self._args.plain_energy:
|
||||
return self._plain_layout()
|
||||
|
||||
return self._science_layout()
|
||||
|
||||
def _make_orient(self, layout):
|
||||
if layout['type'] == 'plain':
|
||||
print('mode: plain: skipping orienation...')
|
||||
return
|
||||
|
||||
print('making orienation file...')
|
||||
gyrofile = layout['orientation']['gyro']
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(gyrofile, sep='_')
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
if self._args.process_l0:
|
||||
auxdir = Dir(self._args.dstdir)
|
||||
else:
|
||||
auxdir = Dir(self._args.dstdir) / 'auxiliary'
|
||||
|
||||
auxdir.check()
|
||||
|
||||
orifile = '{}_ori.fits'.format(stemout)
|
||||
orifull = (auxdir / orifile).get()
|
||||
|
||||
skyaxis = SkyAxis()
|
||||
skyaxis.process(gyrofile).write(orifull)
|
||||
|
||||
self._orifile = orifull
|
||||
|
||||
print('written: {}'.format(orifull))
|
||||
|
||||
def _make_attitude(self, layout):
|
||||
if layout['type'] == 'plain':
|
||||
print('mode: plain: skipping attitude...')
|
||||
return
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(self._orifile, sep='_')
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
print('making attitude file...')
|
||||
attfile = '{}_att.fits'.format(stemout)
|
||||
|
||||
caldb = CaldbOrientation(self._args.caldbdir, 'GYRO')
|
||||
# skycoord = SkyCoord(caldb, 'GYRO')
|
||||
skycoord = SkyCoord(caldb, self._orifile, 'GYRO')
|
||||
|
||||
# skycoord.attitude(self._orifile).write(attfull)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
skycoord.attitude(TEL).collect()
|
||||
|
||||
if self._args.process_l0:
|
||||
attfull = (Dir(self._args.dstdir) / attfile).get()
|
||||
else:
|
||||
attfull = (Dir(self._args.dstdir) / 'auxiliary' / attfile).get()
|
||||
|
||||
skycoord.write(attfull)
|
||||
|
||||
print('written: {}'.format(attfull))
|
||||
|
||||
def _process_events(self, layout):
|
||||
processed = datetime.datetime.now(tz=TZ_MSK).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
metainfo = {
|
||||
'artpipeline': __version__,
|
||||
'processed' : processed
|
||||
}
|
||||
|
||||
if self._args.plain_energy:
|
||||
metainfo['mode'] = 'plain'
|
||||
elif self._args.process_l0:
|
||||
metainfo['mode'] = 'L0'
|
||||
else:
|
||||
metainfo['mode'] = 'science'
|
||||
|
||||
caldbinfo = CaldbInfo(self._args.caldbdir)
|
||||
|
||||
metainfo['caldb'] = caldbinfo.get()['CALDBVER']
|
||||
|
||||
if not self._args.no_random:
|
||||
random = self._random
|
||||
metainfo['seed'] = random.seed
|
||||
else:
|
||||
random = None
|
||||
metainfo['seed'] = None
|
||||
|
||||
metainfo['events'] = {}
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
print('processing: {}'.format(TEL))
|
||||
|
||||
for fitsname in layout['uf'].get(TEL, []):
|
||||
|
||||
print('event file: {}'.format(fitsname))
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(fitsname, TEL)
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
# read events
|
||||
events = Events(fitsname).read()
|
||||
skyflag = np.zeros_like(events.events.column('TIME'))
|
||||
|
||||
if self._args.plain_energy:
|
||||
times = events.events.column('TIME')
|
||||
events.setcol('RA' , np.zeros_like(times))
|
||||
events.setcol('DEC', np.zeros_like(times))
|
||||
else:
|
||||
# calc ra/dec
|
||||
ocaldb = CaldbOrientation(self._args.caldbdir, 'GYRO')
|
||||
skycoord = SkyCoord(ocaldb, self._orifile)
|
||||
|
||||
skyflag = skycoord.events(self._orifile, events, random)
|
||||
|
||||
# calc energies
|
||||
ecaldb = CaldbEnergy(self._args.caldbdir, TEL)
|
||||
energy = Energy(ecaldb)
|
||||
|
||||
en, enbot, entop, grade =\
|
||||
energy.calc(events.teln, events.events, self._random)
|
||||
|
||||
events.events.setcol('ENERGY' , en )
|
||||
events.events.setcol('ENERGY_BOT', enbot)
|
||||
events.events.setcol('ENERGY_TOP', entop)
|
||||
events.events.setcol('GRADE' , grade)
|
||||
|
||||
# calc flag
|
||||
mcaldb = CaldbMask(self._args.caldbdir, TEL)
|
||||
flag = Flag(mcaldb)
|
||||
evtflag = flag.calc(events.events, events.tstart, events.tstop)
|
||||
|
||||
events.events.setcol('FLAG', skyflag + evtflag)
|
||||
|
||||
# print(events.events.frame)
|
||||
|
||||
if self._args.plain_energy:
|
||||
stemout = self._get_stem(fitsname, '.fits')
|
||||
dstfile = '{}.L2.fits'.format(stemout)
|
||||
else:
|
||||
dstfile = '{}{}{}.fits'.format(stemout, events.telname, layout['post'])
|
||||
|
||||
if self._args.plain_energy or self._args.process_l0:
|
||||
dstdir = Dir(self._args.dstdir)
|
||||
else:
|
||||
dstdir = Dir(self._args.dstdir) / 'event_cl'
|
||||
|
||||
dstdir.check()
|
||||
|
||||
dstfull = (dstdir / dstfile).get()
|
||||
events.write_cl(dstfull, caldbinfo.get())
|
||||
|
||||
aday_start = MissionTime(events.tstart).to_artday()
|
||||
aday_stop = MissionTime(events.tstop ).to_artday()
|
||||
|
||||
metainfo['events'][dstfile] = {
|
||||
'artday' : (aday_start, aday_stop),
|
||||
'mission': (events.tstart, events.tstop),
|
||||
'events' : events.size
|
||||
}
|
||||
|
||||
print('written: {}'.format(dstfull))
|
||||
|
||||
# write meta
|
||||
dstdir = Dir(self._args.dstdir)
|
||||
metafname = dstdir.pathfor('meta.info')
|
||||
with open(metafname, 'w') as metafile:
|
||||
json.dump(metainfo, metafile, indent=4)
|
||||
|
||||
print('written: {}'.format(metafname))
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
Pipeline().run()
|
||||
except TaskError as e:
|
||||
print('error: task execution:', e)
|
||||
except Error as e:
|
||||
print('error: unknown:', e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# PipelineTask().runall()
|
||||
573
artpipeline/src/artpipeline/pipeline.py.new.py
Executable file
573
artpipeline/src/artpipeline/pipeline.py.new.py
Executable file
@@ -0,0 +1,573 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
import datetime
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) # UTC time zone
|
||||
TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) # UTC+3 (Moscow) time zone
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arttools.cmd import Command, ExecutionResult
|
||||
from arttools.task import (
|
||||
Task, TaskError, TaskArgs, TaskCmds
|
||||
)
|
||||
from arttools.errors import Error
|
||||
from arttools.fstools import Dir
|
||||
from arttools.argparse import KvAction
|
||||
from arttools.telescope import Teldef
|
||||
from arttools.random import Random
|
||||
from artdactools.arttime import ArtDay, MissionTime
|
||||
|
||||
from artpipeline.version import __version__
|
||||
|
||||
from artpipeline.caldb import CaldbInfo
|
||||
from artpipeline.caldb import CaldbMask
|
||||
from artpipeline.caldb import CaldbEnergy
|
||||
from artpipeline.caldb import CaldbOrientation
|
||||
|
||||
from artpipeline.events import Events
|
||||
from artpipeline.energy import Flag
|
||||
from artpipeline.energy import Energy
|
||||
from artpipeline.skyaxis import SkyAxis
|
||||
from artpipeline.skycoord import SkyCoord
|
||||
|
||||
|
||||
class Args(TaskArgs):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def read_env(self):
|
||||
if not 'ARTCALDB' in os.environ:
|
||||
raise TaskError('ARTCALDB not found (environment variable not set?)')
|
||||
|
||||
self.caldbdir = os.environ['ARTCALDB']
|
||||
|
||||
def read_cfg(self, cfgfile):
|
||||
pass
|
||||
|
||||
def read_args(self):
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument(
|
||||
'--stem',
|
||||
help='newfile creation stem',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--stem-in',
|
||||
type=str,
|
||||
help='input files stem',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'srcdir',
|
||||
help='input directory',
|
||||
action=KvAction
|
||||
)
|
||||
argparser.add_argument(
|
||||
'tmpdir',
|
||||
help='temporary directory',
|
||||
action=KvAction
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--dstdir',
|
||||
help='output directory',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
# argparser.add_argument(
|
||||
# '--user-gti',
|
||||
# type=str,
|
||||
# help='user gti file',
|
||||
# action=KvAction,
|
||||
# default=None
|
||||
# )
|
||||
argparser.add_argument(
|
||||
'--seed',
|
||||
type=str,
|
||||
help='random generator seed',
|
||||
action=KvAction,
|
||||
default=None
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--no-random',
|
||||
help='turn randomization off',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--plain-energy',
|
||||
help='calculate energy on a plain list of files',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
argparser.add_argument(
|
||||
'--L0',
|
||||
help='process L0 files',
|
||||
action='store_true',
|
||||
default=False
|
||||
)
|
||||
args = argparser.parse_args()
|
||||
|
||||
self.stem = args.stem
|
||||
self.stem_in = args.stem_in
|
||||
self.srcdir = args.srcdir
|
||||
self.dstdir = args.dstdir
|
||||
self.tmpdir = os.path.join(args.tmpdir, str(self.pid))
|
||||
self.tmpdst = os.path.join(self.tmpdir, 'dst')
|
||||
# self.usergti = args.usergti
|
||||
self.seed = args.seed
|
||||
self.no_random = args.no_random
|
||||
self.plain_energy = args.plain_energy
|
||||
self.process_l0 = args.L0
|
||||
|
||||
if self.dstdir is None:
|
||||
self.dstdir = self.srcdir
|
||||
|
||||
# if self.seed is not None:
|
||||
# self.seed = int(self.seed)
|
||||
|
||||
def print(self):
|
||||
print('Input parameters:')
|
||||
print(' stem = {}'.format(self.stem ))
|
||||
print(' stem-in = {}'.format(self.stem_in ))
|
||||
print(' srcdir = {}'.format(self.srcdir ))
|
||||
print(' dstdir = {}'.format(self.dstdir ))
|
||||
print(' tmpdir = {}'.format(self.tmpdir ))
|
||||
# print(' user-gti = {}'.format(self.usergti ))
|
||||
print(' seed = {}'.format(self.seed ))
|
||||
print(' no random = {}'.format(self.no_random ))
|
||||
print(' plain energy = {}'.format(self.plain_energy))
|
||||
print(' peocess L0 = {}'.format(self.process_l0 ))
|
||||
print()
|
||||
|
||||
caldbinfo = CaldbInfo(self.caldbdir).get()
|
||||
|
||||
print('CALDB version {} [{}]'.format(caldbinfo['CALDBVER'], caldbinfo['CALDBDIR']))
|
||||
print()
|
||||
|
||||
def check(self):
|
||||
if not Dir(self.srcdir).exists():
|
||||
raise TaskError('error: srcdir not found')
|
||||
Dir(self.dstdir).check()
|
||||
Dir(self.tmpdir).check(clean=True)
|
||||
Dir(self.tmpdst).check(clean=True)
|
||||
|
||||
|
||||
class PipeConfigL0(object):
|
||||
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self._layout = self._get_layout()
|
||||
|
||||
def _get_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'L0'
|
||||
layout['post'] = '_urd.L2'
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick L0 events
|
||||
ufdir = Dir(self._args.srcdir)
|
||||
eventfiles = self._list_files(ufdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
uffiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
uffiles.append((ufdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = uffiles
|
||||
|
||||
# pick orientation
|
||||
orientation = {}
|
||||
|
||||
auxdir = Dir(self._args.srcdir)
|
||||
auxfiles = self._list_files(auxdir.get(), self._args.stem_in)
|
||||
|
||||
for otype in ['gyro', 'sed1', 'sed2', 'bokz']:
|
||||
for auxf in auxfiles:
|
||||
if auxf.endswith('_{}.fits'.format(otype)):
|
||||
ofile = auxdir / auxf
|
||||
|
||||
if ofile.exists():
|
||||
orientation[otype] = ofile.get()
|
||||
|
||||
layout['orientation'] = orientation
|
||||
|
||||
return layout
|
||||
|
||||
@property
|
||||
def layout(self): return self._layout
|
||||
|
||||
|
||||
class PipeConfigPlain(object):
|
||||
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self._layout = self._get_layout()
|
||||
|
||||
def _list_files(self, srcdir, stem_in=None):
|
||||
allfiles = [fi for fi in os.listdir(srcdir)]
|
||||
allfiles.sort()
|
||||
|
||||
if stem_in==None:
|
||||
return allfiles
|
||||
|
||||
return [fi for fi in allfiles if fi.startswith(stem_in)]
|
||||
|
||||
def _get_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'plain'
|
||||
layout['post'] = ''
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick events
|
||||
srcdir = Dir(self._args.srcdir)
|
||||
eventfiles = self._list_files(srcdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
telfiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
telfiles.append((srcdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = telfiles
|
||||
|
||||
return layout
|
||||
|
||||
@property
|
||||
def layout(self): return self._layout
|
||||
|
||||
|
||||
class PipeConfigScience(object):
|
||||
|
||||
def __init__(self, args):
|
||||
self._args = args
|
||||
self._layout = self._get_layout()
|
||||
|
||||
def _list_files(self, srcdir, stem_in=None):
|
||||
allfiles = [fi for fi in os.listdir(srcdir)]
|
||||
allfiles.sort()
|
||||
|
||||
if stem_in==None:
|
||||
return allfiles
|
||||
|
||||
return [fi for fi in allfiles if fi.startswith(stem_in)]
|
||||
|
||||
def _get_layout(self):
|
||||
layout = {}
|
||||
|
||||
layout['type'] = 'science'
|
||||
layout['post'] = '_cl'
|
||||
layout['uf' ] = {}
|
||||
|
||||
# pick uf events
|
||||
ufdir = Dir(self._args.srcdir) / 'event_uf'
|
||||
eventfiles = self._list_files(ufdir.get(), self._args.stem_in)
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
uffiles = []
|
||||
for fname in eventfiles:
|
||||
if TEL in fname:
|
||||
uffiles.append((ufdir / fname).get())
|
||||
|
||||
layout['uf'][TEL] = uffiles
|
||||
|
||||
# pick orientation
|
||||
orientation = {}
|
||||
|
||||
auxdir = Dir(self._args.srcdir) / 'auxiliary'
|
||||
auxfiles = self._list_files(auxdir.get(), self._args.stem_in)
|
||||
|
||||
for otype in ['gyro', 'sed1', 'sed2', 'bokz']:
|
||||
for auxf in auxfiles:
|
||||
if auxf.endswith('_{}.fits'.format(otype)):
|
||||
ofile = auxdir / auxf
|
||||
|
||||
if ofile.exists():
|
||||
orientation[otype] = ofile.get()
|
||||
|
||||
layout['orientation'] = orientation
|
||||
|
||||
return layout
|
||||
|
||||
@property
|
||||
def layout(self): return self._layout
|
||||
|
||||
|
||||
class EventProcContext(object):
|
||||
pass
|
||||
|
||||
|
||||
class Pipeline(Task):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
print('*** artpipeline v.{}'.format(__version__))
|
||||
|
||||
self._name = 'artpipeline v.{}'.format(__version__)
|
||||
|
||||
self._args = Args()
|
||||
|
||||
self._config = None
|
||||
self._random = None
|
||||
self._ofile = None
|
||||
|
||||
def initialize(self):
|
||||
self._args.read()
|
||||
self._args.print()
|
||||
self._args.check()
|
||||
|
||||
if not self._args.no_random:
|
||||
self._random = Random(self._args.seed)
|
||||
|
||||
if self._args.plain_energy:
|
||||
self._config = PipeConfigPlain(self._args)
|
||||
elif self._args.process_l0:
|
||||
self._config = PipeConfigL0(self._args)
|
||||
else:
|
||||
self._config = PipeConfigScience(self._args)
|
||||
|
||||
def finalize(self):
|
||||
Dir(self._args.tmpdir).delete()
|
||||
|
||||
def runmain(self):
|
||||
# layout = self._layout()
|
||||
|
||||
layout = self._config.layout()
|
||||
|
||||
self._make_orient(layout)
|
||||
self._make_attitude(layout)
|
||||
|
||||
self._process_events(layout)
|
||||
|
||||
print('*** artpipeline finished')
|
||||
|
||||
def _get_stem(self, fname, sep):
|
||||
# get filenme from path
|
||||
fn = os.path.basename(fname)
|
||||
|
||||
#find teln in fname
|
||||
fi = fn.find(sep)
|
||||
|
||||
return fn[:fi]
|
||||
|
||||
def _list_files(self, srcdir, stem_in=None):
|
||||
allfiles = [fi for fi in os.listdir(srcdir)]
|
||||
allfiles.sort()
|
||||
|
||||
if stem_in==None:
|
||||
return allfiles
|
||||
|
||||
return [fi for fi in allfiles if fi.startswith(stem_in)]
|
||||
|
||||
|
||||
def _layout(self):
|
||||
if self._args.process_l0:
|
||||
return self._L0_layout()
|
||||
|
||||
if self._args.plain_energy:
|
||||
return self._plain_layout()
|
||||
|
||||
return self._science_layout()
|
||||
|
||||
def _make_orient(self, layout):
|
||||
if layout['type'] == 'plain':
|
||||
print('mode: plain: skipping orienation...')
|
||||
return
|
||||
|
||||
print('making orienation file...')
|
||||
gyrofile = layout['orientation']['gyro']
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(gyrofile, sep='_')
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
if self._args.process_l0:
|
||||
auxdir = Dir(self._args.dstdir)
|
||||
else:
|
||||
auxdir = Dir(self._args.dstdir) / 'auxiliary'
|
||||
|
||||
auxdir.check()
|
||||
|
||||
ofile = '{}_ori.fits'.format(stemout)
|
||||
ofull = (auxdir / ofile).get()
|
||||
|
||||
skyaxis = SkyAxis()
|
||||
skyaxis.process(gyrofile).write(ofull)
|
||||
|
||||
self._ofile = ofull
|
||||
|
||||
print('written: {}'.format(ofull))
|
||||
|
||||
def _make_attitude(self, layout):
|
||||
if layout['type'] == 'plain':
|
||||
print('mode: plain: skipping attitude...')
|
||||
return
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(self._orifile, sep='_')
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
print('making attitude file...')
|
||||
attfile = '{}_att.fits'.format(stemout)
|
||||
|
||||
if self._args.process_l0:
|
||||
attfull = (Dir(self._args.dstdir) / attfile).get()
|
||||
else:
|
||||
attfull = (Dir(self._args.dstdir) / 'auxiliary' / attfile).get()
|
||||
|
||||
caldb = CaldbOrientation(self._args.caldbdir, 'GYRO')
|
||||
skycoord = SkyCoord(caldb, 'GYRO')
|
||||
|
||||
skycoord.attitude(self._orifile).write(attfull)
|
||||
|
||||
print('written: {}'.format(attfull))
|
||||
|
||||
def _process_events(self, layout):
|
||||
processed = datetime.datetime.now(tz=TZ_MSK).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
metainfo = {
|
||||
'artpipeline': __version__,
|
||||
'processed' : processed
|
||||
}
|
||||
|
||||
if self._args.plain_energy:
|
||||
metainfo['mode'] = 'plain'
|
||||
elif self._args.process_l0:
|
||||
metainfo['mode'] = 'L0'
|
||||
else:
|
||||
metainfo['mode'] = 'science'
|
||||
|
||||
caldbinfo = CaldbInfo(self._args.caldbdir)
|
||||
|
||||
metainfo['caldb'] = caldbinfo.get()['CALDBVER']
|
||||
|
||||
if not self._args.no_random:
|
||||
random = self._random
|
||||
metainfo['seed'] = random.seed
|
||||
else:
|
||||
random = None
|
||||
metainfo['seed'] = None
|
||||
|
||||
metainfo['events'] = {}
|
||||
|
||||
for TEL in Teldef.telnames:
|
||||
print('processing: {}'.format(TEL))
|
||||
|
||||
for fitsname in layout['uf'].get(TEL, []):
|
||||
|
||||
print('event file: {}'.format(fitsname))
|
||||
|
||||
if self._args.stem is None:
|
||||
stemout = self._get_stem(fitsname, TEL)
|
||||
else:
|
||||
stemout = self._args.stem
|
||||
|
||||
# read events
|
||||
events = Events(fitsname).read()
|
||||
|
||||
if self._args.plain_energy:
|
||||
times = events.events.column('TIME')
|
||||
events.setcol('RA' , np.zeros_like(times))
|
||||
events.setcol('DEC', np.zeros_like(times))
|
||||
else:
|
||||
# calc ra/dec
|
||||
ocaldb = CaldbOrientation(self._args.caldbdir, 'GYRO')
|
||||
skycoord = SkyCoord(ocaldb, 'GYRO')
|
||||
|
||||
skycoord.events(self._orifile, events, random)
|
||||
|
||||
# calc energies
|
||||
ecaldb = CaldbEnergy(self._args.caldbdir, TEL)
|
||||
energy = Energy(ecaldb)
|
||||
|
||||
en, enbot, entop, grade =\
|
||||
energy.calc(events.teln, events.events, self._random)
|
||||
|
||||
events.events.setcol('ENERGY' , en )
|
||||
events.events.setcol('ENERGY_BOT', enbot)
|
||||
events.events.setcol('ENERGY_TOP', entop)
|
||||
events.events.setcol('GRADE' , grade)
|
||||
|
||||
# calc flag
|
||||
mcaldb = CaldbMask(self._args.caldbdir, TEL)
|
||||
flag = Flag(mcaldb)
|
||||
f = flag.calc(events.events, events.tstart, events.tstop)
|
||||
|
||||
events.events.setcol('FLAG', f)
|
||||
|
||||
# print(events.events.frame)
|
||||
|
||||
if self._args.plain_energy:
|
||||
stemout = self._get_stem(fitsname, '.fits')
|
||||
dstfile = '{}.L2.fits'.format(stemout)
|
||||
else:
|
||||
dstfile = '{}{}{}.fits'.format(stemout, events.telname, layout['post'])
|
||||
|
||||
if self._args.plain_energy or self._args.process_l0:
|
||||
dstdir = Dir(self._args.dstdir)
|
||||
else:
|
||||
dstdir = Dir(self._args.dstdir) / 'event_cl'
|
||||
|
||||
dstdir.check()
|
||||
|
||||
dstfull = (dstdir / dstfile).get()
|
||||
events.write_cl(dstfull, caldbinfo.get())
|
||||
|
||||
aday_start = MissionTime(events.tstart).to_artday()
|
||||
aday_stop = MissionTime(events.tstop ).to_artday()
|
||||
|
||||
metainfo['events'][dstfile] = {
|
||||
'artday' : (aday_start, aday_stop),
|
||||
'mission': (events.tstart, events.tstop),
|
||||
'events' : events.size
|
||||
}
|
||||
|
||||
print('written: {}'.format(dstfull))
|
||||
|
||||
# write meta
|
||||
metafname = (Dir(self._args.dstdir) / 'meta.info').get()
|
||||
with open(metafname, 'w') as metafile:
|
||||
json.dump(metainfo, metafile, indent=4)
|
||||
|
||||
print('written: {}'.format(metafname))
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
Pipeline().run()
|
||||
except TaskError as e:
|
||||
print('error: task execution:', e)
|
||||
except Error as e:
|
||||
print('error: unknown:', e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# PipelineTask().runall()
|
||||
34
artpipeline/src/artpipeline/skyaxis.py
Executable file
34
artpipeline/src/artpipeline/skyaxis.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
class SkyAxis(object):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._srcfile = None
|
||||
|
||||
def process(self, srcfile):
|
||||
self._srcfile = srcfile
|
||||
return self
|
||||
|
||||
def write(self, dstfile):
|
||||
|
||||
if os.path.exists(dstfile):
|
||||
os.remove(dstfile)
|
||||
|
||||
shutil.copy(self._srcfile, dstfile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
194
artpipeline/src/artpipeline/skycoord.py
Executable file
194
artpipeline/src/artpipeline/skycoord.py
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.gti import Gti
|
||||
from arttools.arttime import MJDREF
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
from artpipeline.orientation import Orientation
|
||||
from artpipeline.orientation import AttitudeStatus
|
||||
|
||||
from artpipeline.events import Events
|
||||
|
||||
from artpipeline.version import __version__
|
||||
|
||||
|
||||
class SkyCoord(object):
|
||||
|
||||
def __init__(self, caldb, orifile, instname=None):
|
||||
super().__init__()
|
||||
|
||||
self._caldb = caldb
|
||||
self._ofile = orifile
|
||||
|
||||
self._instname = instname
|
||||
self._inst = None
|
||||
|
||||
self._mjdref = MJDREF.mjd
|
||||
|
||||
self._data = {}
|
||||
self._adata = {}
|
||||
|
||||
self._process_orient()
|
||||
self._calc_base_attitude()
|
||||
|
||||
def _process_orient(self):
|
||||
self._orient = Orientation(self._caldb)
|
||||
self._odata = self._orient.read(self._ofile)
|
||||
self._gti = Gti.from_hduname(self._ofile, 'STDGTI')
|
||||
|
||||
self._otime = self._odata['time']
|
||||
self._ostatus = AttitudeStatus.pack(self._odata['state'])
|
||||
|
||||
def _calc_base_attitude(self):
|
||||
if self._instname is None:
|
||||
return
|
||||
|
||||
att_ra, att_dec, att_roll \
|
||||
= self._orient.calc_attitudes(self._odata, self._instname)
|
||||
|
||||
self._data['ATTITUDE'] = {
|
||||
'time' : self._otime,
|
||||
'ra' : att_ra ,
|
||||
'dec' : att_dec ,
|
||||
'roll' : att_roll ,
|
||||
'status': self._ostatus
|
||||
}
|
||||
|
||||
x_ra, x_dec, x_roll \
|
||||
= self._orient.calc_attitudes(self._odata)
|
||||
|
||||
self._data['SCX'] = {
|
||||
'time' : self._otime,
|
||||
'ra' : x_ra ,
|
||||
'dec' : x_dec ,
|
||||
'roll' : x_roll ,
|
||||
'status': self._ostatus
|
||||
}
|
||||
|
||||
def attitude(self, instname=None):
|
||||
|
||||
if instname is not None:
|
||||
self._inst = instname
|
||||
else:
|
||||
self._inst = self._instname
|
||||
|
||||
ra, dec, roll \
|
||||
= self._orient.calc_attitudes(self._odata, self._inst)
|
||||
|
||||
self._adata[self._inst] = {
|
||||
'time' : self._otime,
|
||||
'ra' : ra ,
|
||||
'dec' : dec ,
|
||||
'roll' : roll ,
|
||||
'status': self._ostatus
|
||||
}
|
||||
|
||||
return self
|
||||
|
||||
def collect(self, name=None):
|
||||
if self._inst is None:
|
||||
return
|
||||
|
||||
if name is None:
|
||||
name = self._inst
|
||||
|
||||
self._data[name] = self._adata.pop(self._inst, None)
|
||||
self._inst = None
|
||||
|
||||
def _make_hdu(self, hduname):
|
||||
|
||||
if hduname not in self._data:
|
||||
|
||||
hdu = fits.BinTableHDU()
|
||||
hdu.name = hduname
|
||||
|
||||
return hdu
|
||||
|
||||
# --- HDU ---
|
||||
a00 = np.array(self._data[hduname]['time' ], dtype=np.double)
|
||||
a01 = np.array(self._data[hduname]['ra' ], dtype=np.double)
|
||||
a02 = np.array(self._data[hduname]['dec' ], dtype=np.double)
|
||||
a03 = np.array(self._data[hduname]['roll' ], dtype=np.double)
|
||||
a04 = np.array(self._data[hduname]['status'], dtype=int )
|
||||
a05 = np.zeros(a00.size, dtype=int)
|
||||
|
||||
c00 = fits.Column(name='TIME' , format='1D', unit='sec', array=a00)
|
||||
c01 = fits.Column(name='RA' , format='1D', unit='deg', array=a01)
|
||||
c02 = fits.Column(name='DEC' , format='1D', unit='deg', array=a02)
|
||||
c03 = fits.Column(name='ROLL' , format='1D', unit='deg', array=a03)
|
||||
c04 = fits.Column(name='STATUS', format='1I', unit='' , array=a04)
|
||||
c05 = fits.Column(name='FLAG' , format='1I', unit='' , array=a05)
|
||||
|
||||
cols = fits.ColDefs([
|
||||
c00, c01, c02, c03, c04, c05
|
||||
])
|
||||
|
||||
hdu = fits.BinTableHDU.from_columns(cols)
|
||||
hdu.name = hduname
|
||||
|
||||
creator = 'artpipeline v.{}'.format(__version__)
|
||||
|
||||
hdu.header['CREATOR' ] = (creator , 'program and version that created this file' )
|
||||
hdu.header['ORIGIN' ] = ('IKI RAS' , 'institution that created this file' )
|
||||
hdu.header['TELESCOP'] = ('SRG/ART-XC', 'mission name' )
|
||||
hdu.header['MJDREF' ] = (self._mjdref, 'MJD corresponding to SC clock start 2000.0 MSK')
|
||||
hdu.header['TSTART' ] = self._gti.start
|
||||
hdu.header['TSTOP' ] = self._gti.stop
|
||||
hdu.header['CALDBVER'] = '{}'.format(self._caldb.version())
|
||||
|
||||
return hdu
|
||||
|
||||
def write(self, attfile):
|
||||
|
||||
print(self._data.keys())
|
||||
|
||||
prihdu = fits.PrimaryHDU()
|
||||
|
||||
# --- make file ---
|
||||
hdulist = fits.HDUList([
|
||||
prihdu,
|
||||
self._make_hdu('ATTITUDE'),
|
||||
self._make_hdu('T1' ),
|
||||
self._make_hdu('T2' ),
|
||||
self._make_hdu('T3' ),
|
||||
self._make_hdu('T4' ),
|
||||
self._make_hdu('T5' ),
|
||||
self._make_hdu('T6' ),
|
||||
self._make_hdu('T7' ),
|
||||
self._make_hdu('SCX' ),
|
||||
self._gti.make_hdu(self._mjdref)
|
||||
])
|
||||
|
||||
hdulist.writeto(attfile, overwrite=True, checksum=True)
|
||||
|
||||
|
||||
def events(self, orifile, events, random=None):
|
||||
ra, dec, mask \
|
||||
= self._orient.calc_events_coords(
|
||||
self._odata, events.events, events.telname, random)
|
||||
|
||||
|
||||
flag = np.zeros_like(events.events.column('TIME' ))
|
||||
flag[~mask] = 100
|
||||
|
||||
events.setcol('RA' , ra )
|
||||
events.setcol('DEC' , dec )
|
||||
|
||||
return flag
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
1
artpipeline/src/artpipeline/version.py
Executable file
1
artpipeline/src/artpipeline/version.py
Executable file
@@ -0,0 +1 @@
|
||||
__version__ = '2.1.0'
|
||||
BIN
artproducts/.DS_Store
vendored
Normal file
BIN
artproducts/.DS_Store
vendored
Normal file
Binary file not shown.
6
artproducts/pyproject.toml
Normal file
6
artproducts/pyproject.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
17
artproducts/setup.py
Normal file
17
artproducts/setup.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from setuptools import setup
|
||||
|
||||
exec(open('src/artproducts/version.py').read())
|
||||
|
||||
setup(
|
||||
name="artproducts",
|
||||
version=__version__,
|
||||
author="M.Pavlinsky SRG/ART-XC software team",
|
||||
description="high-level products extraction",
|
||||
package_dir={"": "src"},
|
||||
packages=["artproducts"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"artproducts = artproducts.products:main",
|
||||
]
|
||||
},
|
||||
)
|
||||
BIN
artproducts/src/.DS_Store
vendored
Normal file
BIN
artproducts/src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
artproducts/src/artproducts/.DS_Store
vendored
Normal file
BIN
artproducts/src/artproducts/.DS_Store
vendored
Normal file
Binary file not shown.
284
artproducts/src/artproducts/enspectrum.py
Executable file
284
artproducts/src/artproducts/enspectrum.py
Executable file
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
from regions import Regions
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
from artproducts.version import __version__
|
||||
|
||||
|
||||
class EnergySpectrum(object):
|
||||
|
||||
def __init__(self, events, make_individual=False):
|
||||
self._events = events
|
||||
self._start = events.tstart
|
||||
self._stop = events.tstop
|
||||
|
||||
self._phadata = {}
|
||||
self._phameta = {}
|
||||
|
||||
self._make_individual = make_individual
|
||||
|
||||
def create(self, region):
|
||||
|
||||
all_phadata = {
|
||||
'pha' : [],
|
||||
'bins' : [],
|
||||
'edges': [],
|
||||
'gti' : []
|
||||
}
|
||||
|
||||
self._phameta['all'] = {
|
||||
'exposure': 0.0,
|
||||
'deadtime': 0.0,
|
||||
'livetime': 0.0,
|
||||
'events' : 0,
|
||||
'selected': 0,
|
||||
'filter' : None
|
||||
}
|
||||
|
||||
for telname, evtdata in self._events:
|
||||
phadata, phameta = self._make_pha(evtdata, region)
|
||||
|
||||
self._phadata[telname] = phadata
|
||||
self._phameta[telname] = phameta
|
||||
|
||||
for key in ['pha', 'bins', 'edges', 'gti']:
|
||||
all_phadata[key].append(phadata[key])
|
||||
|
||||
for key in ['exposure', 'deadtime', 'livetime', 'events', 'selected']:
|
||||
self._phameta['all'][key] += phameta[key]
|
||||
|
||||
self._phameta['all']['projection'] = phameta['projection']
|
||||
self._phameta['all']['filter' ] = phameta['filter' ]
|
||||
|
||||
all_bins = all_phadata['bins' ][0]
|
||||
all_edges = all_phadata['edges'][0]
|
||||
all_pha = np.sum(all_phadata['pha'], axis=0)
|
||||
all_gti = np.prod(all_phadata['gti'])
|
||||
|
||||
self._phadata['all'] = {
|
||||
'pha' : all_pha ,
|
||||
'bins' : all_bins ,
|
||||
'edges': all_edges,
|
||||
'gti' : all_gti
|
||||
}
|
||||
|
||||
def _make_pha(self, evtdata, region):
|
||||
data = evtdata['events' ]
|
||||
projection = evtdata['projection']
|
||||
|
||||
gti = data.gti
|
||||
exp, deadt, livet \
|
||||
= self._make_timekeys(data, gti)
|
||||
mask, filter_expr \
|
||||
= self._make_mask(data, region, projection)
|
||||
|
||||
times = np.array(data.events.column('TIME'))
|
||||
|
||||
bins = np.linspace(0.1, 65., 650)
|
||||
pha, edges = np.histogram(times[mask], bins=bins)
|
||||
|
||||
phadata = {
|
||||
'pha' : pha ,
|
||||
'bins' : bins ,
|
||||
'edges': edges,
|
||||
'gti' : gti
|
||||
}
|
||||
|
||||
phameta = {
|
||||
'projection': projection,
|
||||
'exposure' : exp ,
|
||||
'deadtime' : deadt,
|
||||
'livetime' : livet,
|
||||
'events' : len(times),
|
||||
'selected' : len(times[mask]),
|
||||
'filter' : filter_expr
|
||||
}
|
||||
|
||||
return phadata, phameta
|
||||
|
||||
def _make_timekeys(self, evtdata, gti):
|
||||
exposure = gti.exposure
|
||||
|
||||
deadtime = 0
|
||||
for gtistart, gtistop in gti.array:
|
||||
deadtime += evtdata.deadtime(gtistart, gtistop)
|
||||
|
||||
livetime = exposure - deadtime
|
||||
|
||||
return exposure, deadtime, livetime
|
||||
|
||||
def _make_goodevt_mask(self, evtdata, grade=9, flag=0):
|
||||
maskexpr = '(GRADE < {grade}) && (FLAG == {flag})'
|
||||
maskexpr = maskexpr.format(grade=grade, flag=flag)
|
||||
|
||||
grademask = evtdata.events.column('GRADE') < grade
|
||||
flagmask = evtdata.events.column('FLAG' ) == flag
|
||||
|
||||
return np.all([grademask, flagmask], axis=0), maskexpr
|
||||
|
||||
def _make_mask(self, evtdata, region, projection):
|
||||
goodmask, goodexpr \
|
||||
= self._make_goodevt_mask(evtdata)
|
||||
|
||||
regionmask = region.mask(projection,
|
||||
np.array(evtdata.events.column('RA' )),
|
||||
np.array(evtdata.events.column('DEC')))
|
||||
|
||||
expr = '{}'.format(goodexpr)
|
||||
|
||||
return np.all([goodmask, regionmask], axis=0), expr
|
||||
|
||||
def _write_pha(self, name, dstfname, arffile=None, rmffile=None, backfile=None, backscale=None):
|
||||
if not name in self._phadata:
|
||||
return False
|
||||
|
||||
mjdref = 51543.875
|
||||
|
||||
phadata = self._phadata[name]
|
||||
phameta = self._phameta[name]
|
||||
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
a1 = np.arange(649)
|
||||
a2 = np.array(phadata['pha'])
|
||||
a3 = np.zeros(a1.size)
|
||||
a4 = np.ones (a1.size)
|
||||
|
||||
c1 = fits.Column(name='CHANNEL' , format='1J', unit='' , array=a1)
|
||||
c2 = fits.Column(name='COUNTS' , format='1J', unit='count', array=a2)
|
||||
c3 = fits.Column(name='QUALITY' , format='1I', unit='' , array=a3)
|
||||
c4 = fits.Column(name='GROUPING', format='1I', unit='' , array=a4)
|
||||
|
||||
cols = fits.ColDefs([c1, c2, c3, c4])
|
||||
|
||||
hdu1 = fits.BinTableHDU.from_columns(cols)
|
||||
hdu1.name = 'SPECTRUM'
|
||||
hdu1.header['MISSION' ] = 'SRG'
|
||||
hdu1.header['TELESCOP'] = 'ART-XC'
|
||||
hdu1.header['INSTRUME'] = name
|
||||
hdu1.header['EXPOSURE'] = phameta['exposure'] - phameta['deadtime']
|
||||
|
||||
hdu1.header['AREASCAL'] = 1.
|
||||
|
||||
if backfile is not None:
|
||||
hdu1.header['BACKFILE'] = backfile
|
||||
|
||||
if backscale is not None:
|
||||
hdu1.header['BACKSCAL'] = backscale
|
||||
else:
|
||||
hdu1.header['BACKSCAL'] = 1.
|
||||
|
||||
hdu1.header['CORRFILE'] = ''
|
||||
hdu1.header['CORRSCAL'] = -1
|
||||
|
||||
if arffile is not None:
|
||||
hdu1.header['ANCRFILE'] = arffile
|
||||
|
||||
if rmffile is not None:
|
||||
hdu1.header['RESPFILE'] = rmffile
|
||||
|
||||
hdu1.header['HDUCLASS'] = 'OGIP'
|
||||
hdu1.header['HDUCLAS1'] = 'SPECTRUM'
|
||||
hdu1.header['HDUVERS' ] = '1.2.1'
|
||||
|
||||
hdu1.header['POISSERR'] = True
|
||||
|
||||
hdu1.header['CHANTYPE'] = 'PI'
|
||||
hdu1.header['DETCHANS'] = 649
|
||||
hdu1.header['TLMIN1' ] = 0
|
||||
hdu1.header['TLMAX1' ] = 648
|
||||
|
||||
hdu2 = phadata['gti'].make_hdu(mjdref)
|
||||
|
||||
hdulist = fits.HDUList([hdu, hdu1, hdu2])
|
||||
hdulist.writeto(dstfname, overwrite=True, checksum=True)
|
||||
|
||||
return True
|
||||
|
||||
def write(self, dstfname, respfiles=None, backfiles=None, backscale=None):
|
||||
written = {
|
||||
'files': []
|
||||
}
|
||||
|
||||
allfname = dstfname.format('_all')
|
||||
backname = None
|
||||
if backfiles is not None:
|
||||
if 'all' in backfiles:
|
||||
backname = backfiles['all']
|
||||
|
||||
arffile = None
|
||||
rmffile = None
|
||||
|
||||
if respfiles is not None:
|
||||
if 'all' in respfiles['arf']:
|
||||
arffile = respfiles['arf']['all']
|
||||
|
||||
if 'all' in respfiles['rmf']:
|
||||
rmffile = respfiles['rmf']['all']
|
||||
|
||||
# def _write_pha(self, name, dstfname, arffile=None, respfile=None, backfile=None, backscale=None):
|
||||
|
||||
if self._write_pha(
|
||||
'all',
|
||||
allfname,
|
||||
arffile=arffile,
|
||||
rmffile=rmffile,
|
||||
backfile=backname,
|
||||
backscale=backscale):
|
||||
|
||||
written['all' ] = os.path.basename(allfname)
|
||||
written['files'].append(allfname)
|
||||
|
||||
if not self._make_individual:
|
||||
return written
|
||||
|
||||
for telname in Teldef.telnames:
|
||||
|
||||
print(telname)
|
||||
|
||||
telfname = dstfname.format(telname)
|
||||
backname = None
|
||||
if backfiles is not None:
|
||||
if telname in backfiles:
|
||||
backname = backfiles[telname]
|
||||
|
||||
arffile = None
|
||||
rmffile = None
|
||||
|
||||
if respfiles is not None:
|
||||
if telname in respfiles['arf']:
|
||||
arffile = respfiles['arf'][telname]
|
||||
|
||||
if telname in respfiles['rmf']:
|
||||
rmffile = respfiles['rmf'][telname]
|
||||
|
||||
if self._write_pha(
|
||||
telname,
|
||||
telfname,
|
||||
arffile=arffile,
|
||||
rmffile=rmffile,
|
||||
backfile=backname,
|
||||
backscale=backscale):
|
||||
|
||||
written[telname] = os.path.basename(telfname)
|
||||
written['files'].append(telfname)
|
||||
|
||||
return written
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
128
artproducts/src/artproducts/eventspack.py
Executable file
128
artproducts/src/artproducts/eventspack.py
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arttools.wcs import Wcs
|
||||
from arttools.events import Events
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
|
||||
class Calculate(object):
|
||||
|
||||
def __init__(self, method, parameters, datafields=None):
|
||||
self._method = method
|
||||
self._parameters = parameters
|
||||
self._datafields = datafields
|
||||
|
||||
self._result = {
|
||||
'all': 0
|
||||
}
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
return self._result
|
||||
|
||||
def apply(self, telname, data):
|
||||
params = [data[p] for p in self._parameters]
|
||||
|
||||
value = self._method(*params)
|
||||
|
||||
self._result[telname] = value
|
||||
self._result['all' ] += value
|
||||
|
||||
|
||||
class EventsPack(object):
|
||||
|
||||
def __init__(self, evtdir):
|
||||
self._evtdir = evtdir
|
||||
|
||||
self._epoch0 = 0
|
||||
self._tstart = 0
|
||||
self._tstop = 0
|
||||
self._count = 0
|
||||
|
||||
self._data = {}
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data.items())
|
||||
|
||||
@property
|
||||
def empty(self):
|
||||
return not self._data
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return self._count
|
||||
|
||||
@property
|
||||
def epoch0(self):
|
||||
return self._epoch0
|
||||
|
||||
@property
|
||||
def tstart(self):
|
||||
return self._tstart
|
||||
|
||||
@property
|
||||
def tstop(self):
|
||||
return self._tstop
|
||||
|
||||
def _event_files(self, stem):
|
||||
for telname in Teldef.telnames:
|
||||
evtfile = '{}{}_cl.fits'.format(stem, telname)
|
||||
yield self._evtdir.pathfor(evtfile)
|
||||
|
||||
def read(self, stem, epoch0):
|
||||
starts = []
|
||||
stops = []
|
||||
|
||||
count = 0
|
||||
|
||||
for eventfile in self._event_files(stem):
|
||||
events = Events(eventfile).read()
|
||||
|
||||
if events.empty:
|
||||
continue
|
||||
|
||||
starts.append(events.tstart)
|
||||
stops.append(events.tstop)
|
||||
|
||||
count += events.size
|
||||
|
||||
projection = Wcs.from_eventfile(eventfile)
|
||||
|
||||
self._data[events.telname] = {
|
||||
'events' : events,
|
||||
'projection': projection.get()
|
||||
}
|
||||
|
||||
self._tstart = np.max(starts)
|
||||
self._tstop = np.min(stops )
|
||||
|
||||
if epoch0 is not None:
|
||||
self._epoch0 = epoch0
|
||||
else:
|
||||
self._epoch0 = self._tstart
|
||||
|
||||
self._count = count
|
||||
|
||||
return self
|
||||
|
||||
def call(self, callee):
|
||||
for telname in Teldef.telnames:
|
||||
if telname not in self._data:
|
||||
continue
|
||||
|
||||
callee.apply(telname, self._data[telname])
|
||||
|
||||
return callee.result
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
117
artproducts/src/artproducts/eventswcs.py
Executable file
117
artproducts/src/artproducts/eventswcs.py
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
import warnings
|
||||
warnings.simplefilter('ignore')
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.wcs import Wcs
|
||||
from arttools.fstools import Dir
|
||||
|
||||
from arttools.gti import Gti
|
||||
from arttools.events import Events
|
||||
|
||||
from artproducts.version import __version__
|
||||
|
||||
|
||||
class EventsWcs(object):
|
||||
|
||||
def __init__(self, srcfile, wcs):
|
||||
self._wcs = wcs
|
||||
self._projection = Wcs.from_file(wcs)
|
||||
self._eventfile = Events(srcfile)
|
||||
|
||||
@property
|
||||
def file(self):
|
||||
return self._eventfile
|
||||
|
||||
@property
|
||||
def tstart(self):
|
||||
return self._eventfile.tstart
|
||||
|
||||
@property
|
||||
def tstop(self):
|
||||
return self._eventfile.tstop
|
||||
|
||||
@property
|
||||
def gti(self):
|
||||
return self._eventfile.gti
|
||||
|
||||
def deadtime(self, start, stop):
|
||||
return self._eventfile.deadtime(start, stop)
|
||||
|
||||
def _wcs_mask(self, x, y):
|
||||
xsize = int(self._projection.get().wcs.crpix[0]*2 + 1)
|
||||
ysize = int(self._projection.get().wcs.crpix[1]*2 + 1)
|
||||
|
||||
asize = x * ysize + xsize
|
||||
|
||||
wcs_mask = np.all([x > 0, y > 0, asize < xsize * ysize], axis=0)
|
||||
|
||||
return wcs_mask
|
||||
|
||||
def prepare(self, fitwcs=False, usergti=None):
|
||||
self._eventfile.read()
|
||||
|
||||
ra = np.array(self._eventfile.events.column('RA' ))
|
||||
dec = np.array(self._eventfile.events.column('DEC' ))
|
||||
times = np.array(self._eventfile.events.column('TIME'))
|
||||
evt_gti = self._eventfile.gti
|
||||
|
||||
self._x = np.zeros_like(ra )
|
||||
self._y = np.zeros_like(dec)
|
||||
|
||||
self._x, self._y \
|
||||
= self._projection.world2pix(ra, dec)
|
||||
|
||||
if fitwcs:
|
||||
wcs_mask = self._wcs_mask(self._x, self._y)
|
||||
else:
|
||||
wcs_mask = np.full_like(times, True)
|
||||
|
||||
wcs_gti = Gti.from_mask(times, wcs_mask).normalize(edge_delta=10.)
|
||||
|
||||
gti = evt_gti * wcs_gti
|
||||
|
||||
if usergti is not None:
|
||||
self._gti = gti * Gti.from_hduname(usergti, 'stdgti')
|
||||
else:
|
||||
self._gti = gti
|
||||
|
||||
self._mask = self._gti.mask(times)
|
||||
|
||||
def read(self):
|
||||
self._eventfile.read()
|
||||
|
||||
def write(self, dstfile, creator=''):
|
||||
|
||||
pos = 13
|
||||
keys = ['X', 'Y']
|
||||
layout = {
|
||||
'X': {'format': '1D', 'unit': ''},
|
||||
'Y': {'format': '1D', 'unit': ''},
|
||||
}
|
||||
data = {
|
||||
'X': self._x,
|
||||
'Y': self._y
|
||||
}
|
||||
|
||||
self._eventfile.modify_and_write_cl(dstfile, creator, keys, layout, data, pos, self._mask, self._gti)
|
||||
|
||||
with fits.open(dstfile, mode='update') as dsthdul:
|
||||
dsthdul['EVENTS'].header.update(self._projection.get().to_header())
|
||||
dsthdul['EVENTS'].header['MJDREF'] = (self._eventfile.mjdref, 'MJD corresponding to SC clock start 2000.0 MSK')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
26
artproducts/src/artproducts/histograms.py
Executable file
26
artproducts/src/artproducts/histograms.py
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
class Hist(object):
|
||||
|
||||
def __init__(self, vstart, vstop):
|
||||
pass
|
||||
|
||||
|
||||
class TimeHist(object):
|
||||
|
||||
def __init__(self, epoch0, start, stop, binsize):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
149
artproducts/src/artproducts/image.py
Executable file
149
artproducts/src/artproducts/image.py
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# import warnings
|
||||
# warnings.simplefilter('ignore')
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.wcs import Wcs
|
||||
from arttools.fstools import Dir
|
||||
from arttools.events import Events
|
||||
|
||||
from artproducts.version import __version__
|
||||
|
||||
from artproducts.eventwcs import EventWcs
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
def __init__(self, eventfiles, wcs):
|
||||
self._wcs = wcs
|
||||
self._projection = Wcs.from_file(wcs)
|
||||
self._eventfiles = eventfiles
|
||||
|
||||
self._elow = 0
|
||||
self._ehigh = 0
|
||||
|
||||
self._mjdref = 0
|
||||
self._exposure = 0
|
||||
self._deadtime = 0
|
||||
self._tstart = 0
|
||||
self._tstop = 0
|
||||
self._onsource = 0
|
||||
|
||||
def make(self, elow, ehigh):
|
||||
self._image = self._make_image()
|
||||
self._elow = elow
|
||||
self._ehigh = ehigh
|
||||
|
||||
image_exp = []
|
||||
image_deadt = []
|
||||
image_start = []
|
||||
image_stop = []
|
||||
|
||||
for evtfile in self._eventfiles:
|
||||
events = EventWcs(evtfile, self._wcs)
|
||||
events.read()
|
||||
|
||||
self._mjdref = events.file.mjdref
|
||||
self._image = self._events_to_image(events, elow, ehigh, self._image)
|
||||
|
||||
start = events.tstart
|
||||
stop = events.tstop
|
||||
|
||||
image_exp.append(events.gti.exposure)
|
||||
image_deadt.append(events.deadtime(start, stop))
|
||||
image_start.append(start)
|
||||
image_stop.append(stop)
|
||||
|
||||
self._exposure = np.sum(image_exp )
|
||||
self._deadtime = np.sum(image_deadt)
|
||||
self._tstart = np.min(image_start)
|
||||
self._tstop = np.max(image_stop )
|
||||
self._onsource = self._tstop - self._tstart
|
||||
|
||||
print('onsource [sec] = {}'.format(self._onsource))
|
||||
print('exposure [sec] = {} (average per head {})'.format(self._exposure, self._exposure / 7.))
|
||||
print('deadtime [sec] = {} (average per head {})'.format(self._deadtime, self._deadtime / 7.))
|
||||
print('tstart [sec] = {}'.format(self._tstart ))
|
||||
print('tstop [sec] = {}'.format(self._tstop ))
|
||||
|
||||
def write(self, dstfile):
|
||||
fits.PrimaryHDU(
|
||||
data=self._image,
|
||||
header=self._projection.get().to_header()
|
||||
).writeto(dstfile, overwrite=True, checksum=True)
|
||||
|
||||
gtistart = self._tstart
|
||||
gtistop = self._tstop
|
||||
deadtime = self._deadtime
|
||||
exposure = self._exposure - self._deadtime
|
||||
onsource = self._onsource
|
||||
|
||||
with fits.open(dstfile, mode='update') as hdul:
|
||||
hdul[0].header['MJDREF' ] = (self._mjdref, '[d] MJDREF')
|
||||
hdul[0].header['TSTART' ] = (gtistart , '[sec]' )
|
||||
hdul[0].header['TSTOP' ] = (gtistop , '[sec]' )
|
||||
hdul[0].header['ONSOURCE'] = (onsource , '[sec]' )
|
||||
hdul[0].header['EXPOSURE'] = (exposure , '[sec]' )
|
||||
hdul[0].header['E_LOW' ] = (self._elow , '[keV]' )
|
||||
hdul[0].header['E_HIGH' ] = (self._ehigh , '[keV]' )
|
||||
|
||||
print('written: {}'.format(dstfile))
|
||||
|
||||
def _make_mask(self, events, elow, ehigh):
|
||||
en = events.file.events.column('ENERGY')
|
||||
grade = events.file.events.column('GRADE' )
|
||||
flag = events.file.events.column('FLAG' )
|
||||
|
||||
mask = np.all([
|
||||
en >= elow ,
|
||||
en <= ehigh,
|
||||
grade < 9 ,
|
||||
flag == 0
|
||||
], axis=0)
|
||||
|
||||
return mask
|
||||
|
||||
def _make_image(self):
|
||||
xsize = int(self._projection.get().wcs.crpix[0]*2 + 1)
|
||||
ysize = int(self._projection.get().wcs.crpix[1]*2 + 1)
|
||||
image = np.zeros((ysize, xsize), dtype=np.double)
|
||||
|
||||
return image
|
||||
|
||||
def _events_to_image(self, events, elow, ehigh, image):
|
||||
times = np.array(events.file.events.column('TIME'))
|
||||
ra = np.array(events.file.events.column('RA' ))
|
||||
dec = np.array(events.file.events.column('DEC' ))
|
||||
|
||||
xsize = int(self._projection.get().wcs.crpix[0]*2 + 1)
|
||||
ysize = int(self._projection.get().wcs.crpix[1]*2 + 1)
|
||||
|
||||
x, y = self._projection.world2pix(ra, dec)
|
||||
|
||||
asize = x * ysize + xsize
|
||||
xy_mask = np.all([x > 0, y > 0, asize < xsize * ysize], axis=0)
|
||||
|
||||
evt_mask = events.gti.mask(times)
|
||||
img_mask = self._make_mask(events, elow, ehigh)
|
||||
|
||||
masklist = [evt_mask, img_mask, xy_mask]
|
||||
mask = np.all(masklist, axis=0)
|
||||
|
||||
np.add.at(image, (x[mask], y[mask]), 1)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
327
artproducts/src/artproducts/lightcurve.py
Executable file
327
artproducts/src/artproducts/lightcurve.py
Executable file
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
from regions import Regions
|
||||
|
||||
from arttools.gti import Gti
|
||||
from arttools.wcs import Wcs
|
||||
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
from artproducts.version import __version__
|
||||
|
||||
class Lightcurve(object):
|
||||
|
||||
def __init__(self, events, binsize, make_individual=False):
|
||||
self._events = events
|
||||
self._epoch0 = events.epoch0
|
||||
self._start = events.tstart
|
||||
self._stop = events.tstop
|
||||
self._binsize = binsize
|
||||
|
||||
self._lcdata = {}
|
||||
self._lcmeta = {}
|
||||
|
||||
self._make_inividual = make_individual
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
return self._lcmeta
|
||||
|
||||
@property
|
||||
def get(self):
|
||||
return self._lcdata
|
||||
|
||||
def create(self, region, elow, ehigh):
|
||||
|
||||
all_lcdata = {
|
||||
'time' : [],
|
||||
'count' : [],
|
||||
'error' : [],
|
||||
'exposure': [],
|
||||
'flag' : [],
|
||||
'gti' : []
|
||||
}
|
||||
|
||||
self._lcmeta['all'] = {
|
||||
'exposure': 0.0,
|
||||
'deadtime': 0.0,
|
||||
'livetime': 0.0,
|
||||
'elow' : elow ,
|
||||
'ehigh' : ehigh,
|
||||
'events' : 0,
|
||||
'selected': 0,
|
||||
'filter' : None
|
||||
}
|
||||
|
||||
for telname, evtdata in self._events:
|
||||
|
||||
lcdata, lcmeta = self._make_lcurve(evtdata, region, elow, ehigh)
|
||||
|
||||
self._lcdata[telname] = lcdata
|
||||
self._lcmeta[telname] = lcmeta
|
||||
|
||||
for key in ['time', 'count', 'error', 'exposure', 'flag', 'gti']:
|
||||
all_lcdata[key].append(lcdata[key])
|
||||
|
||||
for key in ['exposure', 'deadtime', 'livetime', 'events', 'selected']:
|
||||
self._lcmeta['all'][key] += lcmeta[key]
|
||||
|
||||
self._lcmeta['all']['filter'] = lcmeta['filter']
|
||||
|
||||
|
||||
all_time = all_lcdata['time'][0]
|
||||
all_count = np.sum(all_lcdata['count'], axis=0)
|
||||
all_error = np.sqrt(np.sum(np.power(all_lcdata['error'], 2.), axis=0))
|
||||
|
||||
all_exposure = np.sum(all_lcdata['exposure'], axis=0)
|
||||
|
||||
all_flag = np.zeros_like(all_lcdata['flag'][0], dtype=int)
|
||||
one_mask = np.any([ff == 1 for ff in all_lcdata['flag']], axis=0)
|
||||
two_mask = np.any([ff == 2 for ff in all_lcdata['flag']], axis=0)
|
||||
|
||||
all_flag[one_mask] = 1
|
||||
all_flag[two_mask] = 2
|
||||
|
||||
all_gti = np.prod(all_lcdata['gti'])
|
||||
|
||||
self._lcdata['all'] = {
|
||||
'time' : all_time,
|
||||
'count' : all_count,
|
||||
'error' : all_error,
|
||||
'exposure': all_exposure,
|
||||
'flag' : all_flag,
|
||||
'gti' : all_gti
|
||||
}
|
||||
|
||||
def _make_bins(self):
|
||||
dt = self._start - self._epoch0
|
||||
steps = np.ceil(dt / self._binsize)
|
||||
t0 = self._epoch0 + self._binsize * steps
|
||||
|
||||
edges = np.arange(t0, self._stop, self._binsize)
|
||||
bins = (edges[ :-1] + edges[ 1: ]) / 2.
|
||||
|
||||
return edges, bins
|
||||
|
||||
def _make_flag(self, edges, bins, gti):
|
||||
flag = np.ones_like(bins, dtype=int)
|
||||
|
||||
edge_intervals = zip(edges[:-1], edges[1:])
|
||||
edge_intervals = Gti([*edge_intervals])
|
||||
|
||||
index = 0
|
||||
for interval in edge_intervals.array:
|
||||
interval_gti = Gti(interval) * gti
|
||||
flag[index] = 2 if np.diff(interval) > interval_gti.exposure else 0
|
||||
|
||||
index += 1
|
||||
|
||||
return flag
|
||||
|
||||
def _make_lcurve(self, evtdata, region, elow, ehigh):
|
||||
data = evtdata['events' ]
|
||||
proj = evtdata['projection']
|
||||
|
||||
edges, bins = self._make_bins()
|
||||
|
||||
gti = data.gti * Gti([self._start, self._stop])
|
||||
exp, deadt, livet \
|
||||
= self._make_timekeys(data, gti)
|
||||
mask, filter_expr \
|
||||
= self._make_mask(data, region, proj, elow, ehigh)
|
||||
|
||||
times = np.array(data.events.column('TIME'))
|
||||
counts, _ = np.histogram(times[mask], bins=edges)
|
||||
errors = np.sqrt(counts)
|
||||
|
||||
binexp = np.diff(edges)
|
||||
bindeadt, _ = data.bindeadtime(edges, bins)
|
||||
|
||||
flag = self._make_flag(edges, bins, gti)
|
||||
|
||||
exposure = binexp - bindeadt
|
||||
|
||||
lcdata = {
|
||||
'time' : bins,
|
||||
'count' : counts,
|
||||
'error' : errors,
|
||||
'exposure': exposure,
|
||||
'flag' : flag,
|
||||
'gti' : gti
|
||||
}
|
||||
|
||||
lcmeta = {
|
||||
'exposure': exp ,
|
||||
'deadtime': deadt,
|
||||
'livetime': livet,
|
||||
'elow' : elow ,
|
||||
'ehigh' : ehigh,
|
||||
'events' : len(times),
|
||||
'selected': len(times[mask]),
|
||||
'filter' : filter_expr
|
||||
}
|
||||
|
||||
return lcdata, lcmeta
|
||||
|
||||
def _make_timekeys(self, evtdata, gti):
|
||||
exposure = gti.exposure
|
||||
|
||||
deadtime = 0
|
||||
for gtistart, gtistop in gti.array:
|
||||
deadtime += evtdata.deadtime(gtistart, gtistop)
|
||||
|
||||
livetime = exposure - deadtime
|
||||
|
||||
return exposure, deadtime, livetime
|
||||
|
||||
def _make_goodevt_mask(self, evtdata, grade=9, flag=0):
|
||||
maskexpr = '(GRADE < {grade}) && (FLAG == {flag})'
|
||||
maskexpr = maskexpr.format(grade=grade, flag=flag)
|
||||
|
||||
grademask = evtdata.events.column('GRADE') < grade
|
||||
flagmask = evtdata.events.column('FLAG' ) == flag
|
||||
|
||||
return np.all([grademask, flagmask], axis=0), maskexpr
|
||||
|
||||
def _make_energy_mask(self, evtdata, elow, ehigh):
|
||||
maskexpr = '(ENERGY >= {elow} && ENERGY <= {ehigh})'
|
||||
maskexpr = maskexpr.format(elow=elow, ehigh=ehigh)
|
||||
|
||||
return np.all([
|
||||
evtdata.events.column('ENERGY') >= elow ,
|
||||
evtdata.events.column('ENERGY') <= ehigh
|
||||
], axis = 0), maskexpr
|
||||
|
||||
def _make_mask(self, evtdata, region, projection, elow, ehigh):
|
||||
goodmask, goodexpr \
|
||||
= self._make_goodevt_mask(evtdata)
|
||||
|
||||
enmask, enexpr \
|
||||
= self._make_energy_mask(evtdata, elow, ehigh)
|
||||
|
||||
regionmask = region.mask(projection,
|
||||
np.array(evtdata.events.column('RA' )),
|
||||
np.array(evtdata.events.column('DEC')))
|
||||
|
||||
expr = '{} && {}'.format(goodexpr, enexpr)
|
||||
|
||||
return np.all([goodmask, enmask, regionmask], axis=0), expr
|
||||
|
||||
def _write_curve(self, name, dstfname, backfile=None):
|
||||
if not name in self._lcdata:
|
||||
return False
|
||||
|
||||
mjdref = 51543.875
|
||||
|
||||
lcdata = self._lcdata[name]
|
||||
lcmeta = self._lcmeta[name]
|
||||
|
||||
time = lcdata['time' ]
|
||||
rate = lcdata['count'] / lcdata['exposure']
|
||||
error = lcdata['error'] / lcdata['exposure']
|
||||
flag = lcdata['flag' ]
|
||||
gti = lcdata['gti' ]
|
||||
|
||||
# # --- DEBUG ---
|
||||
# import matplotlib.pyplot as plt
|
||||
|
||||
# plt.title(name)
|
||||
# # plt.plot(lcdata['time'], rate)
|
||||
# # plt.errorbar(lcdata['time'], rate, yerr=error)
|
||||
# # plt.plot(lcdata['time'], lcdata['count'])
|
||||
# plt.errorbar(lcdata['time'], lcdata['count'], yerr=lcdata['error'])
|
||||
# # plt.plot(hkdata['times'][1:], countdiff)
|
||||
# # plt.plot(edges[1:], fracexp)
|
||||
# plt.show()
|
||||
# # ---
|
||||
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
# --- STDGTI HDU ---
|
||||
a1 = np.array(time )
|
||||
a2 = np.array(rate )
|
||||
a3 = np.array(error)
|
||||
a4 = np.array(flag )
|
||||
|
||||
c1 = fits.Column(name='TIME' , format='1D', unit='sec' , array=a1)
|
||||
c2 = fits.Column(name='RATE' , format='1E', unit='counts/sec', array=a2)
|
||||
c3 = fits.Column(name='ERROR', format='1E', unit='' , array=a3)
|
||||
c4 = fits.Column(name='FLAG' , format='1I', unit='' , array=a4)
|
||||
|
||||
cols = fits.ColDefs([c1, c2, c3, c4])
|
||||
|
||||
hdu1 = fits.BinTableHDU.from_columns(cols)
|
||||
hdu1.name = 'RATE'
|
||||
hdu1.header['ORIGIN' ] = 'IKI RAS'
|
||||
hdu1.header['CREATOR' ] = 'artproducts v.{}'.format(__version__)
|
||||
hdu1.header['TELESCOP'] = 'SRG/ART-XC'
|
||||
hdu1.header['INSTRUME'] = name
|
||||
hdu1.header['MJDREF' ] = mjdref
|
||||
hdu1.header['TSTART' ] = (self._start , '[sec]')
|
||||
hdu1.header['TSTOP' ] = (self._stop , '[sec]')
|
||||
hdu1.header['EXPOSURE'] = (lcmeta['exposure'], '[sec]')
|
||||
hdu1.header['DEADTIME'] = (lcmeta['deadtime'], '[sec]')
|
||||
hdu1.header['LIVETIME'] = (lcmeta['livetime'], '[sec]')
|
||||
hdu1.header['E_LOW' ] = (lcmeta['elow' ], '[keV]')
|
||||
hdu1.header['E_HIGH' ] = (lcmeta['ehigh' ], '[keV]')
|
||||
hdu1.header['BIN' ] = (self._binsize , '[sec]')
|
||||
hdu1.header['TIMEDEL' ] = (self._binsize , '[sec]')
|
||||
|
||||
if backfile is not None:
|
||||
hdu1.header['BACKFILE'] = backfile
|
||||
|
||||
hdu2 = gti.make_hdu(mjdref)
|
||||
|
||||
# --- make FITS file ---
|
||||
hdulist = fits.HDUList([hdu, hdu1, hdu2])
|
||||
hdulist.writeto(dstfname, overwrite=True, checksum=True)
|
||||
|
||||
return True
|
||||
|
||||
def write(self, dstfname, backfiles=None):
|
||||
written = {
|
||||
'files': []
|
||||
}
|
||||
|
||||
allfname = dstfname.format('_all')
|
||||
backname = None
|
||||
if backfiles is not None:
|
||||
if 'all' in backfiles:
|
||||
backname = backfiles['all']
|
||||
|
||||
if self._write_curve('all', allfname, backname):
|
||||
written['all' ] = os.path.basename(allfname)
|
||||
written['files'].append(allfname)
|
||||
|
||||
if not self._make_inividual:
|
||||
return written
|
||||
|
||||
for telname in Teldef.telnames:
|
||||
telfname = dstfname.format(telname)
|
||||
backname = None
|
||||
if backfiles is not None:
|
||||
if telname in backfiles:
|
||||
backname = backfiles[telname]
|
||||
|
||||
if self._write_curve(telname, telfname, backname):
|
||||
written[telname] = os.path.basename(telfname)
|
||||
written['files'].append(telfname)
|
||||
|
||||
return written
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
486
artproducts/src/artproducts/products.py
Executable file
486
artproducts/src/artproducts/products.py
Executable file
@@ -0,0 +1,486 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arttools.cmd import Command, ExecutionResult
|
||||
from arttools.task import (
|
||||
Task, TaskError, TaskArgs, TaskCmds
|
||||
)
|
||||
from arttools.errors import Error
|
||||
from arttools.fstools import Dir
|
||||
from arttools.argparse import KvAction
|
||||
|
||||
from arttools.errors import RegionsError
|
||||
|
||||
from artpipeline.caldb import CaldbInfo
|
||||
from artpipeline.caldb import CaldbOrientation
|
||||
from artpipeline.orientation import Orientation
|
||||
|
||||
from artproducts.version import __version__
|
||||
|
||||
from artproducts.eventswcs import EventsWcs
|
||||
from artproducts.regions import Region
|
||||
|
||||
from artproducts.eventspack import EventsPack, Calculate
|
||||
|
||||
from artproducts.responces import Arf
|
||||
from artproducts.responces import Rmf
|
||||
|
||||
from artproducts.image import Image
|
||||
from artproducts.lightcurve import Lightcurve
|
||||
from artproducts.enspectrum import EnergySpectrum
|
||||
|
||||
|
||||
class Args(TaskArgs):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def read_env(self):
|
||||
if not 'ARTCALDB' in os.environ:
|
||||
raise TaskError('ARTCALDB not found (environment variable not set?)')
|
||||
|
||||
self.caldbdir = os.environ['ARTCALDB']
|
||||
|
||||
def read_cfg(self, cfgfile):
|
||||
pass
|
||||
|
||||
def read_args(self):
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument(
|
||||
'stem',
|
||||
action=KvAction,
|
||||
help='newfile creation stem')
|
||||
argparser.add_argument(
|
||||
'srcdir',
|
||||
action=KvAction,
|
||||
help='input directory')
|
||||
argparser.add_argument(
|
||||
'dstdir',
|
||||
action=KvAction,
|
||||
help='output directory')
|
||||
argparser.add_argument(
|
||||
'tmpdir',
|
||||
action=KvAction,
|
||||
help='temporary directory')
|
||||
argparser.add_argument(
|
||||
'--prepare-skytile',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='input files stem')
|
||||
argparser.add_argument(
|
||||
'--fit-to-wcs',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='process only wcs-bound events')
|
||||
argparser.add_argument(
|
||||
'--wcs',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='WCS region')
|
||||
argparser.add_argument(
|
||||
'--srcra',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='source position RA')
|
||||
argparser.add_argument(
|
||||
'--srcdec',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='source position Dec')
|
||||
argparser.add_argument(
|
||||
'--srcreg',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='source region')
|
||||
argparser.add_argument(
|
||||
'--bkgreg',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='background region')
|
||||
argparser.add_argument(
|
||||
'--elow',
|
||||
default=4.,
|
||||
action=KvAction,
|
||||
help='low energy bound')
|
||||
argparser.add_argument(
|
||||
'--ehigh',
|
||||
default=12.,
|
||||
action=KvAction,
|
||||
help='high energy bound')
|
||||
argparser.add_argument(
|
||||
'--binsize',
|
||||
default=1.,
|
||||
action=KvAction,
|
||||
help='time series bin size')
|
||||
argparser.add_argument(
|
||||
'--epoch0',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='zero epoch for light curves')
|
||||
argparser.add_argument(
|
||||
'--orient',
|
||||
default=None,
|
||||
action=KvAction,
|
||||
help='orientation file')
|
||||
argparser.add_argument(
|
||||
'--usergti',
|
||||
type=str,
|
||||
default=None,
|
||||
help='user gti file',
|
||||
action=KvAction)
|
||||
argparser.add_argument(
|
||||
'--make-individual',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='make individual products for each telescope')
|
||||
args = argparser.parse_args()
|
||||
|
||||
self.stem = args.stem
|
||||
|
||||
self.srcdir = Dir(args.srcdir)
|
||||
self.dstdir = Dir(args.dstdir)
|
||||
|
||||
self.tmpdir = Dir(args.tmpdir) / str(self.pid)
|
||||
self.tmpdst = self.tmpdir / 'dst'
|
||||
|
||||
self.prepare = args.prepare_skytile
|
||||
self.fitwcs = args.fit_to_wcs
|
||||
self.wcs = args.wcs
|
||||
self.srcreg = args.srcreg
|
||||
self.bkgreg = args.bkgreg
|
||||
self.srcra = args.srcra
|
||||
self.srcdec = args.srcdec
|
||||
self.elow = float(args.elow )
|
||||
self.ehigh = float(args.ehigh )
|
||||
self.binsize = float(args.binsize)
|
||||
|
||||
self.epoch0 = None
|
||||
if args.epoch0 is not None:
|
||||
self.epoch0 = float(args.epoch0)
|
||||
|
||||
self.orifile = args.orient
|
||||
self.usergti = args.usergti
|
||||
|
||||
self.make_individual = args.make_individual
|
||||
|
||||
def print(self):
|
||||
print('Input parameters:')
|
||||
print(' stem = {}'.format(self.stem ))
|
||||
print(' srcdir = {}'.format(self.srcdir.get() ))
|
||||
print(' dstdir = {}'.format(self.dstdir.get() ))
|
||||
print(' tmpdir = {}'.format(self.tmpdir.get() ))
|
||||
print(' prepare = {}'.format(self.prepare ))
|
||||
print(' fitwcs = {}'.format(self.fitwcs ))
|
||||
print(' wcs = {}'.format(self.wcs ))
|
||||
print(' srcra = {}'.format(self.srcra ))
|
||||
print(' srcdec = {}'.format(self.srcdec ))
|
||||
print(' srcreg = {}'.format(self.srcreg ))
|
||||
print(' bkgreg = {}'.format(self.bkgreg ))
|
||||
print(' elow = {}'.format(self.elow ))
|
||||
print(' ehigh = {}'.format(self.ehigh ))
|
||||
print(' binsize = {}'.format(self.binsize ))
|
||||
print(' epoch0 = {}'.format(self.epoch0 ))
|
||||
print(' orient = {}'.format(self.orifile ))
|
||||
print(' usergti = {}'.format(self.usergti ))
|
||||
print(' individual = {}'.format(self.make_individual))
|
||||
print()
|
||||
|
||||
def check(self):
|
||||
if not self.srcdir.exists():
|
||||
raise TaskError('<srcdir> not found')
|
||||
|
||||
self.dstdir.check()
|
||||
self.tmpdir.check(clean=True)
|
||||
self.tmpdst.check(clean=True)
|
||||
|
||||
|
||||
class Products(Task):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._name = 'artproducts v.{}'.format(__version__)
|
||||
|
||||
print('*** {}'.format(self._name))
|
||||
|
||||
self._args = Args()
|
||||
|
||||
def initialize(self):
|
||||
self._args.read()
|
||||
self._args.print()
|
||||
self._args.check()
|
||||
|
||||
self._imgdir = self._args.dstdir / 'img'
|
||||
self._phadir = self._args.dstdir / 'pha'
|
||||
self._lcdir = self._args.dstdir / 'lc'
|
||||
|
||||
def finalize(self):
|
||||
self._args.tmpdir.delete()
|
||||
|
||||
def runmain(self):
|
||||
if self._args.prepare:
|
||||
self._evtdir = self._args.dstdir / 'events'
|
||||
self._prepare()
|
||||
else:
|
||||
if self._args.orifile is None:
|
||||
raise TaskError('<orient> file not set')
|
||||
|
||||
if not os.path.exists(self._args.orifile):
|
||||
raise TaskError('<orient> file not found')
|
||||
|
||||
if self._args.srcra is None or self._args.srcdec is None:
|
||||
raise TaskError('<srcra>/<srcdec> not set')
|
||||
|
||||
self._evtdir = self._args.srcdir
|
||||
self._products()
|
||||
|
||||
print('*** artproducts v.{} finished'.format(__version__))
|
||||
|
||||
def _prepare(self):
|
||||
self._evtdir.check(clean=True)
|
||||
self._imgdir.check(clean=True)
|
||||
self._phadir.check(clean=True)
|
||||
self._lcdir.check(clean=True)
|
||||
|
||||
evtfiles = []
|
||||
|
||||
print('Preprocessing event files:')
|
||||
|
||||
for telname in Teldef.telnames:
|
||||
evtfile = ''.join((self._args.stem, telname, '_cl.fits'))
|
||||
evtdst = self._evtdir.pathfor(evtfile)
|
||||
|
||||
evtlist = EventsWcs(self._args.srcdir.pathfor(evtfile), self._args.wcs)
|
||||
evtlist.prepare(self._args.fitwcs, self._args.usergti)
|
||||
|
||||
evtlist.write(evtdst, creator=self._name)
|
||||
print('written: {}'.format(evtdst))
|
||||
|
||||
evtfiles.append(evtdst)
|
||||
|
||||
print()
|
||||
print('Make image:')
|
||||
|
||||
imgfile = ''.join((self._args.stem, '_all.img'))
|
||||
imgdst = self._imgdir.pathfor(imgfile)
|
||||
|
||||
image = Image(evtfiles, self._args.wcs)
|
||||
image.make(self._args.elow, self._args.ehigh)
|
||||
|
||||
image.write(imgdst)
|
||||
|
||||
def _products(self):
|
||||
cdbinfo = CaldbInfo(self._args.caldbdir).get()
|
||||
print('CALDB info: version={}, path={}'.format(cdbinfo['CALDBVER'], cdbinfo['CALDBDIR']))
|
||||
|
||||
self._orient \
|
||||
= Orientation(
|
||||
CaldbOrientation(self._args.caldbdir, 'GYRO'))
|
||||
|
||||
oridata = self._orient.read(self._args.orifile)
|
||||
|
||||
print()
|
||||
print('Reading events...')
|
||||
|
||||
events = EventsPack(self._evtdir).read(self._args.stem, self._args.epoch0)
|
||||
if events.empty:
|
||||
raise TaskError('event files empty')
|
||||
|
||||
print(' GTI = [{}, {}]'.format(events.tstart, events.tstop))
|
||||
print(' epoch0 = {}'.format(events.epoch0))
|
||||
print(' total events = {}'.format(events.count))
|
||||
|
||||
print()
|
||||
print('Reading regions...')
|
||||
|
||||
srcreg = Region(self._args.srcreg)
|
||||
bkgreg = Region(self._args.bkgreg)
|
||||
|
||||
srcarea = events.call(Calculate(srcreg.area, ['projection']))
|
||||
bkgarea = events.call(Calculate(bkgreg.area, ['projection']))
|
||||
|
||||
areascale = 1.
|
||||
|
||||
if bkgarea['all'] > 0:
|
||||
backscale = srcarea['all'] / bkgarea['all']
|
||||
else:
|
||||
backscale = srcarea['all']
|
||||
|
||||
print(' source area = {}'.format(srcarea['all']))
|
||||
print(' background area = {}'.format(bkgarea['all']))
|
||||
|
||||
print(' area scale = {}'.format(areascale))
|
||||
print(' back scale = {}'.format(backscale))
|
||||
|
||||
print()
|
||||
print('Making lightcurves...')
|
||||
self._make_lcurves(events, self._args.binsize, srcreg, bkgreg)
|
||||
|
||||
print()
|
||||
print('Making energy spectra...')
|
||||
self._make_enspectra(events, oridata, srcreg, bkgreg, areascale, backscale)
|
||||
|
||||
|
||||
def _make_lcurves(self, events, binsize, srcreg, bkgreg):
|
||||
|
||||
dstdir = self._args.dstdir / 'lc'
|
||||
dstdir.check()
|
||||
|
||||
print('background:')
|
||||
lcurvebkgfile = ''.join((self._args.stem, '{}', '_bkg.lc'))
|
||||
lcurvebkgfile = dstdir.pathfor(lcurvebkgfile)
|
||||
|
||||
lcurvebkg = Lightcurve(events, binsize, make_individual=self._args.make_individual)
|
||||
lcurvebkg.create(bkgreg, self._args.elow, self._args.ehigh)
|
||||
|
||||
meta = lcurvebkg.meta['all']
|
||||
|
||||
total = meta['events']
|
||||
selected = meta['selected']
|
||||
per = np.round((selected / total) * 100, 2)
|
||||
|
||||
print(' filter = {}'.format(meta['filter']))
|
||||
print(' selected events = {} of {} ({}%)'.format(selected, total, per))
|
||||
|
||||
writtenbkg = lcurvebkg.write(lcurvebkgfile)
|
||||
|
||||
print('written:')
|
||||
for item in writtenbkg['files']:
|
||||
print(' {}'.format(item))
|
||||
|
||||
print('source:')
|
||||
lcurvesrcfile = ''.join((self._args.stem, '{}', '_src.lc'))
|
||||
lcurvesrcfile = dstdir.pathfor(lcurvesrcfile)
|
||||
|
||||
lcurvesrc = Lightcurve(events, binsize, make_individual=self._args.make_individual)
|
||||
lcurvesrc.create(srcreg, self._args.elow, self._args.ehigh)
|
||||
|
||||
meta = lcurvesrc.meta['all']
|
||||
|
||||
total = meta['events']
|
||||
selected = meta['selected']
|
||||
per = np.round((selected / total) * 100, 2)
|
||||
|
||||
print(' filter = {}'.format(meta['filter']))
|
||||
print(' selected events = {} of {} ({}%)'.format(selected, total, per))
|
||||
|
||||
writtensrc = lcurvesrc.write(lcurvesrcfile, writtenbkg)
|
||||
|
||||
print('written:')
|
||||
for item in writtensrc['files']:
|
||||
print(' {}'.format(item))
|
||||
|
||||
def _make_responses(self, events, oridata, srcreg):
|
||||
|
||||
dstdir = self._args.dstdir / 'pha'
|
||||
dstdir.check()
|
||||
|
||||
dstarffile = ''.join((self._args.stem, '{}', '.arf'))
|
||||
dstarffile = dstdir.pathfor(dstarffile)
|
||||
|
||||
arf = Arf(self._args.caldbdir, self._orient, make_individual=self._args.make_individual)
|
||||
arf.create(events, oridata, srcreg, self._args.srcra, self._args.srcdec)
|
||||
|
||||
print('Warning: average ARF responce will *NOT* be generated (use external procedure)')
|
||||
|
||||
arfwritten = arf.write(dstarffile)
|
||||
|
||||
print('written arf responces:')
|
||||
if not arfwritten['files']:
|
||||
print('<none>')
|
||||
else:
|
||||
for arfitem in arfwritten['files']:
|
||||
print(arfitem)
|
||||
|
||||
dstdir = self._args.dstdir / 'pha'
|
||||
dstdir.check()
|
||||
|
||||
dstrmffile = ''.join((self._args.stem, '{}', '.rmf'))
|
||||
dstrmffile = dstdir.pathfor(dstrmffile)
|
||||
|
||||
rmf = Rmf(self._args.caldbdir, make_individual=self._args.make_individual)
|
||||
rmf.create()
|
||||
|
||||
print('Warning: average RMF responce will *NOT* be generated (use external procedure)')
|
||||
|
||||
rmfwritten = rmf.write(dstrmffile)
|
||||
|
||||
print('written rmf responces:')
|
||||
if not rmfwritten['files']:
|
||||
print('<none>')
|
||||
else:
|
||||
for rmfitem in rmfwritten['files']:
|
||||
print(rmfitem)
|
||||
|
||||
return arfwritten, rmfwritten
|
||||
|
||||
def _make_enspectra(self, events, oridata, srcreg, bkgreg, areascale, backscale):
|
||||
|
||||
# responces
|
||||
arffiles, rmffiles = self._make_responses(events, oridata, srcreg)
|
||||
|
||||
responces = {
|
||||
'arf': arffiles,
|
||||
'rmf': rmffiles
|
||||
}
|
||||
|
||||
# background spectra
|
||||
enspecbkgfile = ''.join((self._args.stem, '{}', '_bkg.pha'))
|
||||
enspecbkgfile = (self._args.dstdir / 'pha').pathfor(enspecbkgfile)
|
||||
|
||||
spectrumbkg = EnergySpectrum(events, make_individual=self._args.make_individual)
|
||||
spectrumbkg.create(bkgreg)
|
||||
writtenbkg = spectrumbkg.write(enspecbkgfile)
|
||||
|
||||
print('written background pha:')
|
||||
if not writtenbkg['files']:
|
||||
print('<none>')
|
||||
else:
|
||||
for phaitem in writtenbkg['files']:
|
||||
print(phaitem)
|
||||
|
||||
# source spectra
|
||||
enspecsrcfile = ''.join((self._args.stem, '{}', '_src.pha'))
|
||||
enspecsrcfile = (self._args.dstdir / 'pha').pathfor(enspecsrcfile)
|
||||
|
||||
spectrumsrc = EnergySpectrum(events, make_individual=self._args.make_individual)
|
||||
spectrumsrc.create(srcreg)
|
||||
writtensrc = spectrumsrc.write(enspecsrcfile, responces, writtenbkg, backscale)
|
||||
|
||||
print('written source pha:')
|
||||
if not writtensrc['files']:
|
||||
print('<none>')
|
||||
else:
|
||||
for phaitem in writtensrc['files']:
|
||||
print(phaitem)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
Products().run()
|
||||
except RegionsError as e:
|
||||
print('error: regions:', e)
|
||||
except TaskError as e:
|
||||
print('error: task execution:', e)
|
||||
except Exception as e:
|
||||
print('error: unknown:', e)
|
||||
|
||||
# Products().run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
69
artproducts/src/artproducts/regions.py
Executable file
69
artproducts/src/artproducts/regions.py
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
from regions import Regions
|
||||
from regions import CompoundSkyRegion
|
||||
from regions.shapes.circle import CircleSkyRegion
|
||||
from regions.shapes.annulus import CircleAnnulusSkyRegion
|
||||
|
||||
from astropy.coordinates import SkyCoord
|
||||
|
||||
from arttools.errors import RegionsError
|
||||
|
||||
class Region(object):
|
||||
|
||||
def __init__(self, regfile):
|
||||
self._regfile = regfile
|
||||
|
||||
# -- read regions
|
||||
self._reglist = Regions.read(self._regfile)
|
||||
self._regunit = self._reglist[0].center.info.unit
|
||||
|
||||
# --- check
|
||||
if not self._reglist:
|
||||
raise RegionsError('no regions found')
|
||||
|
||||
for region in self._reglist:
|
||||
region_allowed = \
|
||||
isinstance(region, (CircleSkyRegion, CircleAnnulusSkyRegion))
|
||||
|
||||
if not region_allowed:
|
||||
raise RegionsError('unsupported region: {}'.format(type(region)))
|
||||
|
||||
# -- make union region
|
||||
|
||||
self._region = self._reglist[0]
|
||||
for region in self._reglist[1:]:
|
||||
self._region = self._region.union(region)
|
||||
|
||||
def area(self, projection):
|
||||
area = 0
|
||||
for region in self._reglist:
|
||||
area += region.to_pixel(projection).area
|
||||
|
||||
return area
|
||||
|
||||
def mask(self, projection, ra, dec):
|
||||
|
||||
radec = SkyCoord(ra, dec, unit=self._regunit, frame='fk5')
|
||||
|
||||
return self._region.contains(radec, projection)
|
||||
|
||||
# masks = []
|
||||
# for r in self._regions:
|
||||
# pass
|
||||
|
||||
# rmask = r.contains(radec, projection)
|
||||
# masks.append(rmask)
|
||||
|
||||
# return np.any(masks, axis=0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
316
artproducts/src/artproducts/responces.py
Executable file
316
artproducts/src/artproducts/responces.py
Executable file
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
from arttools.fstools import Dir
|
||||
from arttools.telescope import Teldef
|
||||
|
||||
from artpipeline.caldb import Caldb
|
||||
from artpipeline.caldb import CaldbTelescope
|
||||
|
||||
|
||||
class Arf(object):
|
||||
|
||||
EBINS = np.array([4 , 5 , 6, 8, 10, 14, 18 , 25])
|
||||
EECF = np.array([4.5, 5.5, 7, 9, 12, 16, 21.5 ])
|
||||
|
||||
PCORR = {
|
||||
'T1': [-5.53812028e-06, 5.44531758e-04, -1.85116554e-02, 2.49542143e-01, -4.78704144e-02],
|
||||
'T2': [-9.23161584e-06, 8.50470939e-04, -2.71210316e-02, 3.46807627e-01, -5.10534726e-01],
|
||||
'T3': [-6.39175857e-06, 6.37572616e-04, -2.21113466e-02, 3.02875927e-01, -3.00549874e-01],
|
||||
'T4': [-3.79975811e-06, 3.78351940e-04, -1.38558825e-02, 2.11893093e-01, 5.14311717e-02],
|
||||
'T5': [-8.51754314e-06, 8.25307741e-04, -2.78721554e-02, 3.75886273e-01, -6.00306320e-01],
|
||||
'T6': [-5.44416755e-06, 5.33468306e-04, -1.81215482e-02, 2.45438278e-01, -1.13582748e-01],
|
||||
'T7': [-7.31913644e-06, 6.80886499e-04, -2.23323962e-02, 2.98891459e-01, -3.07578086e-01]
|
||||
}
|
||||
|
||||
ECF_R3 = {
|
||||
'T1': [0.8405893291928408, 0.8578133072455523, 0.8518679621578527, 0.8435798995261736, 0.8289480999358520, 0.7258092846885180, 0.6826893958459460],
|
||||
'T2': [0.7616948204797905, 0.7551934086121032, 0.7265351319204454, 0.7101925259375811, 0.6654367283015024, 0.5911152050970271, 0.6112236775368495],
|
||||
'T3': [0.8291727938992725, 0.8257931679150124, 0.8188083629048590, 0.8057277406342708, 0.7642634099905733, 0.6574702032925820, 0.6553824446937162],
|
||||
'T4': [0.8587518633143223, 0.8629811713390994, 0.8603473794365152, 0.8509428156196533, 0.8255339443817531, 0.7794067227678055, 0.7313918593446697],
|
||||
'T5': [0.8710124678275055, 0.8724032722913310, 0.8690978752281667, 0.8561072162925373, 0.8341782730242377, 0.7799308511134618, 0.6982241619541473],
|
||||
'T6': [0.8621286963648814, 0.8566493477571664, 0.8479740523247319, 0.8344740169388141, 0.8119985053434914, 0.7497446386004684, 0.6533235819021327],
|
||||
'T7': [0.8361233953579142, 0.8314687457264499, 0.8167286887863530, 0.8073078896029924, 0.7795029443860121, 0.6759143436928101, 0.6415284037194291]
|
||||
}
|
||||
|
||||
ECF_R5 ={
|
||||
'T1': [0.8674891551004330, 0.8822134383381507, 0.8788946213372089, 0.8744636104265867, 0.8597222695685300, 0.7557929438315966, 0.7189331472867164],
|
||||
'T2': [0.7936437182568867, 0.7891579186682454, 0.7674259998254548, 0.7589714843682488, 0.7158317895510184, 0.6411584008631043, 0.6751573402559861],
|
||||
'T3': [0.8582713402059252, 0.8542245360657326, 0.8490572253823582, 0.8385523961057851, 0.7992921226502082, 0.7082381667579745, 0.7148249035011437],
|
||||
'T4': [0.8858430082172500, 0.8867274026307359, 0.8870164916860136, 0.8793272870044280, 0.8561885232880430, 0.8040219587253561, 0.7604390487299408],
|
||||
'T5': [0.8930065538441408, 0.8950301833952440, 0.8942787134906093, 0.8854633646996730, 0.8671659219235113, 0.8201161803780476, 0.7338377592101047],
|
||||
'T6': [0.8876123076470380, 0.8835789100530228, 0.8807700953185790, 0.8736529044505901, 0.8565833527159418, 0.7983704564521831, 0.7009986224989362],
|
||||
'T7': [0.8694210354890501, 0.8620577130319129, 0.8498935555726601, 0.8429003979100774, 0.8204142620537092, 0.7239238390896476, 0.6748621998062087]
|
||||
}
|
||||
|
||||
def __init__(self, caldbpath, orient, make_individual=False):
|
||||
self._caldbdir = Dir(caldbpath)
|
||||
self._orient = orient
|
||||
|
||||
self._make_individual = make_individual
|
||||
|
||||
srcvigndir = self._caldbdir / 'ext' / 'vign'
|
||||
self._srcvignfile = srcvigndir.pathfor('art-xc_vignea_full_q200.fits')
|
||||
|
||||
srcarfdir = self._caldbdir / 'ext' / 'arf'
|
||||
self._srcarffile = srcarfdir.pathfor('artxc_arf_20191030_v004.arf')
|
||||
|
||||
self._arcmin2deg = 1. / 60.
|
||||
self._pix2arcmin = 45. / 60.
|
||||
|
||||
self._respdata = {}
|
||||
self._respmeta = {}
|
||||
|
||||
def _read_src_vign(self, vignfile):
|
||||
hdul = fits.open(vignfile)
|
||||
mapshape = hdul['All events' ].data[0][1].shape
|
||||
vignmaps = hdul['All events' ].data
|
||||
vignxoff = hdul['Offset angles'].data['X']
|
||||
vignyoff = hdul['Offset angles'].data['Y']
|
||||
|
||||
return {
|
||||
'vignmaps': vignmaps,
|
||||
'mapshape': mapshape,
|
||||
'xoff' : vignxoff,
|
||||
'yoff' : vignyoff
|
||||
}
|
||||
|
||||
def _read_src_arf(self, arffile):
|
||||
hdul = fits.open(arffile)
|
||||
elow = hdul[1].data['ENERG_LO']
|
||||
ehigh = hdul[1].data['ENERG_HI']
|
||||
resp = hdul[1].data['SPECRESP']
|
||||
|
||||
return {
|
||||
'elow' : elow ,
|
||||
'ehigh' : ehigh,
|
||||
'specresp': resp
|
||||
}
|
||||
|
||||
def _calc_detxy(self, telname, evtdata, oridata, ra, dec):
|
||||
evttimes = np.array(evtdata.events.column('TIME'), dtype=np.double)
|
||||
|
||||
evtra = np.full(shape=evttimes.shape, fill_value=ra , dtype=np.double)
|
||||
evtdec = np.full(shape=evttimes.shape, fill_value=dec, dtype=np.double)
|
||||
|
||||
detx, dety \
|
||||
= self._orient.calc_rawxy(
|
||||
oridata['time'],
|
||||
oridata['quat'],
|
||||
evttimes ,
|
||||
evtra ,
|
||||
evtdec ,
|
||||
telname
|
||||
)
|
||||
|
||||
return np.median(detx).astype(int), np.median(dety).astype(int)
|
||||
|
||||
def _calc_offset(self, telname, x, y):
|
||||
telcaldb = \
|
||||
CaldbTelescope(
|
||||
Caldb(self._caldbdir.get(), telname))
|
||||
|
||||
oapixdist = np.hypot(
|
||||
float(x) - telcaldb.oaxis()['X'],
|
||||
float(y) - telcaldb.oaxis()['Y'])
|
||||
oaoffset = oapixdist * self._pix2arcmin
|
||||
|
||||
return oaoffset
|
||||
|
||||
def _make_masks(self, mapshape, xoff, yoff, offset):
|
||||
tolerance = 0.1
|
||||
|
||||
xoff = Teldef.F * np.tan(np.deg2rad(xoff * self._arcmin2deg))
|
||||
yoff = Teldef.F * np.tan(np.deg2rad(yoff * self._arcmin2deg))
|
||||
|
||||
cxy = np.array(np.meshgrid(xoff, yoff)).T.reshape(-1, 2)
|
||||
cx = cxy.T[0]
|
||||
cy = cxy.T[1]
|
||||
|
||||
offsetmap = np.rad2deg(np.arctan(
|
||||
np.hypot(cx, cy) / Teldef.F)) / self._arcmin2deg
|
||||
|
||||
cenmask = np.abs(offsetmap - 0. ) <= tolerance
|
||||
offmask = np.abs(offsetmap - offset) <= tolerance
|
||||
|
||||
return cenmask.reshape(mapshape), offmask.reshape(mapshape)
|
||||
|
||||
def _make_vign_resp(self, vigndata, offset):
|
||||
cenmask, offmask \
|
||||
= self._make_masks(
|
||||
vigndata['mapshape'],
|
||||
vigndata['xoff' ],
|
||||
vigndata['yoff' ],
|
||||
offset )
|
||||
|
||||
energies = vigndata['vignmaps']['E' ]
|
||||
effareas = vigndata['vignmaps']['EFFAREA']
|
||||
|
||||
scales = []
|
||||
for ea in effareas:
|
||||
scales.append(np.mean(ea[offmask]) / np.mean(ea[cenmask]))
|
||||
|
||||
return energies, scales
|
||||
|
||||
def _make_arf_resp(self, telname, arfdata, vignscale):
|
||||
arfen = (arfdata['elow'] + arfdata['ehigh']) / 2.
|
||||
arfresp = arfdata['specresp'] / 7.
|
||||
|
||||
vignen = vignscale[0]
|
||||
vignresp = vignscale[1]
|
||||
|
||||
scaled = np.interp(arfen, vignen, vignresp)
|
||||
|
||||
newresp = arfresp * scaled
|
||||
|
||||
# --- polycorr ---
|
||||
pcorr = self.PCORR[telname]
|
||||
newresp = newresp * np.polyval(pcorr, arfen)
|
||||
# ---
|
||||
|
||||
# --- ecfcorr ---
|
||||
ecf = np.array(self.ECF_R3[telname])
|
||||
newresp = newresp * np.interp(arfen, self.EECF, ecf)
|
||||
# ---
|
||||
|
||||
return newresp
|
||||
|
||||
def create(self, events, oridata, srcreg, srcra, srcdec):
|
||||
|
||||
srcarfdata = self._read_src_arf(self._srcarffile)
|
||||
srcvigndata = self._read_src_vign(self._srcvignfile)
|
||||
|
||||
for telname, evtdata in events:
|
||||
events = evtdata['events' ]
|
||||
projection = evtdata['projection']
|
||||
|
||||
detx, dety = self._calc_detxy(
|
||||
telname,
|
||||
events ,
|
||||
oridata,
|
||||
srcra ,
|
||||
srcdec
|
||||
)
|
||||
|
||||
offset = self._calc_offset(telname, detx, dety)
|
||||
es, vs = self._make_vign_resp(srcvigndata, offset)
|
||||
arfresp = self._make_arf_resp(telname, srcarfdata, (es, vs))
|
||||
|
||||
self._respdata[telname] = arfresp
|
||||
self._respmeta[telname] = {
|
||||
'ra' : srcra ,
|
||||
'dec' : srcdec,
|
||||
'detx' : detx ,
|
||||
'dety' : dety ,
|
||||
'offset': offset
|
||||
}
|
||||
|
||||
logstr = '{}: ra = {}, dec = {}, x = {}, y = {}, offset = {}'.format(
|
||||
telname, srcra, srcdec, detx, dety, offset
|
||||
)
|
||||
|
||||
print(logstr)
|
||||
|
||||
def _write_resp(self, telname, srcfile, dstfile):
|
||||
|
||||
if not os.path.exists(srcfile):
|
||||
return False
|
||||
|
||||
if telname not in self._respdata:
|
||||
return False
|
||||
|
||||
if os.path.exists(dstfile):
|
||||
os.remove(dstfile)
|
||||
|
||||
shutil.copy(srcfile, dstfile)
|
||||
|
||||
with fits.open(dstfile, mode='update') as hdul:
|
||||
hdul[1].data["SPECRESP"] = self._respdata[telname]
|
||||
hdul.flush()
|
||||
|
||||
return True
|
||||
|
||||
def write(self, dstarf):
|
||||
|
||||
written = {
|
||||
'files': []
|
||||
}
|
||||
|
||||
if not self._make_individual:
|
||||
return written
|
||||
|
||||
for telname in Teldef.telnames:
|
||||
if telname not in self._respdata:
|
||||
continue
|
||||
|
||||
dstarfname = dstarf.format(telname)
|
||||
|
||||
if not self._write_resp(telname, self._srcarffile, dstarfname):
|
||||
continue
|
||||
|
||||
written[telname] = os.path.basename(dstarfname)
|
||||
written['files'].append(dstarfname)
|
||||
|
||||
return written
|
||||
|
||||
|
||||
class Rmf(object):
|
||||
|
||||
def __init__(self, caldbpath, make_individual=False):
|
||||
|
||||
self._caldbdir = Dir(caldbpath)
|
||||
self._make_individual = make_individual
|
||||
|
||||
srcrmfdir = self._caldbdir / 'ext' / 'rmf'
|
||||
self._srcrmffile = srcrmfdir.pathfor('artxc_rmf_{}_20191030_v003.rmf')
|
||||
|
||||
def create(self):
|
||||
pass
|
||||
|
||||
def _write_resp(self, telname, srcfile, dstfile):
|
||||
if not os.path.exists(srcfile):
|
||||
return False
|
||||
|
||||
if os.path.exists(dstfile):
|
||||
os.remove(dstfile)
|
||||
|
||||
shutil.copy(srcfile, dstfile)
|
||||
|
||||
return True
|
||||
|
||||
def write(self, dstrmf):
|
||||
written = {
|
||||
'files': []
|
||||
}
|
||||
|
||||
if not self._make_individual:
|
||||
return written
|
||||
|
||||
for telname in Teldef.telnames:
|
||||
srcrmfname = self._srcrmffile.format(telname)
|
||||
dstrmfname = dstrmf.format(telname)
|
||||
|
||||
if not self._write_resp(telname, srcrmfname, dstrmfname):
|
||||
continue
|
||||
|
||||
written[telname] = os.path.basename(dstrmfname)
|
||||
written['files'].append(dstrmfname)
|
||||
|
||||
return written
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
1
artproducts/src/artproducts/version.py
Executable file
1
artproducts/src/artproducts/version.py
Executable file
@@ -0,0 +1 @@
|
||||
__version__ = '2.0.0'
|
||||
BIN
arttools/.DS_Store
vendored
Normal file
BIN
arttools/.DS_Store
vendored
Normal file
Binary file not shown.
6
arttools/pyproject.toml
Normal file
6
arttools/pyproject.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
19
arttools/setup.py
Normal file
19
arttools/setup.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from setuptools import setup
|
||||
|
||||
# __version__ = "1.0.0"
|
||||
|
||||
exec(open('src/arttools/version.py').read())
|
||||
|
||||
setup(
|
||||
name="arttools",
|
||||
version=__version__,
|
||||
author="M.Pavlinsky SRG/ART-XC software team",
|
||||
description="SRG/ARTXC software tools",
|
||||
package_dir={"": "src"},
|
||||
packages=["arttools"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"arttime = arttools.arttime:main"
|
||||
]
|
||||
}
|
||||
)
|
||||
BIN
arttools/src/.DS_Store
vendored
Normal file
BIN
arttools/src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
arttools/src/arttools/.DS_Store
vendored
Normal file
BIN
arttools/src/arttools/.DS_Store
vendored
Normal file
Binary file not shown.
0
arttools/src/arttools/__init__.py
Executable file
0
arttools/src/arttools/__init__.py
Executable file
27
arttools/src/arttools/argparse.py
Executable file
27
arttools/src/arttools/argparse.py
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class KvAction(argparse.Action):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
super(KvAction, self).__init__(option_strings, dest, **kwargs)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if "=" in values:
|
||||
key, val = values.split("=", 1)
|
||||
else:
|
||||
key, val = self.dest, values
|
||||
|
||||
setattr(namespace, key, val)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
18
arttools/src/arttools/art.py
Executable file
18
arttools/src/arttools/art.py
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
623
arttools/src/arttools/artindex.py
Executable file
623
arttools/src/arttools/artindex.py
Executable file
@@ -0,0 +1,623 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from astropy.io import fits
|
||||
|
||||
import polars as pl
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
from arttools.arttime import Dtime
|
||||
from arttools.arttime import ArtDay
|
||||
|
||||
from arttools.version import __version__
|
||||
|
||||
|
||||
MJDREFD = 51543.875
|
||||
|
||||
MJDREF = Time(51543.875, format='mjd') # 01.01.2000 00:00:00 (UTC+3)
|
||||
TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) # UTC time zone
|
||||
TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) # UTC+3 (Moscow) time zone
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionIndex:
|
||||
def __init__(self, fname):
|
||||
self.key = []
|
||||
self.npkey = np.array(self.key)
|
||||
|
||||
self.stem = []
|
||||
self.station = []
|
||||
self.sequence = []
|
||||
self.data_quality = []
|
||||
self.total_quality = []
|
||||
self.processed = []
|
||||
self.start = []
|
||||
self.stop = []
|
||||
self.T1_start = []
|
||||
self.T1_stop = []
|
||||
self.T2_start = []
|
||||
self.T2_stop = []
|
||||
self.T3_start = []
|
||||
self.T3_stop = []
|
||||
self.T4_start = []
|
||||
self.T4_stop = []
|
||||
self.T5_start = []
|
||||
self.T5_stop = []
|
||||
self.T6_start = []
|
||||
self.T6_stop = []
|
||||
self.T7_start = []
|
||||
self.T7_stop = []
|
||||
self.flag = []
|
||||
|
||||
self._index_file = fname
|
||||
|
||||
if os.path.isfile(self._index_file):
|
||||
self.read(self._index_file)
|
||||
|
||||
def read(self, fname):
|
||||
with fits.open(fname) as hdul:
|
||||
self.stem = hdul[1].data['STEM'].tolist()
|
||||
self.station = hdul[1].data['STATION'].tolist()
|
||||
self.sequence = hdul[1].data['SEQUENCE'].tolist()
|
||||
self.data_quality = hdul[1].data['DATA_QUALITY'].tolist()
|
||||
self.total_quality = hdul[1].data['TOTAL_QUALITY'].tolist()
|
||||
self.processed = hdul[1].data['PROCESSED'].tolist()
|
||||
self.start = hdul[1].data['START' ].tolist()
|
||||
self.stop = hdul[1].data['STOP' ].tolist()
|
||||
self.T1_start = hdul[1].data['T1_START'].tolist()
|
||||
self.T1_stop = hdul[1].data['T1_STOP' ].tolist()
|
||||
self.T2_start = hdul[1].data['T2_START'].tolist()
|
||||
self.T2_stop = hdul[1].data['T2_STOP' ].tolist()
|
||||
self.T3_start = hdul[1].data['T3_START'].tolist()
|
||||
self.T3_stop = hdul[1].data['T3_STOP' ].tolist()
|
||||
self.T4_start = hdul[1].data['T4_START'].tolist()
|
||||
self.T4_stop = hdul[1].data['T4_STOP' ].tolist()
|
||||
self.T5_start = hdul[1].data['T5_START'].tolist()
|
||||
self.T5_stop = hdul[1].data['T5_STOP' ].tolist()
|
||||
self.T6_start = hdul[1].data['T6_START'].tolist()
|
||||
self.T6_stop = hdul[1].data['T6_STOP' ].tolist()
|
||||
self.T7_start = hdul[1].data['T7_START'].tolist()
|
||||
self.T7_stop = hdul[1].data['T7_STOP' ].tolist()
|
||||
self.flag = hdul[1].data['FLAG'].tolist()
|
||||
|
||||
for stm, sta, seq in zip(self.stem, self.station, self.sequence):
|
||||
self.key.append('.'.join((stm, sta, seq)))
|
||||
|
||||
self.npkey = np.array(self.key)
|
||||
|
||||
def process(self, frame):
|
||||
frame.make_key()
|
||||
|
||||
if frame.key in self.npkey:
|
||||
index = np.where(self.npkey == frame.key)[0][0]
|
||||
|
||||
T1_start = frame.intervals['T1'][0]
|
||||
T1_stop = frame.intervals['T1'][1]
|
||||
T2_start = frame.intervals['T2'][0]
|
||||
T2_stop = frame.intervals['T2'][1]
|
||||
T3_start = frame.intervals['T3'][0]
|
||||
T3_stop = frame.intervals['T3'][1]
|
||||
T4_start = frame.intervals['T4'][0]
|
||||
T4_stop = frame.intervals['T4'][1]
|
||||
T5_start = frame.intervals['T5'][0]
|
||||
T5_stop = frame.intervals['T5'][1]
|
||||
T6_start = frame.intervals['T6'][0]
|
||||
T6_stop = frame.intervals['T6'][1]
|
||||
T7_start = frame.intervals['T7'][0]
|
||||
T7_stop = frame.intervals['T7'][1]
|
||||
|
||||
times_ok = [
|
||||
np.isclose(self.T1_start[index], T1_start),
|
||||
np.isclose(self.T1_stop [index], T1_stop ),
|
||||
np.isclose(self.T2_start[index], T2_start),
|
||||
np.isclose(self.T2_stop [index], T2_stop ),
|
||||
np.isclose(self.T3_start[index], T3_start),
|
||||
np.isclose(self.T3_stop [index], T3_stop ),
|
||||
np.isclose(self.T4_start[index], T4_start),
|
||||
np.isclose(self.T4_stop [index], T4_stop ),
|
||||
np.isclose(self.T5_start[index], T5_start),
|
||||
np.isclose(self.T5_stop [index], T5_stop ),
|
||||
np.isclose(self.T6_start[index], T6_start),
|
||||
np.isclose(self.T6_stop [index], T6_stop ),
|
||||
np.isclose(self.T7_start[index], T7_start),
|
||||
np.isclose(self.T7_stop [index], T7_stop ),
|
||||
]
|
||||
|
||||
quality_ok = [
|
||||
np.isclose(self.data_quality[index] , frame.data_quality ),
|
||||
np.isclose(self.total_quality[index], frame.total_quality),
|
||||
]
|
||||
|
||||
all_ok = np.all(times_ok) and np.all(quality_ok)
|
||||
if not all_ok:
|
||||
print(
|
||||
"WARNING: session {}.{}.{} differs from indexed".format(
|
||||
frame.stem, frame.station, frame.sequence
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
self.key.append(frame.key)
|
||||
self.npkey = np.array(self.key)
|
||||
|
||||
self.stem.append(frame.stem)
|
||||
self.station.append(frame.station)
|
||||
self.sequence.append(frame.sequence)
|
||||
self.data_quality.append(frame.data_quality)
|
||||
self.total_quality.append(frame.total_quality)
|
||||
self.processed.append(frame.processed)
|
||||
|
||||
start = np.array([
|
||||
frame.intervals["T1"][0],
|
||||
frame.intervals["T2"][0],
|
||||
frame.intervals["T3"][0],
|
||||
frame.intervals["T4"][0],
|
||||
frame.intervals["T5"][0],
|
||||
frame.intervals["T6"][0],
|
||||
frame.intervals["T7"][0],
|
||||
]).min()
|
||||
|
||||
self.start.append(start)
|
||||
|
||||
stop = np.array([
|
||||
frame.intervals["T1"][1],
|
||||
frame.intervals["T2"][1],
|
||||
frame.intervals["T3"][1],
|
||||
frame.intervals["T4"][1],
|
||||
frame.intervals["T5"][1],
|
||||
frame.intervals["T6"][1],
|
||||
frame.intervals["T7"][1],
|
||||
]).max()
|
||||
|
||||
self.stop.append(stop )
|
||||
|
||||
self.T1_start.append(frame.intervals["T1"][0])
|
||||
self.T1_stop .append(frame.intervals["T1"][1])
|
||||
self.T2_start.append(frame.intervals["T2"][0])
|
||||
self.T2_stop .append(frame.intervals["T2"][1])
|
||||
self.T3_start.append(frame.intervals["T3"][0])
|
||||
self.T3_stop .append(frame.intervals["T3"][1])
|
||||
self.T4_start.append(frame.intervals["T4"][0])
|
||||
self.T4_stop .append(frame.intervals["T4"][1])
|
||||
self.T5_start.append(frame.intervals["T5"][0])
|
||||
self.T5_stop .append(frame.intervals["T5"][1])
|
||||
self.T6_start.append(frame.intervals["T6"][0])
|
||||
self.T6_stop .append(frame.intervals["T6"][1])
|
||||
self.T7_start.append(frame.intervals["T7"][0])
|
||||
self.T7_stop .append(frame.intervals["T7"][1])
|
||||
|
||||
# a FLAG is always 0 as we introduce session to index
|
||||
self.flag.append(0)
|
||||
|
||||
def index(self):
|
||||
return pl.DataFrame(
|
||||
{
|
||||
'key': self.key,
|
||||
'stem': self.stem,
|
||||
'station': self.station,
|
||||
'sequence': self.sequence,
|
||||
'data_quality': self.data_quality,
|
||||
'total_quality': self.total_quality,
|
||||
'processed': self.processed,
|
||||
'start' : self.start ,
|
||||
'stop' : self.stop ,
|
||||
'T1_start' : self.T1_start ,
|
||||
'T1_stop' : self.T1_stop ,
|
||||
'T2_start' : self.T2_start ,
|
||||
'T2_stop' : self.T2_stop ,
|
||||
'T3_start' : self.T3_start ,
|
||||
'T3_stop' : self.T3_stop ,
|
||||
'T4_start' : self.T4_start ,
|
||||
'T4_stop' : self.T4_stop ,
|
||||
'T5_start' : self.T5_start ,
|
||||
'T5_stop' : self.T5_stop ,
|
||||
'T6_start' : self.T6_start ,
|
||||
'T6_stop' : self.T6_stop ,
|
||||
'T7_start' : self.T7_start ,
|
||||
'T7_stop' : self.T7_stop ,
|
||||
'flag' : self.flag ,
|
||||
}
|
||||
).sort('key')
|
||||
|
||||
def write_index_fits(self):
|
||||
df = self.index()
|
||||
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
c00 = fits.Column(name='STEM', format='20A', unit='', array=np.array(df['stem']))
|
||||
c01 = fits.Column(
|
||||
name='STATION', format='2A', unit='', array=np.array(df['station'])
|
||||
)
|
||||
c02 = fits.Column(
|
||||
name='SEQUENCE', format='3A', unit='', array=np.array(df['sequence'])
|
||||
)
|
||||
c03 = fits.Column(name='FLAG' , format='1I' , unit='' , array=np.array(df['flag' ]))
|
||||
c04 = fits.Column(
|
||||
name='DATA_QUALITY',
|
||||
format='1D',
|
||||
unit='%',
|
||||
array=np.array(df['data_quality']),
|
||||
)
|
||||
c05 = fits.Column(
|
||||
name='TOTAL_QUALITY',
|
||||
format='1D',
|
||||
unit='%',
|
||||
array=np.array(df['total_quality']))
|
||||
c06 = fits.Column(name='PROCESSED', format='19A', unit='date', array=np.array(df['processed']))
|
||||
c07 = fits.Column(name='START' , format='1D' , unit='sec' , array=np.array(df['start' ]))
|
||||
c08 = fits.Column(name='STOP' , format='1D' , unit='sec' , array=np.array(df['stop' ]))
|
||||
c09 = fits.Column(name='T1_START' , format='1D' , unit='sec' , array=np.array(df['T1_start' ]))
|
||||
c10 = fits.Column(name='T1_STOP' , format='1D' , unit='sec' , array=np.array(df['T1_stop' ]))
|
||||
c11 = fits.Column(name='T2_START' , format='1D' , unit='sec' , array=np.array(df['T2_start' ]))
|
||||
c12 = fits.Column(name='T2_STOP' , format='1D' , unit='sec' , array=np.array(df['T2_stop' ]))
|
||||
c13 = fits.Column(name='T3_START' , format='1D' , unit='sec' , array=np.array(df['T3_start' ]))
|
||||
c14 = fits.Column(name='T3_STOP' , format='1D' , unit='sec' , array=np.array(df['T3_stop' ]))
|
||||
c15 = fits.Column(name='T4_START' , format='1D' , unit='sec' , array=np.array(df['T4_start' ]))
|
||||
c16 = fits.Column(name='T4_STOP' , format='1D' , unit='sec' , array=np.array(df['T4_stop' ]))
|
||||
c17 = fits.Column(name='T5_START' , format='1D' , unit='sec' , array=np.array(df['T5_start' ]))
|
||||
c18 = fits.Column(name='T5_STOP' , format='1D' , unit='sec' , array=np.array(df['T5_stop' ]))
|
||||
c19 = fits.Column(name='T6_START' , format='1D' , unit='sec' , array=np.array(df['T6_start' ]))
|
||||
c20 = fits.Column(name='T6_STOP' , format='1D' , unit='sec' , array=np.array(df['T6_stop' ]))
|
||||
c21 = fits.Column(name='T7_START' , format='1D' , unit='sec' , array=np.array(df['T7_start' ]))
|
||||
c22 = fits.Column(name='T7_STOP' , format='1D' , unit='sec' , array=np.array(df['T7_stop' ]))
|
||||
|
||||
cols = fits.ColDefs(
|
||||
[
|
||||
c00, c01, c02, c03, c04, c05, c06, c07, c08, c09,
|
||||
c10, c11, c12, c13, c14, c15, c16, c17, c18, c19,
|
||||
c20, c21, c22
|
||||
]
|
||||
)
|
||||
|
||||
indexhdu = fits.BinTableHDU.from_columns(cols)
|
||||
indexhdu.name = "INDEX"
|
||||
|
||||
creator = 'artindex v.{}'.format(__version__)
|
||||
indexhdu.header['creator' ] = (creator, 'program and version that created this file')
|
||||
|
||||
dtnow = datetime.now(tz=TZ_MSK)
|
||||
|
||||
indexhdu.header['mjdref' ] = (MJDREFD , 'MJD corresponding to SC clock start 2000.0 MSK' )
|
||||
indexhdu.header['date' ] = (dtnow.strftime('%Y/%m/%d'), 'date that FITS file was created' )
|
||||
|
||||
hdulist = fits.HDUList([hdu, indexhdu])
|
||||
hdulist.writeto(self._index_file, overwrite=True)
|
||||
|
||||
print("written: {}".format(self._index_file))
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionIndexFrame:
|
||||
def __init__(self):
|
||||
self.key = ""
|
||||
|
||||
self.stem = ""
|
||||
self.station = "00"
|
||||
self.sequence = "000"
|
||||
self.data_quality = 0
|
||||
self.total_quality = 0
|
||||
|
||||
self.processed = ""
|
||||
|
||||
self.intervals = {
|
||||
"T1": (0, 0),
|
||||
"T2": (0, 0),
|
||||
"T3": (0, 0),
|
||||
"T4": (0, 0),
|
||||
"T5": (0, 0),
|
||||
"T6": (0, 0),
|
||||
"T7": (0, 0),
|
||||
}
|
||||
|
||||
self.flag = 0
|
||||
|
||||
def make_key(self):
|
||||
self.key = ".".join((self.stem, self.station, self.sequence))
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArtdayIndex:
|
||||
def __init__(self, fname):
|
||||
self.artday = []
|
||||
self.msktime = []
|
||||
self.complete = []
|
||||
self.status = []
|
||||
self.errors = []
|
||||
self.processed = []
|
||||
self.toolver = []
|
||||
self.sciproc = []
|
||||
self.scitool = []
|
||||
self.scicaldb = []
|
||||
self.start = []
|
||||
self.stop = []
|
||||
self.daylen = []
|
||||
self.t1 = []
|
||||
self.t2 = []
|
||||
self.t3 = []
|
||||
self.t4 = []
|
||||
self.t5 = []
|
||||
self.t6 = []
|
||||
self.t7 = []
|
||||
self.gyro = []
|
||||
self.bokz = []
|
||||
self.sed1 = []
|
||||
self.sed2 = []
|
||||
|
||||
self._index = fname
|
||||
|
||||
if os.path.isfile(self._index):
|
||||
self.read(self._index)
|
||||
|
||||
def read(self, fname):
|
||||
with fits.open(fname) as hdul:
|
||||
self.artday = hdul[1].data['ARTDAY' ].tolist()
|
||||
self.msktime = hdul[1].data['MSKTIME' ].tolist()
|
||||
self.complete = hdul[1].data['COMPLETE' ].tolist()
|
||||
self.status = hdul[1].data['STATUS' ].tolist()
|
||||
self.errors = hdul[1].data['ERRORS' ].tolist()
|
||||
self.processed = hdul[1].data['PROCESSED' ].tolist()
|
||||
self.toolver = hdul[1].data['TOOLVER' ].tolist()
|
||||
self.sciproc = hdul[1].data['SCI_PROCESSED'].tolist()
|
||||
self.scitool = hdul[1].data['SCI_TOOLVER' ].tolist()
|
||||
self.scicaldb = hdul[1].data['SCI_CALDB' ].tolist()
|
||||
self.start = hdul[1].data['START' ].tolist()
|
||||
self.stop = hdul[1].data['STOP' ].tolist()
|
||||
self.daylen = hdul[1].data['DAYLEN' ].tolist()
|
||||
self.t1 = hdul[1].data['T1' ].tolist()
|
||||
self.t2 = hdul[1].data['T2' ].tolist()
|
||||
self.t3 = hdul[1].data['T3' ].tolist()
|
||||
self.t4 = hdul[1].data['T4' ].tolist()
|
||||
self.t5 = hdul[1].data['T5' ].tolist()
|
||||
self.t6 = hdul[1].data['T6' ].tolist()
|
||||
self.t7 = hdul[1].data['T7' ].tolist()
|
||||
self.gyro = hdul[1].data['GYRO' ].tolist()
|
||||
self.bokz = hdul[1].data['BOKZ' ].tolist()
|
||||
self.sed1 = hdul[1].data['SED1' ].tolist()
|
||||
self.sed2 = hdul[1].data['SED2' ].tolist()
|
||||
|
||||
def process(self, frame):
|
||||
|
||||
if frame['artday'] not in self.artday:
|
||||
self.artday.append(frame['artday'])
|
||||
self.msktime.append(frame['msktime'])
|
||||
self.complete.append(frame['complete'])
|
||||
self.status.append(-100)
|
||||
self.errors.append(frame['errors'])
|
||||
self.processed.append(frame['processed'])
|
||||
self.toolver.append(frame['artfitstool'])
|
||||
self.sciproc.append(' ')
|
||||
self.scitool.append(' ')
|
||||
self.scicaldb.append(' ')
|
||||
self.start.append(frame['start'])
|
||||
self.stop.append(frame['stop'])
|
||||
self.daylen.append(frame['daylen'])
|
||||
self.t1.append(frame['T1'])
|
||||
self.t2.append(frame['T2'])
|
||||
self.t3.append(frame['T3'])
|
||||
self.t4.append(frame['T4'])
|
||||
self.t5.append(frame['T5'])
|
||||
self.t6.append(frame['T6'])
|
||||
self.t7.append(frame['T7'])
|
||||
self.gyro.append(frame['GYRO'])
|
||||
self.bokz.append(frame['BOKZ'])
|
||||
self.sed1.append(frame['SED1'])
|
||||
self.sed2.append(frame['SED2'])
|
||||
else:
|
||||
index = np.where(np.array(self.artday) == frame['artday'])
|
||||
if not index:
|
||||
print('warning: failed to update index for artday={}'.format(frame['artday']))
|
||||
return
|
||||
|
||||
index = index[0][0]
|
||||
|
||||
self.complete[index] = frame['complete']
|
||||
self.errors[index] = frame['errors']
|
||||
self.processed[index] = frame['processed']
|
||||
self.toolver[index] = frame['artfitstool']
|
||||
self.start[index] = frame['start']
|
||||
self.stop[index] = frame['stop']
|
||||
self.daylen[index] = frame['daylen']
|
||||
self.t1[index] = frame['T1']
|
||||
self.t2[index] = frame['T2']
|
||||
self.t3[index] = frame['T3']
|
||||
self.t4[index] = frame['T4']
|
||||
self.t5[index] = frame['T5']
|
||||
self.t6[index] = frame['T6']
|
||||
self.t7[index] = frame['T7']
|
||||
self.gyro[index] = frame['GYRO']
|
||||
self.bokz[index] = frame['BOKZ']
|
||||
self.sed1[index] = frame['SED1']
|
||||
self.sed2[index] = frame['SED2']
|
||||
|
||||
|
||||
def index(self):
|
||||
return pl.DataFrame(
|
||||
{
|
||||
'artday' : self.artday ,
|
||||
'msktime' : self.msktime ,
|
||||
'complete' : self.complete ,
|
||||
'status' : self.status ,
|
||||
'errors' : self.errors ,
|
||||
'processed' : self.processed,
|
||||
'toolver' : self.toolver ,
|
||||
'sciproc' : self.sciproc ,
|
||||
'scitool' : self.scitool ,
|
||||
'scicaldb' : self.scicaldb ,
|
||||
'start' : self.start ,
|
||||
'stop' : self.stop ,
|
||||
'daylen' : self.daylen ,
|
||||
'T1' : self.t1 ,
|
||||
'T2' : self.t2 ,
|
||||
'T3' : self.t3 ,
|
||||
'T4' : self.t4 ,
|
||||
'T5' : self.t5 ,
|
||||
'T6' : self.t6 ,
|
||||
'T7' : self.t7 ,
|
||||
'GYRO' : self.gyro ,
|
||||
'BOKZ' : self.bokz ,
|
||||
'SED1' : self.sed1 ,
|
||||
'SED2' : self.sed2 ,
|
||||
}
|
||||
, schema={
|
||||
'artday' : pl.String ,
|
||||
'msktime' : pl.String ,
|
||||
'complete' : pl.Int16 ,
|
||||
'status' : pl.Int16 ,
|
||||
'errors' : pl.Int16 ,
|
||||
'processed' : pl.String ,
|
||||
'toolver' : pl.String ,
|
||||
'sciproc' : pl.String ,
|
||||
'scitool' : pl.String ,
|
||||
'scicaldb' : pl.String ,
|
||||
'start' : pl.Float64,
|
||||
'stop' : pl.Float64,
|
||||
'daylen' : pl.Float64,
|
||||
'T1' : pl.Int16 ,
|
||||
'T2' : pl.Int16 ,
|
||||
'T3' : pl.Int16 ,
|
||||
'T4' : pl.Int16 ,
|
||||
'T5' : pl.Int16 ,
|
||||
'T6' : pl.Int16 ,
|
||||
'T7' : pl.Int16 ,
|
||||
'GYRO' : pl.Int16 ,
|
||||
'BOKZ' : pl.Int16 ,
|
||||
'SED1' : pl.Int16 ,
|
||||
'SED2' : pl.Int16
|
||||
}
|
||||
).sort('artday')
|
||||
|
||||
def _calculate_status(self, df):
|
||||
status = np.array(df['status'])
|
||||
errors = np.array(df['errors'])
|
||||
artdays = np.array(df['artday'], dtype=int)
|
||||
|
||||
dtime = Dtime(datetime.utcnow().isoformat(), tzone=TZ_UTC)
|
||||
nowday = ArtDay(dtime.to_artday()).get()
|
||||
nowdays = np.full_like(artdays, nowday)
|
||||
|
||||
deltadays = nowdays - artdays
|
||||
|
||||
mask_maychange = status != 1
|
||||
mask_haserrors = errors != 0
|
||||
mask_freshdays = deltadays < 4
|
||||
|
||||
newstatus = np.zeros_like(artdays)
|
||||
newstatus[mask_maychange & mask_freshdays & ~mask_haserrors] = -2
|
||||
newstatus[mask_maychange & mask_freshdays & mask_haserrors] = -5
|
||||
newstatus[mask_maychange & ~mask_freshdays & mask_haserrors] = -1
|
||||
newstatus[~mask_maychange] = 1
|
||||
|
||||
return newstatus
|
||||
|
||||
def write_index_fits(self):
|
||||
df = self.index()
|
||||
status = self._calculate_status(df)
|
||||
|
||||
# print(df)
|
||||
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
# status = np.full_like(df['complete' ], -1)
|
||||
|
||||
c00 = fits.Column(name='ARTDAY' , format='6A' , unit='' , array=np.array(df['artday' ]))
|
||||
c01 = fits.Column(name='MSKTIME' , format='19A', unit='date', array=np.array(df['msktime' ]))
|
||||
c02 = fits.Column(name='COMPLETE' , format='1I' , unit='' , array=np.array(df['complete' ]))
|
||||
c03 = fits.Column(name='STATUS' , format='1I' , unit='' , array=np.array(status ))
|
||||
c04 = fits.Column(name='ERRORS' , format='1I' , unit='' , array=np.array(df['errors' ]))
|
||||
c05 = fits.Column(name='PROCESSED' , format='19A', unit='date', array=np.array(df['processed']))
|
||||
c06 = fits.Column(name='TOOLVER' , format='10A', unit='date', array=np.array(df['toolver' ]))
|
||||
c07 = fits.Column(name='SCI_PROCESSED', format='19A', unit='' , array=np.array(df['sciproc' ]))
|
||||
c08 = fits.Column(name='SCI_TOOLVER' , format='10A', unit='' , array=np.array(df['scitool' ]))
|
||||
c09 = fits.Column(name='SCI_CALDB' , format='10A', unit='' , array=np.array(df['scicaldb' ]))
|
||||
c10 = fits.Column(name='START' , format='1D' , unit='sec' , array=np.array(df['start' ]))
|
||||
c11 = fits.Column(name='STOP' , format='1D' , unit='sec' , array=np.array(df['stop' ]))
|
||||
c12 = fits.Column(name='DAYLEN' , format='1D' , unit='sec' , array=np.array(df['daylen' ]))
|
||||
c13 = fits.Column(name='T1' , format='1I' , unit='' , array=np.array(df['T1' ]))
|
||||
c14 = fits.Column(name='T2' , format='1I' , unit='' , array=np.array(df['T2' ]))
|
||||
c15 = fits.Column(name='T3' , format='1I' , unit='' , array=np.array(df['T3' ]))
|
||||
c16 = fits.Column(name='T4' , format='1I' , unit='' , array=np.array(df['T4' ]))
|
||||
c17 = fits.Column(name='T5' , format='1I' , unit='' , array=np.array(df['T5' ]))
|
||||
c18 = fits.Column(name='T6' , format='1I' , unit='' , array=np.array(df['T6' ]))
|
||||
c19 = fits.Column(name='T7' , format='1I' , unit='' , array=np.array(df['T7' ]))
|
||||
c20 = fits.Column(name='GYRO' , format='1I' , unit='' , array=np.array(df['GYRO' ]))
|
||||
c21 = fits.Column(name='BOKZ' , format='1I' , unit='' , array=np.array(df['BOKZ' ]))
|
||||
c22 = fits.Column(name='SED1' , format='1I' , unit='' , array=np.array(df['SED1' ]))
|
||||
c23 = fits.Column(name='SED2' , format='1I' , unit='' , array=np.array(df['SED2' ]))
|
||||
|
||||
cols = fits.ColDefs(
|
||||
[
|
||||
c00, c01, c02, c03, c04, c05, c06, c07, c08, c09,
|
||||
c10, c11, c12, c13, c14, c15, c16, c17, c18, c19,
|
||||
c20, c21, c22, c23
|
||||
]
|
||||
)
|
||||
|
||||
indexhdu = fits.BinTableHDU.from_columns(cols)
|
||||
indexhdu.name = "INDEX"
|
||||
|
||||
creator = 'artindex v.{}'.format(__version__)
|
||||
indexhdu.header['creator' ] = (creator, 'program and version that created this file')
|
||||
|
||||
|
||||
dtnow = datetime.now(tz=TZ_MSK)
|
||||
|
||||
indexhdu.header['mjdref' ] = (MJDREFD , 'MJD corresponding to SC clock start 2000.0 MSK' )
|
||||
indexhdu.header['date' ] = (dtnow.strftime('%Y/%m/%d'), 'date that FITS file was created' )
|
||||
|
||||
|
||||
hdulist = fits.HDUList([hdu, indexhdu])
|
||||
hdulist.writeto(self._index, overwrite=True)
|
||||
|
||||
print("written: {}".format(self._index))
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionIndexFrame:
|
||||
def __init__(self):
|
||||
self.key = ""
|
||||
|
||||
self.stem = ""
|
||||
self.station = "00"
|
||||
self.sequence = "000"
|
||||
self.data_quality = 0
|
||||
self.total_quality = 0
|
||||
|
||||
self.processed = ""
|
||||
|
||||
self.intervals = {
|
||||
"T1": (0, 0),
|
||||
"T2": (0, 0),
|
||||
"T3": (0, 0),
|
||||
"T4": (0, 0),
|
||||
"T5": (0, 0),
|
||||
"T6": (0, 0),
|
||||
"T7": (0, 0),
|
||||
}
|
||||
|
||||
self.flag = 0
|
||||
|
||||
def make_key(self):
|
||||
self.key = ".".join((self.stem, self.station, self.sequence))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
388
arttools/src/arttools/arttime.py
Executable file
388
arttools/src/arttools/arttime.py
Executable file
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinksy SRG/ART-XC telescope software.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.io import fits
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
from arttools.version import __version__
|
||||
|
||||
from arttools.argparse import KvAction
|
||||
from arttools.toolargs import ToolArgs
|
||||
|
||||
|
||||
MJDREF = Time(51543.875, format='mjd') #corresponds to date 01.01.2000 00:00:00 (UTC+3)
|
||||
TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) #UTC time zone
|
||||
TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) #UTC+3 (Moscow) time zone
|
||||
|
||||
|
||||
class ArtTime(object):
|
||||
|
||||
def __init__(self, artday=None, missiontime=None, mjd=None, datetime_utc=None, datetime_msk=None):
|
||||
|
||||
self._has_value = 0
|
||||
self._try_aday(artday)
|
||||
self._try_mtime(missiontime)
|
||||
self._try_mjd(mjd)
|
||||
self._try_dtime_utc(datetime_utc)
|
||||
self._try_dtime_msk(datetime_msk)
|
||||
|
||||
if self._has_value > 1:
|
||||
print('Error: ArtTime needs only one argument')
|
||||
exit()
|
||||
|
||||
if self._has_value == 0:
|
||||
datetime_utc = datetime.utcnow()
|
||||
self._try_dtime_utc(datetime_utc.isoformat())
|
||||
|
||||
def _try_mtime(self, missiontime):
|
||||
if missiontime is None:
|
||||
return
|
||||
|
||||
self._has_value = self._has_value+1
|
||||
|
||||
self._missiontime = MissionTime(missiontime)
|
||||
self._artday = ArtDay(self._missiontime.to_artday())
|
||||
self._mjd = MjdDay(self._missiontime.to_mjd())
|
||||
self._datetime_utc = Dtime(dtime=str(self._missiontime.to_datetime(tzone=TZ_UTC)))
|
||||
self._datetime_msk = Dtime(dtime=str(self._missiontime.to_datetime(tzone=TZ_MSK)))
|
||||
return
|
||||
|
||||
def _try_aday(self, artday):
|
||||
if artday is None:
|
||||
return
|
||||
|
||||
self._has_value = self._has_value+1
|
||||
|
||||
self._artday = ArtDay(artday)
|
||||
self._missiontime = MissionTime(self._artday.to_mission())
|
||||
self._mjd = MjdDay(self._artday.to_mjd())
|
||||
self._datetime_utc = Dtime(dtime=str(self._artday.to_datetime(tzone=TZ_UTC)))
|
||||
self._datetime_msk = Dtime(dtime=str(self._artday.to_datetime(tzone=TZ_MSK)))
|
||||
|
||||
def _try_mjd(self, mjd):
|
||||
if mjd is None:
|
||||
return
|
||||
|
||||
self._has_value = self._has_value+1
|
||||
|
||||
self._mjd = MjdDay(mjd)
|
||||
self._artday = ArtDay(self._mjd.to_artday())
|
||||
self._missiontime = MissionTime(self._mjd.to_mission())
|
||||
self._datetime_utc = Dtime(dtime=str(self._mjd.to_datetime(tzone=TZ_UTC)))
|
||||
self._datetime_msk = Dtime(dtime=str(self._mjd.to_datetime(tzone=TZ_MSK)))
|
||||
|
||||
def _try_dtime_utc(self, datetime_utc):
|
||||
if datetime_utc is None:
|
||||
return
|
||||
|
||||
self._has_value = self._has_value+1
|
||||
|
||||
self._datetime_utc = Dtime(datetime_utc, tzone=TZ_UTC)
|
||||
self._datetime_msk = Dtime(str(self._datetime_utc.to_msk()))
|
||||
self._missiontime = MissionTime(self._datetime_utc.to_mission())
|
||||
self._artday = ArtDay(self._datetime_utc.to_artday())
|
||||
self._mjd = MjdDay(self._datetime_utc.to_mjd())
|
||||
|
||||
def _try_dtime_msk(self, datetime_msk):
|
||||
if datetime_msk is None:
|
||||
return
|
||||
|
||||
self._has_value = self._has_value+1
|
||||
|
||||
self._datetime_msk = Dtime(datetime_msk, tzone=TZ_MSK)
|
||||
self._datetime_utc = Dtime(str(self._datetime_msk.to_utc()))
|
||||
self._missiontime = MissionTime(self._datetime_msk.to_mission())
|
||||
self._artday = ArtDay(self._datetime_msk.to_artday())
|
||||
self._mjd = MjdDay(self._datetime_msk.to_mjd())
|
||||
|
||||
@property
|
||||
def missiontime(self):
|
||||
return self._missiontime
|
||||
|
||||
@property
|
||||
def artday(self):
|
||||
return self._artday
|
||||
|
||||
@property
|
||||
def mjd(self):
|
||||
return self._mjd
|
||||
|
||||
@property
|
||||
def datetime_utc(self):
|
||||
return self._datetime_utc
|
||||
|
||||
@property
|
||||
def datetime_msk(self):
|
||||
return self._datetime_msk
|
||||
|
||||
def __str__(self):
|
||||
aday_str = 'Artday --> ' + str(self._artday )
|
||||
mtime_str = 'Mission time --> ' + str(self._missiontime )
|
||||
mjd_str = 'MJD --> ' + str(self._mjd )
|
||||
date_msk_str = 'Date MSK --> ' + str(self._datetime_msk)
|
||||
date_utc_str = 'Date UTC --> ' + str(self._datetime_utc)
|
||||
|
||||
return '\n'.join((aday_str, mtime_str, mjd_str, date_msk_str, date_utc_str))
|
||||
|
||||
|
||||
class MissionTime(object):
|
||||
|
||||
def __init__(self, missiontime):
|
||||
self._missiontime = missiontime
|
||||
|
||||
def get(self):
|
||||
return self._missiontime
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
return self._missiontime
|
||||
|
||||
def __str__(self):
|
||||
return str(self._missiontime)
|
||||
|
||||
def to_datetime(self, tzone=TZ_UTC):
|
||||
days = self.to_mjd()
|
||||
return days.to_datetime(timezone=tzone)
|
||||
|
||||
def to_artday(self):
|
||||
ARTDAYLEN = erfa.DAYSEC
|
||||
return self._missiontime / ARTDAYLEN
|
||||
|
||||
def to_mjd(self):
|
||||
mjd_value = MJDREF.mjd + Time(self._missiontime / erfa.DAYSEC, format='mjd').mjd
|
||||
return Time(mjd_value, format='mjd')
|
||||
|
||||
|
||||
class ArtDay(object):
|
||||
|
||||
def __init__(self, artday):
|
||||
self._artday = artday
|
||||
|
||||
def get(self):
|
||||
return self._artday
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
return self._artday
|
||||
|
||||
def __str__(self):
|
||||
return str(self._artday)
|
||||
|
||||
def to_mission(self):
|
||||
ARTDAYLEN = erfa.DAYSEC
|
||||
return self._artday * ARTDAYLEN
|
||||
|
||||
def to_datetime(self, tzone=TZ_UTC):
|
||||
days = self.to_mjd()
|
||||
return days.to_datetime(timezone=tzone)
|
||||
|
||||
def to_mjd(self):
|
||||
mjd_value = MJDREF.mjd + Time(self._artday, format='mjd').mjd
|
||||
return Time(mjd_value, format='mjd')
|
||||
|
||||
def to_str(self):
|
||||
return '{:0>6}'.format(self._artday)
|
||||
|
||||
|
||||
class MjdDay(object):
|
||||
|
||||
def __init__(self, mjd):
|
||||
self._mjd = Time(mjd, format='mjd')
|
||||
|
||||
def get(self):
|
||||
return self._mjd
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
return self._mjd
|
||||
|
||||
def __str__(self):
|
||||
return str(self._mjd)
|
||||
|
||||
def to_mission(self):
|
||||
ARTDAYLEN = erfa.DAYSEC
|
||||
return (self._mjd.mjd - MJDREF.mjd) * ARTDAYLEN
|
||||
|
||||
def to_artday(self):
|
||||
return self._mjd.mjd - MJDREF.mjd
|
||||
|
||||
def to_datetime(self, tzone=TZ_UTC):
|
||||
return Time(self._mjd, format='mjd').to_datetime(timezone=tzone)
|
||||
|
||||
|
||||
class Dtime(object):
|
||||
|
||||
def __init__(self, dtime, tzone=None):
|
||||
dtime = datetime.fromisoformat(dtime)
|
||||
if tzone is None:
|
||||
self._dtime = dtime
|
||||
else:
|
||||
self._dtime = dtime.replace(tzinfo=tzone)
|
||||
self._tzone = tzone
|
||||
|
||||
def get(self):
|
||||
return self._dtime
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
return self._dtime
|
||||
|
||||
def __str__(self):
|
||||
return str(self._dtime)
|
||||
|
||||
def to_mjd(self):
|
||||
days = Time(self._dtime)
|
||||
return days
|
||||
|
||||
def to_artday(self):
|
||||
return self.to_mjd().mjd - MJDREF.mjd
|
||||
|
||||
def to_mission(self):
|
||||
ARTDAYLEN = erfa.DAYSEC
|
||||
return self.to_artday() * ARTDAYLEN
|
||||
|
||||
def to_msk(self):
|
||||
dtime_utc = Time(self._dtime)
|
||||
return dtime_utc.to_datetime(timezone=TZ_MSK)
|
||||
|
||||
def to_utc(self):
|
||||
dtime_msk = Time(self._dtime)
|
||||
return dtime_msk.to_datetime(timezone=TZ_UTC)
|
||||
|
||||
|
||||
class Args(ToolArgs):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.artday = None
|
||||
self.missiontime = None
|
||||
self.mjd = None
|
||||
self.datetime_utc = None
|
||||
self.datetime_msk = None
|
||||
|
||||
def read(self):
|
||||
argparser = argparse.ArgumentParser(
|
||||
description=('The arttime tool converts artdays, mission time, MSK and UTC date time'
|
||||
' and MJD to each other. Calling arttime without parameters will use the current '
|
||||
'local time as input.'))
|
||||
argparser.add_argument(
|
||||
'--artday', '--aday',
|
||||
help=('ART-XC days.'
|
||||
'Example: arttime --artday=8000'),
|
||||
action=KvAction)
|
||||
argparser.add_argument(
|
||||
'--missiontime', '--mtime',
|
||||
help=('Onboard ART-XC seconds. '
|
||||
'Example: arttime --missiontime=691200000'),
|
||||
action=KvAction)
|
||||
argparser.add_argument(
|
||||
'--mjd',
|
||||
help=('MJD. '
|
||||
'Example: arttime --mjd=59543.875'),
|
||||
action=KvAction)
|
||||
argparser.add_argument(
|
||||
'--datetime_utc', '--dtime_utc',
|
||||
help=('Date time based on UTC time zone. '
|
||||
'Example: arttime --datetime_utc=2021-11-25T21:00:00'),
|
||||
action=KvAction)
|
||||
argparser.add_argument(
|
||||
'--datetime_msk', '--dtime_msk',
|
||||
help=('Date time based on Moscow time zone (UTC+3). '
|
||||
'Example: arttime --datetime_msk=2021-11-26T00:00:00'),
|
||||
action=KvAction)
|
||||
args = argparser.parse_args()
|
||||
|
||||
if args.artday is not None:
|
||||
self.artday = float(args.artday)
|
||||
if args.missiontime is not None:
|
||||
self.missiontime = float(args.missiontime)
|
||||
if args.mjd is not None:
|
||||
self.mjd = float(args.mjd)
|
||||
self.datetime_utc = args.datetime_utc
|
||||
self.datetime_msk = args.datetime_msk
|
||||
|
||||
def print(self):
|
||||
logstr = '\n'.join((
|
||||
'Input parameters:',
|
||||
' artday = {}'.format(self.artday ),
|
||||
' missiontime = {}'.format(self.missiontime ),
|
||||
' mjd = {}'.format(self.mjd ),
|
||||
' datetime_utc = {}'.format(self.datetime_utc),
|
||||
' datetime_msk = {}'.format(self.datetime_msk)
|
||||
))
|
||||
print(logstr)
|
||||
|
||||
|
||||
class Task(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.initialize()
|
||||
return self
|
||||
|
||||
def __exit__(self, ex_type, ex_value, ex_traceback):
|
||||
self.finalize()
|
||||
|
||||
def initialize(self):
|
||||
print('running: arttime v.{}'.format(__version__) )
|
||||
print('================================================================================')
|
||||
|
||||
self._args = Args()
|
||||
self._args.read()
|
||||
|
||||
self._args.print()
|
||||
|
||||
def finalize(self):
|
||||
print('--------------------------------------------------------------------------------')
|
||||
|
||||
def run(self):
|
||||
if self._args.artday is not None:
|
||||
print(ArtTime(artday=self._args.artday))
|
||||
|
||||
if self._args.missiontime is not None:
|
||||
print(ArtTime(missiontime=self._args.missiontime))
|
||||
|
||||
if self._args.mjd is not None:
|
||||
print(ArtTime(mjd=self._args.mjd))
|
||||
|
||||
if self._args.datetime_utc is not None:
|
||||
print(ArtTime(datetime_utc=self._args.datetime_utc))
|
||||
|
||||
if self._args.datetime_msk is not None:
|
||||
print(ArtTime(datetime_msk=self._args.datetime_msk))
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
datetime_utc = datetime.utcnow()
|
||||
print('*** Convertation for the current local time:')
|
||||
print(ArtTime(datetime_utc=datetime_utc.isoformat()))
|
||||
|
||||
|
||||
def main():
|
||||
with Task() as task:
|
||||
task.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
72
arttools/src/arttools/cmd.py
Executable file
72
arttools/src/arttools/cmd.py
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import asyncio as aio
|
||||
|
||||
from arttools.errors import Error
|
||||
|
||||
|
||||
class InternalError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class UncleanExecutionError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class ExecutionResult(object):
|
||||
|
||||
def __init__(self, cmd, retcode, stdout_data, stderr_data):
|
||||
self.command = cmd
|
||||
self.return_code = retcode
|
||||
self.stdout_data = stdout_data
|
||||
self.stderr_data = stderr_data
|
||||
|
||||
|
||||
class Command(object):
|
||||
|
||||
def __init__(self, cmd, stdout=sys.stdout, stderr=sys.stderr):
|
||||
self._cmd = cmd
|
||||
self._stdout = stdout
|
||||
self._stderr = stderr
|
||||
|
||||
self._result = None
|
||||
|
||||
async def _run(self):
|
||||
process = await aio.create_subprocess_shell(
|
||||
cmd=self._cmd,
|
||||
stdout=self._stdout,
|
||||
stderr=self._stderr
|
||||
)
|
||||
|
||||
stdout_data, stderr_data = await process.communicate()
|
||||
|
||||
if stdout_data is not None:
|
||||
stdout_data = stdout_data.decode()
|
||||
|
||||
if stderr_data is not None:
|
||||
stderr_data = stderr_data.decode()
|
||||
|
||||
self._result = \
|
||||
ExecutionResult(
|
||||
self._cmd,
|
||||
process.returncode,
|
||||
stdout_data,
|
||||
stderr_data
|
||||
)
|
||||
|
||||
def run(self):
|
||||
aio.run(self._run())
|
||||
|
||||
return self._result
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
99
arttools/src/arttools/datatable.py
Executable file
99
arttools/src/arttools/datatable.py
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import polars as pl
|
||||
|
||||
import numpy as np
|
||||
from astropy.io import fits
|
||||
|
||||
|
||||
class DataTable(object):
|
||||
|
||||
def __init__(self):
|
||||
self._dataframe = None
|
||||
self._adapter = None
|
||||
|
||||
@property
|
||||
def frame(self): return self._dataframe
|
||||
|
||||
@property
|
||||
def size(self): return self._dataframe.height
|
||||
|
||||
def adapter(self, adapter):
|
||||
self._adapter = adapter
|
||||
|
||||
return self
|
||||
|
||||
def read(self):
|
||||
if self._adapter is not None:
|
||||
self._dataframe = self._adapter.read()
|
||||
|
||||
return self
|
||||
|
||||
def column(self, name):
|
||||
return self._dataframe.get_column(name)
|
||||
|
||||
def setcol(self, name, arr):
|
||||
self._dataframe =\
|
||||
self._dataframe.with_columns(
|
||||
pl.Series(name, arr)
|
||||
)
|
||||
|
||||
|
||||
class FitsAdapter(object):
|
||||
|
||||
def __init__(self, filename, tablename):
|
||||
self._FORMATS = {
|
||||
'D': pl.Float64,
|
||||
'J': pl.UInt32 ,
|
||||
'I': pl.UInt16 ,
|
||||
'B': pl.UInt8 ,
|
||||
'A': pl.String
|
||||
}
|
||||
|
||||
self._filename = filename
|
||||
self._tablename = tablename
|
||||
|
||||
def _schema_for(self, columns):
|
||||
schema = []
|
||||
for col in columns:
|
||||
colformat = ''.join([ch for ch in col.format if ch.isalpha()])
|
||||
schema.append((col.name, self._FORMATS[colformat]))
|
||||
|
||||
return schema
|
||||
|
||||
def read(self):
|
||||
with fits.open(self._filename) as hdul:
|
||||
hdu = hdul[self._tablename]
|
||||
|
||||
schema = self._schema_for(hdu.data.columns)
|
||||
|
||||
data = {}
|
||||
for col in hdu.data.columns:
|
||||
colformat = ''.join([ch for ch in col.format if ch.isalpha()])
|
||||
|
||||
if colformat == 'D': data[col.name] = np.array(hdu.data[col.name], dtype=np.float64)
|
||||
elif colformat == 'I': data[col.name] = np.array(hdu.data[col.name], dtype=np.uint16 )
|
||||
else:
|
||||
data[col.name] = hdu.data[col.name]
|
||||
|
||||
return pl.DataFrame(data, schema=schema)
|
||||
|
||||
def write(self):
|
||||
pass
|
||||
|
||||
|
||||
class Adapter(object):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
21
arttools/src/arttools/errors.py
Executable file
21
arttools/src/arttools/errors.py
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the MVN software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class ConfigNotFoundError(Error):
|
||||
pass
|
||||
|
||||
class RegionsError(Error):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
404
arttools/src/arttools/events.py
Executable file
404
arttools/src/arttools/events.py
Executable file
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import datetime
|
||||
|
||||
import numpy as np
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
from astropy.io import fits
|
||||
|
||||
import astropy.units as u
|
||||
from astropy.time.formats import erfa
|
||||
from astropy.time import Time
|
||||
from astropy.time import TimezoneInfo
|
||||
|
||||
TZ_UTC = TimezoneInfo(utc_offset=0*u.hour) # UTC time zone
|
||||
TZ_MSK = TimezoneInfo(utc_offset=3*u.hour) # UTC+3 (Moscow) time zone
|
||||
from arttools.arttime import MissionTime
|
||||
|
||||
from arttools.gti import Gti
|
||||
from arttools.datatable import DataTable, FitsAdapter
|
||||
|
||||
from arttools.version import __version__
|
||||
|
||||
|
||||
class Events(object):
|
||||
_deadtimes = {
|
||||
1: 770e-6,
|
||||
2: 770e-6,
|
||||
3: 770e-6,
|
||||
4: 770e-6,
|
||||
5: 770e-6,
|
||||
6: 1060e-6,
|
||||
7: 770e-6,
|
||||
}
|
||||
|
||||
_eventlist_keys = [
|
||||
'TIME',
|
||||
'TIME_I',
|
||||
'TIME_F',
|
||||
'TIME_CORR',
|
||||
'ENERGY',
|
||||
'ENERGY_TOP',
|
||||
'ENERGY_BOT',
|
||||
'GRADE',
|
||||
'FLAG',
|
||||
'RA',
|
||||
'DEC',
|
||||
'RAW_X',
|
||||
'RAW_Y',
|
||||
'PHA_BOT',
|
||||
'PHA_BOT_ADD1',
|
||||
'PHA_BOT_SUB1',
|
||||
'PHA_TOP',
|
||||
'PHA_TOP_ADD1',
|
||||
'PHA_TOP_SUB1',
|
||||
'TRIGGER',
|
||||
]
|
||||
|
||||
_eventlist_layout = {
|
||||
'TIME' : {'format': '1D', 'unit': 'sec' },
|
||||
'TIME_I' : {'format': '1J', 'unit': 'sec', 'bzero': 2147483648},
|
||||
'TIME_F' : {'format': '1D', 'unit': 'sec' },
|
||||
'TIME_CORR' : {'format': '1D', 'unit': 'sec' },
|
||||
'ENERGY' : {'format': '1D', 'unit': 'keV' },
|
||||
'ENERGY_TOP' : {'format': '1D', 'unit': 'keV' },
|
||||
'ENERGY_BOT' : {'format': '1D', 'unit': 'keV' },
|
||||
'GRADE' : {'format': '1I', 'unit': '' },
|
||||
'FLAG' : {'format': '1I', 'unit': '' },
|
||||
'RA' : {'format': '1D', 'unit': 'deg' },
|
||||
'DEC' : {'format': '1D', 'unit': 'deg' },
|
||||
'RAW_X' : {'format': '1I', 'unit': 'pix' },
|
||||
'RAW_Y' : {'format': '1I', 'unit': 'pix' },
|
||||
'PHA_BOT' : {'format': '1I', 'unit': '' },
|
||||
'PHA_BOT_ADD1': {'format': '1I', 'unit': '' },
|
||||
'PHA_BOT_SUB1': {'format': '1I', 'unit': '' },
|
||||
'PHA_TOP' : {'format': '1I', 'unit': '' },
|
||||
'PHA_TOP_ADD1': {'format': '1I', 'unit': '' },
|
||||
'PHA_TOP_SUB1': {'format': '1I', 'unit': '' },
|
||||
'TRIGGER' : {'format': '1I', 'unit': '' },
|
||||
}
|
||||
|
||||
def __init__(self, srcfile):
|
||||
super().__init__()
|
||||
|
||||
self._srcfile = srcfile
|
||||
|
||||
self._telname = 0
|
||||
self._teln = 0
|
||||
|
||||
self._tstart = 0
|
||||
self._tstop = 0
|
||||
|
||||
self._events = DataTable()
|
||||
self._hk = DataTable()
|
||||
self._gti = Gti()
|
||||
|
||||
@property
|
||||
def empty(self):
|
||||
return self.size == 0
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._events.size
|
||||
|
||||
@property
|
||||
def teln(self):
|
||||
return self._teln
|
||||
|
||||
@property
|
||||
def telname(self):
|
||||
return self._telname
|
||||
|
||||
@property
|
||||
def mjdref(self):
|
||||
return self._mjdref
|
||||
|
||||
@property
|
||||
def tstart(self):
|
||||
return self._tstart
|
||||
|
||||
@property
|
||||
def tstop(self):
|
||||
return self._tstop
|
||||
|
||||
# @property
|
||||
# def deadtime(self):
|
||||
# return self._deadtime
|
||||
|
||||
@property
|
||||
def events(self):
|
||||
return self._events
|
||||
|
||||
@property
|
||||
def hk(self):
|
||||
return self._hk
|
||||
|
||||
@property
|
||||
def gti(self):
|
||||
return self._gti
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._srcfile
|
||||
|
||||
def _telescope_deadtime(self):
|
||||
return self._deadtimes[self._teln]
|
||||
|
||||
def deadtime(self, start, stop):
|
||||
interval = stop - start
|
||||
|
||||
hktimes = np.array(self.hk.column('TIME' ))
|
||||
hkevt = np.array(self.hk.column('EVENTS')).astype(np.int64)
|
||||
|
||||
X = hktimes[1:]
|
||||
Y = np.diff(hkevt) / np.diff(hktimes)
|
||||
hkmask = Y > 0
|
||||
|
||||
X = X[hkmask]
|
||||
Y = Y[hkmask]
|
||||
|
||||
datamask = (X >= start) & (X <= stop)
|
||||
interp = interp1d(X, Y, kind='linear', bounds_error=False, fill_value='extrapolate')
|
||||
|
||||
begin = interp(start).item()
|
||||
end = interp(stop).item()
|
||||
middle = Y[datamask]
|
||||
|
||||
cnttimes = np.array([start, *X[datamask], stop])
|
||||
countrate = np.array([begin, *middle, end])
|
||||
|
||||
return np.average(((countrate[1:] + countrate[:-1]) / 2.), weights=np.diff(cnttimes)) * interval * self._telescope_deadtime()
|
||||
|
||||
def bindeadtime(self, edges, bins):
|
||||
hktimes = np.array(self.hk.column('TIME' ))
|
||||
hkevt = np.array(self.hk.column('EVENTS')).astype(np.int64)
|
||||
|
||||
binrate = np.ones_like(bins)
|
||||
|
||||
evtcountfn = interp1d(hktimes, hkevt,
|
||||
kind='linear', bounds_error=False, fill_value='extrapolate')
|
||||
|
||||
edge_events = evtcountfn(edges)
|
||||
|
||||
# 2) make mask for event counter reversals (negative diffs)
|
||||
edgediff = np.diff(edge_events)
|
||||
edgemask = edgediff > 0
|
||||
|
||||
# # 3) interpolate filtered (2) points
|
||||
edgecountfn = interp1d(
|
||||
edges[1:][edgemask], edgediff[edgemask],
|
||||
kind='linear', bounds_error=False, fill_value='extrapolate')
|
||||
|
||||
# 4) calculate deadtimes and fractional exposure for bins
|
||||
bindeadt = edgecountfn(edges) * self._telescope_deadtime()
|
||||
binrate -= bindeadt[1:] / np.diff(edges)
|
||||
|
||||
return bindeadt[1:], binrate
|
||||
|
||||
def _read_header(self):
|
||||
with fits.open(self._srcfile) as hdul:
|
||||
self._teln = hdul['events'].header['teln' ]
|
||||
self._telname = hdul['events'].header['instrume']
|
||||
self._mjdref = hdul['events'].header['mjdref' ]
|
||||
self._tstart = hdul['events'].header['tstart' ]
|
||||
self._tstop = hdul['events'].header['tstop' ]
|
||||
|
||||
def read(self):
|
||||
self._read_header()
|
||||
|
||||
self._events = DataTable().adapter(FitsAdapter(self._srcfile, 'events')).read()
|
||||
self._hk = DataTable().adapter(FitsAdapter(self._srcfile, 'hk' )).read()
|
||||
self._gti = Gti.from_hduname(self._srcfile, 'stdgti')
|
||||
|
||||
return self
|
||||
|
||||
def filter(self, gti):
|
||||
pass
|
||||
|
||||
def write_cl(self, dstfile, cdbinfo=[]):
|
||||
|
||||
srchdul = fits.open(self._srcfile)
|
||||
|
||||
# --- primary HDU ---
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
# --- EVENTS HDU ---
|
||||
a00 = np.array(self._events.column('TIME' ))
|
||||
a01 = np.array(self._events.column('TIME_I' ))
|
||||
a02 = np.array(self._events.column('TIME_F' ))
|
||||
a03 = np.array(self._events.column('TIME_CORR' ))
|
||||
a04 = np.array(self._events.column('ENERGY' ))
|
||||
a05 = np.array(self._events.column('ENERGY_TOP' ))
|
||||
a06 = np.array(self._events.column('ENERGY_BOT' ))
|
||||
a07 = np.array(self._events.column('GRADE' ))
|
||||
a08 = np.array(self._events.column('FLAG' ))
|
||||
a09 = np.array(self._events.column('RA' ))
|
||||
a10 = np.array(self._events.column('DEC' ))
|
||||
a11 = np.array(self._events.column('RAW_X' ))
|
||||
a12 = np.array(self._events.column('RAW_Y' ))
|
||||
a13 = np.array(self._events.column('PHA_BOT' ))
|
||||
a14 = np.array(self._events.column('PHA_BOT_ADD1'))
|
||||
a15 = np.array(self._events.column('PHA_BOT_SUB1'))
|
||||
a16 = np.array(self._events.column('PHA_TOP' ))
|
||||
a17 = np.array(self._events.column('PHA_TOP_ADD1'))
|
||||
a18 = np.array(self._events.column('PHA_TOP_SUB1'))
|
||||
a19 = np.array(self._events.column('TRIGGER' ))
|
||||
|
||||
c00 = fits.Column(name='TIME' , format='1D', unit='sec', array=a00 )
|
||||
c01 = fits.Column(name='TIME_I' , format='1J', unit='sec', array=a01, bzero=2147483648)
|
||||
c02 = fits.Column(name='TIME_F' , format='1D', unit='sec', array=a02 )
|
||||
c03 = fits.Column(name='TIME_CORR' , format='1D', unit='sec', array=a03 )
|
||||
c04 = fits.Column(name='ENERGY' , format='1D', unit='keV', array=a04 )
|
||||
c05 = fits.Column(name='ENERGY_TOP' , format='1D', unit='keV', array=a05 )
|
||||
c06 = fits.Column(name='ENERGY_BOT' , format='1D', unit='keV', array=a06 )
|
||||
c07 = fits.Column(name='GRADE' , format='1I', unit='' , array=a07 )
|
||||
c08 = fits.Column(name='FLAG' , format='1I', unit='' , array=a08 )
|
||||
c09 = fits.Column(name='RA' , format='1D', unit='deg', array=a09 )
|
||||
c10 = fits.Column(name='DEC' , format='1D', unit='deg', array=a10 )
|
||||
c11 = fits.Column(name='RAW_X' , format='1I', unit='pix', array=a11 )
|
||||
c12 = fits.Column(name='RAW_Y' , format='1I', unit='pix', array=a12 )
|
||||
c13 = fits.Column(name='PHA_BOT' , format='1I', unit='' , array=a13 )
|
||||
c14 = fits.Column(name='PHA_BOT_ADD1', format='1I', unit='' , array=a14 )
|
||||
c15 = fits.Column(name='PHA_BOT_SUB1', format='1I', unit='' , array=a15 )
|
||||
c16 = fits.Column(name='PHA_TOP' , format='1I', unit='' , array=a16 )
|
||||
c17 = fits.Column(name='PHA_TOP_ADD1', format='1I', unit='' , array=a17 )
|
||||
c18 = fits.Column(name='PHA_TOP_SUB1', format='1I', unit='' , array=a18 )
|
||||
c19 = fits.Column(name='TRIGGER' , format='1I', unit='' , array=a19 )
|
||||
|
||||
cols = fits.ColDefs([
|
||||
c00, c01, c02, c03, c04, c05, c06, c07, c08, c09,
|
||||
c10, c11, c12, c13, c14, c15, c16, c17, c18, c19
|
||||
])
|
||||
|
||||
evthdu = fits.BinTableHDU.from_columns(cols)
|
||||
evthdu.name = 'EVENTS'
|
||||
|
||||
creator = 'artpipeline v.{}'.format(__version__)
|
||||
|
||||
evthdu.header['creator' ] = (creator , 'program and version that created this file' )
|
||||
evthdu.header['origin' ] = ('IKI RAS' , 'institution that created this file' )
|
||||
evthdu.header['telescop'] = ('SRG/ART-XC', 'mission name' )
|
||||
|
||||
# copy header keys
|
||||
COPYKEYS = [
|
||||
'instrume', 'teln', 'detn', 'urdn',
|
||||
'mjdref', 'artday', 'tstart', 'tstop',
|
||||
'date', 'date-obs', 'time-obs', 'date-end', 'time-end',
|
||||
'maxticks', 'timedel', 'timeunit', 'timeref', 'tassign'
|
||||
]
|
||||
|
||||
for key in COPYKEYS:
|
||||
try:
|
||||
evthdu.header[key] = srchdul['EVENTS'].header[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
# add caldb key
|
||||
for key in cdbinfo:
|
||||
evthdu.header[key] = cdbinfo[key]
|
||||
|
||||
# --- make file ---
|
||||
hdulist = fits.HDUList([hdu, evthdu, srchdul['KVEA'], srchdul['HK'], srchdul['STDGTI']])
|
||||
|
||||
hdulist.writeto(dstfile, overwrite=True, checksum=True)
|
||||
|
||||
def modify_and_write_cl(self, dstfile, creator, keys, layout, data, position, mask, gti, cdbinfo=[]):
|
||||
|
||||
srchdul = fits.open(self._srcfile)
|
||||
mjdref = srchdul['EVENTS'].header['MJDREF']
|
||||
|
||||
# -- prepare keys
|
||||
newkeys = self._eventlist_keys[:position] + keys + self._eventlist_keys[position:]
|
||||
|
||||
# -- prepare layout
|
||||
newlayout = self._eventlist_layout
|
||||
newlayout.update(layout)
|
||||
|
||||
# -- prepare data
|
||||
newdata = { }
|
||||
for key in self._eventlist_keys:
|
||||
newdata[key] = np.array(self._events.column(key))
|
||||
|
||||
for key in keys:
|
||||
newdata[key] = np.array(data[key])
|
||||
|
||||
# -- prepare fits structure
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
cols = []
|
||||
for key in newkeys:
|
||||
lay = newlayout[key]
|
||||
|
||||
if 'bzero' in lay:
|
||||
col = fits.Column(name=key, format=lay['format'], unit=lay['unit'], array=newdata[key][mask], bzero=lay['bzero'])
|
||||
else:
|
||||
col = fits.Column(name=key, format=lay['format'], unit=lay['unit'], array=newdata[key][mask])
|
||||
|
||||
cols.append(col)
|
||||
|
||||
evthdu = fits.BinTableHDU.from_columns(cols)
|
||||
evthdu.name = 'EVENTS'
|
||||
|
||||
evthdu.header['creator' ] = (creator , 'program and version that created this file' )
|
||||
evthdu.header['origin' ] = ('IKI RAS' , 'institution that created this file' )
|
||||
evthdu.header['telescop'] = ('SRG/ART-XC', 'mission name' )
|
||||
|
||||
# copy header keys
|
||||
COPYKEYS = [
|
||||
'instrume', 'teln', 'detn', 'urdn',
|
||||
'maxticks', 'timedel', 'timeunit', 'timeref', 'tassign'
|
||||
]
|
||||
|
||||
for key in COPYKEYS:
|
||||
try:
|
||||
evthdu.header[key] = srchdul['EVENTS'].header[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
dtnow = datetime.datetime.now(tz=TZ_MSK)
|
||||
dtobs = MissionTime(self.gti.start).to_datetime(TZ_MSK)
|
||||
dtend = MissionTime(self.gti.stop ).to_datetime(TZ_MSK)
|
||||
|
||||
deadtime = self.deadtime(self.gti.start, self.gti.stop)
|
||||
|
||||
evthdu.header['MJDREF' ] = (mjdref , 'MJD corresponding to SC clock start 2000.0 MSK')
|
||||
|
||||
evthdu.header['tstart' ] = (self.gti.start , 'start time of the observation [sec]' )
|
||||
evthdu.header['tstop' ] = (self.gti.stop , 'end time of the observation [sec]' )
|
||||
|
||||
evthdu.header['date' ] = (dtnow.strftime('%Y/%m/%d') , 'date that FITS file was created' )
|
||||
evthdu.header['date-obs'] = (dtobs.strftime('%Y/%m/%d') , 'start date of the observation MSK')
|
||||
evthdu.header['date-end'] = (dtend.strftime('%Y/%m/%d') , 'end date of the observation MSK' )
|
||||
evthdu.header['time-obs'] = (dtobs.strftime('%H:%M:%S') , 'start time of the observation MSK')
|
||||
evthdu.header['time-end'] = (dtend.strftime('%H:%M:%S') , 'end time of the observation MSK' )
|
||||
|
||||
evthdu.header['onsource'] = (self.gti.onsource , '[sec]' )
|
||||
evthdu.header['exposure'] = (self.gti.exposure - deadtime, '[sec]' )
|
||||
|
||||
# add caldb key
|
||||
# for key in cdbinfo:
|
||||
# evthdu.header[key] = cdbinfo[key]
|
||||
|
||||
# --- make file ---
|
||||
hdulist = fits.HDUList([hdu, evthdu, srchdul['KVEA'], srchdul['HK'], gti.make_hdu(mjdref)])
|
||||
|
||||
hdulist.writeto(dstfile, overwrite=True, checksum=True)
|
||||
|
||||
def setcol(self, name, arr):
|
||||
self._events.setcol(name, arr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
87
arttools/src/arttools/fstools.py
Executable file
87
arttools/src/arttools/fstools.py
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Dir(object):
|
||||
|
||||
class Type(Enum):
|
||||
path = 1
|
||||
file = 2
|
||||
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._path
|
||||
|
||||
def get(self):
|
||||
return self._path
|
||||
|
||||
def pathfor(self, name):
|
||||
return os.path.join(self._path, name)
|
||||
|
||||
def __truediv__(self, path):
|
||||
return Dir(os.path.join(self._path, path))
|
||||
|
||||
def ls(self, type=None, filt=None):
|
||||
flist = [f for f in os.listdir(self._path)]
|
||||
flist.sort()
|
||||
|
||||
if filt is not None:
|
||||
flist = [f for f in flist if filt(f)]
|
||||
|
||||
typefn = {
|
||||
self.Type.path: os.path.isdir ,
|
||||
self.Type.file: os.path.isfile,
|
||||
}.get(type, None)
|
||||
|
||||
if typefn is not None:
|
||||
flist = [f for f in flist if typefn(os.path.join(self._path, f))]
|
||||
|
||||
return flist
|
||||
|
||||
def lsdirs(self, filt=None):
|
||||
return self.ls(type=self.Type.path, filt=filt)
|
||||
|
||||
def lsfiles(self, filt=None):
|
||||
return self.ls(type=self.Type.file, filt=filt)
|
||||
|
||||
def empty(self):
|
||||
if not os.path.isdir(self._path):
|
||||
return False
|
||||
|
||||
return len(os.listdir(self._path)) == 0
|
||||
|
||||
def delete(self):
|
||||
if self.exists():
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
def exists(self):
|
||||
if self._path is None:
|
||||
return False
|
||||
|
||||
return os.path.exists(self._path)
|
||||
|
||||
def check(self, clean=False):
|
||||
if clean:
|
||||
self.delete()
|
||||
|
||||
if not self.exists():
|
||||
os.makedirs(self._path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
254
arttools/src/arttools/gti.py
Normal file
254
arttools/src/arttools/gti.py
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import numpy as np
|
||||
from astropy.io import fits
|
||||
|
||||
class Gti(object):
|
||||
|
||||
def __init__(self, array=np.zeros((0,2), dtype=np.double), start=None, stop=None):
|
||||
self._array = np.reshape(np.asarray(array), (-1,2))
|
||||
|
||||
self._start = self._ntime(start, default=lambda: self._array.min(axis=0)[0])
|
||||
self._stop = self._ntime(stop , default=lambda: self._array.max(axis=0)[1])
|
||||
|
||||
@property
|
||||
def array(self):
|
||||
return self._array
|
||||
|
||||
@property
|
||||
def empty(self):
|
||||
return self._array.size == 0
|
||||
|
||||
@property
|
||||
def size(self) :
|
||||
return self._array.shape[0]
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def stop(self) :
|
||||
return self._stop
|
||||
|
||||
@property
|
||||
def exposure(self):
|
||||
return -np.sum(np.subtract.reduce(self._array, axis=1))
|
||||
|
||||
@property
|
||||
def onsource(self):
|
||||
return self._stop - self._start
|
||||
|
||||
def _ntime(self, val, default):
|
||||
if val is not None:
|
||||
return val
|
||||
if self._array.size == 0:
|
||||
return None
|
||||
return default()
|
||||
|
||||
def mask(self, times):
|
||||
masks = []
|
||||
for istart, istop in self._array:
|
||||
masks.append(np.logical_and(times >= istart, times < istop))
|
||||
|
||||
return np.any(masks, axis=0)
|
||||
|
||||
@staticmethod
|
||||
def from_mask(times, mask):
|
||||
slices = np.ma.clump_masked(np.ma.masked_where(mask, mask))
|
||||
gtis = [[times[s.start], times[s.stop - 1]] for s in slices]
|
||||
return Gti(gtis)
|
||||
|
||||
@staticmethod
|
||||
def from_hduname(fitsfile, hduname):
|
||||
with fits.open(fitsfile) as hdul:
|
||||
hdu = hdul[hduname]
|
||||
|
||||
array = np.array([hdu.data['START'], hdu.data['STOP']]).T
|
||||
try:
|
||||
start = hdu.header['TSTART']
|
||||
stop = hdu.header['TSTOP' ]
|
||||
except KeyError:
|
||||
start = hdu.data['START'][ 0]
|
||||
stop = hdu.data['STOP' ][-1]
|
||||
|
||||
return Gti(array, start, stop)
|
||||
|
||||
@staticmethod
|
||||
def from_hdu(hdu):
|
||||
array = np.array([hdu.data['START'], hdu.data['STOP']]).T
|
||||
try:
|
||||
start = hdu.header['TSTART']
|
||||
stop = hdu.header['TSTOP' ]
|
||||
except KeyError:
|
||||
start = hdu.data['START'][ 0]
|
||||
stop = hdu.data['STOP' ][-1]
|
||||
|
||||
return Gti(array, start, stop)
|
||||
|
||||
def clone(self):
|
||||
return Gti(self._array, self._start, self._stop)
|
||||
|
||||
def normalize(self, edge_delta=0.5):
|
||||
if self._array.size == 0:
|
||||
return
|
||||
|
||||
gti_edges_dt = self._array[1:, 0] - self._array[:-1, 1]
|
||||
gti_edges_mask = gti_edges_dt > edge_delta / 2.
|
||||
gti_indices = np.ravel(np.where(gti_edges_mask))
|
||||
|
||||
new_gtis = []
|
||||
current_index = 0
|
||||
|
||||
for index in gti_indices:
|
||||
start = self._array[current_index, 0]
|
||||
stop = self._array[index, 1]
|
||||
new_gtis.append([start, stop])
|
||||
|
||||
current_index = index + 1
|
||||
|
||||
start = self._array[current_index, 0]
|
||||
new_gtis.append([start, self._stop])
|
||||
|
||||
return Gti(new_gtis)
|
||||
|
||||
def __eq__(self, other):
|
||||
if self is other: return True
|
||||
if other is None : return False
|
||||
|
||||
return np.array_equal(self._array, other.array) \
|
||||
and (self._start == other.start) and (self._stop == other.stop)
|
||||
|
||||
def __add__(self, other):
|
||||
if self is other: return self
|
||||
if other is None : return self
|
||||
if other.empty : return self
|
||||
if self.empty : return other
|
||||
|
||||
array = self._union(other.array)
|
||||
start = min(self._start, other.start)
|
||||
stop = max(self._stop , other.stop )
|
||||
|
||||
return Gti(array, start, stop)
|
||||
|
||||
def __sub__(self, other):
|
||||
if self is other: return Gti()
|
||||
if self.empty : return Gti()
|
||||
if other is None : return self
|
||||
if other.empty : return self
|
||||
|
||||
array = self._diff(other.array)
|
||||
|
||||
return Gti(array, self._start, self._stop)
|
||||
|
||||
def __mul__(self, other):
|
||||
if self is other: return self
|
||||
if other is None : return self
|
||||
if self.empty : return Gti()
|
||||
if other.empty : return Gti()
|
||||
|
||||
array = self._intersect(other.array)
|
||||
start = min(self._start, other.start)
|
||||
stop = max(self._stop , other.stop )
|
||||
|
||||
return Gti(array, start, stop)
|
||||
|
||||
def _union(self, array):
|
||||
array = np.concatenate((self._array, array), axis=0)
|
||||
array = np.sort(array, axis=0)
|
||||
|
||||
starts = array[:,0]
|
||||
stops = array[:,1]
|
||||
|
||||
mask = np.ones(array.shape[0] + 1, dtype=bool)
|
||||
mask[ 1:-1] = starts[1:] >= stops[:-1]
|
||||
|
||||
return np.vstack((starts[:][mask[:-1]], stops[:][mask[1:]])).T
|
||||
|
||||
def _diff(self, array):
|
||||
starts = self._array[:,0]
|
||||
stops = self._array[:,1]
|
||||
|
||||
for item in array:
|
||||
istart = item[0]
|
||||
istop = item[1]
|
||||
|
||||
mask_lower = np.logical_and(istart >= starts, istart <= stops)
|
||||
mask_upper = np.logical_and(istop >= starts, istop <= stops)
|
||||
|
||||
starts[mask_lower] = istart
|
||||
stops [mask_upper] = istop
|
||||
|
||||
return np.dstack((starts, stops))
|
||||
|
||||
def _intersect_helper(self, arr1, arr2):
|
||||
newarray = []
|
||||
|
||||
for start1, stop1 in arr1:
|
||||
for start2, stop2 in arr2:
|
||||
|
||||
start = max(start1, start2)
|
||||
stop = min(stop1 , stop2 )
|
||||
|
||||
has_intersections = stop > start
|
||||
if has_intersections:
|
||||
newarray.append([start, stop])
|
||||
|
||||
return newarray
|
||||
|
||||
def _intersect(self, array):
|
||||
newarray = []
|
||||
|
||||
newarray1 = self._intersect_helper(self._array, array)
|
||||
newarray2 = self._intersect_helper(array, self._array)
|
||||
|
||||
newarray.extend(newarray1)
|
||||
newarray.extend(newarray2)
|
||||
|
||||
return np.unique(np.array(newarray), axis=0)
|
||||
|
||||
def make_hdu(self, mjdref):
|
||||
a1 = np.array(self._array[:, 0])
|
||||
a2 = np.array(self._array[:, 1])
|
||||
c1 = fits.Column(name='START', format='1D', unit='sec', array=a1)
|
||||
c2 = fits.Column(name='STOP' , format='1D', unit='sec', array=a2)
|
||||
cols = fits.ColDefs([c1, c2])
|
||||
|
||||
gtihdu = fits.BinTableHDU.from_columns(cols)
|
||||
gtihdu.name = 'STDGTI'
|
||||
gtihdu.header['TSTART'] = self._start
|
||||
gtihdu.header['TSTOP' ] = self._stop
|
||||
gtihdu.header['MJDREF'] = (mjdref, 'MJD corresponding to SC clock start 2000.0 MSK')
|
||||
|
||||
return gtihdu
|
||||
|
||||
def write_fits(self, gtifile, mjdref):
|
||||
# --- GTI file structure
|
||||
hdu = fits.PrimaryHDU()
|
||||
|
||||
a1 = np.array(self._array[:, 0])
|
||||
a2 = np.array(self._array[:, 1])
|
||||
c1 = fits.Column(name='START', format='1D', unit='sec', array=a1)
|
||||
c2 = fits.Column(name='STOP' , format='1D', unit='sec', array=a2)
|
||||
cols = fits.ColDefs([c1, c2])
|
||||
|
||||
gtihdu = fits.BinTableHDU.from_columns(cols)
|
||||
gtihdu.name = 'STDGTI'
|
||||
gtihdu.header['TSTART'] = self._start
|
||||
gtihdu.header['TSTOP' ] = self._stop
|
||||
gtihdu.header['MJDREF'] = (mjdref, 'MJD corresponding to SC clock start 2000.0 MSK')
|
||||
|
||||
# --- wirte GTI
|
||||
hdulist = fits.HDUList([hdu, gtihdu])
|
||||
hdulist.writeto(gtifile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
93
arttools/src/arttools/heatools.py
Executable file
93
arttools/src/arttools/heatools.py
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
from arttools.cmd import Command, ExecutionResult
|
||||
from arttools.errors import Error
|
||||
|
||||
|
||||
class HeatoolsError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class Heatools(object):
|
||||
_ftcopy = 'ftcopy {src}[{ext}] {dst} copyall=no history=no clobber=yes'
|
||||
_ftappend = 'ftappend {srcfile}[{srcext}]{filter} {dstfile} history=yes'
|
||||
_maketime = 'maketime {srcfile}[{srcext}] {gtifile} "{filter_expr}" time=TIME prefr=0 postfr=0 compact=no histkw=no'
|
||||
_ftmgtime = 'ftmgtime {gtifilelist} {dstgti} merge=and emptygti=apply history=yes clobber=yes'
|
||||
_ftselect = 'ftselect "{srcfile}[{srcext}]" {dstfile} \'gtifilter("{gtifile}")\' copyall=no history=yes clobber=yes'
|
||||
_ftchksum = 'ftchecksum {srcfile} update=yes chatter=0'
|
||||
|
||||
@classmethod
|
||||
def check(cls, envvar='HEADAS'):
|
||||
if not envvar in os.environ:
|
||||
raise HeatoolsError('error: Heasoft environment not found (is it initialzed?)')
|
||||
|
||||
@classmethod
|
||||
def ftcopy(cls, src, dst, ext):
|
||||
Command(
|
||||
cls._ftcopy.format(
|
||||
src=src,
|
||||
dst=dst,
|
||||
ext=ext
|
||||
)
|
||||
).run()
|
||||
|
||||
@classmethod
|
||||
def ftappend(cls, srcfile, srcext, dstfile, filt=''):
|
||||
Command(
|
||||
cls._ftappend.format(
|
||||
srcfile=srcfile,
|
||||
srcext=srcext,
|
||||
filter=filt,
|
||||
dstfile=dstfile
|
||||
)
|
||||
).run()
|
||||
|
||||
def maketime(self, srcfile, srcext, gtifile, filter_expr):
|
||||
Command(
|
||||
self._maketime.format(
|
||||
srcfile=srcfile,
|
||||
srcext=srcext,
|
||||
gtifile=gtifile,
|
||||
filter_expr=filter_expr
|
||||
)
|
||||
).run()
|
||||
|
||||
def ftmgtime(self, gtifilelist, dstgti):
|
||||
Command(
|
||||
self._ftmgtime.format(
|
||||
gtifilelist=gtifilelist,
|
||||
dstgti=dstgti
|
||||
)
|
||||
).run()
|
||||
|
||||
def ftselect(self, srcfile, srcext, dstfile, gtifile):
|
||||
Command(
|
||||
self._ftselect.format(
|
||||
srcfile=srcfile,
|
||||
srcext=srcext,
|
||||
dstfile=dstfile,
|
||||
gtifile=gtifile
|
||||
)
|
||||
).run()
|
||||
|
||||
def ftchecksum(self, srcfile):
|
||||
Command(
|
||||
self._ftchksum.format(
|
||||
srcfile=srcfile
|
||||
)
|
||||
).run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
137
arttools/src/arttools/printer.py
Executable file
137
arttools/src/arttools/printer.py
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# class Log(object):
|
||||
|
||||
# @staticmethod
|
||||
# def configure(who, logfile):
|
||||
# import logging
|
||||
|
||||
# logging.basicConfig(
|
||||
# level=logging.INFO,
|
||||
# format='{}: %(asctime)s [%(levelname)-7s] %(message)s'.format(who),
|
||||
# handlers=[
|
||||
# logging.FileHandler(logfile),
|
||||
# logging.StreamHandler()
|
||||
# ]
|
||||
# )
|
||||
|
||||
|
||||
class PrintType(Enum):
|
||||
LOG = 'log'
|
||||
INFO = 'info'
|
||||
WARNING = 'warning'
|
||||
ERROR = 'error'
|
||||
|
||||
|
||||
class PrintFormatter(object):
|
||||
|
||||
def __init__(self):
|
||||
self._newline = '\n'
|
||||
self._line_width = 80
|
||||
self._line_type_1 = '-' * self._line_width
|
||||
self._line_type_2 = '=' * self._line_width
|
||||
self._line_type_3 = '*' * 5
|
||||
self._offset_w = 0
|
||||
self._offset_str = ' ' * self._offset_w
|
||||
self._separator = self._newline + self._offset_str
|
||||
|
||||
def offset(self, offset):
|
||||
self._offset_w = offset
|
||||
self._offset_str = ' ' * self._offset_w
|
||||
self._separator = self._newline + self._offset_str
|
||||
|
||||
def header(self, toolname, version):
|
||||
return self.multiline([
|
||||
'[' + self._timestr() + '] running: {} v.{}'.format(toolname, version),
|
||||
self._line_type_2
|
||||
])
|
||||
|
||||
def footer(self, toolname, version, message):
|
||||
return self.multiline([
|
||||
'{} v.{}: exited with status: {}'.format(toolname, version, message),
|
||||
self._line_type_1
|
||||
])
|
||||
|
||||
def log(self, message):
|
||||
return self._offset_str + message
|
||||
|
||||
def message(self, level, message):
|
||||
return self._format(level, message)
|
||||
|
||||
def multiline(self, vmessage):
|
||||
return self._separator.join(vmessage)
|
||||
|
||||
def _format(self, level, message):
|
||||
return '[{}] {:>7}: {}'.format(self._timestr(), level, message)
|
||||
|
||||
def _timestr(self):
|
||||
return str(datetime.now())
|
||||
|
||||
|
||||
class Printer(object):
|
||||
|
||||
def __init__(self, toolname, version, logfile=None):
|
||||
super().__init__()
|
||||
self._toolname = toolname
|
||||
self._version = version
|
||||
self._formatter = PrintFormatter()
|
||||
|
||||
# -- setup logger
|
||||
logfmt = logging.Formatter('%(message)s')
|
||||
self._logger = logging.getLogger(self._toolname)
|
||||
self._logger.setLevel(logging.INFO)
|
||||
self._logger.handlers = [] # reset log handlers
|
||||
|
||||
logsh = logging.StreamHandler(sys.stdout)
|
||||
logsh.setFormatter(logfmt)
|
||||
self._logger.addHandler(logsh)
|
||||
|
||||
if logfile is not None:
|
||||
logfh = logging.FileHandler(logfile)
|
||||
logfh.setFormatter(logfmt)
|
||||
self._logger.addHandler(logfh)
|
||||
|
||||
def info(self, message):
|
||||
self._logger.info(
|
||||
self._formatter.message('INFO' , message))
|
||||
|
||||
def warning(self, message):
|
||||
self._logger.warning(
|
||||
self._formatter.message('WARNING', message))
|
||||
|
||||
def error(self, message):
|
||||
self._logger.error(
|
||||
self._formatter.message('ERROR' , message))
|
||||
|
||||
def log(self, message):
|
||||
self._logger.info(
|
||||
self._formatter.log(message))
|
||||
|
||||
def header(self):
|
||||
self._logger.info(
|
||||
self._formatter.header(self._toolname, self._version))
|
||||
|
||||
def footer(self, message):
|
||||
self._logger.info(
|
||||
self._formatter.footer(self._toolname, self._version, message))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
42
arttools/src/arttools/random.py
Executable file
42
arttools/src/arttools/random.py
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Random(object):
|
||||
|
||||
def __init__(self, seed=None):
|
||||
super().__init__()
|
||||
|
||||
self._seed = seed
|
||||
self._rng = None
|
||||
|
||||
self._initialize()
|
||||
|
||||
def _initialize(self):
|
||||
if self._seed is None:
|
||||
sseq = np.random.SeedSequence()
|
||||
self._seed = sseq.entropy
|
||||
else:
|
||||
sseq = np.random.SeedSequence(self._seed)
|
||||
|
||||
self._rng = np.random.default_rng(sseq)
|
||||
|
||||
@property
|
||||
def seed(self): return self._seed
|
||||
|
||||
def uniform(self, low, high, size=None):
|
||||
return self._rng.uniform(low, high, size)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
93
arttools/src/arttools/task.py
Executable file
93
arttools/src/arttools/task.py
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from arttools.cmd import Command
|
||||
from arttools.errors import Error
|
||||
|
||||
|
||||
class TaskError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class TaskArgs(ABC):
|
||||
|
||||
def __init__(self, cfgfile=None):
|
||||
super().__init__()
|
||||
|
||||
self.pid = os.getpid()
|
||||
self.env = {}
|
||||
self.cfg = {}
|
||||
|
||||
self.read_env()
|
||||
self.read_cfg(cfgfile)
|
||||
|
||||
@abstractmethod
|
||||
def read_env(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_cfg(self, cfgfile):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_args(self):
|
||||
pass
|
||||
|
||||
def read(self):
|
||||
self.read_args()
|
||||
|
||||
@abstractmethod
|
||||
def check(self):
|
||||
pass
|
||||
|
||||
|
||||
class TaskCmds(ABC):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
|
||||
class Task(ABC):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@abstractmethod
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finalize(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def runmain(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.initialize()
|
||||
self.runmain()
|
||||
self.finalize()
|
||||
except TaskError as e:
|
||||
print('error: task execution:', e)
|
||||
except Error as e:
|
||||
print('error: unknown:', e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
45
arttools/src/arttools/telescope.py
Normal file
45
arttools/src/arttools/telescope.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2016-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
class Teldef(object):
|
||||
F = 2693.
|
||||
d = .595
|
||||
centeroffset = 23.5
|
||||
assemblies = [
|
||||
{'detn': 33, 'urdn': 28, 'teln': 1},
|
||||
{'detn': 34, 'urdn': 22, 'teln': 2},
|
||||
{'detn': 24, 'urdn': 23, 'teln': 3},
|
||||
{'detn': 25, 'urdn': 24, 'teln': 4},
|
||||
{'detn': 22, 'urdn': 25, 'teln': 5},
|
||||
{'detn': 26, 'urdn': 26, 'teln': 6},
|
||||
{'detn': 23, 'urdn': 30, 'teln': 7}
|
||||
]
|
||||
|
||||
urdalist = ['02', '04', '08', '10', '20', '40', '80']
|
||||
urdnlist = [ 28 , 22 , 23 , 24 , 25 , 26 , 30 ]
|
||||
detnlist = [ 33 , 34 , 24 , 24 , 22 , 26 , 23 ]
|
||||
telnlist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ]
|
||||
telnames = [''.join(('T', str(teln))) for teln in telnlist]
|
||||
|
||||
@classmethod
|
||||
def telname(cls, teln):
|
||||
if teln not in cls.telnlist:
|
||||
return 'T0'
|
||||
return ''.join(('T', str(teln)))
|
||||
|
||||
@classmethod
|
||||
def teln(cls, detn, urdn):
|
||||
for item in cls.assemblies:
|
||||
if item['detn'] == detn and item['urdn'] == urdn:
|
||||
return item['teln']
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
45
arttools/src/arttools/toolargs.py
Executable file
45
arttools/src/arttools/toolargs.py
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2016-2024 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinksy SRG/ART-XC telescope software.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
class ToolArgs(object):
|
||||
|
||||
def __init__(self, configfile=None):
|
||||
self._configfile = configfile
|
||||
|
||||
self.pid = os.getpid()
|
||||
self.config = {}
|
||||
|
||||
self._read_env()
|
||||
|
||||
def _read_env(self):
|
||||
if self._configfile is None:
|
||||
return
|
||||
|
||||
def make_tmpdir(self, path):
|
||||
return os.path.join(path, str(self.pid))
|
||||
|
||||
def clean_path(self, path):
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
def check_path(self, path, clean=False):
|
||||
if clean and os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
|
||||
1
arttools/src/arttools/version.py
Executable file
1
arttools/src/arttools/version.py
Executable file
@@ -0,0 +1 @@
|
||||
__version__ = '2.0.8'
|
||||
143
arttools/src/arttools/wcs.py
Normal file
143
arttools/src/arttools/wcs.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2021-2026 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
|
||||
from astropy.io import fits
|
||||
from astropy.wcs import WCS
|
||||
|
||||
import astropy.units as u
|
||||
|
||||
from astropy.coordinates import Angle
|
||||
from astropy.coordinates import SkyCoord
|
||||
|
||||
|
||||
class Fov(object):
|
||||
|
||||
def __init__(self, ra_cen, dec_cen):
|
||||
self._ra_cen = ra_cen
|
||||
self._dec_cen = dec_cen
|
||||
|
||||
fov_size = 0.66 * u.deg
|
||||
pix_size = 45.5728027182 * u.arcsec / u.pix
|
||||
|
||||
fov_wcs, fov_shape = \
|
||||
self._make_fov_wcs(
|
||||
self._ra_cen,
|
||||
self._dec_cen,
|
||||
fov_size,
|
||||
pix_size
|
||||
)
|
||||
|
||||
self._wcs = Wcs()
|
||||
self._wcs.init(fov_wcs)
|
||||
|
||||
self._shape = fov_shape
|
||||
|
||||
@property
|
||||
def wcs(self):
|
||||
return self._wcs
|
||||
|
||||
@property
|
||||
def ra(self):
|
||||
return self._ra_cen
|
||||
|
||||
@property
|
||||
def dec(self):
|
||||
return self._dec_cen
|
||||
|
||||
def _make_fov_wcs(self,
|
||||
ra_cen, dec_cen,
|
||||
fov_size, pix_size,
|
||||
ref_pix=None,
|
||||
projection='TAN' ):
|
||||
|
||||
center = SkyCoord(ra_cen, dec_cen, unit=u.deg)
|
||||
|
||||
fov_width = Angle(fov_size, unit=u.deg)
|
||||
fov_height = Angle(fov_size, unit=u.deg)
|
||||
|
||||
pix_width = Angle(pix_size)
|
||||
pix_height = Angle(pix_size)
|
||||
|
||||
nx = int(np.ceil((fov_width / pix_width ).decompose().value))
|
||||
ny = int(np.ceil((fov_height / pix_height).decompose().value))
|
||||
|
||||
if ref_pix is None:
|
||||
ref_pix = [(nx + 1) / 2., (ny + 1) / 2.]
|
||||
|
||||
wcs = Wcs(naxis=2)
|
||||
|
||||
wcs.wcs.radesys = 'FK5'
|
||||
wcs.wcs.equinox = 2000.
|
||||
|
||||
wcs.wcs.crval = [center.ra.deg, center.dec.deg]
|
||||
wcs.wcs.crpix = ref_pix
|
||||
|
||||
wcs.wcs.cdelt = [-pix_width.value, pix_height.value]
|
||||
|
||||
return wcs, (ny, nx)
|
||||
|
||||
|
||||
class Wcs(object):
|
||||
|
||||
def __init__(self):
|
||||
self._strip_size = 45.5728027182
|
||||
|
||||
def init(self, wcs):
|
||||
self._wcs = wcs
|
||||
self._projection = self._make_projection(self._wcs)
|
||||
|
||||
return self
|
||||
|
||||
def from_other(self, projection):
|
||||
self._wcs = None
|
||||
self._projection = projection
|
||||
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def from_file(fname):
|
||||
with open(fname) as wcsfile:
|
||||
return Wcs().init(json.load(wcsfile))
|
||||
|
||||
@staticmethod
|
||||
def from_eventfile(fname):
|
||||
with fits.open(fname) as hdul:
|
||||
projection = WCS(hdul['EVENTS'].header)
|
||||
return Wcs().from_other(projection)
|
||||
|
||||
def get(self):
|
||||
return self._projection
|
||||
|
||||
def world2pix(self, ra, dec):
|
||||
y, x = (
|
||||
self._projection.all_world2pix(
|
||||
np.array([ra, dec]).T, 1) - 0.5
|
||||
).astype(int).T
|
||||
|
||||
return x, y
|
||||
|
||||
def _make_projection(self, wcs, subpixels=5):
|
||||
pixsize = self._strip_size / 3600. / subpixels
|
||||
|
||||
projection = WCS(naxis=2)
|
||||
projection.wcs.crpix = [wcs['nx' ], wcs['ny' ]]
|
||||
projection.wcs.crval = [wcs['crval1'], wcs['crval2']]
|
||||
projection.wcs.cdelt = [pixsize, pixsize]
|
||||
projection.wcs.ctype = ["RA---TAN", "DEC--TAN"]
|
||||
projection.wcs.radesys = "FK5"
|
||||
|
||||
return projection
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
30
arttools/tests/test_artdatatable.py
Normal file
30
arttools/tests/test_artdatatable.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020 Space Research Institute (http://iki.rssi.ru/)
|
||||
#
|
||||
# This file is part of ARTXC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os, sys
|
||||
import unittest
|
||||
import unittest.mock as mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.append('../src')
|
||||
|
||||
from pyartxc.gti import Gti
|
||||
|
||||
|
||||
class Test_(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_create_empty(self):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
208
arttools/tests/test_arttools_gti.py
Executable file
208
arttools/tests/test_arttools_gti.py
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf8 -*-
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2020-2025 IKI RAS (http://iki.rssi.ru/)
|
||||
# Space Research Institute of the Russian Academy of Science
|
||||
#
|
||||
# This file is part of the M. Pavlinsky SRG/ART-XC software project.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
import os, sys
|
||||
import unittest
|
||||
import unittest.mock as mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), '../src/'))
|
||||
|
||||
|
||||
import arttools
|
||||
from arttools.gti import Gti
|
||||
|
||||
|
||||
class TestGti(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.gti_0 = Gti()
|
||||
self.gti_1 = Gti([[100., 200.],])
|
||||
self.gti_2 = Gti([[100., 200.], [300, 305]])
|
||||
self.gti_3 = Gti([100., 200., 300., 400.])
|
||||
|
||||
def test_create_empty(self):
|
||||
self.assertTrue (self.gti_0.empty)
|
||||
self.assertEqual(self.gti_0.size, 0)
|
||||
|
||||
def test_create_from_intervals_1(self):
|
||||
self.assertFalse(self.gti_1.empty)
|
||||
self.assertEqual(self.gti_1.size, 1)
|
||||
self.assertEqual(self.gti_1.exposure(), 100.)
|
||||
|
||||
def test_create_from_intervals_2(self):
|
||||
self.assertFalse(self.gti_2.empty)
|
||||
self.assertEqual(self.gti_2.size, 2)
|
||||
self.assertEqual(self.gti_2.exposure(), 105.)
|
||||
|
||||
def test_create_from_intervals_3(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Gti([100., 200., 300.])
|
||||
|
||||
def test_startstop(self):
|
||||
self.assertEqual(self.gti_0.start, None)
|
||||
self.assertEqual(self.gti_0.stop , None)
|
||||
self.assertEqual(self.gti_2.start, 100.)
|
||||
self.assertEqual(self.gti_2.stop , 305.)
|
||||
|
||||
def test_create_from_timemask(self):
|
||||
times = np.array([100, 200, 300, 400, 500, 600])
|
||||
param = np.array([-99, -98, -10, 0, -98, -99])
|
||||
|
||||
gti = Gti.from_timemask(times, param < -95)
|
||||
|
||||
self.assertFalse(gti.empty)
|
||||
self.assertEqual(gti.size , 2)
|
||||
self.assertEqual(gti.exposure(), 200)
|
||||
self.assertEqual(gti.start , 100)
|
||||
self.assertEqual(gti.stop , 600)
|
||||
|
||||
def test_clone(self):
|
||||
gti_clone = self.gti_2.clone()
|
||||
|
||||
self.assertFalse(gti_clone.empty)
|
||||
self.assertEqual(gti_clone.size, 2)
|
||||
self.assertEqual(gti_clone.exposure(), 105.)
|
||||
|
||||
def test_equal(self):
|
||||
gti = Gti([100., 200.])
|
||||
|
||||
self.assertTrue (gti == gti )
|
||||
self.assertTrue (gti == self.gti_1)
|
||||
self.assertFalse(gti == self.gti_2)
|
||||
self.assertFalse(gti == None )
|
||||
|
||||
def test_union_1(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti()
|
||||
|
||||
res_gti_1 = gti_1 + None
|
||||
res_gti_2 = gti_1 + gti_1
|
||||
res_gti_3 = gti_1 + gti_2
|
||||
res_gti_4 = gti_2 + gti_1
|
||||
|
||||
self.assertTrue(res_gti_1 == gti_1 )
|
||||
self.assertTrue(res_gti_2 == gti_1 )
|
||||
self.assertTrue(res_gti_3 == gti_1 )
|
||||
self.assertTrue(res_gti_4 == gti_1 )
|
||||
self.assertTrue(res_gti_3 == res_gti_4)
|
||||
|
||||
def test_union_2(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti([150., 250.])
|
||||
|
||||
res_gti_1 = gti_1 + gti_2
|
||||
res_gti_2 = gti_2 + gti_1
|
||||
|
||||
self.assertEqual(res_gti_1.size , 1)
|
||||
self.assertEqual(res_gti_1.exposure(), 150)
|
||||
self.assertEqual(res_gti_1.start , 100)
|
||||
self.assertEqual(res_gti_1.stop , 250)
|
||||
self.assertTrue (res_gti_1 == res_gti_2)
|
||||
|
||||
def test_union_3(self):
|
||||
gti_1 = Gti([100., 200., 300., 400.])
|
||||
gti_2 = Gti([150., 250., 350., 450., 370., 470.])
|
||||
gti_3 = Gti([170., 270.])
|
||||
|
||||
res_gti = gti_1 + gti_2 + gti_3
|
||||
|
||||
self.assertEqual(res_gti.size , 2)
|
||||
self.assertEqual(res_gti.exposure(), 340)
|
||||
self.assertEqual(res_gti.start , 100)
|
||||
self.assertEqual(res_gti.stop , 470)
|
||||
|
||||
def test_subtraction_1(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti()
|
||||
|
||||
res_gti_1 = gti_1 - None
|
||||
res_gti_2 = gti_1 - gti_1
|
||||
res_gti_3 = gti_1 - gti_2
|
||||
res_gti_4 = gti_2 - gti_1
|
||||
|
||||
self.assertTrue(res_gti_1 == gti_1 )
|
||||
self.assertTrue(res_gti_2 == self.gti_0)
|
||||
self.assertTrue(res_gti_3 == gti_1 )
|
||||
self.assertTrue(res_gti_4 == self.gti_0)
|
||||
|
||||
def test_subtraction_2(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti([150., 250.])
|
||||
|
||||
res_gti = gti_1 - gti_2
|
||||
|
||||
self.assertEqual(res_gti.size , 1)
|
||||
self.assertEqual(res_gti.start , 100)
|
||||
self.assertEqual(res_gti.stop , 200)
|
||||
self.assertEqual(res_gti.exposure(), 50)
|
||||
self.assertTrue(
|
||||
np.array_equal(res_gti.array, np.array([[150., 200.]])))
|
||||
|
||||
|
||||
def test_subtraction_3(self):
|
||||
gti_2 = Gti([50., 120., 170., 250.])
|
||||
gti_1 = Gti([100., 200.])
|
||||
|
||||
res_gti = gti_2 - gti_1
|
||||
|
||||
self.assertEqual(res_gti.size , 2)
|
||||
self.assertEqual(res_gti.start , 50)
|
||||
self.assertEqual(res_gti.stop , 250)
|
||||
self.assertEqual(res_gti.exposure(), 50)
|
||||
self.assertTrue(
|
||||
np.array_equal(res_gti.array, np.array([[100., 120.], [170., 200.]])))
|
||||
|
||||
def test_intersection_1(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti()
|
||||
|
||||
res_gti_1 = gti_1 * None
|
||||
res_gti_2 = gti_1 * gti_1
|
||||
res_gti_3 = gti_1 * gti_2
|
||||
res_gti_4 = gti_2 * gti_1
|
||||
|
||||
self.assertTrue(res_gti_1 == self.gti_0)
|
||||
self.assertTrue(res_gti_2 == gti_1 )
|
||||
self.assertTrue(res_gti_3 == self.gti_0)
|
||||
self.assertTrue(res_gti_4 == self.gti_0)
|
||||
|
||||
def test_intersection_2(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti([50., 120., 170., 250.])
|
||||
|
||||
res_gti = gti_2 * gti_1
|
||||
|
||||
self.assertEqual(res_gti.size , 2)
|
||||
self.assertEqual(res_gti.start , 50)
|
||||
self.assertEqual(res_gti.stop , 250)
|
||||
self.assertEqual(res_gti.exposure(), 50)
|
||||
self.assertTrue(
|
||||
np.array_equal(res_gti.array, np.array([[100., 120.], [170., 200.]])))
|
||||
|
||||
def test_intersection_3(self):
|
||||
gti_1 = Gti([100., 200.])
|
||||
gti_2 = Gti([50., 120., 170., 250.])
|
||||
|
||||
res_gti = gti_1 * gti_2
|
||||
|
||||
self.assertEqual(res_gti.size , 2)
|
||||
self.assertEqual(res_gti.start , 50)
|
||||
self.assertEqual(res_gti.stop , 250)
|
||||
self.assertEqual(res_gti.exposure(), 50)
|
||||
self.assertTrue(
|
||||
np.array_equal(res_gti.array, np.array([[100., 120.], [170., 200.]])))
|
||||
|
||||
# def test_inversion(self):
|
||||
# pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
BIN
config/.DS_Store
vendored
Normal file
BIN
config/.DS_Store
vendored
Normal file
Binary file not shown.
3
config/artinit-dev-ashty.sh
Executable file
3
config/artinit-dev-ashty.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
export ARTCONFIG="/home/ashty/devel/projects/iki/artxc/software/artpipeline/config/artpipeline.cfg"
|
||||
|
||||
conda activate /home/ashty/devel/projects/iki/artxc/env/newartenv
|
||||
3
config/artinit.sh
Executable file
3
config/artinit.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
export ARTCONFIG="/home/ashty/devel/projects/iki/artxc/software/artpipeline/config/artpipeline.cfg"
|
||||
|
||||
conda activate /home/ashty/devel/projects/iki/artxc/env/newartenv
|
||||
10
config/artpipeline.cfg
Normal file
10
config/artpipeline.cfg
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"inbox": "/home/ashty/data/iki/mvn_sandbox/mvn/inbox",
|
||||
"newbox": "/home/ashty/data/iki/mvn_sandbox/new",
|
||||
"tmpdir": "/home/ashty/data/iki/mvn_sandbox/temp",
|
||||
"mvndir": "/home/ashty/data/iki/mvn_sandbox/mvn",
|
||||
"datadir": "/home/ashty/data/iki/mvn_sandbox/mvn/data",
|
||||
"indexdir": "/home/ashty/data/iki/mvn_sandbox/mvn/index",
|
||||
"dnlogsdir": "/home/ashty/data/iki/mvn_sandbox/mvn/dnlogs",
|
||||
"vvlbasedir": "/home/ashty/data/iki/mvn_sandbox/mvn/vvlbase"
|
||||
}
|
||||
1
config/devel/activate-conda.sh
Executable file
1
config/devel/activate-conda.sh
Executable file
@@ -0,0 +1 @@
|
||||
conda activate ${PWD}/../mvnenv/
|
||||
1
config/devel/activate.sh
Executable file
1
config/devel/activate.sh
Executable file
@@ -0,0 +1 @@
|
||||
conda activate ${PWD}/../mvnenv/
|
||||
5
config/devel/install.sh
Executable file
5
config/devel/install.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
pip install mvntools/
|
||||
pip install mvndac/
|
||||
8
config/devel/uninstall.sh
Executable file
8
config/devel/uninstall.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
pip uninstall mvntools --yes
|
||||
rm -rf mvntools/build
|
||||
|
||||
pip uninstall mvndac --yes
|
||||
rm -rf mvndac/build
|
||||
11
config/setup/conda/configure.sh
Executable file
11
config/setup/conda/configure.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "cleaning previous environment..."
|
||||
rm -rf .venv
|
||||
|
||||
echo "creating new environment..."
|
||||
conda create --yes --prefix $(pwd)/env python=3.11 pip
|
||||
|
||||
echo "finished"
|
||||
|
||||
|
||||
7
config/setup/conda/environment.txt
Executable file
7
config/setup/conda/environment.txt
Executable file
@@ -0,0 +1,7 @@
|
||||
|
||||
rm -rf .venv
|
||||
|
||||
conda create --yes --prefix \$PWD/.venv/ python=3.11 pip
|
||||
conda activate \$PWD/.venv/
|
||||
|
||||
pip install -r requirements.txt
|
||||
17
config/setup/configure-dev.sh
Executable file
17
config/setup/configure-dev.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
echo "cleaning previous environment..."
|
||||
rm -rf .venv
|
||||
|
||||
echo "creating new environment..."
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
echo "install pip..."
|
||||
python3 -m ensurepip --default-pip
|
||||
|
||||
echo "install requirements..."
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
echo "finished"
|
||||
18
config/setup/configure.sh
Executable file
18
config/setup/configure.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "cleaning previous environment..."
|
||||
rm -rf .venv
|
||||
|
||||
echo "creating new environment..."
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
echo "install pip..."
|
||||
python3 -m ensurepip --default-pip
|
||||
|
||||
echo "install requirements..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "finished"
|
||||
|
||||
|
||||
10
requirements-dev.txt
Normal file
10
requirements-dev.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
pydantic
|
||||
refurb
|
||||
ruff
|
||||
typer[all]
|
||||
rich
|
||||
numpy
|
||||
scipy
|
||||
polars
|
||||
astropy
|
||||
regions
|
||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
pydantic
|
||||
typer[all]
|
||||
rich
|
||||
numpy
|
||||
scipy
|
||||
polars
|
||||
astropy
|
||||
regions
|
||||
Reference in New Issue
Block a user