Commit 0861a483890b990836c40c53c2e06caafb70efcf

Authored by Alexis Koralewski
2 parents 7cc13fec 6438ef81
Exists in dev

Merge branch 'dev' of https://gitlab.irap.omp.eu/pyros-irap/pyros into dev

config/pyros_observatory/general/schemas/schema_observatory-2.0.yml
... ... @@ -135,6 +135,34 @@ schema;schema_FN_CONTEXTS:
135 135 type: map
136 136 required: False
137 137 mapping:
  138 + cal_L0:
  139 + type: map
  140 + required: False
  141 + mapping:
  142 + root_dir:
  143 + type: str
  144 + description:
  145 + type: str
  146 + extension:
  147 + type: str
  148 + naming:
  149 + type: str
  150 + pathnaming:
  151 + type: str
  152 + cal_L1:
  153 + type: map
  154 + required: False
  155 + mapping:
  156 + root_dir:
  157 + type: str
  158 + description:
  159 + type: str
  160 + extension:
  161 + type: str
  162 + naming:
  163 + type: str
  164 + pathnaming:
  165 + type: str
138 166 img_L0:
139 167 type: map
140 168 required: False
... ... @@ -162,7 +190,77 @@ schema;schema_FN_CONTEXTS:
162 190 naming:
163 191 type: str
164 192 pathnaming:
  193 + type: str
  194 + img_L1a:
  195 + type: map
  196 + required: False
  197 + mapping:
  198 + root_dir:
  199 + type: str
  200 + description:
  201 + type: str
  202 + extension:
  203 + type: str
  204 + naming:
  205 + type: str
  206 + pathnaming:
  207 + type: str
  208 + img_L1b:
  209 + type: map
  210 + required: False
  211 + mapping:
  212 + root_dir:
  213 + type: str
  214 + description:
165 215 type: str
  216 + extension:
  217 + type: str
  218 + naming:
  219 + type: str
  220 + pathnaming:
  221 + type: str
  222 + img_L1c:
  223 + type: map
  224 + required: False
  225 + mapping:
  226 + root_dir:
  227 + type: str
  228 + description:
  229 + type: str
  230 + extension:
  231 + type: str
  232 + naming:
  233 + type: str
  234 + pathnaming:
  235 + type: str
  236 + img_L1d:
  237 + type: map
  238 + required: False
  239 + mapping:
  240 + root_dir:
  241 + type: str
  242 + description:
  243 + type: str
  244 + extension:
  245 + type: str
  246 + naming:
  247 + type: str
  248 + pathnaming:
  249 + type: str
  250 + img_L2:
  251 + type: map
  252 + required: False
  253 + mapping:
  254 + root_dir:
  255 + type: str
  256 + description:
  257 + type: str
  258 + extension:
  259 + type: str
  260 + naming:
  261 + type: str
  262 + pathnaming:
  263 + type: str
166 264 img_tmp:
167 265 type: map
168 266 required: False
... ...
src/core/pyros_django/img_process/A_ImgProcessor.py
... ... @@ -63,7 +63,7 @@ class A_ImgProcessor(Agent):
63 63 # AgentCmd.CMD_STATUS_CODE.CMD_EXECUTED
64 64 _TEST_COMMANDS_LIST = [
65 65 # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status),
66   - (True, "self do_create_test_images_1", 500, "", Agent.CMD_STATUS.CMD_EXECUTED),
  66 + (True, "self do_create_test_images_2", 500, "", Agent.CMD_STATUS.CMD_EXECUTED),
67 67 (True, "self do_stop asap", 500, "STOPPING", Agent.CMD_STATUS.CMD_EXECUTED),
68 68 ]
69 69  
... ... @@ -80,7 +80,7 @@ class A_ImgProcessor(Agent):
80 80  
81 81 def _init(self):
82 82 super()._init()
83   - log.debug("end super init()")
  83 + log.debug("End super init()")
84 84 log.info(f"self.TEST_MODE = {self.TEST_MODE}")
85 85  
86 86 # === Get the config object
... ... @@ -105,6 +105,7 @@ class A_ImgProcessor(Agent):
105 105 if channel == None:
106 106 raise Exception(f"{agent_alias} has no channel file contexts {channel=}")
107 107 #log.info(f"{channel=}")
  108 + self._channel = channel
108 109  
109 110 # === Get all file contexts from the channel config
110 111 self._fn = channel['fn_contexts']
... ... @@ -117,8 +118,7 @@ class A_ImgProcessor(Agent):
117 118 log.info(f"{longitude=}")
118 119 self._ima.longitude(longitude)
119 120  
120   - # === Copy the channel file context into the Ima object
121   - # --- TODO make a Filenames function to do that
  121 + # === Copy the channel file contexts into the Ima object
