|
23 | 23 |
|
24 | 24 | from pymetis.base import MetisRecipe
|
25 | 25 | 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 |
28 | 30 | from pymetis.inputs.mixins import PersistenceInputSetMixin
|
29 | 31 | from pymetis.prefab.darkimage import DarkImageProcessor
|
30 | 32 |
|
31 | 33 |
|
32 |
| -class RsrfMasterDarkInput(MasterDarkInput): |
33 |
| - pass |
34 | 34 |
|
35 | 35 |
|
36 |
| -class DistortionTableInput(SinglePipelineInput): |
37 |
| - _tags = re.compile(r"IFU_DISTORTION_TABLE") |
38 |
| - _title = "distortion table" |
39 |
| - _group = cpl.ui.Frame.FrameGroup.CALIB |
40 |
| - |
41 | 36 |
|
42 | 37 | class MetisIfuRsrfImpl(DarkImageProcessor):
|
43 |
| - class InputSet(PersistenceInputSetMixin, DarkImageProcessor.InputSet): |
| 38 | + class InputSet(DarkImageProcessor.InputSet): |
44 | 39 | class RawInput(RawInput):
|
45 | 40 | _tags = re.compile(r"IFU_RSRF_RAW")
|
46 | 41 | _title = "IFU rsrf raw"
|
47 | 42 |
|
48 | 43 | MasterDarkInput = MasterDarkInput
|
49 | 44 |
|
| 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 | + |
50 | 53 | def __init__(self, frameset: cpl.ui.FrameSet):
|
51 | 54 | super().__init__(frameset)
|
| 55 | + self.background = self.RsrfWcuOffInput(frameset) |
| 56 | + self.linearity = LinearityInput(frameset) |
52 | 57 | self.gain_map = GainMapInput(frameset)
|
53 | 58 | 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 |
54 | 74 |
|
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}" |
56 | 91 |
|
57 | 92 | class ProductMasterFlatIfu(PipelineProduct):
|
58 |
| - category = rf"MASTER_FLAT_IFU" |
59 |
| - tag = category |
| 93 | + group = cpl.ui.Frame.FrameGroup.CALIB # TBC |
60 | 94 | level = cpl.ui.Frame.FrameLevel.FINAL
|
61 | 95 | frame_type = cpl.ui.Frame.FrameType.IMAGE
|
62 | 96 |
|
| 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 | + |
63 | 115 | class ProductRsrfIfu(PipelineProduct):
|
64 |
| - category = rf"RSRF_IFU" |
65 |
| - tag = category |
| 116 | + group = cpl.ui.Frame.FrameGroup.CALIB # TBC |
66 | 117 | 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}" |
68 | 136 |
|
69 | 137 | class ProductBadpixMapIfu(PipelineProduct):
|
70 |
| - category = rf"BADPIX_MAP_IFU" |
71 |
| - tag = category |
| 138 | + group = cpl.ui.Frame.FrameGroup.CALIB # TBC |
72 | 139 | level = cpl.ui.Frame.FrameLevel.FINAL
|
73 | 140 | frame_type = cpl.ui.Frame.FrameType.IMAGE
|
74 | 141 |
|
| 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 | + |
75 | 159 | 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") |
78 | 215 |
|
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) |
82 | 227 |
|
| 228 | + # instantiate products |
83 | 229 | 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), |
86 | 238 | }
|
| 239 | + |
87 | 240 | return self.products
|
88 | 241 |
|
| 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 | + |
89 | 259 |
|
90 | 260 | class MetisIfuRsrf(MetisRecipe):
|
91 | 261 | _name = "metis_ifu_rsrf"
|
92 | 262 | _version = "0.1"
|
93 |
| - _author = "Martin Baláž" |
94 |
| - |
95 |
| - _synopsis = "Determine the relative spectral response function" |
96 |
| - _description = ( |
97 |
| - "Currently just a skeleton prototype." |
98 |
| - ) |
| 263 | + _author = "Janus Brink" |
| 264 | + |
| 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 | + """ |
99 | 303 |
|
100 | 304 | # This should not be here but without it pyesorex crashes
|
101 | 305 | parameters = cpl.ui.ParameterList([
|
|
0 commit comments