Skip to content

Commit 1c485e5

Browse files
committed
Merge branch 'jb/ifu_rsrf_dev' into mb/usedframes
2 parents de1dcac + ba5496a commit 1c485e5

File tree

3 files changed

+238
-32
lines changed

3 files changed

+238
-32
lines changed

metisp/pymetis/src/pymetis/prefab/rawimage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def combine_images(cls,
6666
Probably not a universal panacea, but it recurs often enough to warrant being here.
6767
"""
6868
Msg.info(cls.__qualname__,
69-
f"Combining images using method {method!r}")
69+
f"Combining {len(images)} images using method {method!r}")
7070
combined_image = None
7171
match method:
7272
case "add":

metisp/pymetis/src/pymetis/recipes/ifu/metis_ifu_rsrf.py

+235-31
Original file line numberDiff line numberDiff line change
@@ -23,79 +23,283 @@
2323

2424
from pymetis.base import MetisRecipe
2525
from pymetis.base.product import PipelineProduct
26-
from pymetis.inputs import SinglePipelineInput, MultiplePipelineInput, BadpixMapInput, \
27-
MasterDarkInput, LinearityInput, RawInput, GainMapInput
26+
from pymetis.inputs.common import SinglePipelineInput, MultiplePipelineInput, \
27+
BadpixMapInput, MasterDarkInput, LinearityInput, \
28+
RawInput, GainMapInput, PersistenceMapInput, \
29+
WavecalInput, DistortionTableInput
2830
from pymetis.inputs.mixins import PersistenceInputSetMixin
2931
from pymetis.prefab.darkimage import DarkImageProcessor
3032

3133

32-
class RsrfMasterDarkInput(MasterDarkInput):
33-
pass
3434

3535

36-
class DistortionTableInput(SinglePipelineInput):
37-
_tags = re.compile(r"IFU_DISTORTION_TABLE")
38-
_title = "distortion table"
39-
_group = cpl.ui.Frame.FrameGroup.CALIB
40-
4136

4237
class MetisIfuRsrfImpl(DarkImageProcessor):
43-
class InputSet(PersistenceInputSetMixin, DarkImageProcessor.InputSet):
38+
class InputSet(DarkImageProcessor.InputSet):
4439
class RawInput(RawInput):
4540
_tags = re.compile(r"IFU_RSRF_RAW")
4641
_title = "IFU rsrf raw"
4742

4843
MasterDarkInput = MasterDarkInput
4944

45+
class RsrfWcuOffInput(RawInput):
46+
"""
47+
WCU_OFF input illuminated by the WCU up-to and including the
48+
integrating sphere, but no source.
49+
"""
50+
_tags = re.compile(r"IFU_WCU_OFF_RAW")
51+
_title = "IFU WCU off"
52+
5053
def __init__(self, frameset: cpl.ui.FrameSet):
5154
super().__init__(frameset)
55+
self.background = self.RsrfWcuOffInput(frameset)
56+
self.linearity = LinearityInput(frameset)
5257
self.gain_map = GainMapInput(frameset)
5358
self.distortion_table = DistortionTableInput(frameset)
59+
self.wavecal = WavecalInput(frameset)
60+
self.persistence = PersistenceMapInput(frameset, required=False)
61+
self.badpixmap = BadpixMapInput(frameset, required=False)
62+
63+
self.inputs += [self.background, self.linearity,
64+
self.gain_map, self.distortion_table,
65+
self.wavecal, self.persistence, self.badpixmap]
66+
67+
class ProductBackground(PipelineProduct):
68+
"""
69+
Intermediate product: the instrumental background (WCU OFF)
70+
"""
71+
group = cpl.ui.Frame.FrameGroup.PRODUCT # TBC
72+
level = cpl.ui.Frame.FrameLevel.INTERMEDIATE
73+
frame_type = cpl.ui.Frame.FrameType.IMAGE
5474

55-
self.inputs += [self.gain_map, self.distortion_table]
75+
# SKEL: copy product keywords from header
76+
def add_properties(self):
77+
super().add_properties()
78+
self.properties.append(self.header)
79+
80+
@property
81+
def category(self) -> str:
82+
return "IFU_RSRF_BACKGROUND"
83+
84+
@property
85+
def output_file_name(self) -> str:
86+
return f"{self.category}.fits"
87+
88+
@property
89+
def tag(self) -> str:
90+
return rf"{self.category}"
5691

5792
class ProductMasterFlatIfu(PipelineProduct):
58-
category = rf"MASTER_FLAT_IFU"
59-
tag = category
93+
group = cpl.ui.Frame.FrameGroup.CALIB # TBC
6094
level = cpl.ui.Frame.FrameLevel.FINAL
6195
frame_type = cpl.ui.Frame.FrameType.IMAGE
6296

97+
# SKEL: copy product keywords from header
98+
def add_properties(self):
99+
super().add_properties()
100+
self.properties.append(self.header)
101+
102+
@property
103+
def category(self) -> str:
104+
return rf"MASTER_FLAT_IFU"
105+
106+
@property
107+
def output_file_name(self) -> str:
108+
return f"{self.category}.fits"
109+
110+
@property
111+
def tag(self) -> str:
112+
return rf"{self.category}"
113+
114+
63115
class ProductRsrfIfu(PipelineProduct):
64-
category = rf"RSRF_IFU"
65-
tag = category
116+
group = cpl.ui.Frame.FrameGroup.CALIB # TBC
66117
level = cpl.ui.Frame.FrameLevel.FINAL
67-
frame_type = cpl.ui.Frame.FrameType.IMAGE
118+
frame_type = cpl.ui.Frame.FrameType.IMAGE # set of 1D spectra?
119+
120+
# SKEL: copy product keywords from header
121+
def add_properties(self):
122+
super().add_properties()
123+
self.properties.append(self.header)
124+
125+
@property
126+
def category(self) -> str:
127+
return rf"RSRF_IFU"
128+
129+
@property
130+
def output_file_name(self) -> str:
131+
return f"{self.category}.fits"
132+
133+
@property
134+
def tag(self) -> str:
135+
return rf"{self.category}"
68136

69137
class ProductBadpixMapIfu(PipelineProduct):
70-
category = rf"BADPIX_MAP_IFU"
71-
tag = category
138+
group = cpl.ui.Frame.FrameGroup.CALIB # TBC
72139
level = cpl.ui.Frame.FrameLevel.FINAL
73140
frame_type = cpl.ui.Frame.FrameType.IMAGE
74141

142+
# SKEL: copy product keywords from header
143+
def add_properties(self):
144+
super().add_properties()
145+
self.properties.append(self.header)
146+
147+
@property
148+
def category(self) -> str:
149+
return rf"BADPIX_MAP_IFU"
150+
151+
@property
152+
def output_file_name(self) -> str:
153+
return f"{self.category}.fits"
154+
155+
@property
156+
def tag(self) -> str:
157+
return rf"{self.category}"
158+
75159
def process_images(self) -> Dict[str, PipelineProduct]:
76-
# self.correct_telluric()
77-
# self.apply_fluxcal()
160+
# TODO: FUNC: basic raw processing of RSRF and WCU_OFF input frames:
161+
# - dark subtraction? (subtracting WCU_OFF frame might suffice?)
162+
# - gain / linearity correction? (as for dark subtraction)
163+
# - master dark will be used for bad-pixel map as a minimum
164+
165+
# create bad pixel map
166+
# TODO: FUNC: create updated bad pixel map
167+
badpix_hdr = cpl.core.PropertyList()
168+
# placeholder data for now - bad-pixel map based on master_dark
169+
badpix_img = cpl.core.Image.load(self.inputset.master_dark.frame.file,
170+
extension=0)
171+
# TODO: create QC1 parameters:
172+
qc_badpix_count = 0
173+
# SKEL: Add QC keywords
174+
badpix_hdr.append(
175+
cpl.core.Property(
176+
"QC IFU RSRF NBADPIX",
177+
cpl.core.Type.INT,
178+
qc_badpix_count,
179+
)
180+
)
181+
182+
# create master WCU_OFF background image
183+
background_hdr = \
184+
cpl.core.PropertyList()
185+
# self.inputset.background.frameset.dump() # debug
186+
bg_images = self.load_images(self.inputset.background.frameset)
187+
background_img = self.combine_images(bg_images, "median") # if >2 images
188+
# TODO: SKEL: define usedframes?
189+
# TODO: SKEL: Add product keywords - currently none defined in DRLD
190+
191+
# create 2D flat images (one for each raw input image?)
192+
spec_flat_hdr = \
193+
cpl.core.PropertyList()
194+
raw_images = self.load_images(self.inputset.raw.frameset)
195+
raw_images.subtract_image(background_img)
196+
# TODO: FUNC: group RSRF input frames (using EDPS wokflow?) by
197+
# 1. BB temperature
198+
# 2. int_sphere entrance aperture size
199+
# 3. Chopper mirror position
200+
# TODO: FUNC: collapse each group into a 2D image
201+
# TODO: FUNC: apply distortion correction and wavelength calibration
202+
# TODO: FUNC: divide each collapsed image by ideal continuum spec image,
203+
# given T_BB_lamp and normalise
204+
# SKEL: Add QC keywords
205+
spec_flat_hdr.append(
206+
cpl.core.Property(
207+
"QC IFU RSRF NBADPIX",
208+
cpl.core.Type.INT,
209+
qc_badpix_count,
210+
)
211+
)
212+
213+
# SKEL: placeholder single-file, single-extension data product for now
214+
spec_flat_img = self.combine_images(raw_images, "add")
78215

79-
header = cpl.core.PropertyList()
80-
images = self.inputset.load_raw_images()
81-
image = self.combine_images(images, "add")
216+
# create 1D RSRF
217+
# TODO: FUNC: average 2D flat in spatial direction for each trace
218+
rsrf_hdr = \
219+
cpl.core.PropertyList()
220+
# TODO: SKEL: Add product keywords - currently none defined in DRLD
221+
# SKEL: placeholder data for now
222+
# NOTE: rebin() cpl documentation is incorrect -
223+
# ystart, xstart parameters are *1-based*, NOT 0-based
224+
img_height = spec_flat_img.height
225+
rsrf_img = spec_flat_img.rebin(1, 1, img_height, 1)
226+
rsrf_img.divide_scalar(img_height)
82227

228+
# instantiate products
83229
self.products = {
84-
product.category: product(self, header, image)
85-
for product in [self.ProductMasterFlatIfu, self.ProductRsrfIfu, self.ProductBadpixMapIfu]
230+
self.ProductBackground.tag:
231+
self.ProductBackground(self, background_hdr, background_img),
232+
self.ProductMasterFlatIfu.tag:
233+
self.ProductMasterFlatIfu(self, spec_flat_hdr, spec_flat_img),
234+
self.ProductRsrfIfu.tag:
235+
self.ProductRsrfIfu(self, rsrf_hdr, rsrf_img),
236+
self.ProductBadpixMapIfu.tag:
237+
self.ProductBadpixMapIfu(self, badpix_hdr, badpix_img),
86238
}
239+
87240
return self.products
88241

242+
def load_images(self, frameset: cpl.ui.FrameSet) -> cpl.core.ImageList:
243+
"""Load an imagelist from a FrameSet
244+
245+
This is a temporary implementation that should be generalised to the
246+
entire pipeline package. It uses cpl functions - these should be
247+
replaced with hdrl functions once they become available, in order
248+
to use uncertainties and masks.
249+
"""
250+
output = cpl.core.ImageList()
251+
252+
for idx, frame in enumerate(frameset):
253+
cpl.core.Msg.info(self.__class__.__qualname__,
254+
f"Processing input frame #{idx}: {frame.file!r}...")
255+
output.append(cpl.core.Image.load(frame.file, extension=1))
256+
257+
return output
258+
89259

90260
class MetisIfuRsrf(MetisRecipe):
91261
_name = "metis_ifu_rsrf"
92262
_version = "0.1"
93-
_author = "Martin Baláž"
94-
_email = "[email protected]"
95-
_synopsis = "Determine the relative spectral response function"
96-
_description = (
97-
"Currently just a skeleton prototype."
98-
)
263+
_author = "Janus Brink"
264+
_email = "[email protected]"
265+
_synopsis = "Determine the relative spectral response function."
266+
_description = """\
267+
Create relative spectral response function for the IFU detector
268+
269+
Inputs
270+
IFU_RSRF_RAW: Raw RSRF images [1-n]
271+
IFU_WCU_RAW_OFF: Background images with WCU black-body closed [1-n]
272+
MASTER_DARK_IFU: Master dark frame [optional?]
273+
BADPIX_MAP_IFU: Bad-pixel map for 2RG detector [optional]
274+
PERSISTENCE_MAP: Persistence map [optional]
275+
GAIN_MAP_IFU: Gain map for 2RG detector
276+
LINEARITY_IFU: Linearity map for 2RG detector
277+
IFU_DISTORTION_TABLE: Distortion coefficients for an IFU data set
278+
IFU_WAVECAL: IFU wavelength calibration
279+
280+
Matched Keywords
281+
DET.DIT
282+
DET.NDIT
283+
DRS.IFU
284+
285+
Outputs
286+
MASTER_FLAT_IFU: Master flat frame for IFU image data
287+
RSRF_IFU: 1D relative spectral response function
288+
BADPIX_MAP_IFU: Updated bad-pixel map
289+
290+
Algorithm
291+
Average / median stack WCU_OFF images to create background image
292+
Subtract background image from individual RSRF RAW frames
293+
TBC: subtract master_dark from above frames first?
294+
TBC: apply gain / linearity corrections to above frames?
295+
TBC: obtain bad pixel map from master_dark?
296+
Create continuum image by mapping Planck spectrum at Tlamp to wavelength
297+
image.
298+
Divide exposures by continuum image.
299+
Create master flat (2D RSRF) - TBC one extension per input exposure?
300+
Average in spatial direction to obtain relative response function
301+
(1D RSRF) - TBC multiple FITS extensions with spectral traces?
302+
"""
99303

100304
# This should not be here but without it pyesorex crashes
101305
parameters = cpl.ui.ParameterList([

metisp/pyrecipes/metis_recipes.py

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from pymetis.recipes.ifu.metis_ifu_distortion import MetisIfuDistortion
2626
from pymetis.recipes.ifu.metis_ifu_calibrate import MetisIfuCalibrate
2727
from pymetis.recipes.ifu.metis_ifu_postprocess import MetisIfuPostprocess
28+
from pymetis.recipes.ifu.metis_ifu_rsrf import MetisIfuRsrf
2829
from pymetis.recipes.ifu.metis_ifu_reduce import MetisIfuReduce
2930
from pymetis.recipes.ifu.metis_ifu_rsrf import MetisIfuRsrf
3031
from pymetis.recipes.ifu.metis_ifu_telluric import MetisIfuTelluric
@@ -41,6 +42,7 @@
4142
MetisIfuDistortion,
4243
MetisIfuCalibrate,
4344
MetisIfuPostprocess,
45+
MetisIfuRsrf,
4446
MetisIfuReduce,
4547
MetisIfuRsrf,
4648
MetisIfuTelluric,

0 commit comments

Comments
 (0)