122 122 self._ima.fcontext_replace(self._fn)
123 123 log.info(f"=== List of file name contexts available for the channel {channel['name']} ({channel['symbol']})")
124 124 for fcname in self._ima.fcontexts:
... ... @@ -174,6 +174,15 @@ class A_ImgProcessor(Agent):
174 174 """
175 175  
176 176 def glob_images_to_process(self):
  177 + """Get the list of L0A (and L1A or L1B) files compatibles with the unit and the channel.
  178 +
  179 + The list is sorted to have the oldest file at the first place.
  180 + Files are FITS format.
  181 + The context file is 'img_L0'
  182 +
  183 + Returns:
  184 + The list of files sorted from the oldest to the earlier. If L1A or L1B files exist the only the list of these files is returned.
  185 + """
177 186  
178 187 # - select the incoming directory file context
179 188 fcontext = "img_L0"
... ... @@ -181,138 +190,308 @@ class A_ImgProcessor(Agent):
181 190 self._ima.fcontext = fcontext
182 191 except Exception as e:
183 192 raise Exception(f"glob_images_to_process: {e}")
184   - # - Compute the wildcard for glob
185   - # TODO fix the bug of * in the file name
186   - # wildcard = self.ima.join("*")
187   - # The current solution is:
188   - wildcard = os.path.join(self._ima.rootdir,"*"+self._ima.extension)
189   - print(f"{wildcard}")
  193 + # - Get the fcontext naming wildcard
  194 + wildcard = self._ima.naming_wildcard()
  195 + # - Get param dict from the fcontext naming wildcard
  196 + param = self._ima.naming_get(wildcard)
  197 + # - Set the constraints of the wildcard
  198 + param['unit'] = self.config.unit_name
  199 + param['channel'] = self._channel['symbol']
  200 + wildcard = self._ima.naming_set(param)
  201 + wildcard = self._ima.join(wildcard)
190 202 # - glob the incoming directory contents:
  203 + log.debug(f"glob_images_to_process: {wildcard=}")
191 204 fitsfiles = glob.glob(wildcard)
192 205 # - Please sort list of files in increasing dates (TODO)
193 206 fitsfiles.sort()
  207 + # - Get only L1? file types if exist
  208 + fics = []
  209 + for fitsfile in fitsfiles:
  210 + param = self._ima.naming_get(fitsfile)
  211 + if param['ftype'][:1] == "L1":
  212 + fics.append(fitsfile)
  213 + if len(fics):
  214 + fitsfiles = fics
194 215 return fitsfiles
195 216  
196 217 def bias_correction(self):
197   -
198   - # - Search the bias
199   - path_bias = os.path.join( self._paths['ima_bias'], self._date_night )
200   - fitsbiasfiles = glob.glob(f"{path_bias}/*.fit")
201   - log.info(f"fitsbiasfiles = {fitsbiasfiles}")
202   - if len(fitsbiasfiles) > 0:
203   -
204   - # - Select the bias
205   - pass
  218 + """Process bias correction.
  219 + """
  220 + log.info("No correction needed")
206 221  
207 222 def dark_correction(self):
  223 + """Process dark correction.
  224 +
  225 + This method is called by process_one_image().
  226 + Consider the file contexts 'img_tmp' and 'cal_L1'. Consider the <night> and <filter>.
  227 + The processing is performed with all files lying only in the directory 'img_tmp'.
  228 +
  229 + Prerequesite: The method process_one_image() copied a science image from the directory 'img_L0' to a file named img_<channel>_<filter>.fit in the directory 'img_tmp'.
  230 +
  231 + Algorithm:
  232 +
  233 + * Check if the file dark_<channel>_<night> exists in the directory 'img_tmp'.
  234 + * If not, check if a DA1 superdark file exists in the directory 'cal_L1'/<night> and copy it in the directory 'img_tmp' as the name dark_<channel_<night>.
  235 + * Load the image img_<channel>_<filter> (= self._fimg)
  236 + * Sub the image dark_<channel>_<night>
  237 + """
208 238  
209   - # - Search the dark
210   - path_darks = os.path.join( self._paths['ima_darks'], self._date_night )
211   - fitsdarkfiles = glob.glob(f"{path_darks}/*.fit")
212   - log.info(f"fitsdarkfiles = {fitsdarkfiles}")
213   - if len(fitsdarkfiles) > 0:
214   -
215   - # - Select two darks and compute the therm using exposure
216   - # - Correction of dark
217   - pass
  239 + # - Dark file name in tmp of this night
  240 + self._ima.fcontext = "img_tmp"
  241 + fdark = "dark_" + self._channel['symbol'] + "_" + self._datedict['night']
  242 + fdark_out = self._ima.join(fdark)
  243 +
  244 + # - Delete dark files in tmp that are not of this night
  245 + # This action prevents orphan files in the tmp folder
  246 + wildcard = self._ima.join("dark_" + self._channel['symbol'] + "_*")
  247 + fdarkes = glob.glob(wildcard)
  248 + for fdarke in fdarkes:
  249 + if fdarke != fdark_out:
  250 + log.info(f"Clean temporary file {fdarke}")
  251 + os.remove(fdarke)
  252 +
  253 + # - Place the night dark in tmp if needed
  254 + fdark_in = None
  255 + if not os.path.exists(fdark_out):
  256 + # - The night dark does not exists in tmp
  257 + self._ima.fcontext = "cal_L1"
  258 + # - Get the fcontext naming wildcard
  259 + wildcard = self._ima.naming_wildcard()
  260 + # - Get param dict from the fcontext naming wildcard
  261 + param_dark = self._ima.naming_get(wildcard)
  262 + # - Set the constraints of the wildcard
  263 + param_dark['ftype'] = 'DA1'
  264 + param_dark['unit'] = self.config.unit_name
  265 + param_dark['channel'] = self._channel['symbol']
  266 + wildcard = self._ima.naming_set(param_dark)
  267 + wildcard = os.path.join( self._ima.rootdir, self._datedict['night'], wildcard + self._ima.extension)
  268 + # - glob the incoming directory contents:
  269 + #log.info(f"dark glob : {wildcard=}")
  270 + fitsfiles = glob.glob(wildcard)
  271 + #log.info(f"dark files : {fitsfiles=}")
  272 + if len(fitsfiles) > 0:
  273 + fdark_in = fitsfiles[0]
  274 + # - Copy the night dark into tmp
  275 + #log.info(f"{fdark_in=}")
  276 + #log.info(f"{fdark_out=}")
  277 + shutil.copyfile(fdark_in, fdark_out)
  278 +
  279 + # - Dark correction if dark exists
  280 + if os.path.exists(fdark_out):
  281 + # - Load img and get infos
  282 + self._ima.fcontext = "img_tmp"
  283 + self._ima.load(self._fimg)
  284 + # - Dark correction
  285 + log.info(f"Dark used: {fdark}")
  286 + self._ima.sub(fdark)
  287 + self._ima.save(self._fimg)
  288 + log.info("Dark correction done")
  289 + else:
  290 + log.info("No dark correction")
218 291  
219 292 def flat_correction(self):
  293 + """Process flat correction.
  294 +
  295 + This method is called by process_one_image().
  296 + Consider the file contexts 'img_tmp' and 'cal_L1'. Consider the <night> and <filter>.
  297 + The processing is performed with all files lying only in the directory 'img_tmp'.
  298 +
  299 + Prerequesite: The method process_one_image() copied a science image from the directory 'img_L0' to a file named img_<channel>_<filter>.fit in the directory 'img_tmp'. The image must be corrected by bias and dark.
  300 +
  301 + Algorithm:
  302 +
  303 + * Check if the file flat_<channel>_<night>_<filter> exists in the directory 'img_tmp'.
  304 + * If not, check if a FL1 superdark file exists in the directory 'cal_L1'/<night> and copy it in the directory 'img_tmp' as the name flat_<channel_<night>_<filter>.
  305 + * Load the image img_<channel>_<filter> (= self._fimg)
  306 + * Div the image flat_<channel>_<night>_<filter>
  307 + """
220 308  
221   - # - Search the flat
222   - path_flats = os.path.join( self._paths['ima_flats'], self._date_night )
223   - fitsflatfiles = glob.glob(f"{path_flats}/*.fit")
224   - log.info(f"fitsflatfiles = {fitsflatfiles}")
225   - if len(fitsflatfiles) > 0:
226   -
227   - # - Select the flat (with the filter)
228   - # - Correction of flat
229   - pass
  309 + # - Flat file name in tmp of this night
  310 + self._ima.fcontext = "img_tmp"
  311 + fflat = "flat_" + self._channel['symbol'] + "_" + self._datedict['night'] + "_" + self._filter_symbol
  312 +
  313 + fflat_out = self._ima.join(fflat)
  314 +
  315 + # - Delete flat files in tmp that are not of this night
  316 + # This action prevents orphan files in the tmp folder
  317 + wildcard = self._ima.join("flat_" + self._channel['symbol'] + "_" + self._filter_symbol + "_*")
  318 + fflates = glob.glob(wildcard)
  319 + for fflate in fflates:
  320 + if fflate != fflat_out:
  321 + log.info(f"Clean temporary file {fflate}")
  322 + os.remove(fflate)
  323 +
  324 + # - Place the night flat in tmp if needed
  325 + fflat_in = None
  326 + if not os.path.exists(fflat_out):
  327 + # - The night flat does not exists in tmp
  328 + self._ima.fcontext = "cal_L1"
  329 + # - Get the fcontext naming wildcard
  330 + wildcard = self._ima.naming_wildcard()
  331 + # - Get param dict from the fcontext naming wildcard
  332 + param_flat = self._ima.naming_get(wildcard)
  333 + # - Set the constraints of the wildcard
  334 + param_flat['ftype'] = 'FL1'
  335 + param_flat['unit'] = self.config.unit_name
  336 + param_flat['channel'] = self._channel['symbol']
  337 + param_flat['filter'] = self._filter_symbol
  338 + wildcard = self._ima.naming_set(param_flat)
  339 + wildcard = os.path.join( self._ima.rootdir, self._datedict['night'], wildcard + self._ima.extension)
  340 + # - glob the incoming directory contents:
  341 + #log.info(f"flat glob : {wildcard=}")
  342 + fitsfiles = glob.glob(wildcard)
  343 + #log.info(f"flat files : {fitsfiles=}")
  344 + if len(fitsfiles) > 0:
  345 + fflat_in = fitsfiles[0]
  346 + # - Copy the night flat into tmp
  347 + #log.info(f"{fflat_in=}")
  348 + #log.info(f"{fflat_out=}")
  349 + shutil.copyfile(fflat_in, fflat_out)
  350 +
  351 + # - Flat correction if flat exists
  352 + if os.path.exists(fflat_out):
  353 + # - Load img and get infos
  354 + self._ima.fcontext = "img_tmp"
  355 + #self._ima.load(self._fimg)
  356 + # - Flat correction
  357 + log.info(f"Flat used: {fflat}")
  358 + self._ima.div(fflat, 10000)
  359 + self._ima.save(self._fimg)
  360 + log.info("Flat correction done")
  361 + else:
  362 + log.info("No flat correction")
230 363  
231 364 def inversion_correction(self):
232   - pass
  365 + """Process inversion corrections.
  366 + """
  367 + log.info("No inversion correction needed")
233 368  
234 369 def cosmetic_correction(self):
235   - pass
  370 + """Process cosmetic correction.
  371 + """
  372 + log.info("No cosmetic correction needed")
236 373  
237 374 def wcs_calibration(self):
238   - return 0
  375 + """Process WCS calibration.
  376 +
  377 + This method is called by process_one_image().
  378 + Consider the file context 'img_tmp'. Consider the <night> and <filter>.
  379 + The processing is performed with all files lying only in the directory 'img_tmp'.
  380 +
  381 + Prerequesite: The method process_one_image() copied a science image from the directory 'img_L0' to a file named img_<channel>_<filter>.fit in the directory 'img_tmp'. The image must be corrected by bias, dark and flat.
  382 +
  383 + Algorithm:
  384 +
  385 + * Load the image img_<channel>_<filter> (= self._fimg)
  386 + * Set the current working directory to the 'img_tmp' rootdir.
  387 + * Create the dictionary of settings to prepare the calibration call.
  388 + * Call the method of calibwcs.
  389 + """
  390 + method = ""
  391 + #method = "upload"
  392 + if method == "upload":
  393 + self._ima.fcontext = "img_tmp"
  394 + os.chdir(self._ima.rootdir)
  395 + self._ima.config("astrometrynet", API_KEY = "amxbkytvjishmehq")
  396 + settings = {}
  397 + settings["center_ra"] = self._ima.getkwd('RA')
  398 + settings["center_dec"] = self._ima.getkwd('DEC')
  399 + settings["radius"] = 1.0
  400 + success, comment, detail = self._ima.calibwcs("upload", **settings)
  401 + log.info(f"WCS {success=}")
  402 + log.info(f"WCS {comment=}")
  403 + log.info(f"WCS {detail=}")
  404 + log.info("WCS correction done")
  405 + else:
  406 + log.info("No WCS correction needed")
239 407  
240 408 def process_one_image(self, fitsfile: str):
241   - """This is the general algorithm of processing
  409 + """This is the general algorithm of processing to transform L0 to L1b
  410 +
  411 + The processing consists to make corrections of dark, flat, inversions, cosmetic (L1a) and perform WCS calibration (L1b).
242 412  
243   - The processing consists to make corrections of dark, flat, inversions, cosmetic
244   - and perform WCS calibration.
  413 + This method is called by the method _routine_process_iter_end_body().
  414 + The method _routine_process_iter_end_body() first call the method glob_images_to_process() to get the list of file names to process.
  415 + The list of file names can be L0A images of L1A, L1B, ...
  416 + The normal use of process_one_image() is to process L0A but it is possible to copy by hand L1A or L1B files in the incoming directory to force a new WCS calibration if needed.
245 417  
246 418 Args:
247   - fitsfile: The file of the FITS file to process.
  419 + fitsfile: The file name of the FITS file to process. The file name must be contains path, name, extension.
248 420  
249 421 """
250 422  
  423 + log.info("\n" + "="*70 + f"\n=== Start process an image for L0->L1\n" + "="*70 + "\n")
  424 +
