diff --git a/tomcat_bec/scans/__init__.py b/tomcat_bec/scans/__init__.py index 230e33e..ab461c9 100644 --- a/tomcat_bec/scans/__init__.py +++ b/tomcat_bec/scans/__init__.py @@ -1,2 +1,2 @@ -from .tutorial_fly_scan import AcquireDark, AcquireWhite, TutorialFlyScanContLine +from .tutorial_fly_scan import AcquireDark, AcquireWhite, AcquireRefs, TutorialFlyScanContLine from .tomcat_scans import TomcatStepScan, TomcatSnapNStep, TomcatSimpleSequence diff --git a/tomcat_bec/scans/tutorial_fly_scan.py b/tomcat_bec/scans/tutorial_fly_scan.py index 8470680..fe0ea53 100644 --- a/tomcat_bec/scans/tutorial_fly_scan.py +++ b/tomcat_bec/scans/tutorial_fly_scan.py @@ -46,7 +46,7 @@ class AcquireDark(Acquire): def scan_core(self): # close the shutter - yield from self._move_scan_motors_and_wait(self.dark_shutter_pos) +# yield from self._move_scan_motors_and_wait(self.dark_shutter_pos) #yield from self.stubs.set_and_wait(device=[self.shutter], positions=[0]) yield from super().scan_core() @@ -95,15 +95,205 @@ class AcquireWhite(Acquire): self.sample_angle_out = sample_angle_out self.scan_motors = ["eyex", "eyez", "es1_roty"] # change to the correct shutter device -# self.scan_motors = ["eyex", "eyez"] # change to the correct shutter device - self.dark_shutter_pos = 1 ### change with a variable + self.dark_shutter_pos_out = 1 ### change with a variable + self.dark_shutter_pos_in = 0 ### change with a variable def scan_core(self): # open the shutter and move the sample stage to the out position - yield from self._move_scan_motors_and_wait([self.dark_shutter_pos, self.sample_position_out, self.sample_angle_out]) -# yield from self._move_scan_motors_and_wait([self.dark_shutter_pos, self.sample_position_out]) + self.scan_motors = ["eyez", "es1_roty"] # change to the correct shutter device + yield from self._move_scan_motors_and_wait([self.sample_position_out, self.sample_angle_out]) + self.scan_motors = ["eyex"] # change to the correct shutter device + yield from self._move_scan_motors_and_wait([self.dark_shutter_pos_out]) + # TODO add opening of fast shutter yield from super().scan_core() + + # TODO add closing of fast shutter + yield from self._move_scan_motors_and_wait([self.dark_shutter_pos_in]) + +class AcquireProjectins(Acquire): + scan_name = "acquire_projections" + required_kwargs = ["exp_burst", "sample_position_in", "start_position", "angular_range"] + gui_config = {"Acquisition parameters": ["exp_burst"]} + + def __init__(self, + exp_burst: int, + sample_position_in: float, + start_position: float, + angular_range: float, + **kwargs): + """ + Acquire projection images. + + Args: + exp_burst : int + Number of flat field images to acquire (no default) + sample_position_in : float + Position to move the sample stage to position the sample in the beam + start_position : float + Angular start position for the scan + angular_range : float + Angular range + exp_time : float, optional + Exposure time [ms]. If not specified, the currently configured value on the camera will be used + exp_period : float, optional + Exposure period [ms]. If not specified, the currently configured value on the camera will be used + image_width : int, optional + ROI size in the x-direction [pixels]. If not specified, the currently configured value on the camera will be used + image_height : int, optional + ROI size in the y-direction [pixels]. If not specified, the currently configured value on the camera will be used + acq_mode : str, optional + Predefined acquisition mode (default= 'default') + file_path : str, optional + File path for standard daq + + Returns: + ScanReport + + Examples: + >>> scans.acquire_white(5, 20) + + """ + super().__init__(**kwargs) + self.burst_at_each_point = 1 + self.sample_position_in = sample_position_in + self.start_position = start_position + self.angular_range = angular_range + + self.scan_motors = ["eyex", "eyez", "es1_roty"] # change to the correct shutter device + self.dark_shutter_pos_out = 1 ### change with a variable + self.dark_shutter_pos_in = 0 ### change with a variable + + + def scan_core(self): + # open the shutter and move the sample stage to the out position + self.scan_motors = ["eyez", "es1_roty"] # change to the correct shutter device + yield from self._move_scan_motors_and_wait([self.sample_position_out, self.sample_angle_out]) + self.scan_motors = ["eyex"] # change to the correct shutter device + yield from self._move_scan_motors_and_wait([self.dark_shutter_pos_out]) + # TODO add opening of fast shutter + yield from super().scan_core() + + # TODO add closing of fast shutter + yield from self._move_scan_motors_and_wait([self.dark_shutter_pos_in]) + + +class AcquireRefs(Acquire): + scan_name = "acquire_refs" + required_kwargs = [] + gui_config = {} + + def __init__( + self, + num_darks: int = 0, + num_flats: int = 0, + sample_angle_out: float = 0, + sample_position_in: float = 0, + sample_position_out: float = 5000, + file_prefix_dark: str = 'tmp_dark', + file_prefix_white: str = 'tmp_white', + exp_time: float = 0, + exp_period: float = 0, + image_width: int = 2016, + image_height: int = 2016, + acq_mode: str = 'default', + file_path: str = 'tmp', + nr_writers: int = 2, + base_path: str = 'tmp', + **kwargs + ): + """ + Acquire reference images (darks + whites) and return to beam position. + + Reference images are acquired automatically in an optimized sequence and + the sample is returned to the sample_in_position afterwards. + + Args: + num_darks : int , optional + Number of dark field images to acquire + num_flats : int , optional + Number of white field images to acquire + sample_position_in : float , optional + Sample stage X position for sample in beam [um] + sample_position_out : float ,optional + Sample stage X position for sample out of the beam [um] + sample_angle_out : float , optional + Angular position where to take the flat field images + exp_time : float, optional + Exposure time [ms]. If not specified, the currently configured value on the camera will be used + exp_period : float, optional + Exposure period [ms]. If not specified, the currently configured value on the camera will be used + image_width : int, optional + ROI size in the x-direction [pixels]. If not specified, the currently configured value on the camera will be used + image_height : int, optional + ROI size in the y-direction [pixels]. If not specified, the currently configured value on the camera will be used + acq_mode : str, optional + Predefined acquisition mode (default= 'default') + file_path : str, optional + File path for standard daq + + Returns: + ScanReport + + Examples: + >>> scans.acquire_refs(sample_angle_out=90, sample_position_in=10, num_darks=5, num_flats=5, exp_time=0.1) + + """ + super().__init__(**kwargs) + self.sample_position_in = sample_position_in + self.sample_position_out = sample_position_out + self.sample_angle_out = sample_angle_out + self.num_darks = num_darks + self.num_flats = num_flats + self.file_prefix_dark = file_prefix_dark + self.file_prefix_white = file_prefix_white + self.exp_time = exp_time, + self.exp_period = exp_period, + self.image_width = image_width, + self.image_height = image_height, + self.acq_mode = acq_mode, + self.file_path = file_path, + self.nr_writers = nr_writers, + self.base_path = base_path, + + def scan_core(self): + + ## TODO move sample in position and do not wait + ## TODO move angle in position and do not wait + if self.num_darks: + self.connector.send_client_info( + f"Acquiring {self.num_darks} dark images", + show_asap=True, + rid=self.metadata.get("RID"), + ) + darks = AcquireDark( + exp_burst=self.num_darks, + file_prefix=self.file_prefix_dark, + device_manager=self.device_manager, + metadata=self.metadata + ) + yield from darks.scan_core() + self.point_id = darks.point_id + + if self.num_flats: + self.connector.send_client_info( + f"Acquiring {self.num_flats} flat field images", + show_asap=True, + rid=self.metadata.get("RID"), + ) + flats = AcquireWhite( + exp_burst=self.num_flats, + sample_position_out=self.sample_position_out, + sample_angle_out=self.sample_angle_out, + file_prefix=self.file_prefix_white, + device_manager=self.device_manager, + metadata=self.metadata, + ) + flats.point_id = self.point_id + yield from flats.scan_core() + self.point_id = flats.point_id + ## TODO move sample in beam and do not wait + ## TODO move rotation to angle and do not wait class TutorialFlyScanContLine(AsyncFlyScanBase): diff --git a/tomcat_bec/scripts/scans_fede.py b/tomcat_bec/scripts/scans_fede.py index 917194d..a4ce07b 100644 --- a/tomcat_bec/scripts/scans_fede.py +++ b/tomcat_bec/scripts/scans_fede.py @@ -8,6 +8,9 @@ class Measurement: def __init__(self): self.sample_name = 'tmp' self.data_path = 'disk_test' + bec.system_config.file_suffix = None + bec.system_config.file_directory = os.path.join(self.data_path,self.sample_name) + self.nimages = 1000 self.nimages_dark = 50 self.nimages_white = 100 @@ -23,16 +26,24 @@ class Measurement: self.roix = None self.roiy = None - enabled_detectors = [obj for obj in dev.get_devices_with_tags('camera') if obj.enabled] - if len(enabled_detectors) != 1: - print('WARNING! More than 1 detector active') - self.det = enabled_detectors[0] - self.device_name = enabled_detectors[0].name - - bec.system_config.file_suffix = None - bec.system_config.file_directory = os.path.join(self.data_path,self.sample_name) - - self.build_filename() + self.get_available_detectors() + self.get_enabled_detectors() + if len(self.enabled_detectors) > 1: + print('WARNING! More than 1 detector enabled') + self.show_enabled_detectors() + elif len(self.enabled_detectors) == 0: + print("WARNING! No detector enabled!!") + self.show_available_detectors() + else: + self.show_enabled_detectors() + self.det = self.enabled_detectors[0] + self.device_name = self.enabled_detectors[0].name + self.build_filename() + self.exposure_time = self.det.cfgExposure.get() + self.exposure_period = self.det.cfgFramerate.get() + self.roix = self.det.cfgRoiX.get() + self.roiy = self.det.cfgRoiY.get() + def build_filename(self, acquisition_type='data'): """ @@ -130,6 +141,99 @@ class Measurement: self.build_filename() + def disable_detector(self, detector_name): + """ + Disable detector + """ + + if detector_name not in self.available_detectors_names: + print("WARNING! The detector " + str(detector_name + " is not available!")) + print("You can check the available detectors with the command dev.show_all()") + + devices_to_be_disabled = [obj for obj in dev.get_devices_with_tags(detector_name)] + for i in range(len(devices_to_be_disabled)): + devices_to_be_disabled[i].enabled = False + + self.get_enabled_detectors() + + if len(self.enabled_detectors) > 1: + print('WARNING! More than 1 detector enabled') + self.show_enabled_detectors() + elif len(self.enabled_detectors) == 0: + print("WARNING! No detector enabled!!") + self.show_available_detectors() + else: + self.show_enabled_detectors() + self.det = self.enabled_detectors[0] + self.device_name = self.enabled_detectors[0].name + self.build_filename() + + + def enable_detector(self, detector_name): + """ + Enable detector + """ + + if detector_name not in self.available_detectors_names: + print("WARNING! The detector " + str(detector_name + " is not available!")) + print("You can check the available detectors with the command dev.show_all()") + + devices_to_be_enabled = [obj for obj in dev.get_devices_with_tags(detector_name)] + for i in range(len(devices_to_be_enabled)): + devices_to_be_enabled[i].enabled = True + dev.daq_stream1.enabled = False + + self.get_enabled_detectors() + + if len(self.enabled_detectors) > 1: + print('WARNING! More than 1 detector enabled') + self.show_enabled_detectors() + elif len(self.enabled_detectors) == 0: + print("WARNING! No detector enabled!!") + self.show_available_detectors() + else: + self.show_enabled_detectors() + self.det = self.enabled_detectors[0] + self.device_name = self.enabled_detectors[0].name + self.build_filename() + + + + def get_available_detectors(self): + """ + Check available detectors + """ + self.available_detectors = [obj for obj in dev.get_devices_with_tags('camera')] + + self.available_detectors_names = [] + for i in range(len(self.available_detectors)): + self.available_detectors_names.append(self.available_detectors[i].name) + + def get_enabled_detectors(self): + """ + Get enabled detectors + """ + self.enabled_detectors = [obj for obj in dev.get_devices_with_tags('camera') if obj.enabled] + + + def show_available_detectors(self): + """ + Show available detectors + """ + print("Available detectors:") + for i in range(len(self.available_detectors)): + print(self.available_detectors[i].name + '\tenabled ' + str(self.available_detectors[i].enabled)) + + + def show_enabled_detectors(self): + """ + List enabled detectors + """ + print("Enabled detectors:") + for i in range(len(self.enabled_detectors)): + print(self.enabled_detectors[i].name) + + def show_all(self): """ @@ -145,18 +249,22 @@ class Measurement: print("Number of flats: " + str(self.nimages_white)) if self.exposure_time == None: print("Exposure time: " + str(self.det.cfgExposure.get())) + self.exposure_time = self.det.cfgExposure.get() else: print("Exposure time: " + str(self.exposure_time)) if self.exposure_period == None: - print("Exposure peirod: " + str(self.det.cfgFramerate.get())) + print("Exposure period: " + str(self.det.cfgFramerate.get())) + self.exposure_period = self.det.cfgFramerate.get() else: print("Exposure period: " + str(self.exposure_period)) if self.roix == None: print("Roix: " + str(self.det.cfgRoiX.get())) + self.roix = self.det.cfgRoiX.get() else: print("Roix: " + str(self.roix)) if self.roiy == None: print("Roiy: " + str(self.det.cfgRoiY.get())) + self.roiy = self.det.cfgRoiY.get() else: print("Roiy: " + str(self.roiy)) print("Start angle: " + str(self.start_angle)) @@ -165,14 +273,14 @@ class Measurement: print("Sample position out: " + str(self.sample_position_out)) - def acquire_darks(self,nimages_dark, exposure_time=None, exposure_period=None, + def acquire_darks(self,nimages_dark=None, exposure_time=None, exposure_period=None, roix=None, roiy=None, acq_mode=None): """ Acquire a set of dark images with shutters closed. Parameters ---------- - nimages_dark : int + nimages_dark : int, optional Number of dark images to acquire (no default) exposure_time : float, optional Exposure time [ms]. If not specified, the currently configured value on the camera will be used @@ -187,18 +295,11 @@ class Measurement: Example: -------- - m.acquire_darks(100, exposure_time=5) + m.acquire_darks(nimages_darks=100, exposure_time=5) """ - # dev.es1_tasks.enabled = False - # dev.es1_psod.enabled = False - # dev.es1_ddaq.enabled = False - # dev.es1_ismc.enabled = False - # dev.es1_roty.enabled = False - dev.gfcam.enabled = True - dev.gfdaq.enabled = True - dev.daq_stream0.enabled = True - dev.daq_stream1.enabled = False - + + if nimages_dark != None: + self.nimages_dark = nimages_dark if exposure_time != None: self.exposure_time = exposure_time if exposure_period != None: @@ -212,11 +313,12 @@ class Measurement: ### TODO: camera reset print("Handing over to 'scans.acquire_dark") - scans.acquire_dark(exp_burst=nimages_dark, exp_time=self.exposure_time, exp_period=self.exposure_period, image_width=self.roix, + scans.acquire_dark(exp_burst=self.nimages_dark, exp_time=self.exposure_time, exp_period=self.exposure_period, image_width=self.roix, image_height=self.roiy, acq_mode=acq_mode, file_path=self.file_path, nr_writers=2, base_path=self.base_path, file_prefix=self.file_prefix) - def acquire_whites(self,nimages_whites, sample_angle_out=None, sample_position_out=None, + + def acquire_whites(self,nimages_white=None, sample_angle_out=None, sample_position_out=None, exposure_time=None, exposure_period=None, roix=None, roiy=None, acq_mode=None): """ @@ -224,7 +326,7 @@ class Measurement: Parameters ---------- - nimages_whites : int + nimages_whites : int, optional Number of white images to acquire (no default) sample_angle_out : float, optional Sample rotation angle for sample out of the beam [deg] @@ -243,18 +345,11 @@ class Measurement: Example: -------- - m.acquire_whites(100, exposure_time=5) + m.acquire_whites(nimages_whites=100, exposure_time=5) """ - # dev.es1_tasks.enabled = False - # dev.es1_psod.enabled = False - # dev.es1_ddaq.enabled = False - # dev.es1_ismc.enabled = False - # dev.es1_roty.enabled = False - dev.gfcam.enabled = True - dev.gfdaq.enabled = True - dev.daq_stream0.enabled = True - dev.daq_stream1.enabled = False - + + if nimages_white != None: + self.nimages_white = nimages_white if sample_angle_out != None: self.sample_angle_out = sample_angle_out if sample_position_out != None: @@ -272,7 +367,78 @@ class Measurement: ### TODO: camera reset print("Handing over to 'scans.acquire_whites") - scans.acquire_white(exp_burst=nimages_whites, sample_angle_out=self.sample_angle_out, sample_position_out= self.sample_position_out, + scans.acquire_white(exp_burst=self.nimages_white, sample_angle_out=self.sample_angle_out, sample_position_out= self.sample_position_out, exp_time=self.exposure_time, exp_period=self.exposure_period, image_width=self.roix, image_height=self.roiy, acq_mode=acq_mode, file_path=self.file_path, nr_writers=2, base_path=self.base_path, - file_prefix=self.file_prefix) \ No newline at end of file + file_prefix=self.file_prefix) + + + def acquire_refs(self,nimages_dark=None, nimages_white=None, sample_angle_out=None, + sample_position_in=None, sample_position_out=None, + exposure_time=None, exposure_period=None, + roix=None, roiy=None, acq_mode=None): + """ + Acquire reference images (darks + whites) and return to beam position. + + Reference images are acquired automatically in an optimized sequence and + the sample is returned to the sample_in_position afterwards. + + Parameters + ---------- + darks : int, optional + Number of dark images to acquire (no default) + nimages_whites : int, optional + Number of white images to acquire (no default) + sample_angle_out : float, optional + Sample rotation angle for sample out of the beam [deg] + sample_position_in : float, optional + Sample stage X position for sample in of the beam [um] + sample_position_out : float, optional + Sample stage X position for sample out of the beam [um] + exposure_time : float, optional + Exposure time [ms]. If not specified, the currently configured value on the camera will be used + exposure_period : float, optional + Exposure period [ms] + roix : int, optional + ROI size in the x-direction [pixels] + roiy : int, optional + ROI size in the y-direction [pixels] + acq_mode : str, optional + Predefined acquisition mode (default=None) + + Example: + -------- + m.acquire_refs(nimages_whites=100, exposure_time=5) + """ + + if nimages_dark != None: + self.nimages_dark = nimages_dark + if nimages_white != None: + self.nimages_white = nimages_white + if sample_angle_out != None: + self.sample_angle_out = sample_angle_out + if sample_position_in != None: + self.sample_position_in = sample_position_in + if sample_position_out != None: + self.sample_position_out = sample_position_out + if exposure_time != None: + self.exposure_time = exposure_time + if exposure_period != None: + self.exposure_period = exposure_period + if roix != None: + self.roix = roix + if roiy != None: + self.roiy = roiy + + self.build_filename(acquisition_type='dark') + file_prefix_dark = self.file_prefix + self.build_filename(acquisition_type='white') + file_prefix_white = self.file_prefix + + ### TODO: camera reset + print("Handing over to 'scans.acquire_refs") + scans.acquire_refs(num_darks=self.nimages_dark, num_flats=self.nimages_white, sample_angle_out=self.sample_angle_out, + sample_position_in=self.sample_position_in, sample_position_out=self.sample_position_out, + exp_time=self.exposure_time, exp_period=self.exposure_period, image_width=self.roix, + image_height=self.roiy, acq_mode='default', file_path=self.file_path, nr_writers=2, base_path=self.base_path, + file_prefix_dark=file_prefix_dark, file_prefix_white=file_prefix_white) \ No newline at end of file