@@ -59,8 +59,38 @@ class AndorStatus:
59
59
DrvP6Invalid = 20077
60
60
61
61
62
+ def window_sensor_region (region , window ):
63
+ """Calculate new region coordinates when cropped to a given window"""
64
+ x1 = max (0 , region [0 ] - window [0 ])
65
+ x2 = min (region [1 ] - window [0 ], window [1 ] - window [0 ])
66
+ y1 = max (0 , region [2 ] - window [2 ])
67
+ y2 = min (region [3 ] - window [2 ], window [3 ] - window [2 ])
68
+ if x1 > x2 or y1 > y2 :
69
+ return None
70
+
71
+ return [x1 , x2 , y1 , y2 ]
72
+
73
+
74
+ def bin_sensor_region (region , binning ):
75
+ """Calculate new region coordinates when binned by a given value"""
76
+ if region is None :
77
+ return region
78
+
79
+ return [
80
+ (region [0 ] + binning [0 ] - 1 ) // binning [0 ],
81
+ region [1 ] // binning [0 ],
82
+ (region [2 ] + binning [1 ] - 1 ) // binning [1 ],
83
+ region [3 ] // binning [1 ]
84
+ ]
85
+
86
+
87
+ def format_sensor_region (region ):
88
+ """Format a 0-indexed region as a 1-indexed fits region"""
89
+ return f'[{ region [0 ] + 1 } :{ region [1 ] + 1 } ,{ region [2 ] + 1 } :{ region [3 ] + 1 } ]'
90
+
91
+
62
92
def process_frames (process_queue , stop_signal , camera_id , filter_name , output_path ,
63
- header_card_capacity , log_name ,
93
+ header_card_capacity , log_name , native_image_region ,
64
94
pipeline_daemon_name , pipeline_handover_timeout ):
65
95
"""
66
96
Helper process to save frames to disk.
@@ -70,14 +100,15 @@ def process_frames(process_queue, stop_signal, camera_id, filter_name, output_pa
70
100
while True :
71
101
frame = process_queue .get ()
72
102
73
- if frame ['image_region' ] is not None :
74
- i = frame ['image_region' ]
75
- image_region_header = ('IMAG-RGN' , f'[{ i [0 ]} :{ i [1 ]} ,{ i [2 ]} :{ i [3 ]} ]' ,
103
+ # Imaging region in binned/windowed coords
104
+ image_region = window_sensor_region (native_image_region , frame ['window_region' ])
105
+ image_region = bin_sensor_region (image_region , frame ['window_bin' ])
106
+ if image_region is not None :
107
+ image_region_header = ('IMAG-RGN' , format_sensor_region (image_region ),
76
108
'[x1:x2,y1:y2] image region (image coords)' )
77
109
else :
78
110
image_region_header = ('COMMENT' , ' IMAG-RGN not available' , '' )
79
111
80
- w = frame ['window_region' ]
81
112
header = [
82
113
(None , None , None ),
83
114
('COMMENT' , ' --- DATE/TIME --- ' , '' ),
@@ -97,11 +128,12 @@ def process_frames(process_queue, stop_signal, camera_id, filter_name, output_pa
97
128
('TEMP-LCK' , frame ['temperature_locked' ], 'CCD temperature is locked to set point' ),
98
129
('CAM-XBIN' , frame ['window_bin' ][0 ], '[px] x binning' ),
99
130
('CAM-YBIN' , frame ['window_bin' ][1 ], '[px] y binning' ),
100
- ('CAM-WIND' , f'[{ w [0 ]} :{ w [1 ]} ,{ w [2 ]} :{ w [3 ]} ]' , '[x1:x2,y1:y2] readout region (detector coords)' ),
101
131
('CAM-GAIN' , frame ['gain' ], 'preamplifier gain setting' ),
102
132
('CAM-RSPD' , frame ['horizontal_shift_speed' ], '[MHz] CCD readout speed' ),
103
- ('SHUTTER' , 'AUTO' if frame ['shutter_enabled' ] else 'CLOSED' , 'shutter mode' ),
133
+ ('CAM-WIND' , format_sensor_region (frame ['window_region' ]),
134
+ '[x1:x2,y1:y2] readout region (detector coords)' ),
104
135
image_region_header ,
136
+ ('SHUTTER' , 'AUTO' if frame ['shutter_enabled' ] else 'CLOSED' , 'shutter mode' ),
105
137
('EXPCNT' , frame ['exposure_count' ], 'running exposure count since EXPCREF' ),
106
138
('EXPCREF' , frame ['exposure_count_reference' ], 'date the exposure counter was reset' ),
107
139
('SHTRCNT' , frame ['shutter_count' ], 'running shutter count since SHTRCREF' ),
@@ -174,9 +206,6 @@ class CameraDaemon:
174
206
self ._window_region = [0 , 0 , 0 , 0 ]
175
207
self ._window_bin = [1 , 1 ]
176
208
177
- # Image geometry: masking away the overscan on the red camera
178
- self ._image_region = [0 , 0 , 0 , 0 ]
179
-
180
209
# Shutter enabled (opens during exposures), or remains closed
181
210
self ._shutter_enabled = False
182
211
@@ -243,7 +272,7 @@ class CameraDaemon:
243
272
244
273
Process (target = process_frames , daemon = True , args = (
245
274
self ._processing_queue , self ._processing_stop_signal , config .camera_id , config .filter , config .output_path ,
246
- config .header_card_capacity , config .log_name ,
275
+ config .header_card_capacity , config .log_name , config . image_region ,
247
276
config .pipeline_daemon_name , config .pipeline_handover_timeout )
248
277
).start ()
249
278
@@ -364,6 +393,7 @@ class CameraDaemon:
364
393
readout_width = (self ._window_region [1 ] - self ._window_region [0 ] + 1 ) // self ._window_bin [0 ]
365
394
readout_height = (self ._window_region [3 ] - self ._window_region [2 ] + 1 ) // self ._window_bin [1 ]
366
395
pixel_count = readout_width * readout_height
396
+
367
397
while not self ._stop_acquisition and not self ._processing_stop_signal .value :
368
398
framedata = bytearray (pixel_count * 2 )
369
399
@@ -435,7 +465,6 @@ class CameraDaemon:
435
465
'shutter_count_reference' : self ._shutter_count_reference ,
436
466
'window_bin' : self ._window_bin ,
437
467
'window_region' : self ._window_region ,
438
- 'image_region' : self ._image_region
439
468
})
440
469
441
470
# Continue exposure sequence?
@@ -712,14 +741,7 @@ class CameraDaemon:
712
741
self ._ccd_width = width .value
713
742
self ._ccd_height = height .value
714
743
self ._window_bin = [1 , 1 ]
715
- self ._window_region = [1 , self ._ccd_width , 1 , self ._ccd_height ]
716
- self ._image_region = [
717
- self ._config .overscan [0 ] + 1 ,
718
- self ._ccd_width - self ._config .overscan [1 ],
719
- 1 ,
720
- self ._ccd_height
721
- ]
722
-
744
+ self ._window_region = [0 , self ._ccd_width - 1 , 0 , self ._ccd_height - 1 ]
723
745
self ._gains = gains
724
746
self ._horizontal_shift_speeds = speeds
725
747
self ._status = CameraStatus .Idle
@@ -976,26 +998,9 @@ class CameraDaemon:
976
998
print (f'failed to set geometry { region } ({ binning [0 ]} x{ binning [1 ]} )' )
977
999
return False
978
1000
1001
+ # Convert from 1-indexed to 0-indexed
1002
+ self ._window_region = [x - 1 for x in window ]
979
1003
self ._window_bin = binning
980
- self ._window_region = window
981
-
982
- # Round prescan and postscan up to nearest binned pixel to avoid bleeding
983
- prescan = int ((self ._config .overscan [0 ] + binning [0 ] - 1 ) / binning [0 ])
984
- postscan = int ((self ._config .overscan [1 ] + binning [0 ] - 1 ) / binning [0 ])
985
-
986
- # Work out how much pre/post scan columns are left in the requested window
987
- window_x = int ((window [0 ] - 1 ) / binning [0 ])
988
- window_y = int ((window [2 ] - 1 ) / binning [1 ])
989
- image_x1 = 1 + max (prescan , int ((window [0 ] - 1 ) / binning [0 ])) - window_x
990
- image_x2 = min (int (self ._ccd_width / binning [0 ]) - postscan , int (window [1 ] / binning [0 ])) - window_x
991
- image_y1 = 1 + int ((window [2 ] - 1 ) / binning [1 ]) - window_y
992
- image_y2 = int (window [3 ] / binning [1 ]) - window_y
993
-
994
- # Window is completely in the overscan
995
- if image_x2 < image_x1 :
996
- self ._image_region = None
997
- else :
998
- self ._image_region = [image_x1 , image_x2 , image_y1 , image_y2 ]
999
1004
1000
1005
return True
1001
1006
@@ -1015,7 +1020,7 @@ class CameraDaemon:
1015
1020
if self ._status != CameraStatus .Idle :
1016
1021
return CommandStatus .CameraNotIdle
1017
1022
1018
- if not self .__set_readout_window (self ._window_region , [bin_x , bin_y ]):
1023
+ if not self .__set_readout_window ([ x + 1 for x in self ._window_region ] , [bin_x , bin_y ]):
1019
1024
return CommandStatus .BinningIncompatibleWithWindow
1020
1025
1021
1026
if not quiet :
0 commit comments