251 425 # - Get informations from the file name according the fcontext
252   - log.info(f"{self._ima.fcontext} : {self._ima.fdescription} {self._ima.rootdir}/[{self._ima.pathing()}]/[{self._ima.naming()}]{self._ima.extension}")
253   - infos = self._ima.naming_get(fitsfile)
254   - print(f"{infos=}")
  426 + self._ima.fcontext = "img_L0"
  427 + #log.info(f"{self._ima.fcontext} : {self._ima.fdescription} {self._ima.rootdir}/[{self._ima.pathing()}]/[{self._ima.naming()}]{self._ima.extension}")
  428 + param_img = self._ima.naming_get(fitsfile)
  429 + #log.info(f"{param_img=}")
255 430  
256 431 # - Load file in memory
257   - log.info("Load the file in memory")
258   - infos = self._ima.load(fitsfile)
259   - print(f"{infos=}")
260   - #f = self._ima.genename(self._ima.load(fitsfile))
261   - #log.info(f"{f=}")
  432 + log.info(f"Image to process {fitsfile}")
  433 + self._ima.load(fitsfile)
262 434  
263   - # - Save as tmp
  435 + # - Get datedict from DATE-OBS of img
  436 + date_obs = self._ima.getkwd("DATE-OBS")
  437 + log.info(f"DATE-OBS: {date_obs}")
  438 + self._datedict = self._ima.naming_date(date_obs)
  439 + log.info(f"night: {self._datedict['night']}")
  440 +
  441 + # - Get filter
  442 + self._filter_symbol = self._ima.getkwd("FILTER")
  443 +
  444 + # - Save as img in tmp
  445 + self._fimg = "img_" + self._channel['symbol'] + "_" + self._filter_symbol
