Commit 6a4ed2c2c06ecfe730948620a9abed2d75c3bd2d

Authored by Alain Klotz
1 parent 1cff4249
Exists in dev

Start the AgentImagesCalibrator coding.

privatedev/config/tnc/observatory_tnc.yml
... ... @@ -322,6 +322,11 @@ OBSERVATORY:
322 322 computer: MainComputer
323 323 protocol: private/plugin/agent/AgentScheduler.py
324 324  
  325 + # SF10-CAL (for AKz)
  326 + - AGENT:
  327 + name: AgentImagesCalibrator
  328 + computer: MainComputer
  329 +
325 330 # SF11-IPC (for AKz)
326 331 - AGENT:
327 332 name: AgentImagesProcessor
... ...
pyros.py
... ... @@ -61,6 +61,7 @@ AGENTS = {
61 61 "AgentSP": "scientific_programs",
62 62 "AgentScheduler": "scheduler",
63 63 "AgentImagesProcessor": "observation_manager",
  64 + "AgentImagesCalibrator": "observation_manager",
64 65 "agentSST": "agent",
65 66 "Agent": "agent",
66 67 "Agent2": "agent",
... ...
src/core/pyros_django/obsconfig/obsconfig_class.py
... ... @@ -33,8 +33,8 @@ class OBSConfig:
33 33 "AgentM":None
34 34  
35 35 }
36   -
37   -
  36 +
  37 +
38 38 def verify_if_pickle_needs_to_be_updated(self, observatory_config_file) -> bool:
39 39 """
40 40  
... ... @@ -159,7 +159,7 @@ class OBSConfig:
159 159  
160 160 Args:
161 161 yaml_file (str): Path to the config_file to be validated
162   - schema_file (str): Path to the schema file
  162 + schema_file (str): Path to the schema file
163 163  
164 164 Returns:
165 165 dict: dictionary of the config file (with values)
... ... @@ -195,7 +195,7 @@ class OBSConfig:
195 195  
196 196 Args:
197 197 yaml_file (str): Path to the config_file to be validated
198   - schema_file (str): Path to the schema file
  198 + schema_file (str): Path to the schema file
199 199  
200 200 Returns:
201 201 any: boolean (True) if the configuration is valid according the schema or a list of error otherwise
... ... @@ -333,7 +333,7 @@ class OBSConfig:
333 333  
334 334 def get_devices_names_and_file(self) -> dict:
335 335 """
336   - Return a dictionary giving the device file name by the device name
  336 + Return a dictionary giving the device file name by the device name
337 337 Returns:
338 338 dict: key is device name, value is file name
339 339 """
... ... @@ -549,7 +549,7 @@ class OBSConfig:
549 549 self.unit_name = self.get_units_name()[0]
550 550 else:
551 551 self.unit_name = unit_name
552   - # call get_agents so the class will check if mandatory agents are in the obsconfig
  552 + # call get_agents so the class will check if mandatory agents are in the obsconfig
553 553 self.get_agents(self.unit_name)
554 554  
555 555 def get_obs_name(self) -> str:
... ... @@ -630,7 +630,7 @@ class OBSConfig:
630 630  
631 631 def get_agents(self, unit_name) -> dict:
632 632 """
633   - return dictionary of agents
  633 + return dictionary of agents
634 634  
635 635 Args:
636 636 unit_name (str): name of the unit
... ... @@ -695,7 +695,7 @@ class OBSConfig:
695 695 Return information of the given channel name of a unit
696 696  
697 697 Args:
698   - unit_name (str): Name of the unit
  698 + unit_name (str): Name of the unit
699 699 channel_name (str): name of the channel
700 700  
701 701 Returns:
... ... @@ -727,6 +727,32 @@ class OBSConfig:
727 727 topology[key] = branch
728 728 return topology
729 729  
  730 + def get_image_calibrations(self, unit_name: str, channel_name: str, category: str) -> dict:
  731 + """
  732 + Return dictionary of image calibrations
  733 +
  734 + Args:
  735 + unit_name (str): name of the unit
  736 + channel_name (str): name of the channel
  737 + category (str): category name of image calibration (BI, DA, FL)
  738 +
  739 + Returns:
  740 + dict: dictionary of image calibrations
  741 + """
  742 + unit = self.get_unit_by_name(unit_name)
  743 + info = {}
  744 + info["image_calibrations"] = {}
  745 + for kserie in range(len(unit["IMAGE_CALIBRATIONS"]["SERIES"])):
  746 + scategory = unit["IMAGE_CALIBRATIONS"]["SERIES"][kserie]["category"]
  747 + if category == scategory:
  748 + for kchannel in range(len(unit["IMAGE_CALIBRATIONS"]["SERIES"][kserie]["CHANNELS"])):
  749 + schannel = unit["IMAGE_CALIBRATIONS"]["SERIES"][kserie]["CHANNELS"][kchannel]["name"]
  750 + if channel_name == schannel:
  751 + dico = unit["IMAGE_CALIBRATIONS"]["SERIES"][kserie]["CHANNELS"][kchannel]
  752 + for key, val in dico:
  753 + info[key] = val
  754 + return info
  755 +
