plato-fits 0.11.5__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plato-fits
3
- Version: 0.11.5
3
+ Version: 0.12.0
4
4
  Summary: FITS Persistence implementation for CGSE
5
5
  Author: IvS KU Leuven
6
6
  Maintainer-email: Rik Huygen <rik.huygen@kuleuven.be>, Sara Regibo <sara.regibo@kuleuven.be>
7
7
  License-Expression: MIT
8
8
  Keywords: CGSE,Common-EGSE,hardware testing,software framework
9
- Requires-Python: >=3.9
9
+ Requires-Python: >=3.10
10
10
  Requires-Dist: astropy>=6.0.1
11
11
  Requires-Dist: cgse-common
12
12
  Requires-Dist: plato-spw
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plato-fits"
3
- version = "0.11.5"
3
+ version = "0.12.0"
4
4
  description = "FITS Persistence implementation for CGSE"
5
5
  authors = [
6
6
  {name = "IvS KU Leuven"}
@@ -10,7 +10,7 @@ maintainers = [
10
10
  {name = "Sara Regibo", email = "sara.regibo@kuleuven.be"}
11
11
  ]
12
12
  readme = {"file" = "README.md", "content-type" = "text/markdown"}
13
- requires-python = ">=3.9"
13
+ requires-python = ">=3.10"
14
14
  license = "MIT"
15
15
  keywords = [
16
16
  "CGSE",
@@ -20,19 +20,20 @@ CCD_SETTINGS = Settings.load("CCD")
20
20
 
21
21
 
22
22
  class FITSPersistenceError(Exception):
23
- """ Error for the FITS persistence layer."""
23
+ """Error for the FITS persistence layer."""
24
+
24
25
  pass
25
26
 
26
27
 
27
28
  class FITS(PersistenceLayer):
28
- """ Persistence layer that saves (image) data in a FITS file."""
29
+ """Persistence layer that saves (image) data in a FITS file."""
29
30
 
30
31
  extension = "fits"
31
32
 
32
- warnings.simplefilter('ignore', category=AstropyWarning)
33
+ warnings.simplefilter("ignore", category=AstropyWarning)
33
34
 
34
35
  def __init__(self, filename: str, prep: dict):
35
- """ Initialisation of the FITS persistence layer.
36
+ """Initialisation of the FITS persistence layer.
36
37
 
37
38
  This consists of the following steps:
38
39
 
@@ -89,15 +90,17 @@ class FITS(PersistenceLayer):
89
90
  self.data_arrays = {}
90
91
  self.init_data_arrays()
91
92
 
92
- self.frame_number = {ccd: {self.fee_side.LEFT_SIDE: 0, self.fee_side.RIGHT_SIDE: 0} for ccd in self.selected_ccds}
93
+ self.frame_number = {
94
+ ccd: {self.fee_side.LEFT_SIDE: 0, self.fee_side.RIGHT_SIDE: 0} for ccd in self.selected_ccds
95
+ }
93
96
  self.timestamp = None
94
97
  self.finetime = None
95
98
 
96
99
  # How can you know whether or not all data for a given CCD has been received?
97
100
  # (this depends on whether or not there is parallel over-scan data and on which CCD side(s) will be transmitted)
98
101
 
99
- self.expected_last_packet_flags = prep["expected_last_packet_flags"] # To be received
100
- self.received_last_packet_flags = {ccd: [False] * 4 for ccd in self.selected_ccds} # Actually received
102
+ self.expected_last_packet_flags = prep["expected_last_packet_flags"] # To be received
103
+ self.received_last_packet_flags = {ccd: [False] * 4 for ccd in self.selected_ccds} # Actually received
101
104
 
102
105
  LOGGER.debug(f"Init last packets flag: {self.received_last_packet_flags}")
103
106
 
@@ -112,28 +115,27 @@ class FITS(PersistenceLayer):
112
115
 
113
116
  # Define the values of the WCS keywords
114
117
 
115
-
116
- self.v_start = prep["v_start"] # First transmitted row
117
- self.v_end = prep["v_end"] # Last transmitted row
118
- self.h_end = prep["h_end"] # Last transmitted column
119
- self.rows_final_dump = prep["rows_final_dump"] # Number of rows to be dumped after readout
120
- self.cycle_time = prep["cycle_time"] # Image cycle time [s]
121
- self.cgse_version = prep["cgse_version"] # Version of the Common EGSE
118
+ self.v_start = prep["v_start"] # First transmitted row
119
+ self.v_end = prep["v_end"] # Last transmitted row
120
+ self.h_end = prep["h_end"] # Last transmitted column
121
+ self.rows_final_dump = prep["rows_final_dump"] # Number of rows to be dumped after readout
122
+ self.cycle_time = prep["cycle_time"] # Image cycle time [s]
123
+ self.cgse_version = prep["cgse_version"] # Version of the Common EGSE
122
124
  self.obsid = prep["obsid"]
123
125
 
124
- self.register_map = prep["register_map"] # Register map
126
+ self.register_map = prep["register_map"] # Register map
125
127
 
126
128
  # Read information from the setup
127
129
 
128
130
  self.setup: Setup = prep["setup"]
129
131
 
130
- self.site_name = self.setup["site_id"] # Site ID
131
- self.setup_id = self.setup.get_id() # Setup ID
132
- self.camera_id = self.setup.camera.get("ID") # Camera ID (None if not present in the setup)
132
+ self.site_name = self.setup["site_id"] # Site ID
133
+ self.setup_id = self.setup.get_id() # Setup ID
134
+ self.camera_id = self.setup.camera.get("ID") # Camera ID (None if not present in the setup)
133
135
 
134
- self.readout_time = self.calculate_readout_time(self.setup) # Readout time [s]
136
+ self.readout_time = self.calculate_readout_time(self.setup) # Readout time [s]
135
137
  try:
136
- self.exposure_time = self.cycle_time - self.readout_time # Exposure time [s]
138
+ self.exposure_time = self.cycle_time - self.readout_time # Exposure time [s]
137
139
  except TypeError:
138
140
  # Image cycle time is unknown (None)
139
141
  self.exposure_time = None
@@ -152,7 +154,7 @@ class FITS(PersistenceLayer):
152
154
  self.is_fits_file_open = False
153
155
 
154
156
  def calculate_readout_time(self, setup: Setup):
155
- """ Calculate the readout time.
157
+ """Calculate the readout time.
156
158
 
157
159
  The readout time consists of:
158
160
 
@@ -174,13 +176,13 @@ class FITS(PersistenceLayer):
174
176
  return time_read_rows + time_dump_rows
175
177
 
176
178
  def get_vgd(self):
177
- """ Extract the VGD voltage from the register map.
179
+ """Extract the VGD voltage from the register map.
178
180
 
179
181
  Return: Configured VGD voltage [V].
180
182
  """
181
183
 
182
- vgd_19 = self.register_map[('reg_19_config', 'ccd_vgd_config')]
183
- vgd_20 = self.register_map[('reg_20_config', 'ccd_vgd_config')]
184
+ vgd_19 = self.register_map[("reg_19_config", "ccd_vgd_config")]
185
+ vgd_20 = self.register_map[("reg_20_config", "ccd_vgd_config")]
184
186
 
185
187
  return ((vgd_20 << 4) + vgd_19) / 1000 * 5.983
186
188
 
@@ -192,11 +194,10 @@ class FITS(PersistenceLayer):
192
194
 
193
195
  Return: Configured VRD voltage.
194
196
  """
195
- vrd_18 = self.register_map[('reg_18_config', 'ccd2_vrd_config')]
196
- vrd_19 = self.register_map[('reg_19_config', 'ccd2_vrd_config')]
197
-
198
- return int(f'0x{vrd_19:x}{vrd_18:x}', 16)
197
+ vrd_18 = self.register_map[("reg_18_config", "ccd2_vrd_config")]
198
+ vrd_19 = self.register_map[("reg_19_config", "ccd2_vrd_config")]
199
199
 
200
+ return int(f"0x{vrd_19:x}{vrd_18:x}", 16)
200
201
 
201
202
  def get_ccd3_vrd(self):
202
203
  """
@@ -206,14 +207,13 @@ class FITS(PersistenceLayer):
206
207
 
207
208
  Return: Configured VRD voltage.
208
209
  """
209
- vrd_18 = self.register_map[('reg_18_config', 'ccd3_vrd_config')]
210
- vrd_19 = self.register_map[('reg_19_config', 'ccd3_vrd_config')]
211
-
212
- return int(f'0x{vrd_19:x}{vrd_18:x}', 16)
210
+ vrd_18 = self.register_map[("reg_18_config", "ccd3_vrd_config")]
211
+ vrd_19 = self.register_map[("reg_19_config", "ccd3_vrd_config")]
213
212
 
213
+ return int(f"0x{vrd_19:x}{vrd_18:x}", 16)
214
214
 
215
215
  def init_data_arrays(self):
216
- """ Initialise data arrays in which the data content of the SpW packets will be dumped.
216
+ """Initialise data arrays in which the data content of the SpW packets will be dumped.
217
217
 
218
218
  At this point, we have already determined which side(s) of which CCD(s) will be read out. For each of them,
219
219
  a placeholder will be foreseen in a dedicated dictionary. The structure of this dictionary is the following,
@@ -241,7 +241,7 @@ class FITS(PersistenceLayer):
241
241
  self.data_arrays[self.fee_side.RIGHT_SIDE] = right_side_arrays
242
242
 
243
243
  def clear_for_next_exposure(self, ccd_number: int, ccd_side):
244
- """ Indicate that no data has been received yet for the next exposure of the given CCD.
244
+ """Indicate that no data has been received yet for the next exposure of the given CCD.
245
245
 
246
246
  At the end of an exposure, when the data have been assembled and written to FITS:
247
247
 
@@ -260,7 +260,7 @@ class FITS(PersistenceLayer):
260
260
  self.clear_last_packet_received(ccd_number)
261
261
 
262
262
  def clear_data_arrays(self, ccd_number: int, ccd_side):
263
- """ Clear the data arrays for the given CCD.
263
+ """Clear the data arrays for the given CCD.
264
264
 
265
265
  At the end of an exposure, when the data have been assembled and written to FITS, the data arrays must be
266
266
  cleared for the next exposure.
@@ -272,7 +272,7 @@ class FITS(PersistenceLayer):
272
272
  self.data_arrays[ccd_side][ccd_number] = np.array([], dtype=np.uint16)
273
273
 
274
274
  def clear_last_packet_received(self, ccd_number: int):
275
- """ Clear the information about the last packet being received for the given CCD.
275
+ """Clear the information about the last packet being received for the given CCD.
276
276
 
277
277
  At the end of an exposure, when the data have been assembled and written to FITS, it must be indicated that the
278
278
  last packet has not been received for the next exposure for the given CCD.
@@ -300,26 +300,30 @@ class FITS(PersistenceLayer):
300
300
  # return self.data_arrays[ccd_side][ccd_number]
301
301
 
302
302
  def check_readout_mode(self):
303
- """ For now only checks whether the N-FEE is in the correct mode, i.e. full-image (pattern) mode.
303
+ """For now only checks whether the N-FEE is in the correct mode, i.e. full-image (pattern) mode.
304
304
 
305
305
  In the future, if deemed necessary, windowing (pattern) mode may be implemented as well.
306
306
  """
307
307
 
308
- if self.ccd_mode_config in [n_fee_mode.FULL_IMAGE_MODE, n_fee_mode.FULL_IMAGE_PATTERN_MODE,
309
- n_fee_mode.PARALLEL_TRAP_PUMPING_1_MODE, n_fee_mode.PARALLEL_TRAP_PUMPING_2_MODE,
310
- n_fee_mode.SERIAL_TRAP_PUMPING_1_MODE, n_fee_mode.SERIAL_TRAP_PUMPING_2_MODE]:
311
-
308
+ if self.ccd_mode_config in [
309
+ n_fee_mode.FULL_IMAGE_MODE,
310
+ n_fee_mode.FULL_IMAGE_PATTERN_MODE,
311
+ n_fee_mode.PARALLEL_TRAP_PUMPING_1_MODE,
312
+ n_fee_mode.PARALLEL_TRAP_PUMPING_2_MODE,
313
+ n_fee_mode.SERIAL_TRAP_PUMPING_1_MODE,
314
+ n_fee_mode.SERIAL_TRAP_PUMPING_2_MODE,
315
+ ]:
312
316
  return False
313
317
 
314
318
  if self.ccd_mode_config in [n_fee_mode.WINDOWING_PATTERN_MODE, n_fee_mode.WINDOWING_MODE]:
315
-
316
319
  return True
317
320
 
318
321
  else:
319
-
320
- raise FITSPersistenceError("Construction of FITS files from SpW packets only implemented for full-image "
321
- "(pattern) mode, windowing (pattern) mode, and parallel/serial trap pumping 1/2 "
322
- "mode")
322
+ raise FITSPersistenceError(
323
+ "Construction of FITS files from SpW packets only implemented for full-image "
324
+ "(pattern) mode, windowing (pattern) mode, and parallel/serial trap pumping 1/2 "
325
+ "mode"
326
+ )
323
327
 
324
328
  # def init_window_list(self, window_list: dict):
325
329
  # """ Compile the window list.
@@ -399,14 +403,14 @@ class FITS(PersistenceLayer):
399
403
  # return window_indices
400
404
 
401
405
  def create_primary_header(self):
402
- """ Creates the primary header (i.e. the header of the primary HDU).
406
+ """Creates the primary header (i.e. the header of the primary HDU).
403
407
 
404
408
  This contains information that is specific for the camera.
405
409
  """
406
410
 
407
411
  primary_header = fits.PrimaryHDU().header
408
412
 
409
- primary_header["LEVEL"] = 1 # Flat structure
413
+ primary_header["LEVEL"] = 1 # Flat structure
410
414
 
411
415
  primary_header["V_START"] = (self.v_start, "Index of 1st row that is transmitted (counting starts at 0)")
412
416
  primary_header["V_END"] = (self.v_end, "Index of last row that is transmitted (counting starts at 0)")
@@ -414,38 +418,63 @@ class FITS(PersistenceLayer):
414
418
  primary_header["ROWS_FINAL_DUMP"] = (self.rows_final_dump, "Number of rows for clearout after readout")
415
419
  primary_header["READ_MODE"] = (n_fee_mode(self.ccd_mode_config).name, "N-FEE operating mode")
416
420
 
417
- primary_header["CI_WIDTH"] = (self.register_map["charge_injection_width"],
418
- "Number of rows in each charge injection region")
419
- primary_header["CI_GAP"] = (self.register_map["charge_injection_gap"],
420
- "Number of rows between charge injection regions")
421
- primary_header["PARALLEL_TOI_PERIOD"] = (self.register_map["parallel_toi_period"],
422
- "Duration of a parallel overlap period (TOI) [us]")
423
- primary_header["PARALLEL_CLK_OVERLAP"] = (self.register_map["parallel_clk_overlap"],
424
- "Extra parallel clock overlap [us]")
425
- primary_header["CI_EN"] = (self.register_map["charge_injection_width"],
426
- "Charge injection enabled (1) / disabled (0)")
427
- primary_header["TRI_LEVEL_CLK_EN"] = (self.register_map["tri_level_clk_en"],
428
- "Generating bi-level parallel clocks (0) / tri-level parallel clocks (1)")
429
- primary_header["IMG_CLK_DIR"] = (self.register_map["img_clk_dir"],
430
- "Generating reverse parallel clocks (1) / normal forward parallel clocks (0)")
431
- primary_header["REG_CLK_DIR"] = (self.register_map["reg_clk_dir"],
432
- "Generating reverse serial clocks (1) / normal forward serial clocks (0)")
433
- primary_header["PACKET_SIZE"] = (self.register_map["packet_size"],
434
- "Packet size = load bytes + 10 (header bytes)")
435
-
436
- primary_header["TRAP_PUMP_DWELL_CTR"] = (self.register_map["Trap_Pumping_Dwell_counter"],
437
- "Dwell timer for trap pumping [ns]")
421
+ primary_header["CI_WIDTH"] = (
422
+ self.register_map["charge_injection_width"],
423
+ "Number of rows in each charge injection region",
424
+ )
425
+ primary_header["CI_GAP"] = (
426
+ self.register_map["charge_injection_gap"],
427
+ "Number of rows between charge injection regions",
428
+ )
429
+ primary_header["PARALLEL_TOI_PERIOD"] = (
430
+ self.register_map["parallel_toi_period"],
431
+ "Duration of a parallel overlap period (TOI) [us]",
432
+ )
433
+ primary_header["PARALLEL_CLK_OVERLAP"] = (
434
+ self.register_map["parallel_clk_overlap"],
435
+ "Extra parallel clock overlap [us]",
436
+ )
437
+ primary_header["CI_EN"] = (
438
+ self.register_map["charge_injection_width"],
439
+ "Charge injection enabled (1) / disabled (0)",
440
+ )
441
+ primary_header["TRI_LEVEL_CLK_EN"] = (
442
+ self.register_map["tri_level_clk_en"],
443
+ "Generating bi-level parallel clocks (0) / tri-level parallel clocks (1)",
444
+ )
445
+ primary_header["IMG_CLK_DIR"] = (
446
+ self.register_map["img_clk_dir"],
447
+ "Generating reverse parallel clocks (1) / normal forward parallel clocks (0)",
448
+ )
449
+ primary_header["REG_CLK_DIR"] = (
450
+ self.register_map["reg_clk_dir"],
451
+ "Generating reverse serial clocks (1) / normal forward serial clocks (0)",
452
+ )
453
+ primary_header["PACKET_SIZE"] = (
454
+ self.register_map["packet_size"],
455
+ "Packet size = load bytes + 10 (header bytes)",
456
+ )
457
+
458
+ primary_header["TRAP_PUMP_DWELL_CTR"] = (
459
+ self.register_map["Trap_Pumping_Dwell_counter"],
460
+ "Dwell timer for trap pumping [ns]",
461
+ )
438
462
  primary_header["SENSOR_SEL"] = (self.register_map["sensor_sel"], "CCD port data transmission selection control")
439
463
  primary_header["SYNC_SEL"] = (self.register_map["sync_sel"], "Internal (1) / external (0) sync")
440
- primary_header["DIGITISE_EN"] = (self.register_map["digitise_en"],
441
- "Digitised data transferred to the N-DPU (1) or not (0) during image mode")
464
+ primary_header["DIGITISE_EN"] = (
465
+ self.register_map["digitise_en"],
466
+ "Digitised data transferred to the N-DPU (1) or not (0) during image mode",
467
+ )
442
468
  primary_header["DG_EN"] = (self.register_map["DG_en"], "Dump gate high (1) / low (0)")
443
469
  primary_header["CCD_READ_EN"] = (self.register_map["ccd_read_en"], "CCD readout enabled (1) / disabled (0)")
444
- primary_header["CONV_DLY"] = (self.register_map["conv_dly"],
445
- "Delay value from rising edge of CCD_R_EF_DRV (where ADC convert pulse "
446
- "is generated) [ns]")
447
- primary_header["HIGH_PRECISION_HK_EN"] = (self.register_map["High_precision_HK_en"],
448
- "Sending high-precision HK (1) / pixel data (0)")
470
+ primary_header["CONV_DLY"] = (
471
+ self.register_map["conv_dly"],
472
+ "Delay value from rising edge of CCD_R_EF_DRV (where ADC convert pulse is generated) [ns]",
473
+ )
474
+ primary_header["HIGH_PRECISION_HK_EN"] = (
475
+ self.register_map["High_precision_HK_en"],
476
+ "Sending high-precision HK (1) / pixel data (0)",
477
+ )
449
478
 
450
479
  primary_header["CCD_VOD"] = (self.register_map["ccd_vod_config"], "Configured VOD")
451
480
 
@@ -464,27 +493,37 @@ class FITS(PersistenceLayer):
464
493
  primary_header["CCD_IG_LO"] = (self.register_map["ccd_ig_lo_config"], "Configured IG-high")
465
494
  primary_header["TRK_HLD_HI"] = (self.register_map["trk_hld_hi"], "Track and hold high")
466
495
  primary_header["TRK_HLD_LO"] = (self.register_map["trk_hld_lo"], "Track and hold low")
467
- primary_header["CONT_RST_ON"] = (self.register_map["cont_rst_on"],
468
- "When 1, FPGA generates continuous reset clock during readout")
496
+ primary_header["CONT_RST_ON"] = (
497
+ self.register_map["cont_rst_on"],
498
+ "When 1, FPGA generates continuous reset clock during readout",
499
+ )
469
500
  if not PLATO_CAMERA_IS_EM:
470
- primary_header["CONT_CDSCLP_ON"] = (self.register_map["cont_cdsclp_on"],
471
- "When 1, FPGA generates continuous CDS clamp during readout")
472
- primary_header["CONT_ROWCLP_ON"] = (self.register_map["cont_rowclp_on"],
473
- "When 1, FPGA generates continuous row clamp during readout")
501
+ primary_header["CONT_CDSCLP_ON"] = (
502
+ self.register_map["cont_cdsclp_on"],
503
+ "When 1, FPGA generates continuous CDS clamp during readout",
504
+ )
505
+ primary_header["CONT_ROWCLP_ON"] = (
506
+ self.register_map["cont_rowclp_on"],
507
+ "When 1, FPGA generates continuous row clamp during readout",
508
+ )
474
509
  primary_header["R_CFG1"] = (self.register_map["r_cfg1"], "Clock cycle for Rph3-low, Rph1-high")
475
510
  primary_header["R_CFG2"] = (self.register_map["r_cfg2"], "Clock cycle for Rph1-low, Rph2-high")
476
511
  primary_header["CDSCLP_LO"] = (self.register_map["cdsclp_lo"], "Clock cycle for cdsclp low")
477
512
  if not PLATO_CAMERA_IS_EM:
478
- primary_header["ADC_PWRDN_EN"] = (self.register_map["adc_pwrdn_en"],
479
- "ADC power-down enabled (0) / disabled (1)")
513
+ primary_header["ADC_PWRDN_EN"] = (
514
+ self.register_map["adc_pwrdn_en"],
515
+ "ADC power-down enabled (0) / disabled (1)",
516
+ )
480
517
  primary_header["CDSCLP_HI"] = (self.register_map["cdsclp_hi"], "Clock cycle for cdsclp high")
481
518
  primary_header["ROWCLP_HI"] = (self.register_map["rowclp_hi"], "Clock cycle for rowclp high")
482
519
  primary_header["ROWCLP_LO"] = (self.register_map["rowclp_lo"], "Clock cycle for rowclp low")
483
520
  # primary_header["SURFACE_INV_CTR"] = (self.register_map["Surface_Inversion_counter"],
484
521
  # "Surface inversion counter")
485
522
  primary_header["READOUT_PAUSE_CTR"] = (self.register_map["Readout_pause_counter"], "Readout pause counter")
486
- primary_header["TRAP_PUMP_SHUFFLE_CTR"] = (self.register_map["Trap_Pumping_Shuffle_counter"],
487
- "Trap pumping shuffle counter")
523
+ primary_header["TRAP_PUMP_SHUFFLE_CTR"] = (
524
+ self.register_map["Trap_Pumping_Shuffle_counter"],
525
+ "Trap pumping shuffle counter",
526
+ )
488
527
 
489
528
  # primary_header["FOCALLEN"] = (FOV_SETTINGS["FOCAL_LENGTH"], "Focal length [mm]")
490
529
 
@@ -497,40 +536,50 @@ class FITS(PersistenceLayer):
497
536
  primary_header["SETUP"] = (self.setup_id, "Setup ID")
498
537
  primary_header["CCD_READOUT_ORDER"] = (str(self.ccd_readout_order), "Transmitted CCDs")
499
538
  primary_header["CYCLETIME"] = (self.cycle_time, "Image cycle time [s]")
500
- primary_header["READTIME"] = (self.readout_time,
501
- "Time needed to read out the requested part for a single CCD side [s]")
539
+ primary_header["READTIME"] = (
540
+ self.readout_time,
541
+ "Time needed to read out the requested part for a single CCD side [s]",
542
+ )
502
543
 
503
544
  if self.register_map["sync_sel"] == 1: # See https://github.com/IvS-KULeuven/plato-common-egse/issues/2475
504
- primary_header["READPERIOD"] = (self.cycle_time + INT_SYNC_TIMING_OFFSET, "Time between frames [s] "
505
- "(internal sync)")
545
+ primary_header["READPERIOD"] = (
546
+ self.cycle_time + INT_SYNC_TIMING_OFFSET,
547
+ "Time between frames [s] (internal sync)",
548
+ )
506
549
  texp_cmd = self.cycle_time - self.readout_time
507
550
  primary_header["TEXP_CMD"] = (texp_cmd, "Commanded exposure time [s] (internal sync)")
508
- primary_header["TEXP_EFF"] = (texp_cmd + INT_SYNC_TIMING_OFFSET, "Effective exposure time [s] (internal "
509
- "sync)")
551
+ primary_header["TEXP_EFF"] = (
552
+ texp_cmd + INT_SYNC_TIMING_OFFSET,
553
+ "Effective exposure time [s] (internal sync)",
554
+ )
510
555
 
511
556
  primary_header["CGSE"] = (self.cgse_version, "Version of the Common EGSE")
512
557
 
513
558
  LOGGER.info(f"Obsid in FITS persistence layer: {self.obsid}")
514
559
 
515
560
  if self.obsid is not None:
516
-
517
561
  LOGGER.debug(f"{self.obsid = }")
518
562
 
519
563
  primary_header["OBSID"] = (self.obsid, "Observation identifier")
520
564
 
521
- primary_header["PRE_SC_N"] = (CCD_SETTINGS.LENGTH_SERIAL_PRESCAN,
522
- "Number of pixels/columns in the serial pre-scan")
523
- primary_header["OVR_SC_N"] = (max(0, self.h_end + 1
524
- - CCD_SETTINGS.LENGTH_SERIAL_PRESCAN - CCD_SETTINGS.NUM_COLUMNS // 2),
525
- "Number of virtual pixels / columns in the serial over-scan")
526
- primary_header["OVR_SC_R"] = (max(0, self.v_end + 1 - CCD_SETTINGS.NUM_ROWS),
527
- "Number of rows in the parallel over-scan")
565
+ primary_header["PRE_SC_N"] = (
566
+ CCD_SETTINGS.LENGTH_SERIAL_PRESCAN,
567
+ "Number of pixels/columns in the serial pre-scan",
568
+ )
569
+ primary_header["OVR_SC_N"] = (
570
+ max(0, self.h_end + 1 - CCD_SETTINGS.LENGTH_SERIAL_PRESCAN - CCD_SETTINGS.NUM_COLUMNS // 2),
571
+ "Number of virtual pixels / columns in the serial over-scan",
572
+ )
573
+ primary_header["OVR_SC_R"] = (
574
+ max(0, self.v_end + 1 - CCD_SETTINGS.NUM_ROWS),
575
+ "Number of rows in the parallel over-scan",
576
+ )
528
577
  primary_header["IMG_REPR"] = ("FOV_IMG", "Right CCD side flipped w.r.t. readout direction")
529
578
 
530
579
  return primary_header
531
580
 
532
581
  def create_base_image_wcs(self):
533
- """ Create a basic FITS header for the image.
582
+ """Create a basic FITS header for the image.
534
583
 
535
584
  Not all information can be filled out at this point (i.c. extension, CCD identifier, number of columns,
536
585
  rotation, reference pixel). This will be done at the moment the serial pre-scan will be written to file.
@@ -553,29 +602,55 @@ class FITS(PersistenceLayer):
553
602
 
554
603
  num_rows = min(CCD_SETTINGS["NUM_ROWS"] - 1, self.v_end) - self.v_start + 1
555
604
 
556
- image_header["NAXIS"] = (2, "Dimensionality of the image",)
557
- image_header["NAXIS2"] = (num_rows, "Number of rows in the image",)
558
-
605
+ image_header["NAXIS"] = (
606
+ 2,
607
+ "Dimensionality of the image",
608
+ )
609
+ image_header["NAXIS2"] = (
610
+ num_rows,
611
+ "Number of rows in the image",
612
+ )
559
613
 
560
614
  # Focal length (this is needed for the conversion to field angles)
561
615
 
562
- image_header["FOCALLEN"] = (FOV_SETTINGS["FOCAL_LENGTH"], "Focal length [mm]",) # TODO
616
+ image_header["FOCALLEN"] = (
617
+ FOV_SETTINGS["FOCAL_LENGTH"],
618
+ "Focal length [mm]",
619
+ ) # TODO
563
620
 
564
621
  # Linear coordinate transformation from sub-field to focal-plane coordinates
565
622
 
566
- image_header["ctype1"] = ("LINEAR", "Linear coordinate transformation",)
567
- image_header["ctype2"] = ("LINEAR", "Linear coordinate transformation",)
623
+ image_header["ctype1"] = (
624
+ "LINEAR",
625
+ "Linear coordinate transformation",
626
+ )
627
+ image_header["ctype2"] = (
628
+ "LINEAR",
629
+ "Linear coordinate transformation",
630
+ )
568
631
 
569
632
  # Focal-plane coordinates are expressed in mm
570
633
 
571
- image_header["CUNIT1"] = ("MM", "Target unit in the column direction (mm)",)
572
- image_header["CUNIT2"] = ("MM", "Target unit in the row direction (mm)",)
634
+ image_header["CUNIT1"] = (
635
+ "MM",
636
+ "Target unit in the column direction (mm)",
637
+ )
638
+ image_header["CUNIT2"] = (
639
+ "MM",
640
+ "Target unit in the row direction (mm)",
641
+ )
573
642
 
574
643
  # Pixel size
575
644
 
576
645
  cdelt = CCD_SETTINGS["PIXEL_SIZE"] / 1000.0 # Pixel size [mm]
577
- image_header["CDELT1"] = (cdelt, "Pixel size in the x-direction [mm]",)
578
- image_header["CDELT2"] = (cdelt, "Pixel size in the y-direction [mm]",)
646
+ image_header["CDELT1"] = (
647
+ cdelt,
648
+ "Pixel size in the x-direction [mm]",
649
+ )
650
+ image_header["CDELT2"] = (
651
+ cdelt,
652
+ "Pixel size in the y-direction [mm]",
653
+ )
579
654
 
580
655
  # Additional keywords
581
656
 
@@ -593,7 +668,7 @@ class FITS(PersistenceLayer):
593
668
  return image_header
594
669
 
595
670
  def create_base_serial_prescan_wcs(self):
596
- """ Create a basic FITS header for the serial pre-scan.
671
+ """Create a basic FITS header for the serial pre-scan.
597
672
 
598
673
  Not all information can be filled out at this point (i.c. extension, CCD identifier). This will be
599
674
  done at the moment the serial pre-scan will be written to file.
@@ -616,9 +691,18 @@ class FITS(PersistenceLayer):
616
691
  num_rows = self.v_end - self.v_start + 1
617
692
  num_columns = CCD_SETTINGS.LENGTH_SERIAL_PRESCAN
618
693
 
619
- serial_prescan_wcs["NAXIS"] = (2, "Dimensionality of the serial pre-scan",)
620
- serial_prescan_wcs["NAXIS1"] = (num_columns, "Number of columns in the serial pre-scan",)
621
- serial_prescan_wcs["NAXIS2"] = (num_rows, "Number of rows in the serial pre-scan",)
694
+ serial_prescan_wcs["NAXIS"] = (
695
+ 2,
696
+ "Dimensionality of the serial pre-scan",
697
+ )
698
+ serial_prescan_wcs["NAXIS1"] = (
699
+ num_columns,
700
+ "Number of columns in the serial pre-scan",
701
+ )
702
+ serial_prescan_wcs["NAXIS2"] = (
703
+ num_rows,
704
+ "Number of rows in the serial pre-scan",
705
+ )
622
706
 
623
707
  serial_prescan_wcs["TELESCOP"] = "PLATO"
624
708
  if self.camera_id:
@@ -631,7 +715,7 @@ class FITS(PersistenceLayer):
631
715
  return serial_prescan_wcs
632
716
 
633
717
  def create_base_serial_overscan_wcs(self):
634
- """ Create a basic FITS header for the serial over-scan.
718
+ """Create a basic FITS header for the serial over-scan.
635
719
 
636
720
  Not all information can be filled out at this point (i.c. extension, CCD identifier). This will be
637
721
  done at the moment the serial over-scan will be written to file.
@@ -654,9 +738,18 @@ class FITS(PersistenceLayer):
654
738
  num_rows = self.v_end - self.v_start + 1
655
739
  num_columns = self.v_end + 1 - CCD_SETTINGS.LENGTH_SERIAL_PRESCAN - CCD_SETTINGS.NUM_COLUMNS / 2
656
740
 
657
- serial_overscan_wcs["NAXIS"] = (2, "Dimensionality of the serial over-scan",)
658
- serial_overscan_wcs["NAXIS1"] = (num_columns, "Number of columns in the serial over-scan",)
659
- serial_overscan_wcs["NAXIS2"] = (num_rows, "Number of rows in the serial over-scan",)
741
+ serial_overscan_wcs["NAXIS"] = (
742
+ 2,
743
+ "Dimensionality of the serial over-scan",
744
+ )
745
+ serial_overscan_wcs["NAXIS1"] = (
746
+ num_columns,
747
+ "Number of columns in the serial over-scan",
748
+ )
749
+ serial_overscan_wcs["NAXIS2"] = (
750
+ num_rows,
751
+ "Number of rows in the serial over-scan",
752
+ )
660
753
 
661
754
  # Site name
662
755
 
@@ -671,7 +764,7 @@ class FITS(PersistenceLayer):
671
764
  return serial_overscan_wcs
672
765
 
673
766
  def create_base_parallel_overscan_wcs(self):
674
- """ Create a basic FITS header for the parallel over-scan.
767
+ """Create a basic FITS header for the parallel over-scan.
675
768
 
676
769
  Not all information can be filled out at this point (i.c. extension, CCD identifier, number of columns). This
677
770
  will be done at the moment the parallel over-scan will be written to file.
@@ -694,8 +787,14 @@ class FITS(PersistenceLayer):
694
787
 
695
788
  num_rows_parallel_overscan = max(0, self.v_end - CCD_SETTINGS.NUM_ROWS + 1)
696
789
 
697
- parallel_overscan_wcs["NAXIS"] = (2, "Dimensionality of the parallel over-scan",)
698
- parallel_overscan_wcs["NAXIS2"] = (num_rows_parallel_overscan, "Number of rows in the parallel over-scan",)
790
+ parallel_overscan_wcs["NAXIS"] = (
791
+ 2,
792
+ "Dimensionality of the parallel over-scan",
793
+ )
794
+ parallel_overscan_wcs["NAXIS2"] = (
795
+ num_rows_parallel_overscan,
796
+ "Number of rows in the parallel over-scan",
797
+ )
699
798
 
700
799
  # Site name
701
800
 
@@ -710,7 +809,6 @@ class FITS(PersistenceLayer):
710
809
  return parallel_overscan_wcs
711
810
 
712
811
  def open(self, mode=None):
713
-
714
812
  primary_header = self.create_primary_header()
715
813
 
716
814
  # The primary HDU contains only this header and no image data
@@ -731,7 +829,6 @@ class FITS(PersistenceLayer):
731
829
  self.is_fits_file_open = False
732
830
 
733
831
  def exists(self):
734
-
735
832
  return self._filepath.exists()
736
833
 
737
834
  def create(self, data: dict):
@@ -744,24 +841,21 @@ class FITS(PersistenceLayer):
744
841
  """
745
842
 
746
843
  for key, spw_packet in data.items():
747
-
748
844
  # if isinstance(spw_packet, TimecodePacket):
749
845
  #
750
846
  # self.timestamp = spw_packet.
751
847
 
752
848
  if key == "Timestamp":
753
-
754
849
  self.timestamp = spw_packet
755
850
  self.finetime = time_since_epoch_1958(self.timestamp)
756
851
 
757
852
  elif isinstance(spw_packet, DataPacket):
758
-
759
853
  try:
760
854
  ccd_bin_to_id = self.setup.camera.fee.ccd_numbering.CCD_BIN_TO_ID
761
855
  except AttributeError:
762
856
  raise SetupError("No entry in the setup for camera.fee.ccd_numbering.CCD_BIN_TO_ID")
763
857
  spw_packet_data_type = spw_packet.type
764
- ccd_number = ccd_bin_to_id[spw_packet_data_type.ccd_number] # 1-4
858
+ ccd_number = ccd_bin_to_id[spw_packet_data_type.ccd_number] # 1-4
765
859
  ccd_side = self.fee_side(spw_packet_data_type.ccd_side)
766
860
  data_array = spw_packet.data_as_ndarray
767
861
 
@@ -778,11 +872,9 @@ class FITS(PersistenceLayer):
778
872
  # - add to the FITS file.
779
873
 
780
874
  if self.is_all_data_received(spw_packet_data_type, ccd_number, ccd_side):
781
-
782
875
  # Write the information to FITS
783
876
 
784
877
  for ccd_side in self.fee_side:
785
-
786
878
  try:
787
879
  if self.data_arrays[ccd_side][ccd_number].size > 0:
788
880
  self.assemble_slice(ccd_number, ccd_side)
@@ -795,7 +887,7 @@ class FITS(PersistenceLayer):
795
887
  self.clear_for_next_exposure(ccd_number, ccd_side)
796
888
 
797
889
  def is_all_data_received(self, spw_packet_data_type: DataPacketType, ccd_number: int, ccd_side):
798
- """ Check if all data has been received for the current exposure.
890
+ """Check if all data has been received for the current exposure.
799
891
 
800
892
  Args:
801
893
  - spw_packet_data_type: Last received data packet type.
@@ -808,33 +900,26 @@ class FITS(PersistenceLayer):
808
900
  from egse.dpu import got_all_last_packets
809
901
 
810
902
  if spw_packet_data_type.last_packet:
811
-
812
903
  packet_type = spw_packet_data_type.packet_type
813
904
 
814
905
  if packet_type == PacketType.DATA_PACKET:
815
-
816
906
  if ccd_side == self.fee_side.E_SIDE:
817
-
818
907
  self.received_last_packet_flags[ccd_number][0] = True
819
908
 
820
909
  elif ccd_side == self.fee_side.F_SIDE:
821
-
822
910
  self.received_last_packet_flags[ccd_number][1] = True
823
911
 
824
912
  elif packet_type == PacketType.OVERSCAN_DATA:
825
-
826
913
  if ccd_side == self.fee_side.E_SIDE:
827
-
828
914
  self.received_last_packet_flags[ccd_number][2] = True
829
915
 
830
916
  elif ccd_side == self.fee_side.F_SIDE:
831
-
832
917
  self.received_last_packet_flags[ccd_number][3] = True
833
918
 
834
919
  return got_all_last_packets(self.received_last_packet_flags[ccd_number], self.expected_last_packet_flags)
835
920
 
836
921
  def got_all_last_packets(self, ccd_number: int, ccd_side):
837
- """ Check whether all the expected last-packet flags have been seen for the given CCD side.
922
+ """Check whether all the expected last-packet flags have been seen for the given CCD side.
838
923
 
839
924
  Args:
840
925
  - ccd_number: CCD from which the last received data originates (1-4).
@@ -847,19 +932,21 @@ class FITS(PersistenceLayer):
847
932
  received_last_packet_flags = self.received_last_packet_flags[ccd_number]
848
933
 
849
934
  if ccd_side == self.fee_side.E_SIDE:
850
-
851
- return received_last_packet_flags[0] == self.expected_last_packet_flags[0] \
852
- and received_last_packet_flags[2] == self.expected_last_packet_flags[2]
935
+ return (
936
+ received_last_packet_flags[0] == self.expected_last_packet_flags[0]
937
+ and received_last_packet_flags[2] == self.expected_last_packet_flags[2]
938
+ )
853
939
 
854
940
  elif ccd_side == self.fee_side.F_SIDE:
855
-
856
- return received_last_packet_flags[1] == self.expected_last_packet_flags[1] \
857
- and received_last_packet_flags[3] == self.expected_last_packet_flags[3]
941
+ return (
942
+ received_last_packet_flags[1] == self.expected_last_packet_flags[1]
943
+ and received_last_packet_flags[3] == self.expected_last_packet_flags[3]
944
+ )
858
945
 
859
946
  return False
860
947
 
861
948
  def assemble_slice(self, ccd_number: int, ccd_side):
862
- """ Assemble the data for the given CCD and write it to FITS.
949
+ """Assemble the data for the given CCD and write it to FITS.
863
950
 
864
951
  Args:
865
952
  - ccd_number: CCD identifier (1/2/3/4).
@@ -868,14 +955,12 @@ class FITS(PersistenceLayer):
868
955
  # Windowing (pattern) mode
869
956
 
870
957
  if self.is_windowing_mode:
871
-
872
958
  # self.assemble_slice_windowing_mode(ccd_number, ccd_side)
873
959
  pass
874
960
 
875
961
  # Full-image (pattern) mode
876
962
 
877
963
  else:
878
-
879
964
  self.assemble_slice_full_image_mode(ccd_number, ccd_side)
880
965
 
881
966
  # def assemble_slice_windowing_mode(self, ccd_number: int):
@@ -959,7 +1044,7 @@ class FITS(PersistenceLayer):
959
1044
  # self.append_image(image, ccd_number)
960
1045
 
961
1046
  def assemble_slice_full_image_mode(self, ccd_number: int, ccd_side):
962
- """ Assemble the data for the given CCD and write it to FITS, for full-image mode or full-image pattern mode.
1047
+ """Assemble the data for the given CCD and write it to FITS, for full-image mode or full-image pattern mode.
963
1048
 
964
1049
  This consists of the following steps:
965
1050
 
@@ -986,13 +1071,16 @@ class FITS(PersistenceLayer):
986
1071
  # Re-shape the 1D array to a 2D array and extract the image and scan maps
987
1072
 
988
1073
  try:
989
- data_array = np.reshape(self.data_arrays[ccd_side][ccd_number], (num_rows, num_columns)) # 1D -> 2D
1074
+ data_array = np.reshape(self.data_arrays[ccd_side][ccd_number], (num_rows, num_columns)) # 1D -> 2D
990
1075
  except ValueError as exc:
991
- raise MissingSpWPacketsError(f"Incomplete SpW data for frame {self.frame_number[ccd_number][ccd_side]} "
992
- f"for the {ccd_side.name[0]}-side off CCD{ccd_number}. Check the DPU "
993
- f"Processing logs for more information.") from exc
994
- side_image, side_serial_prescan, side_serial_overscan, side_parallel_overscan = \
995
- self.extract_full_image_mode(data_array)
1076
+ raise MissingSpWPacketsError(
1077
+ f"Incomplete SpW data for frame {self.frame_number[ccd_number][ccd_side]} "
1078
+ f"for the {ccd_side.name[0]}-side off CCD{ccd_number}. Check the DPU "
1079
+ f"Processing logs for more information."
1080
+ ) from exc
1081
+ side_image, side_serial_prescan, side_serial_overscan, side_parallel_overscan = self.extract_full_image_mode(
1082
+ data_array
1083
+ )
996
1084
 
997
1085
  # Serial pre-scan
998
1086
 
@@ -1001,13 +1089,11 @@ class FITS(PersistenceLayer):
1001
1089
  # Serial over-scan
1002
1090
 
1003
1091
  if self.has_serial_overscan:
1004
-
1005
1092
  self.append_serial_overscan(side_serial_overscan, ccd_number, ccd_side)
1006
1093
 
1007
1094
  # Parallel over-scan
1008
1095
 
1009
1096
  if self.has_parallel_overscan:
1010
-
1011
1097
  self.append_parallel_overscan(side_parallel_overscan, ccd_number, ccd_side)
1012
1098
 
1013
1099
  # Image
@@ -1037,42 +1123,40 @@ class FITS(PersistenceLayer):
1037
1123
  # the row index of the first transmitted parallel over-scan row).
1038
1124
 
1039
1125
  start_column_image = CCD_SETTINGS.LENGTH_SERIAL_PRESCAN
1040
- end_column_image_plus_1 = min(CCD_SETTINGS.LENGTH_SERIAL_PRESCAN + CCD_SETTINGS.NUM_COLUMNS // 2, self.h_end + 1)
1126
+ end_column_image_plus_1 = min(
1127
+ CCD_SETTINGS.LENGTH_SERIAL_PRESCAN + CCD_SETTINGS.NUM_COLUMNS // 2, self.h_end + 1
1128
+ )
1041
1129
  end_row_image_plus_1 = min(CCD_SETTINGS.NUM_ROWS - 1, self.v_end) - self.v_start + 1
1042
1130
 
1043
1131
  # Serial pre-scan (all rows, but only the first couple of rows)
1044
1132
 
1045
- serial_prescan = data_array[:, 0: start_column_image]
1133
+ serial_prescan = data_array[:, 0:start_column_image]
1046
1134
 
1047
1135
  # Serial over-scan (all rows, omit the columns from the serial pre-scan and the serial over-scan)
1048
1136
 
1049
1137
  if self.has_serial_overscan:
1050
-
1051
1138
  serial_overscan = data_array[:, end_column_image_plus_1:]
1052
1139
 
1053
1140
  else:
1054
-
1055
1141
  serial_overscan = None
1056
1142
 
1057
1143
  # Image (omit the rows from the parallel over-scan
1058
1144
  # and the columns from the serial pre-scan and the serial over-scan)
1059
1145
 
1060
- image = data_array[0: end_row_image_plus_1, start_column_image: end_column_image_plus_1]
1146
+ image = data_array[0:end_row_image_plus_1, start_column_image:end_column_image_plus_1]
1061
1147
 
1062
1148
  # Parallel over-scan
1063
1149
 
1064
1150
  if self.has_parallel_overscan:
1065
-
1066
- parallel_overscan = data_array[end_row_image_plus_1:, start_column_image: end_column_image_plus_1]
1151
+ parallel_overscan = data_array[end_row_image_plus_1:, start_column_image:end_column_image_plus_1]
1067
1152
 
1068
1153
  else:
1069
-
1070
1154
  parallel_overscan = None
1071
1155
 
1072
1156
  return image, serial_prescan, serial_overscan, parallel_overscan
1073
1157
 
1074
1158
  def append_serial_prescan(self, serial_prescan, ccd_number: int, ccd_side: str):
1075
- """ Append the given serial pre-scan to the FITS file (after completing its header).
1159
+ """Append the given serial pre-scan to the FITS file (after completing its header).
1076
1160
 
1077
1161
  Args:
1078
1162
  - serial_prescan: Serial pre-scan.
@@ -1083,10 +1167,19 @@ class FITS(PersistenceLayer):
1083
1167
  extension = f"SPRE_{ccd_number}_{self.fee_side(ccd_side).name[0]}"
1084
1168
  self.serial_prescan_header["EXTNAME"] = extension
1085
1169
  self.serial_prescan_header["EXTVER"] = self.frame_number[ccd_number][ccd_side]
1086
- self.serial_prescan_header["CCD_ID"] = (ccd_number, "CCD identifier",)
1170
+ self.serial_prescan_header["CCD_ID"] = (
1171
+ ccd_number,
1172
+ "CCD identifier",
1173
+ )
1087
1174
  self.serial_prescan_header["SENSOR_SEL"] = (self.fee_side(ccd_side).name[0], "CCD side")
1088
- self.serial_prescan_header["DATE-OBS"] = (self.timestamp, "Timestamp for 1st frame",)
1089
- self.serial_prescan_header["FINETIME"] = (self.finetime, "Finetime representation of DATE-OBS",)
1175
+ self.serial_prescan_header["DATE-OBS"] = (
1176
+ self.timestamp,
1177
+ "Timestamp for 1st frame",
1178
+ )
1179
+ self.serial_prescan_header["FINETIME"] = (
1180
+ self.finetime,
1181
+ "Finetime representation of DATE-OBS",
1182
+ )
1090
1183
 
1091
1184
  if ccd_side in [self.fee_side.RIGHT_SIDE.name, self.fee_side.RIGHT_SIDE]:
1092
1185
  serial_prescan = np.fliplr(serial_prescan)
@@ -1094,7 +1187,7 @@ class FITS(PersistenceLayer):
1094
1187
  fits.append(str(self._filepath), serial_prescan, self.serial_prescan_header)
1095
1188
 
1096
1189
  def append_serial_overscan(self, serial_overscan, ccd_number: int, ccd_side: str):
1097
- """ Append the given serial over-scan to the FITS file (after completing its header).
1190
+ """Append the given serial over-scan to the FITS file (after completing its header).
1098
1191
 
1099
1192
  Args:
1100
1193
  - serial_overscan: Serial over-scan.
@@ -1105,10 +1198,19 @@ class FITS(PersistenceLayer):
1105
1198
  extension = f"SOVER_{ccd_number}_{self.fee_side(ccd_side).name[0]}"
1106
1199
  self.serial_overscan_header["EXTNAME"] = extension
1107
1200
  self.serial_overscan_header["EXTVER"] = self.frame_number[ccd_number][ccd_side]
1108
- self.serial_overscan_header["CCD_ID"] = (ccd_number, "CCD identifier",)
1201
+ self.serial_overscan_header["CCD_ID"] = (
1202
+ ccd_number,
1203
+ "CCD identifier",
1204
+ )
1109
1205
  self.serial_overscan_header["SENSOR_SEL"] = (self.fee_side(ccd_side).name[0], "CCD side")
1110
- self.serial_overscan_header["NAXIS1"] = (serial_overscan.shape[1], "Number of columns in the serial over-scan",)
1111
- self.serial_overscan_header["DATE-OBS"] = (self.timestamp, "Timestamp for 1st frame",)
1206
+ self.serial_overscan_header["NAXIS1"] = (
1207
+ serial_overscan.shape[1],
1208
+ "Number of columns in the serial over-scan",
1209
+ )
1210
+ self.serial_overscan_header["DATE-OBS"] = (
1211
+ self.timestamp,
1212
+ "Timestamp for 1st frame",
1213
+ )
1112
1214
  self.serial_overscan_header["FINETIME"] = (self.finetime, "Finetime representation of DATE-OBS")
1113
1215
 
1114
1216
  if ccd_side in [self.fee_side.RIGHT_SIDE.name, self.fee_side.RIGHT_SIDE]:
@@ -1117,7 +1219,7 @@ class FITS(PersistenceLayer):
1117
1219
  fits.append(str(self._filepath), serial_overscan, self.serial_overscan_header)
1118
1220
 
1119
1221
  def append_parallel_overscan(self, parallel_overscan, ccd_number: int, ccd_side: str):
1120
- """ Append the given parallel over-scan to the FITS file (after completing its header).
1222
+ """Append the given parallel over-scan to the FITS file (after completing its header).
1121
1223
 
1122
1224
  Args:
1123
1225
  - parallel_overscan: Parallel over-scan.
@@ -1128,12 +1230,23 @@ class FITS(PersistenceLayer):
1128
1230
  extension = f"POVER_{ccd_number}_{self.fee_side(ccd_side).name[0]}"
1129
1231
  self.parallel_overscan_header["EXTNAME"] = extension
1130
1232
  self.parallel_overscan_header["EXTVER"] = self.frame_number[ccd_number][ccd_side]
1131
- self.parallel_overscan_header["CCD_ID"] = (ccd_number, "CCD identifier",)
1233
+ self.parallel_overscan_header["CCD_ID"] = (
1234
+ ccd_number,
1235
+ "CCD identifier",
1236
+ )
1132
1237
  self.parallel_overscan_header["SENSOR_SEL"] = (self.fee_side(ccd_side).name[0], "CCD side")
1133
- self.parallel_overscan_header["NAXIS1"] = (parallel_overscan.shape[1],
1134
- "Number of columns in the parallel over-scan",)
1135
- self.parallel_overscan_header["DATE-OBS"] = (self.timestamp, "Timestamp for 1st frame",)
1136
- self.parallel_overscan_header["FINETIME"] = (self.finetime, "Finetime representation of DATE-OBS",)
1238
+ self.parallel_overscan_header["NAXIS1"] = (
1239
+ parallel_overscan.shape[1],
1240
+ "Number of columns in the parallel over-scan",
1241
+ )
1242
+ self.parallel_overscan_header["DATE-OBS"] = (
1243
+ self.timestamp,
1244
+ "Timestamp for 1st frame",
1245
+ )
1246
+ self.parallel_overscan_header["FINETIME"] = (
1247
+ self.finetime,
1248
+ "Finetime representation of DATE-OBS",
1249
+ )
1137
1250
 
1138
1251
  if ccd_side in [self.fee_side.RIGHT_SIDE.name, self.fee_side.RIGHT_SIDE]:
1139
1252
  parallel_overscan = np.fliplr(parallel_overscan)
@@ -1141,7 +1254,7 @@ class FITS(PersistenceLayer):
1141
1254
  fits.append(str(self._filepath), parallel_overscan, self.parallel_overscan_header)
1142
1255
 
1143
1256
  def append_image(self, image, ccd_number: int, ccd_side: str):
1144
- """ Append the given image to the FITS file (after completing its header).
1257
+ """Append the given image to the FITS file (after completing its header).
1145
1258
 
1146
1259
  Args:
1147
1260
  - image: Image.
@@ -1152,46 +1265,82 @@ class FITS(PersistenceLayer):
1152
1265
  extension = f"IMAGE_{ccd_number}_{self.fee_side(ccd_side).name[0]}"
1153
1266
  self.image_header["EXTNAME"] = extension
1154
1267
  self.image_header["EXTVER"] = self.frame_number[ccd_number][ccd_side]
1155
- self.image_header["CCD_ID"] = (ccd_number, "CCD identifier",)
1268
+ self.image_header["CCD_ID"] = (
1269
+ ccd_number,
1270
+ "CCD identifier",
1271
+ )
1156
1272
  self.image_header["SENSOR_SEL"] = (self.fee_side(ccd_side).name[0], "CCD side")
1157
- self.image_header["NAXIS1"] = (image.shape[1], "Number of columns in the image",)
1273
+ self.image_header["NAXIS1"] = (
1274
+ image.shape[1],
1275
+ "Number of columns in the image",
1276
+ )
1158
1277
 
1159
1278
  ccd_orientation_degrees = CCD_SETTINGS.ORIENTATION[ccd_number - 1]
1160
1279
  ccd_orientation_radians = radians(ccd_orientation_degrees)
1161
1280
 
1162
- self.image_header["CROTA2"] = (ccd_orientation_degrees, "CCD orientation angle [degrees]",)
1281
+ self.image_header["CROTA2"] = (
1282
+ ccd_orientation_degrees,
1283
+ "CCD orientation angle [degrees]",
1284
+ )
1163
1285
 
1164
1286
  cdelt = CCD_SETTINGS["PIXEL_SIZE"] / 1000.0 # Pixel size [mm]
1165
1287
 
1166
- self.image_header["CD1_1"] = (cdelt * cos(ccd_orientation_radians),
1167
- "Pixel size x cos(CCD orientation angle)",)
1168
- self.image_header["CD1_2"] = (-cdelt * sin(ccd_orientation_radians),
1169
- "-Pixel size x sin(CCD orientation angle)",)
1170
- self.image_header["CD2_1"] = (cdelt * sin(ccd_orientation_radians),
1171
- "Pixel size x sin(CCD orientation angle)",)
1172
- self.image_header["CD2_2"] = (cdelt * cos(ccd_orientation_radians),
1173
- "Pixel size x cos(CCD orientation angle)",)
1288
+ self.image_header["CD1_1"] = (
1289
+ cdelt * cos(ccd_orientation_radians),
1290
+ "Pixel size x cos(CCD orientation angle)",
1291
+ )
1292
+ self.image_header["CD1_2"] = (
1293
+ -cdelt * sin(ccd_orientation_radians),
1294
+ "-Pixel size x sin(CCD orientation angle)",
1295
+ )
1296
+ self.image_header["CD2_1"] = (
1297
+ cdelt * sin(ccd_orientation_radians),
1298
+ "Pixel size x sin(CCD orientation angle)",
1299
+ )
1300
+ self.image_header["CD2_2"] = (
1301
+ cdelt * cos(ccd_orientation_radians),
1302
+ "Pixel size x cos(CCD orientation angle)",
1303
+ )
1174
1304
 
1175
1305
  zeropoint_x, zeropoint_y = -np.array(CCD_SETTINGS.ZEROPOINT)
1176
1306
  crval1 = zeropoint_x * cos(ccd_orientation_radians) - zeropoint_y * sin(ccd_orientation_radians)
1177
1307
  crval2 = zeropoint_x * sin(ccd_orientation_radians) + zeropoint_y * cos(ccd_orientation_radians)
1178
1308
 
1179
- self.image_header["CRVAL1"] = (crval1, "FP x-coordinate of the CCD origin [mm]",)
1180
- self.image_header["CRVAL2"] = (crval2, "FP y-coordinate of the CCD origin [mm]",)
1309
+ self.image_header["CRVAL1"] = (
1310
+ crval1,
1311
+ "FP x-coordinate of the CCD origin [mm]",
1312
+ )
1313
+ self.image_header["CRVAL2"] = (
1314
+ crval2,
1315
+ "FP y-coordinate of the CCD origin [mm]",
1316
+ )
1181
1317
 
1182
- self.image_header["CRPIX2"] = (-self.v_start,
1183
- "CCD origin row wrt 1st transmitted row",)
1318
+ self.image_header["CRPIX2"] = (
1319
+ -self.v_start,
1320
+ "CCD origin row wrt 1st transmitted row",
1321
+ )
1184
1322
 
1185
1323
  if ccd_side in [self.fee_side.LEFT_SIDE.name, self.fee_side.LEFT_SIDE]:
1186
- self.image_header["CRPIX1"] = (0, "CCD origin column wrt lower left corner",)
1324
+ self.image_header["CRPIX1"] = (
1325
+ 0,
1326
+ "CCD origin column wrt lower left corner",
1327
+ )
1187
1328
 
1188
1329
  if ccd_side in [self.fee_side.RIGHT_SIDE.name, self.fee_side.RIGHT_SIDE]:
1189
1330
  image = np.fliplr(image)
1190
- self.image_header["CRPIX1"] = (-CCD_SETTINGS.NUM_ROWS + image.shape[1],
1191
- "CCD origin column wrt lower left corner")
1192
-
1193
- self.image_header["DATE-OBS"] = (self.timestamp, "Timestamp for 1st frame",)
1194
- self.image_header["FINETIME"] = (self.finetime, "Finetime representation of DATE-OBS",)
1331
+ self.image_header["CRPIX1"] = (
1332
+ -CCD_SETTINGS.NUM_ROWS + image.shape[1],
1333
+ "CCD origin column wrt lower left corner",
1334
+ )
1335
+
1336
+ self.image_header["DATE-OBS"] = (
1337
+ self.timestamp,
1338
+ "Timestamp for 1st frame",
1339
+ )
1340
+ self.image_header["FINETIME"] = (
1341
+ self.finetime,
1342
+ "Finetime representation of DATE-OBS",
1343
+ )
1195
1344
 
1196
1345
  fits.append(str(self._filepath), image, self.image_header)
1197
1346
 
@@ -1209,13 +1358,10 @@ class FITS(PersistenceLayer):
1209
1358
  raise NotImplementedError("Persistence layers must implement a read method")
1210
1359
 
1211
1360
  def update(self, idx, data):
1212
-
1213
1361
  pass
1214
1362
 
1215
1363
  def delete(self, idx):
1216
-
1217
1364
  LOGGER.warning("The delete functionality is not implemented for the CSV persistence layer.")
1218
1365
 
1219
1366
  def get_filepath(self):
1220
-
1221
1367
  return self._filepath
File without changes
File without changes