264 446 self._ima.fcontext = "img_tmp"
265   - f = self._ima.join("tmp.fit")
266   - log.info("Save the temporary file as tmp name")
  447 + #log.info(f"{self._ima.fcontext} : {self._ima.fdescription} {self._ima.rootdir}/[{self._ima.pathing()}]/[{self._ima.naming()}]{self._ima.extension}")
  448 + f = self._ima.join(self._fimg)
  449 + log.info(f"Save the temporary file as {f}")
267 450 self._ima.save(f)
268 451  
269   - # - Load tmp and get infos
270   - self._ima.load("tmp")
271   - date_obs = self._ima.getkwd("DATE-OBS")
272   - self._date_night = self._ima.get_night(date_obs)
273   - log.info(f"Date_obs = {date_obs}")
274   - log.info(f"Night = {self._date_night}")
275   - exposure = self._ima.getkwd("EXPOSURE")
276   - log.info(f"Exposure = {exposure}")
277   -
278   - # - Bias correction
279   - self.bias_correction()
280   -
281   - # - Dark correction
282   - self.dark_correction()
283   -
284   - # - Flat correction
285   - self.flat_correction()
286   -
287   - # - Save tmp corrected by dark and flat
288   - self._ima.path(self._paths['ima_tmp'])
289   - self._ima.save("tmp")
290   -
291   - # - Inversion of mirrors or mirorxy
292   - self.inversion_correction()
293   -
294   - # - Cosmetic correction
295   - self.cosmetic_correction()
296   -
297   - # - WCS calibration
298   - nmatched = self.wcs_calibration()
299   -
300   - # - Prepare the output file name
301   - log.info("Decode the filename")
302   - fgen_in = f['genename'] + f['sep'] + f['indexes'][0] + f['suffix']
303   - fext_in = f['file_extension']
304   - fext_out = ".fits"
305   -
306   - # - Save in processed
307   - yyyy = self._date_night[0:4]
308   - mm = self._date_night[4:6]
309   - dd = self._date_night[6:8]
310   - path_processed = os.path.join( self._paths['ima_processed'], yyyy, mm, dd )
311   - self._ima.path(path_processed)
312   - fname_out = fgen_in + fext_out
313   - fname = self._ima.save(fname_out)
314   - log.info(f"Save the processed image {fname}")
315   -
  452 + if param_img['ftype'] == "L0A":
  453 + # - Bias correction
  454 + log.info("\n" + "-"*60 + f"\n--- Bias correction\n" + "-"*60 + "\n")
  455 + self.bias_correction()
  456 +
  457 + # - Dark correction
  458 + log.info("\n" + "-"*60 + f"\n--- Dark correction\n" + "-"*60 + "\n")
  459 + self.dark_correction()
  460 +
  461 + # - Flat correction
  462 + log.info("\n" + "-"*60 + f"\n--- Flat correction\n" + "-"*60 + "\n")
  463 + self.flat_correction()
  464 +
  465 + # - Inversion of mirrors or mirorxy
  466 + log.info("\n" + "-"*60 + f"\n--- Mirror correction\n" + "-"*60 + "\n")
  467 + self.inversion_correction()
  468 +
  469 + # - Cosmetic correction
  470 + log.info("\n" + "-"*60 + f"\n--- Cosmetic correction\n" + "-"*60 + "\n")
  471 + self.cosmetic_correction()
  472 +
  473 + # - Save the L1a file name
  474 + self._ima.fcontext = "img_L1a"
  475 + param_img['ftype'] = 'L1A'
  476 + fname = self._ima.naming_set(param_img)
  477 + fname = self._ima.join(fname)
  478 + self._ima.save(fname)
  479 +
  480 + if param_img['ftype'] == "L1A" or param_img['ftype'] == "L1B":
  481 +
  482 + # - WCS calibration
  483 + log.info("\n" + "-"*60 + f"\n--- WCS calibration\n" + "-"*60 + "\n")
  484 + nmatched = self.wcs_calibration()
  485 +
  486 + # - Save the L1a file name
  487 + self._ima.fcontext = "img_L1b"
  488 + param_img['ftype'] = 'L1B'
  489 + fname = self._ima.naming_set(param_img)
  490 + fname = self._ima.join(fname)
  491 + self._ima.save(fname)
  492 +
  493 + # - TODO Resampling (img_L1c)
  494 +