730 756 def get_active_agents(self, unit_name: str) -> list:
731 757 """
732 758 Return the list of active agents (i.e. agents that have an association with a device)
... ... @@ -780,7 +806,7 @@ class OBSConfig:
780 806  
781 807 def get_units_name(self) -> list:
782 808 """
783   - Return list of units names
  809 + Return list of units names
784 810  
785 811 Returns:
786 812 [list]: names of units
... ... @@ -890,7 +916,7 @@ class OBSConfig:
890 916  
891 917 def get_device_information(self, device_name: str) -> dict:
892 918 """
893   - Give the dictionary of the attributes of the device
  919 + Give the dictionary of the attributes of the device
894 920  
895 921 Args:
896 922 device_name (str): device name
... ... @@ -1144,7 +1170,7 @@ class OBSConfig:
1144 1170 # default with docker should be /home/pyros_user/app
1145 1171 path_data_root = os.environ['PROJECT_ROOT_PATH']
1146 1172 return path_data_root
1147   -
  1173 +
1148 1174 def get_agent_path_data_tree(self, agent_name:str, makedirs: bool=False) -> dict:
1149 1175 """
1150 1176 Return a dictionary containing all paths to store data.
... ... @@ -1171,11 +1197,38 @@ class OBSConfig:
1171 1197 for path in data_paths.values():
1172 1198 os.makedirs(path, exist_ok = True)
1173 1199 return data_paths
1174   -
  1200 +
  1201 + def get_image_calibrations(self, unit_name: str) -> dict:
  1202 + """
  1203 + Return a dictionary containing all strategies of image calibration.
  1204 +
  1205 + Args:
  1206 + agent_name (str): _description_
  1207 +
  1208 + Returns:
  1209 + All
  1210 +
  1211 + """
  1212 + path_data_root = self.get_agent_path_data_root(agent_name)
  1213 + data_paths = {}
  1214 + data_paths['data_root'] = path_data_root
  1215 + data_paths['ima_incoming'] = os.path.join(path_data_root,"data/images/incoming")
  1216 + data_paths['ima_processed'] = os.path.join(path_data_root,"data/images/processed")
  1217 + data_paths['ima_tmp'] = os.path.join(path_data_root,"data/images/tmp")
  1218 + data_paths['ima_darks'] = os.path.join(path_data_root,"data/images/darks")
  1219 + data_paths['ima_flats'] = os.path.join(path_data_root,"data/images/flats")
  1220 + data_paths['ima_bias'] = os.path.join(path_data_root,"data/images/bias")
  1221 + data_paths['triton_input'] = os.path.join(path_data_root,"data/triton/input")
  1222 + data_paths['triton_output'] = os.path.join(path_data_root,"data/triton/out")
  1223 + if makedirs == True:
  1224 + for path in data_paths.values():
  1225 + os.makedirs(path, exist_ok = True)
  1226 + return data_paths
  1227 +