316 495 # - Delete the file in incoming directory
317 496 os.remove(fitsfile)
318 497 log.info(f"Delete the raw image {fitsfile}")
... ... @@ -320,50 +499,276 @@ class A_ImgProcessor(Agent):
320 499 # - Update the running state
321 500 self._routine_running = self.RUNNING_NOTHING
322 501  
323   - time.sleep(5)
324   - print("\n ...End of image calibration\n")
  502 + log.info("\n" + "="*70 + f"\n=== End process an image for L0->L1\n" + "="*70 + "\n")
325 503  
326 504 """
327 505 =================================================================
328   - Internal methods
  506 + Protected methods
329 507 =================================================================
330 508 """
331 509  
332 510 def _create_test_images_1(self):
333   - # === Define an image to test the processing and copy it in incoming directory
334   - file_in = os.path.join(os.environ['PROJECT_ROOT_PATH'],"vendor","guitastro","tests","data","m57.fit")
  511 + """Copy the image m57.fit of Guitastro as a test image.
  512 +
  513 + A filter C is assumed.
  514 + Create bias, dark and flat calibration images.
  515 +
  516 + This method does not need external internal call.
  517 + """
  518 +
  519 + try:
  520 + self._ima.fcontext = "default"
  521 + except Exception as e:
  522 + raise Exception(f"_create_test_images_1: {e}")
  523 + self._ima.rootdir = os.path.join(os.environ['PROJECT_ROOT_PATH'],"vendor","guitastro","tests","data")
  524 + self._ima.extension = ".fit"
  525 + file_in = self._ima.join("m57")
  526 + # --- Load the image
  527 + log.info(f"{file_in=}")
  528 + self._ima.load(file_in)
  529 + log.info("Load test image OK")
  530 + # --- Get image shape
  531 + naxis1, naxis2 = self._ima._array.shape
  532 + log.info(f"Test image shape is: {naxis1=} {naxis2=}")
  533 + # --- Get datedict
  534 + date_obs = self._ima.getkwd("DATE-OBS")
  535 + log.info(f"Test image DATE-OBS: {date_obs}")
  536 + datedict = self._ima.naming_date(date_obs)
  537 + log.info(f"Test image night: {datedict['night']}")
  538 + # --- Get other infos
  539 + filter_symbol = "C"
  540 + id_sequence = 123456789
  541 +
  542 + # --- Rename the image file name when copying to be compatible with the img_L0 file context
335 543 try:
336 544 self._ima.fcontext = "img_L0"
337 545 except Exception as e:
338 546 raise Exception(f"_create_test_images_1: {e}")
339 547 param = {}
340 548 param['ftype'] = "L0A"
341   - param['date'] = "20000713"
342   - param['time'] = "224045000000"
343   - param['unit'] = "TNC"
  549 + param['date'] = datedict['yyyymmdd']
  550 + param['time'] = datedict['hhmmssssssss']
  551 + param['unit'] = self.config.unit_name
344 552 param['version'] = 1
345   - param['channel'] = "CH1"
346   - param['id_seq'] = 123456789
  553 + param['channel'] = self._channel['symbol']
  554 + param['id_seq'] = id_sequence
347 555 param['plane'] = 1
348 556 param['frame'] = 1
349 557 fname = self._ima.naming_set(param)
350 558 file_out = self._ima.join(fname)
351   - print(f"{file_in=}")
352   - print(f"{file_out=}")
353   - shutil.copyfile(file_in, file_out)
  559 + log.info(f"{file_out=}")
  560 + self._ima.setkwd("FILTER", filter_symbol)
  561 + self._ima.save(file_out)
  562 +
  563 + # --- Build a bias image filled by zeros
  564 + log.info("Build a test bias")
  565 + try:
  566 + self._ima.fcontext = "cal_L1"
  567 + except Exception as e:
  568 + raise Exception(f"_create_test_images_1: {e}")
  569 + self._ima.mult(0)
  570 + param = {}
  571 + param['ftype'] = "BI1"
  572 + param['date'] = datedict['yyyymmdd']
  573 + param['time'] = datedict['hhmmssssssss']
  574 + param['unit'] = self.config.unit_name
  575 + param['version'] = 1
  576 + param['channel'] = self._channel['symbol']
  577 + param['id_seq'] = id_sequence - 3
  578 + param['plane'] = 1
  579 + param['filter'] = filter_symbol
  580 + fname = self._ima.naming_set(param)
  581 + file_out = self._ima.join(fname)
  582 + log.info(f"{file_out=}")
  583 + self._ima.setkwd("FILTER", filter_symbol)
  584 + self._ima.save(file_out)
  585 +
  586 + # --- Build a dark image filled by zeros
  587 + log.info("Build a test dark")
  588 + param = {}
  589 + param['ftype'] = "DA1"
  590 + param['date'] = datedict['yyyymmdd']
  591 + param['time'] = datedict['hhmmssssssss']
  592 + param['unit'] = self.config.unit_name
  593 + param['version'] = 1
  594 + param['channel'] = self._channel['symbol']
  595 + param['id_seq'] = id_sequence - 2
  596 + param['plane'] = 1
  597 + param['filter'] = filter_symbol
  598 + fname = self._ima.naming_set(param)
  599 + file_out = self._ima.join(fname)
  600 + log.info(f"{file_out=}")
  601 + self._ima.setkwd("FILTER", filter_symbol)
  602 + self._ima.save(file_out)
  603 +
  604 + # --- Build a flat image filled by cst=10000
  605 + log.info("Build a test flat")
  606 + flatnorm = 10000
  607 + self._ima.offset(flatnorm)
  608 + self._ima.setkwd("FLATNORM", flatnorm, "Normalisation of the superflat")
  609 + param = {}
  610 + param['ftype'] = "FL1"
  611 + param['date'] = datedict['yyyymmdd']
  612 + param['time'] = datedict['hhmmssssssss']
  613 + param['unit'] = self.config.unit_name
  614 + param['version'] = 1
  615 + param['channel'] = self._channel['symbol']
  616 + param['id_seq'] = id_sequence - 1
  617 + param['plane'] = 1
  618 + param['filter'] = filter_symbol
  619 + fname = self._ima.naming_set(param)
  620 + file_out = self._ima.join(fname)
  621 + log.info(f"{file_out=}")
  622 + self._ima.setkwd("FILTER", filter_symbol)
  623 + self._ima.save(file_out)