1175 1228 def get_agent_sst_of_computer(self,computer:str)->str:
1176 1229 """
1177 1230 Return agent SST config name
1178   - The agentSST's name in obsconfig must contains AgentSST
  1231 + The agentSST's name in obsconfig must contains AgentSST
1179 1232  
1180 1233 Args:
1181 1234 computer (str): _description_
... ... @@ -1196,7 +1249,7 @@ class OBSConfig:
1196 1249 if agent.startswith(base_agent_name):
1197 1250 return agent
1198 1251 except:
1199   - # do nothing, only goes to in except condition when launching pyros tests because we're simulating obsconfig
  1252 + # do nothing, only goes to in except condition when launching pyros tests because we're simulating obsconfig
1200 1253 pass
1201 1254  
1202 1255 def get_dependencies(self):
... ...
src/core/pyros_django/observation_manager/AgentImagesCalibrator.py 0 โ†’ 100644
... ... @@ -0,0 +1,333 @@
  1 +#!/usr/bin/env python3
  2 +#
  3 +# To launch this agent from the root of Pyros:
  4 +# cd /srv/develop/pyros
  5 +# .\PYROS -t start AgentImagesCalibrator -o tnc -fg
  6 +#
  7 +# Edit the file pyros/pyros.py,
  8 +# Add a new entry in the dict AGENT:
  9 +# "AgentImagesCalibrator": "observation_manager",
  10 +# ---------------------------------------------------
  11 +
  12 +import sys
  13 +import time
  14 +
  15 +import os
  16 +pwd = os.environ['PROJECT_ROOT_PATH']
  17 +if pwd not in sys.path:
  18 + sys.path.append(pwd)
  19 +
  20 +short_paths = ['src', 'src/core/pyros_django']
  21 +for short_path in short_paths:
  22 + path = os.path.join(pwd, short_path)
  23 + if path not in sys.path:
  24 + sys.path.insert(0, path)
  25 +
  26 +from src.core.pyros_django.agent.Agent import Agent, build_agent, log
  27 +
  28 +# = Specials
  29 +import glob
  30 +import shutil
  31 +import guitastro
  32 +
  33 +class AgentImagesCalibrator(Agent):
  34 +
  35 + # - All possible running states
  36 + RUNNING_NOTHING = 0
  37 + RUNNING_ONE_IMAGE_PROCESSING = 1
  38 + RUNNING_COMPUTE_RON_GAIN = 2
  39 +
  40 + # TODO: Redefine valid timeout
  41 + _AGENT_SPECIFIC_COMMANDS = [
  42 + ("do_create_test_images_1",60, 0), # self.EXEC_MODE.SEQUENTIAL
  43 + ("do_create_test_images_2",60, 0), # self.EXEC_MODE.THREAD
  44 + ("do_stop_current_processing",60, 0), # self.EXEC_MODE.PROCESS
  45 + ]
  46 +
  47 + # Scenario to be executed
  48 + # "self do_stop_current_processing"
  49 + # AgentCmd.CMD_STATUS_CODE.CMD_EXECUTED
  50 + _TEST_COMMANDS_LIST = [
  51 + # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status),
  52 + (True, "self do_create_test_images_1", 200, '', Agent.CMD_STATUS.CMD_EXECUTED),
  53 + (True, "self do_exit", 500, "STOPPING", Agent.CMD_STATUS.CMD_EXECUTED),
  54 + ]
  55 +
  56 + """
  57 + =================================================================
  58 + Methods running inside main thread
  59 + =================================================================
  60 + """
  61 +
  62 + def __init__(self, name:str=None):
  63 + if name is None:
  64 + name = self.__class__.__name__
  65 + super().__init__()
  66 +
  67 + def _init(self):
  68 + super()._init()
  69 + log.debug("end super init()")
  70 + log.info(f"self.TEST_MODE = {self.TEST_MODE}")
  71 +
  72 + # === Get config infos
  73 + agent_alias = self.name
  74 + log.info(f"agent_alias = {agent_alias}")
  75 + # === Get the config object
  76 + self.config = self._oc['config']
  77 + # === Get self._path_data_root
  78 + self._path_data_root = self.config.get_agent_path_data_root(agent_alias)
  79 + # === Get self._home of current unit
  80 + self._home = self.config.getHome()
  81 + # === Get self._paths the directories for all data (images). See obsconfig_class.py to know keys
  82 + self._paths = self.config.get_agent_path_data_tree(agent_alias, True)
  83 + # === Get bias strategies
  84 + unit_name = "TNC"
  85 + channel_name = "OpticalChannel_up1"
  86 + category = "BI"
  87 + self_strategy_bias = self.config.get_image_calibrations(unit_name, channel_name, category)
  88 + log.debug(f"self_strategy_bias={self_strategy_bias}")
  89 +
  90 + # === Instanciate an object Ima to make image processing
  91 + self._ima = guitastro.Ima()
  92 + home = guitastro.Home(self._home)
  93 +
  94 + # === Instanciate an object Filenames to manage file names
  95 + self._filename_manager = guitastro.Filenames()
  96 + self._filename_manager.naming("PyROS.1")
  97 +
  98 + # === Set longitude to ima object to generate the night yyyymmdd and subdirectories yyyy/mm/dd
  99 + longitude = home.longitude
  100 + log.info(f"Longitude={longitude}")
  101 + self._ima.longitude(longitude)
  102 + log.info("Init done with success")
  103 +
  104 + # === Status of routine processing
  105 + self._routine_running = self.RUNNING_NOTHING
  106 + log.debug("end init()")
  107 +
  108 + # Note : called by _routine_process() in Agent
  109 + # @override
  110 + def _routine_process_iter_start_body(self):
  111 + log.debug("in routine_process_before_body()")
  112 +
  113 + # Note : called by _routine_process() in Agent
  114 + # @override
  115 + def _routine_process_iter_end_body(self):
  116 + log.debug("in routine_process_after_body()")
  117 + if self._routine_running == self.RUNNING_NOTHING:
  118 + # Get files to process
  119 + fitsfiles = self.glob_images_to_process()
  120 + n = len(fitsfiles)
  121 + log.info(f"There are {n} image{self._plural(n)} to process")
  122 + if n > 0:
  123 + # - We select the oldest image
  124 + fitsfile = fitsfiles[0]
  125 + log.info(f"Process the file {fitsfile}")
  126 + # - Thread TODO
  127 + self._routine_running = self.RUNNING_ONE_IMAGE_PROCESSING
  128 + self.process_one_image(fitsfile)
  129 +
  130 + """
  131 + =================================================================
  132 + Methods of specific commands
  133 + =================================================================
  134 + """
  135 +
  136 + def do_stop_current_processing(self):
  137 + pass
  138 +
  139 + def do_create_test_images_1(self):
  140 + self._create_test_images_1()
  141 +
  142 + def do_create_test_images_2(self):
  143 + self._create_test_images_2()
  144 +
  145 + """
  146 + =================================================================
  147 + Methods called by commands or routine. Overload these methods
  148 + =================================================================
  149 + """
  150 +
  151 + def glob_images_to_process(self):
  152 +
  153 + # - glob the incoming directory:
  154 + fitsfiles = glob.glob(f"{self._paths['ima_incoming']}/BI_*.fit")
  155 + # - Please sort list of files in increasing dates (TODO)
  156 + return fitsfiles
  157 +
  158 + def bias_correction(self):
  159 +
  160 + # - Search the bias
  161 + path_bias = os.path.join( self._paths['ima_bias'], self._date_night )
  162 + fitsbiasfiles = glob.glob(f"{path_bias}/*.fit")
  163 + log.info(f"fitsbiasfiles = {fitsbiasfiles}")
  164 + if len(fitsbiasfiles) > 0:
  165 +
  166 + # - Select the bias
  167 + pass
  168 +
  169 + def dark_correction(self):
  170 +
  171 + # - Search the dark
  172 + path_darks = os.path.join( self._paths['ima_darks'], self._date_night )
  173 + fitsdarkfiles = glob.glob(f"{path_darks}/*.fit")
  174 + log.info(f"fitsdarkfiles = {fitsdarkfiles}")
  175 + if len(fitsdarkfiles) > 0:
  176 +
  177 + # - Select two darks and compute the therm using exposure
  178 + # - Correction of dark
  179 + pass
  180 +
  181 + def flat_correction(self):
  182 +
  183 + # - Search the flat
  184 + path_flats = os.path.join( self._paths['ima_flats'], self._date_night )
  185 + fitsflatfiles = glob.glob(f"{path_flats}/*.fit")
  186 + log.info(f"fitsflatfiles = {fitsflatfiles}")
  187 + if len(fitsflatfiles) > 0:
  188 +
  189 + # - Select the flat (with the filter)
  190 + # - Correction of flat
  191 + pass
  192 +
  193 + def inversion_correction(self):
  194 + pass
  195 +
  196 + def cosmetic_correction(self):
  197 + pass
  198 +
  199 + def wcs_calibration(self):
  200 + return 0
  201 +
  202 + def process_one_image(self, fitsfile: str):
  203 + """This is the general algorithm of processing
  204 +
  205 + The processing consists to make corrections of dark, flat, inversions, cosmetic
  206 + and perform WCS calibration.
  207 +
  208 + Args:
  209 + fitsfile: The file of the FITS file to process.
  210 +
  211 + """
  212 +
  213 + # - Load file in memory
  214 + log.info("Load the file in memory")
  215 + #self.set_infos("Load the file in memory")
  216 + f = self._ima.genename(self._ima.load(fitsfile))
  217 + # log.info(f"f={f}")
  218 +
  219 + # - Save as tmp
  220 + self._ima.path(self._paths['ima_tmp'])
  221 + log.info("Save the temporary file as tmp name")
  222 + self._ima.save("tmp")
  223 +
  224 + # - Load tmp and get infos
  225 + self._ima.load("tmp")
  226 + date_obs = self._ima.getkwd("DATE-OBS")
  227 + self._date_night = self._ima.get_night(date_obs)
  228 + log.info(f"Date_obs = {date_obs}")
  229 + log.info(f"Night = {self._date_night}")
  230 + exposure = self._ima.getkwd("EXPOSURE")
  231 + log.info(f"Exposure = {exposure}")
  232 +
  233 + # - Bias correction
  234 + self.bias_correction()
  235 +
  236 + # - Dark correction
  237 + self.dark_correction()
  238 +
  239 + # - Flat correction
  240 + self.flat_correction()
  241 +
  242 + # - Save tmp corrected by dark and flat
  243 + self._ima.path(self._paths['ima_tmp'])
  244 + self._ima.save("tmp")
  245 +
  246 + # - Inversion of mirrors or mirorxy
  247 + self.inversion_correction()
  248 +
  249 + # - Cosmetic correction
  250 + self.cosmetic_correction()
  251 +
  252 + # - WCS calibration
  253 + nmatched = self.wcs_calibration()
  254 +
  255 + # - Prepare the output file name
  256 + log.info("Decode the filename")
  257 + fgen_in = f['genename'] + f['sep'] + f['indexes'][0] + f['suffix']
  258 + fext_in = f['file_extension']
  259 + fext_out = ".fits"
  260 +
  261 + # - Save in processed
  262 + yyyy = self._date_night[0:4]
  263 + mm = self._date_night[4:6]
  264 + dd = self._date_night[6:8]
  265 + path_processed = os.path.join( self._paths['ima_processed'], yyyy, mm, dd )
  266 + self._ima.path(path_processed)
  267 + fname_out = fgen_in + fext_out
  268 + fname = self._ima.save(fname_out)
  269 + log.info(f"Save the processed image {fname}")
  270 +
  271 + # - Delete the file in incoming directory
  272 + os.remove(fitsfile)
  273 + log.info(f"Delete the raw image {fitsfile}")
  274 +
  275 + # - Update the running state
  276 + self._routine_running = self.RUNNING_NOTHING
  277 +
  278 + time.sleep(5)
  279 + print("\n ...End of image calibration\n")
  280 +
  281 + """
  282 + =================================================================
  283 + Internal methods
  284 + =================================================================
  285 + """
  286 +
  287 + def _create_test_images_1(self):
  288 + try:
  289 + # === Define an image to test the processing and copy it in incoming directory
  290 + self._file_ima_test = os.path.join(self._path_data_root,"vendor/guitastro/tests/data/m57.fit")
  291 + file_in = self._file_ima_test
  292 + file_out = f"{self._paths['ima_incoming']}/m57.fit"
  293 + shutil.copyfile(file_in, file_out)
  294 + self._filename_manager.naming("")
  295 + except:
  296 + raise
  297 +
  298 + def _create_test_images_2(self):
  299 + try:
  300 + self._ima.etc.camera("Kepler 4040")
  301 + self._ima.etc.optics("Takahashi_180ED")
  302 + self._ima.etc.params("msky",18)
  303 + ra = 132.84583
  304 + dec = 11.81333
  305 + at = self._ima.simulation("GAIA", "PHOTOM", shutter_mode="closed", t=50)
  306 + file_out = os.path.join(self._paths['ima_tmp'], "m67.ecsv")
  307 + print(f"STEP TOTO 1 = {at}")
  308 + at.t.write(file_out, format='astrotable', overwrite=True)
  309 + print(f"STEP TOTO 2")
  310 + date_obs = self.getkwd("DATE-OBS")
  311 + except:
  312 + raise
  313 +
  314 + def _plural(self, n: int) -> str:
  315 + """Return "s" if n>1 for plurals.
  316 +
  317 + Args:
  318 + n: Number of entities
  319 +
  320 + Returns:
  321 + The string "s" or ""
  322 + """
  323 + if n > 1:
  324 + s = "s"
  325 + else:
  326 + s = ""
  327 + return s
  328 +
  329 +if __name__ == "__main__":
  330 +
  331 + agent = build_agent(AgentImagesCalibrator)
  332 + print(agent)
  333 + agent.run()
... ...
src/core/pyros_django/observation_manager/AgentImagesProcessor.py
... ... @@ -2,7 +2,7 @@
2 2 #
3 3 # To launch this agent from the root of Pyros:
4 4 # cd /srv/develop/pyros
5   -# .\PYROS -t start agentImagesProcessor -o tnc -fg
  5 +# .\PYROS -t start AgentImagesProcessor -o tnc -fg
6 6 #
7 7 # ---------------------------------------------------
8 8  
... ...