354 624  
355 625 def _create_test_images_2(self):
  626 + """Simulate an image centered on Messier 67 as a test image.
  627 +
  628 + A filter C is assumed.
  629 + Create bias, dark and flat calibration images.
  630 +
  631 + This method needs external internal call.
  632 + """
  633 +
  634 + # --- Configure the simulator
356 635 self._ima.etc.camera("Kepler 4040")
357 636 self._ima.etc.optics("Takahashi_180ED")
358   - self._ima.etc.params("msky",18)
  637 + bias_level = 1000
  638 +
  639 + # --- Set the target coordinates
359 640 ra = 132.84583
360 641 dec = 11.81333
361   - at = self._ima.simulation("GAIA", "PHOTOM", shutter_mode="closed", t=50, ra=ra, dec=dec)
362   - file_out = os.path.join(self._paths['ima_tmp'], "m67.ecsv")
363   - print(f"STEP TOTO 1 = {at}")
364   - at.t.write(file_out, format='astrotable', overwrite=True)
365   - print(f"STEP TOTO 2")
366   - date_obs = self.getkwd("DATE-OBS")
  642 +
  643 + # --- Get datedict
  644 + date_obs = guitastro.Date("now").iso(nb_subdigit=3)
  645 + log.info(f"Test image DATE-OBS: {date_obs}")
  646 + datedict = self._ima.naming_date(date_obs)
  647 + log.info(f"Test image night: {datedict['night']}")
  648 +
  649 + # --- Get the table of stars (simu_<channel>.ecsv)
  650 + shutter_mode = "synchro"
  651 + self._ima.etc.params("msky",19)
  652 + t = 10
  653 + log.info("Build a test image")
  654 + try:
  655 + self._ima.fcontext = "img_tmp"
  656 + except Exception as e:
  657 + raise Exception(f"_create_test_images_2: {e}")
  658 + ext = self._ima.extension
  659 + self._ima.extension = ".ecsv"
  660 + fsimu = "simu_" + self._channel['symbol']
  661 + fsimu = self._ima.join(fsimu)
  662 + self._ima.extension = ext
  663 + if os.path.exists(fsimu):
  664 + att = guitastro.AstroTable()
  665 + att.read(fsimu, format="ascii.ecsv")
  666 + at = self._ima.simulation("ASTROTABLE", att, shutter_mode=shutter_mode, t=t, ra=ra, dec=dec)
  667 + else:
  668 + at = self._ima.simulation("GAIA", "PHOTOM", shutter_mode=shutter_mode, t=t, ra=ra, dec=dec, column_filters = {"Gmag": "<14"})
  669 + at.t.write(fsimu, format='ascii.ecsv', overwrite=True)
  670 + att = at
  671 + # --- Get other infos
  672 + filter_symbol = "C"
  673 + id_sequence = 123452789
  674 +
  675 + # --- Save the image file name to be compatible with the img_L0 file context
  676 + try:
  677 + self._ima.fcontext = "img_L0"
  678 + except Exception as e:
  679 + raise Exception(f"_create_test_images_2: {e}")
  680 + param = {}
  681 + param['ftype'] = "L0A"
  682 + param['date'] = datedict['yyyymmdd']
  683 + param['time'] = datedict['hhmmssssssss']
  684 + param['unit'] = self.config.unit_name
  685 + param['version'] = 1
  686 + param['channel'] = self._channel['symbol']
  687 + param['id_seq'] = id_sequence
  688 + param['plane'] = 1
  689 + param['frame'] = 1
  690 + fname = self._ima.naming_set(param)
  691 + file_out = self._ima.join(fname)
  692 + self._ima.setkwd("DATE-OBS", date_obs)
  693 + self._ima.setkwd("FILTER", filter_symbol)
  694 + self._ima.save(file_out)
  695 +
  696 + # --- Build a bias image
  697 + log.info("Build a test bias")
  698 + shutter_mode = "closed"
  699 + self._ima.simulation("ASTROTABLE", att, shutter_mode=shutter_mode, t=0.01, ra=ra, dec=dec)
  700 + try:
  701 + self._ima.fcontext = "cal_L1"
  702 + except Exception as e:
  703 + raise Exception(f"_create_test_images_2: {e}")
  704 + param = {}
  705 + param['ftype'] = "BI1"
  706 + param['date'] = datedict['yyyymmdd']
  707 + param['time'] = datedict['hhmmssssssss']
  708 + param['unit'] = self.config.unit_name
  709 + param['version'] = 1
  710 + param['channel'] = self._channel['symbol']
  711 + param['id_seq'] = id_sequence - 3
  712 + param['plane'] = 1
  713 + param['filter'] = filter_symbol
  714 + fname = self._ima.naming_set(param)
  715 + file_out = self._ima.join(fname)
  716 + import numpy as np
  717 + log.info(f"{file_out=}")
  718 + self._ima.setkwd("DATE-OBS", date_obs)
  719 + self._ima.setkwd("FILTER", filter_symbol)
  720 + self._ima.save(file_out)
  721 +
  722 + # --- Build a dark image
  723 + log.info("Build a test dark")
  724 + shutter_mode = "closed"
  725 + self._ima.simulation("ASTROTABLE", att, shutter_mode=shutter_mode, t=t, ra=ra, dec=dec)
  726 + param = {}
  727 + param['ftype'] = "DA1"
  728 + param['date'] = datedict['yyyymmdd']
  729 + param['time'] = datedict['hhmmssssssss']
  730 + param['unit'] = self.config.unit_name
  731 + param['version'] = 1
  732 + param['channel'] = self._channel['symbol']
  733 + param['id_seq'] = id_sequence - 2
  734 + param['plane'] = 1
  735 + param['filter'] = filter_symbol
  736 + fname = self._ima.naming_set(param)
  737 + file_out = self._ima.join(fname)
  738 + log.info(f"{file_out=}")
  739 + self._ima.setkwd("DATE-OBS", date_obs)
  740 + self._ima.setkwd("FILTER", filter_symbol)
  741 + self._ima.save(file_out)
  742 +
  743 + # --- Build a flat image normalized by cst=10000
  744 + log.info("Build a test flat")
  745 + shutter_mode = "synchro"
  746 + self._ima.etc.params("msky",-1)
  747 + tf = t/1000000.0
  748 + #tf = t
  749 + self._ima.simulation("ASTROTABLE", att, shutter_mode=shutter_mode, t=tf, ra=ra, dec=dec)
  750 + log.info(f"MEAN={np.mean(self._ima._array)}")
  751 + self._ima.offset(-bias_level)
  752 + flatnorm = 10000.0
  753 + self._ima.ngain(flatnorm)
  754 + self._ima.setkwd("FLATNORM", flatnorm, "Normalisation of the superflat")
  755 + param = {}
  756 + param['ftype'] = "FL1"
  757 + param['date'] = datedict['yyyymmdd']
  758 + param['time'] = datedict['hhmmssssssss']
  759 + param['unit'] = self.config.unit_name
  760 + param['version'] = 1
  761 + param['channel'] = self._channel['symbol']
  762 + param['id_seq'] = id_sequence - 1
  763 + param['plane'] = 1
  764 + param['filter'] = filter_symbol
  765 + fname = self._ima.naming_set(param)
  766 + file_out = self._ima.join(fname)
  767 + log.info(f"{file_out=}")
  768 + self._ima.setkwd("DATE-OBS", date_obs)
  769 + self._ima.setkwd("FILTER", filter_symbol)
  770 + self._ima.save(file_out)
  771 +
367 772  
368 773 def _plural(self, n: int) -> str:
369 774 """Return "s" if n>1 for plurals.
... ... @@ -382,7 +787,6 @@ class A_ImgProcessor(Agent):
382 787  
383 788 if __name__ == "__main__":
384 789 args = parse_args(sys.argv[1:])
385   - #args = vars(parser.parse_args())
386 790 agent = build_agent(A_ImgProcessor, param_constr=args)
387 791 print(agent)
388 792 agent.run()
... ...