From 1b532961be20112bf8135331c3f878e6947a3120 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 14 May 2025 09:52:00 -0400 Subject: [PATCH 01/23] MNT: setup script for DS --- startup/02-data_security.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 startup/02-data_security.py diff --git a/startup/02-data_security.py b/startup/02-data_security.py new file mode 100644 index 0000000..6b4896b --- /dev/null +++ b/startup/02-data_security.py @@ -0,0 +1,9 @@ +# To be deleted when DS is done + +RE.md.pop('experiment_alias_directory') +RE.md.pop('experiment_user') +RE.md.pop('experiment_SAF_number') +RE.md.pop('experiment_proposal_number') +RE.md.pop('experiment_project') +RE.md.pop('experiment_group') +RE.md.pop('experiment_cycle') From 3c847313a5dd02914838428caf03c54ee65f37e2 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 14 May 2025 09:56:16 -0400 Subject: [PATCH 02/23] FIX: DS script --- startup/02-data_security.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/startup/02-data_security.py b/startup/02-data_security.py index 6b4896b..3b64749 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -1,9 +1,9 @@ # To be deleted when DS is done -RE.md.pop('experiment_alias_directory') -RE.md.pop('experiment_user') -RE.md.pop('experiment_SAF_number') -RE.md.pop('experiment_proposal_number') -RE.md.pop('experiment_project') -RE.md.pop('experiment_group') -RE.md.pop('experiment_cycle') +RE.md.pop('experiment_alias_directory', None) +RE.md.pop('experiment_user', None) +RE.md.pop('experiment_SAF_number', None) +RE.md.pop('experiment_proposal_number', None) +RE.md.pop('experiment_project', None) +RE.md.pop('experiment_group', None) +RE.md.pop('experiment_cycle', None) From 10ac21d1ce292d0a637f193b1b0123cde370e607 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 14 May 2025 15:12:06 -0400 Subject: [PATCH 03/23] WIP: data security 05/14/2025 --- startup/00-startup.py | 247 +++++++++-------------------------- startup/20-area-detectors.py | 153 ++++++++++------------ startup/92-magics.py | 2 +- 3 files changed, 132 insertions(+), 270 deletions(-) diff --git a/startup/00-startup.py b/startup/00-startup.py index 9172316..2723a7d 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -1,204 +1,75 @@ print(f'Loading {__file__}') -# import logging -# import caproto -# handler = logging.FileHandler('pilatus-trigger-log.txt') -# from caproto._log import LogFormatter, color_log_format, log_date_format -# handler.setFormatter( -# LogFormatter(color_log_format, datefmt=log_date_format)) -# caproto_log = logging.getLogger('caproto') -# caproto_log.handlers.clear() -# caproto_log.addHandler(handler) -# logging.getLogger('caproto.ch').setLevel('DEBUG') import nslsii -import redis +import os +from tiled.client import from_profile +from databroker import Broker from redis_json_dict import RedisJSONDict -nslsii.configure_base(get_ipython().user_ns, "cms", publish_documents_with_kafka=True, redis_url="info.cms.nsls2.bnl.gov") - -from bluesky.magics import BlueskyMagics -from bluesky.preprocessors import pchain - -# At the end of every run, verify that files were saved and -# print a confirmation message. -from bluesky.callbacks.broker import verify_files_saved - -# RE.subscribe(post_run(verify_files_saved), 'stop') +from IPython import get_ipython +from IPython.terminal.prompts import Prompts, Token + +class ProposalIDPrompt(Prompts): + def in_prompt_tokens(self, cli=None): + return [ + ( + Token.Prompt, + f"{RE.md.get('data_session', 'N/A')} [", + ), + (Token.PromptNum, str(self.shell.execution_count)), + (Token.Prompt, "]: "), + ] + + +ip = get_ipython() +ip.prompts = ProposalIDPrompt(ip) + +# # Configure a Tiled writing client +# tiled_writing_client = from_profile("nsls2", api_key=os.environ["TILED_BLUESKY_WRITING_API_KEY_CMS"])["cms"]["raw"] + +class TiledInserter: + + name = 'cms' + def insert(self, name, doc): + ATTEMPTS = 20 + error = None + for _ in range(ATTEMPTS): + try: + tiled_writing_client.post_document(name, doc) + except Exception as exc: + print("Document saving failure:", repr(exc)) + error = exc + else: + break + time.sleep(2) + else: + # Out of attempts + raise error + +tiled_inserter = TiledInserter() + +nslsii.configure_base(get_ipython().user_ns, + tiled_inserter, + publish_documents_with_kafka=True, + redis_url="info.cms.nsls2.bnl.gov") + +print("Initializing Tiled reading client...\nMake sure you check for duo push.") +tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] + +db = Broker(tiled_reading_client) from pyOlog.ophyd_tools import * -# Uncomment the following lines to turn on verbose messages for -# debugging. -# import logging -# ophyd.logger.setLevel(logging.DEBUG) -# logging.basicConfig(level=logging.DEBUG) - -# Add a callback that prints scan IDs at the start of each scan. -# def print_scan_ids(name, start_doc): -# print("Transient Scan ID: {0} @ {1}".format(start_doc['scan_id'],time.strftime("%Y/%m/%d %H:%M:%S"))) -# print("Persistent Unique Scan ID: '{0}'".format(start_doc['uid'])) -# -# RE.subscribe(print_scan_ids, 'start') - -# - HACK #1 - patch EpicsSignal.get to retry when timeouts happen stolen from HXN -import ophyd - - -def _epicssignal_get(self, *, as_string=None, connection_timeout=1.0, **kwargs): - """Get the readback value through an explicit call to EPICS - Parameters - ---------- - count : int, optional - Explicitly limit count for array data - as_string : bool, optional - Get a string representation of the value, defaults to as_string - from this signal, optional - as_numpy : bool - Use numpy array as the return type for array data. - timeout : float, optional - maximum time to wait for value to be received. - (default = 0.5 + log10(count) seconds) - use_monitor : bool, optional - to use value from latest monitor callback or to make an - explicit CA call for the value. (default: True) - connection_timeout : float, optional - If not already connected, allow up to `connection_timeout` seconds - for the connection to complete. - """ - if as_string is None: - as_string = self._string - - with self._metadata_lock: - if not self._read_pv.connected: - if not self._read_pv.wait_for_connection(connection_timeout): - raise TimeoutError("Failed to connect to %s" % self._read_pv.pvname) - - ret = None - attempts = 0 - max_attempts = 4 - while ret is None and attempts < max_attempts: - attempts += 1 - ret = self._read_pv.get(as_string=as_string, **kwargs) - if ret is None: - print(f"*** PV GET TIMED OUT {self._read_pv.pvname} *** attempt #{attempts}/{max_attempts}") - if ret is None: - print(f"*** PV GET TIMED OUT {self._read_pv.pvname} *** return `None` as value :(") - # TODO we really want to raise TimeoutError here, but that may cause more - # issues in the codebase than we have the time to fix... - # If this causes issues, remove it to keep the old functionality... - raise TimeoutError("Failed to get %s after %d attempts" % (self._read_pv.pvname, attempts)) - if attempts > 1: - print(f"*** PV GET succeeded {self._read_pv.pvname} on attempt #{attempts}") - - if as_string: - return ophyd.signal.waveform_to_string(ret) - - return ret - - -from ophyd import EpicsSignal -from ophyd import EpicsSignalRO - -# from ophyd import EpicsSignalBase - -from ophyd.areadetector import EpicsSignalWithRBV - # Increase the timeout for EpicsSignal.get() # This beamline was occasionally getting ReadTimeoutErrors -# EpicsSignal.set_defaults(timeout=10) -# EpicsSignalRO.set_defaults(timeout=10) +import ophyd ophyd.signal.EpicsSignalBase.set_defaults(timeout=120) - -# We have commented this because we would like to identify the PVs that are causing problems. -# Then the controls group can investigate why it is not working as expected. -# Increasing the get() timeout argument is the prefered way to work around this problem. -# EpicsSignal.get = _epicssignal_get -# EpicsSignalRO.get = _epicssignal_get -# EpicsSignalWithRBV.get = _epicssignal_get - -from pathlib import Path - -import appdirs - - -try: - from bluesky.utils import PersistentDict -except ImportError: - import msgpack - import msgpack_numpy - import zict - - class PersistentDict(zict.Func): - """ - A MutableMapping which syncs it contents to disk. - The contents are stored as msgpack-serialized files, with one file per item - in the mapping. - Note that when an item is *mutated* it is not immediately synced: - >>> d['sample'] = {"color": "red"} # immediately synced - >>> d['sample']['shape'] = 'bar' # not immediately synced - but that the full contents are synced to disk when the PersistentDict - instance is garbage collected. - """ - - def __init__(self, directory): - self._directory = directory - self._file = zict.File(directory) - self._cache = {} - super().__init__(self._dump, self._load, self._file) - self.reload() - - # Similar to flush() or _do_update(), but without reference to self - # to avoid circular reference preventing collection. - # NOTE: This still doesn't guarantee call on delete or gc.collect()! - # Explicitly call flush() if immediate write to disk required. - def finalize(zfile, cache, dump): - zfile.update((k, dump(v)) for k, v in cache.items()) - - import weakref - - self._finalizer = weakref.finalize(self, finalize, self._file, self._cache, PersistentDict._dump) - - @property - def directory(self): - return self._directory - - def __setitem__(self, key, value): - self._cache[key] = value - super().__setitem__(key, value) - - def __getitem__(self, key): - return self._cache[key] - - def __delitem__(self, key): - del self._cache[key] - super().__delitem__(key) - - def __repr__(self): - return f"<{self.__class__.__name__} {dict(self)!r}>" - - @staticmethod - def _dump(obj): - "Encode as msgpack using numpy-aware encoder." - # See https://github.com/msgpack/msgpack-python#string-and-binary-type - # for more on use_bin_type. - return msgpack.packb(obj, default=msgpack_numpy.encode, use_bin_type=True) - - @staticmethod - def _load(file): - return msgpack.unpackb(file, object_hook=msgpack_numpy.decode, raw=False) - - def flush(self): - """Force a write of the current state to disk""" - for k, v in self.items(): - super().__setitem__(k, v) - - def reload(self): - """Force a reload from disk, overwriting current cache""" - self._cache = dict(super().items()) - - #this replaces RE() < from bluesky.utils import register_transform register_transform('RE', prefix='<') + +# Setup the path to the secure assets folder for the current proposal +def assets_path(): + return f"/nsls2/data/cms/proposals/{RE.md['cycle']}/{RE.md['data_session']}/assets/" \ No newline at end of file diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index b41ffba..cb20ac0 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -225,8 +225,8 @@ def setExposureTime(self, exposure_time, verbosity=3): yield from mv( self.cam.acquire_time, exposure_time, - self.cam.acquire_period, - exposure_time + 0.1, + # self.cam.acquire_period, + # exposure_time + 0.1, ) # caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime', exposure_time) # caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod', exposure_time+0.1) @@ -238,17 +238,6 @@ def setExposureNumber(self, exposure_number, verbosity=3): yield from mv(self.cam.num_images, exposure_number) -# class Pilatus800V33(PilatusV33): -# tiff = Cpt(TIFFPluginWithFileStore, -# suffix='TIFF1:', -# # write_path_template='/Pilatus800K/%Y/%m/%d/', -# # read_path_template='/nsls2/xf11bm/Pilatus800K/%Y/%m/%d/', -# # root='/nsls2/xf11bm') -# write_path_template='/nsls2/data/cms/legacy/xf11bm/Pilatus800/%Y/%m/%d/', -# # read_path_template='/nsls2/data/cms/legacy/xf11bm/Pilatus800/%Y/%m/%d/', -# root='/nsls2/data/cms/legacy/xf11bm') - - class Pilatus800V33(SingleTriggerV33, PilatusDetector): cam = Cpt(PilatusDetectorCamV33, "cam1:") image = Cpt(ImagePlugin, "image1:") @@ -266,21 +255,20 @@ class Pilatus800V33(SingleTriggerV33, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", - # write_path_template='/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/', # GPFS client - # write_path_template='/Pilatus2M/%Y/%m/%d/', # NSF-mount of GPFS directory - # root='/nsls2/xf11bm' - read_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus800/%Y/%m/%d/", - write_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus800/%Y/%m/%d/", - root="/nsls2/data/cms/legacy/xf11bm", ) - # root='/') + + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) def setExposureTime(self, exposure_time, verbosity=3): yield from mv( self.cam.acquire_time, exposure_time, - self.cam.acquire_period, - exposure_time + 0.1, + # self.cam.acquire_period, + # exposure_time + 0.1, ) # self.cam.acquire_time.put(exposure_time) # self.cam.acquire_period.put(exposure_time+.1) @@ -299,15 +287,17 @@ class Pilatus8002V33(PilatusV33): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", - # write_path_template='/Pilatus800K/%Y/%m/%d/', - # read_path_template='/nsls2/xf11bm/Pilatus800K/%Y/%m/%d/', - # root='/nsls2/xf11bm') - write_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus800_2/%Y/%m/%d/", - root="/nsls2/data/cms/legacy/xf11bm", # write_path_template="/ramdisk/", # root="/ramdisk/", ) + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) + + # class Pilatus300V33(PilatusV33): # tiff = Cpt( @@ -340,10 +330,14 @@ class Pilatus2M(SingleTrigger, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", - write_path_template="/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/", - root="/nsls2/xf11bm", ) + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) + def setExposureTime(self, exposure_time, verbosity=3): # how to do this with stage_sigs (warning, need to change this every time # if you set) @@ -378,13 +372,14 @@ class Pilatus2MV33(SingleTriggerV33, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", - # write_path_template='/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/', # GPFS client - # write_path_template='/Pilatus2M/%Y/%m/%d/', # NSF-mount of GPFS directory - # root='/nsls2/xf11bm' - write_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus2M/%Y/%m/%d/", # Lustre client - root="/nsls2/data/cms/legacy/xf11bm", ) - # root='/') + + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) + def setExposureTime(self, exposure_time, verbosity=3): yield from mv( self.cam.acquire_time, @@ -427,20 +422,14 @@ class Pilatus2MV33_h5(SingleTriggerV33, PilatusDetector): h5 = Cpt( HDF5PluginWithFileStore, suffix="HDF1:", - # write_path_template='/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/', # GPFS client - # write_path_template='/Pilatus2M/%Y/%m/%d/', # NSF-mount of GPFS directory - # root='/nsls2/xf11bm' - write_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus2M/%Y/%m/%d/", # Lustre client - root="/nsls2/data/cms/legacy/xf11bm", ) - def setExposureTime(self, exposure_time, verbosity=3): yield from mv( self.cam.acquire_time, exposure_time, - self.cam.acquire_period, - exposure_time + 0.1, + # self.cam.acquire_period, + # exposure_time + 0.1, ) # self.cam.acquire_time.put(exposure_time) # self.cam.acquire_period.put(exposure_time+.1) @@ -454,8 +443,12 @@ def setExposureNumber(self, exposure_number, verbosity=3): yield from mv(self.cam.num_images, exposure_number) def stage(self): - error = None + self.h5.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.h5.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.h5.reg_root = assets_path() + f'{self.name}' + # wrap the staging process in a retry loop + error = None for retry in range(5): try: return super().stage() @@ -465,13 +458,13 @@ def stage(self): # Stash the exception as the variable 'error' error = err else: - # Staging worked. Strop retyring. + # Staging worked. Stop retyring. break else: # We exhausted all retires and none worked. # Raise the error captured above to produce a useful error message. raise error - + return super().stage(*args, **kwargs) # print( 'This is the 20-area-dec py.') # class StandardProsilicaWithTIFF(StandardProsilica): @@ -530,42 +523,42 @@ def stage(self): - fs1.tiff.read_attrs = [] - fs1.stats3.total.kind = "hinted" - fs1.stats4.total.kind = "hinted" - STATS_NAMES = ["stats1", "stats2", "stats3", "stats4", "stats5"] - fs1.read_attrs = ["tiff"] + STATS_NAMES - for stats_name in STATS_NAMES: - stats_plugin = getattr(fs1, stats_name) - stats_plugin.read_attrs = ["total"] + # fs1.tiff.read_attrs = [] + # fs1.stats3.total.kind = "hinted" + # fs1.stats4.total.kind = "hinted" + # STATS_NAMES = ["stats1", "stats2", "stats3", "stats4", "stats5"] + # fs1.read_attrs = ["tiff"] + STATS_NAMES + # for stats_name in STATS_NAMES: + # stats_plugin = getattr(fs1, stats_name) + # stats_plugin.read_attrs = ["total"] - for item in fs1.stats1.configuration_attrs: - item_check = getattr(fs1.stats1, item) - item_check.kind = "omitted" + # for item in fs1.stats1.configuration_attrs: + # item_check = getattr(fs1.stats1, item) + # item_check.kind = "omitted" - for item in fs1.stats2.configuration_attrs: - item_check = getattr(fs1.stats2, item) - item_check.kind = "omitted" + # for item in fs1.stats2.configuration_attrs: + # item_check = getattr(fs1.stats2, item) + # item_check.kind = "omitted" - for item in fs1.stats3.configuration_attrs: - item_check = getattr(fs1.stats3, item) - item_check.kind = "omitted" + # for item in fs1.stats3.configuration_attrs: + # item_check = getattr(fs1.stats3, item) + # item_check.kind = "omitted" - for item in fs1.stats4.configuration_attrs: - item_check = getattr(fs1.stats4, item) - item_check.kind = "omitted" + # for item in fs1.stats4.configuration_attrs: + # item_check = getattr(fs1.stats4, item) + # item_check.kind = "omitted" - for item in fs1.stats5.configuration_attrs: - item_check = getattr(fs1.stats5, item) - item_check.kind = "omitted" + # for item in fs1.stats5.configuration_attrs: + # item_check = getattr(fs1.stats5, item) + # item_check.kind = "omitted" - for item in fs1.tiff.configuration_attrs: - item_check = getattr(fs1.tiff, item) - item_check.kind = "omitted" + # for item in fs1.tiff.configuration_attrs: + # item_check = getattr(fs1.tiff, item) + # item_check.kind = "omitted" - for item in fs1.cam.configuration_attrs: - item_check = getattr(fs1.cam, item) - item_check.kind = "omitted" + # for item in fs1.cam.configuration_attrs: + # item_check = getattr(fs1.cam, item) + # item_check.kind = "omitted" @@ -641,10 +634,8 @@ def stage(self): pilatus300 = "Pil300ISNOTWORKING" # pilatus800 section -# if False: -# if True: if Pilatus800_on == True: - pilatus800 = Pilatus800V33("XF:11BMB-ES{Det:PIL800K}:", name="pilatus800") + pilatus800 = Pilatus800V33("XF:11BMB-ES{Det:PIL800K}:", name="pilatus800k-1") pilatus800.tiff.read_attrs = [] pilatus800.stats3.total.kind = "hinted" pilatus800.stats4.total.kind = "hinted" @@ -689,7 +680,7 @@ def stage(self): # if True: if Pilatus800_2_on == True: # TODO: - pilatus8002 = Pilatus8002V33("XF:11BMB-ES{Det:PIL800K2}:", name="pilatus8002") # change PV + pilatus8002 = Pilatus8002V33("XF:11BMB-ES{Det:PIL800K2}:", name="pilatus800k-2") # change PV pilatus8002.tiff.read_attrs = [] pilatus8002.stats3.total.kind = "hinted" pilatus8002.stats4.total.kind = "hinted" @@ -732,7 +723,7 @@ def stage(self): # pilatus2M section # if False: if Pilatus2M_on == True: - pilatus2M = Pilatus2MV33("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2M") + pilatus2M = Pilatus2MV33("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2m-1") pilatus2M.tiff.read_attrs = [] STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] @@ -775,7 +766,7 @@ def stage(self): elif Pilatus2M_on == 'h5': - pilatus2M = Pilatus2MV33_h5("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2M") + pilatus2M = Pilatus2MV33_h5("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2m-1") pilatus2M.h5.read_attrs = [] STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] diff --git a/startup/92-magics.py b/startup/92-magics.py index 7b2230e..dfb0487 100644 --- a/startup/92-magics.py +++ b/startup/92-magics.py @@ -1,4 +1,4 @@ -# BlueskyMagics were imported and registered in 00-startup.py +from bluesky.magics import BlueskyMagics BlueskyMagics.detectors = [pilatus2M] BlueskyMagics.positioners = [ From 6fd442e444c17c4fd266a1654dd1f768be016646 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Thu, 15 May 2025 09:47:20 -0400 Subject: [PATCH 04/23] WIP: tests of Pilatus detectors --- acceptance_tests/01_count_pilatus300.py | 14 -------------- startup/00-startup.py | 8 ++++---- startup/20-area-detectors.py | 4 ++++ 3 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 acceptance_tests/01_count_pilatus300.py diff --git a/acceptance_tests/01_count_pilatus300.py b/acceptance_tests/01_count_pilatus300.py deleted file mode 100644 index f737a85..0000000 --- a/acceptance_tests/01_count_pilatus300.py +++ /dev/null @@ -1,14 +0,0 @@ -from bluesky.plans import count -from bluesky.plans import bps - - -def pil_test(): - uid = RE(count([pilatus2M])) - h = db[uid[0]] - # print(uid) - print(h.start) - return h - - -def motor_test(pos=0): - RE(bps.mov(smx, pos)) diff --git a/startup/00-startup.py b/startup/00-startup.py index 2723a7d..21e5fca 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -21,7 +21,6 @@ def in_prompt_tokens(self, cli=None): (Token.Prompt, "]: "), ] - ip = get_ipython() ip.prompts = ProposalIDPrompt(ip) @@ -48,16 +47,17 @@ def insert(self, name, doc): raise error tiled_inserter = TiledInserter() +tiled_inserter = "cms" nslsii.configure_base(get_ipython().user_ns, tiled_inserter, publish_documents_with_kafka=True, redis_url="info.cms.nsls2.bnl.gov") -print("Initializing Tiled reading client...\nMake sure you check for duo push.") -tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] +# print("Initializing Tiled reading client...\nMake sure you check for duo push.") +# tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] -db = Broker(tiled_reading_client) +# db = Broker(tiled_reading_client) from pyOlog.ophyd_tools import * diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index cb20ac0..cb8994c 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -255,6 +255,7 @@ class Pilatus800V33(SingleTriggerV33, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", + write_path_template = "", ) def stage(self, *args, **kwargs): @@ -330,6 +331,7 @@ class Pilatus2M(SingleTrigger, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", + write_path_template = "", ) def stage(self, *args, **kwargs): @@ -372,6 +374,7 @@ class Pilatus2MV33(SingleTriggerV33, PilatusDetector): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", + write_path_template = "", ) def stage(self, *args, **kwargs): @@ -422,6 +425,7 @@ class Pilatus2MV33_h5(SingleTriggerV33, PilatusDetector): h5 = Cpt( HDF5PluginWithFileStore, suffix="HDF1:", + write_path_template = "", ) def setExposureTime(self, exposure_time, verbosity=3): From 1e86c51430ca98b6312c07866af2f04d8b18d02c Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Thu, 15 May 2025 17:38:53 -0400 Subject: [PATCH 05/23] WIP: configured detector paths --- acceptance_tests/01_count_pilatus.py | 14 ++++ startup/00-startup.py | 6 +- startup/20-area-detectors.py | 121 +++++++-------------------- 3 files changed, 46 insertions(+), 95 deletions(-) create mode 100644 acceptance_tests/01_count_pilatus.py diff --git a/acceptance_tests/01_count_pilatus.py b/acceptance_tests/01_count_pilatus.py new file mode 100644 index 0000000..f737a85 --- /dev/null +++ b/acceptance_tests/01_count_pilatus.py @@ -0,0 +1,14 @@ +from bluesky.plans import count +from bluesky.plans import bps + + +def pil_test(): + uid = RE(count([pilatus2M])) + h = db[uid[0]] + # print(uid) + print(h.start) + return h + + +def motor_test(pos=0): + RE(bps.mov(smx, pos)) diff --git a/startup/00-startup.py b/startup/00-startup.py index 21e5fca..fb24b23 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -54,10 +54,10 @@ def insert(self, name, doc): publish_documents_with_kafka=True, redis_url="info.cms.nsls2.bnl.gov") -# print("Initializing Tiled reading client...\nMake sure you check for duo push.") -# tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] +print("Initializing Tiled reading client...\nMake sure you check for duo push.") +tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] -# db = Broker(tiled_reading_client) +db = Broker(tiled_reading_client) from pyOlog.ophyd_tools import * diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index cb8994c..f221fb5 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -54,7 +54,7 @@ Pilatus800_2_on = False # Pilatus800_on = False -# Pilatus800_2_on = True +Pilatus800_2_on = True # Pilatus800_2_on = False # Pilatus2M_on = False @@ -87,34 +87,10 @@ def ensure_nonblocking(self): cpt.ensure_nonblocking() -# class Pilatus2M(SingleTrigger, PilatusDetector): -# image = Cpt(ImagePlugin, 'image1:') -# stats1 = Cpt(StatsPlugin, 'Stats1:') -# stats2 = Cpt(StatsPlugin, 'Stats2:') -# stats3 = Cpt(StatsPlugin, 'Stats3:') -# stats4 = Cpt(StatsPlugin, 'Stats4:') -# stats5 = Cpt(StatsPlugin, 'Stats5:') -# roi1 = Cpt(ROIPlugin, 'ROI1:') -# roi2 = Cpt(ROIPlugin, 'ROI2:') -# roi3 = Cpt(ROIPlugin, 'ROI3:') -# roi4 = Cpt(ROIPlugin, 'ROI4:') -# proc1 = Cpt(ProcessPlugin, 'Proc1:') - -# tiff = Cpt(TIFFPluginWithFileStore, -# suffix='TIFF1:', -# # write_path_template='/nsls2/data/cms/legacy/xf11bm/Pilatus2M/%Y/%m/%d/', -# write_path_template='/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/', -# root='/nsls2/xf11bm') - -# def setExposureTime(self, exposure_time, verbosity=3): -# caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime', exposure_time) -# caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod', exposure_time+0.1) - - class StandardProsilica(SingleTrigger, ProsilicaDetector): - # tiff = Cpt(TIFFPluginWithFileStore, - # suffix='TIFF1:', - # write_path_template='/XF11ID/data/') + tiff = Cpt(TIFFPluginWithFileStore, + suffix='TIFF1:', + write_path_template='',) image = Cpt(ImagePlugin, "image1:") stats1 = Cpt(StatsPluginV33, "Stats1:") stats2 = Cpt(StatsPluginV33, "Stats2:") @@ -128,11 +104,17 @@ class StandardProsilica(SingleTrigger, ProsilicaDetector): roi4 = Cpt(ROIPlugin, "ROI4:") proc1 = Cpt(ProcessPlugin, "Proc1:") + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) + class StandardProsilicaV33(SingleTriggerV33, ProsilicaDetector): - # tiff = Cpt(TIFFPluginWithFileStore, - # suffix='TIFF1:', - # write_path_template='/XF11ID/data/') + tiff = Cpt(TIFFPluginWithFileStore, + suffix='TIFF1:', + write_path_template='',) cam = Cpt(ProsilicaDetectorCamV33, "cam1:") image = Cpt(ImagePlugin, "image1:") stats1 = Cpt(StatsPluginV33, "Stats1:") @@ -147,16 +129,11 @@ class StandardProsilicaV33(SingleTriggerV33, ProsilicaDetector): roi4 = Cpt(ROIPlugin, "ROI4:") proc1 = Cpt(ProcessPlugin, "Proc1:") - # tiff = Cpt( - # TIFFPluginWithFileStore, - # suffix="TIFF1:", - # # write_path_template='/nsls2/xf11bm/Pilatus2M/%Y/%m/%d/', # GPFS client - # # write_path_template='/Pilatus2M/%Y/%m/%d/', # NSF-mount of GPFS directory - # # root='/nsls2/xf11bm' - # read_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus2M/%Y/%m/%d/", - # write_path_template="/nsls2/data/cms/legacy/xf11bm/Pilatus2M/%Y/%m/%d/", - # root="/nsls2/data/cms/legacy/xf11bm", - # ) + def stage(self, *args, **kwargs): + self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.tiff.reg_root = assets_path() + f'{self.name}' + return super().stage(*args, **kwargs) class PilatusDetectorCamV33(PilatusDetectorCam): """This is used to update the standard prosilica to AD33.""" @@ -177,29 +154,6 @@ def ensure_nonblocking(self): cpt.ensure_nonblocking() -# class Pilatus(SingleTrigger, PilatusDetector): -# image = Cpt(ImagePlugin, 'image1:') -# stats1 = Cpt(StatsPluginV33, 'Stats1:') -# stats2 = Cpt(StatsPluginV33, 'Stats2:') -# stats3 = Cpt(StatsPluginV33, 'Stats3:') -# stats4 = Cpt(StatsPluginV33, 'Stats4:') -# stats5 = Cpt(StatsPluginV33, 'Stats5:') -# roi1 = Cpt(ROIPlugin, 'ROI1:') -# roi2 = Cpt(ROIPlugin, 'ROI2:') -# roi3 = Cpt(ROIPlugin, 'ROI3:') -# roi4 = Cpt(ROIPlugin, 'ROI4:') -# proc1 = Cpt(ProcessPlugin, 'Proc1:') - -# tiff = Cpt(TIFFPluginWithFileStore, -# suffix='TIFF1:', -# write_path_template='/nsls2/xf11bm/Pilatus300/%Y/%m/%d/', -# root='/nsls2/xf11bm') - -# def setExposureTime(self, exposure_time, verbosity=3): -# caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime', exposure_time) -# caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod', exposure_time+0.1) - - class PilatusV33(SingleTriggerV33, PilatusDetector): cam = Cpt(PilatusDetectorCamV33, "cam1:") image = Cpt(ImagePlugin, "image1:") @@ -288,7 +242,7 @@ class Pilatus8002V33(PilatusV33): tiff = Cpt( TIFFPluginWithFileStore, suffix="TIFF1:", - # write_path_template="/ramdisk/", + write_path_template="", # "/ramdisk/", # root="/ramdisk/", ) @@ -470,28 +424,6 @@ def stage(self): raise error return super().stage(*args, **kwargs) -# print( 'This is the 20-area-dec py.') -# class StandardProsilicaWithTIFF(StandardProsilica): -# tiff = Cpt(TIFFPluginWithFileStore, -# suffix='TIFF1:', -# write_path_template='/nsls2/xf11bm/data/%Y/%m/%d/', -# root='/nsls2/xf11bm/') - - -## This renaming should be reversed: no correspondance between CSS screens, PV names and ophyd.... -# xray_eye1 = StandardProsilica('XF:11IDA-BI{Bpm:1-Cam:1}', name='xray_eye1') -# xray_eye2 = StandardProsilica('XF:11IDB-BI{Mon:1-Cam:1}', name='xray_eye2') -# xray_eye3 = StandardProsilica('XF:11IDB-BI{Cam:08}', name='xray_eye3') -# xray_eye1_writing = StandardProsilicaWithTIFF('XF:11IDA-BI{Bpm:1-Cam:1}', name='xray_eye1') -# xray_eye2_writing = StandardProsilicaWithTIFF('XF:11IDB-BI{Mon:1-Cam:1}', name='xray_eye2') -# xray_eye3_writing = StandardProsilicaWithTIFF('XF:11IDB-BI{Cam:08}', name='xray_eye3') -# fs1 = StandardProsilica('XF:11IDA-BI{FS:1-Cam:1}', name='fs1') -# fs2 = StandardProsilica('XF:11IDA-BI{FS:2-Cam:1}', name='fs2') -# fs_wbs = StandardProsilica('XF:11IDA-BI{BS:WB-Cam:1}', name='fs_wbs') -# dcm_cam = StandardProsilica('XF:11IDA-BI{Mono:DCM-Cam:1}', name='dcm_cam') -# fs_pbs = StandardProsilica('XF:11IDA-BI{BS:PB-Cam:1}', name='fs_pbs') -# elm = Elm('XF:11IDA-BI{AH401B}AH401B:',) - import time @@ -499,14 +431,19 @@ def stage(self): if Camera_on==True: # time.sleep(1) - fs1 = StandardProsilicaV33('XF:11BMA-BI{FS:1-Cam:1}', name='fs1') + fs1 = StandardProsilicaV33('XF:11BMA-BI{FS:1-Cam:1}', name='webcam-1') # time.sleep(1) - fs2 = StandardProsilicaV33("XF:11BMA-BI{FS:2-Cam:1}", name="fs2") + fs2 = StandardProsilicaV33("XF:11BMA-BI{FS:2-Cam:1}", name="webcam-2") # time.sleep(1) - fs3 = StandardProsilicaV33("XF:11BMB-BI{FS:3-Cam:1}", name="fs3") + fs3 = StandardProsilicaV33("XF:11BMB-BI{FS:3-Cam:1}", name="webcam-3") # time.sleep(1) - fs4 = StandardProsilicaV33("XF:11BMB-BI{FS:4-Cam:1}", name="fs4") - fs5 = StandardProsilicaV33("XF:11BMB-BI{FS:Test-Cam:1}", name="fs5") + fs4 = StandardProsilicaV33("XF:11BMB-BI{FS:4-Cam:1}", name="webcam-4") + # mobile camera + fs5 = StandardProsilicaV33("XF:11BMB-BI{FS:Test-Cam:1}", name="webcam-5") + # off-axis camera + fs6 = StandardProsilicaV33("XF:11BMB-BI{OffAxis-Cam:1}", name="webcam-6") + # on-axis camera + fs9 = StandardProsilicaV33("XF:11BMB-BI{OnAxis-Cam:2}", name="webcam-9") all_standard_pros = [fs2, fs3, fs4] From 08aa79defd714a21c4158b3ecc4127bee075e998 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 16 May 2025 11:22:12 -0400 Subject: [PATCH 06/23] WIP: cameras writing to Tiled --- .../94-sample-checkpoint.py | 44 ------ startup/00-startup.py | 7 +- startup/20-area-detectors.py | 129 ++++++------------ startup/86-live-spec.py | 2 +- startup/90-bluesky.py | 3 - startup/94-sample.py | 23 ---- startup/user_collection/user_HighVoltage.py | 2 +- startup/user_collection/user_LinkamTensile.py | 2 +- startup/user_collection/user_LinkamThermal.py | 2 +- .../user_TSAXSWAXS_stitching.py | 2 +- .../user_openMAXS.py.LZhu2022C1 | 2 +- 11 files changed, 53 insertions(+), 165 deletions(-) diff --git a/startup/.ipynb_checkpoints/94-sample-checkpoint.py b/startup/.ipynb_checkpoints/94-sample-checkpoint.py index 743c62c..8206e4c 100644 --- a/startup/.ipynb_checkpoints/94-sample-checkpoint.py +++ b/startup/.ipynb_checkpoints/94-sample-checkpoint.py @@ -1940,53 +1940,9 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p percentage = 100 * (time.time() - start_time) / max_exposure_time print("After re-exposing .... percentage = {} ".format(percentage)) - # if detector.name is 'pilatus300': - # if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is 'pilatus2M': - # if caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is 'pilatus800': - # if caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is 'PhotonicSciences_CMS': - # if not detector.detector_is_ready(verbosity=0): - # status *= 0 - - # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: - # print('Warning: Detector pilatus300 still not done acquiring.') - - # #if verbosity>=3 and caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: - # #print('Warning: Detector pilatus300 still not done acquiring.') - - # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: - # print('Warning: Detector pilatus2M still not done acquiring.') get_beamline().beam.off() - # save the percentage information - # if verbosity>=5: - # folder = '/nsls2/data/cms/legacy/xf11bm/data/2022_1/TKoga2/' - # # filename = '' - - # current_data = {'a_sample': self.name, - # 'b_exposure_time': detector.cam.acquire_time.get(), - # 'c_exposure_percentage': percentage, - # 'd_align_time': md['filename'] - # } - - # temp_data = pds.DataFrame([current_data]) - - # # INT_FILENAME='{}/data/{}.csv'.format(os.path.dirname(__file__) , 'alignment_results.csv') - # INT_FILENAME='{}/data/{}.csv'.format(folder , 'exposure_info.csv') - - # if os.path.isfile(INT_FILENAME): - # output_data = pds.read_csv(INT_FILENAME, index_col=0) - # output_data = output_data.append(temp_data, ignore_index=True) - # output_data.to_csv(INT_FILENAME) - # else: - # temp_data.to_csv(INT_FILENAME) - if handlefile == True: for detector in get_beamline().detector: self.handle_file(detector, extra=extra, verbosity=verbosity, **md) diff --git a/startup/00-startup.py b/startup/00-startup.py index fb24b23..4427948 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -57,7 +57,7 @@ def insert(self, name, doc): print("Initializing Tiled reading client...\nMake sure you check for duo push.") tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] -db = Broker(tiled_reading_client) +# db = Broker(tiled_reading_client) from pyOlog.ophyd_tools import * @@ -71,5 +71,8 @@ def insert(self, name, doc): register_transform('RE', prefix='<') # Setup the path to the secure assets folder for the current proposal +def proposal_path(): + return f"/nsls2/data/cms/proposals/{RE.md['cycle']}/{RE.md['data_session']}/" + def assets_path(): - return f"/nsls2/data/cms/proposals/{RE.md['cycle']}/{RE.md['data_session']}/assets/" \ No newline at end of file + return proposal_path() + "assets/" \ No newline at end of file diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index f221fb5..7f73ef9 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -1,5 +1,5 @@ -# import time as ttime # tea time -# from datetime import datetime +print(f'Loading {__file__}') + from ophyd import ( ProsilicaDetector, SingleTrigger, @@ -28,14 +28,7 @@ from ophyd.areadetector.filestore_mixins import FileStoreHDF5IterativeWrite from ophyd.areadetector.plugins import HDF5Plugin #,register_plugin,PluginBase - -print(f'Loading {__file__}') - -# import filestore.api as fs - - -# class Elm(SingleTrigger, DetectorBase): -# pass +import time Pilatus2M_on = True @@ -61,9 +54,30 @@ ''' H5Plugin = HDF5Plugin ''' - class TIFFPluginWithFileStore(TIFFPlugin, FileStoreTIFFIterativeWrite): - pass + def describe(self): + ret = super().describe() + key = self.parent._image_name + color_mode = self.parent.cam.color_mode.get(as_string=True) + if color_mode == 'Mono': + ret[key]['shape'] = [ + self.parent.cam.num_images.get(), + #self.array_size.depth.get(), + self.array_size.height.get(), + self.array_size.width.get() + ] + + elif color_mode in ['RGB1', 'Bayer']: + ret[key]['shape'] = [self.parent.cam.num_images.get(), *self.array_size.get()] + else: + raise RuntimeError("Color mode not supported") + + cam_dtype = self.parent.cam.data_type.get(as_string=True) + type_map = {'UInt8': '|u1', 'UInt16': '=5: - # folder = '/nsls2/data/cms/legacy/xf11bm/data/2022_1/TKoga2/' - # # filename = '' - - # current_data = {'a_sample': self.name, - # 'b_exposure_time': detector.cam.acquire_time.get(), - # 'c_exposure_percentage': percentage, - # 'd_align_time': md['filename'] - # } - - # temp_data = pds.DataFrame([current_data]) - - # # INT_FILENAME='{}/data/{}.csv'.format(os.path.dirname(__file__) , 'alignment_results.csv') - # INT_FILENAME='{}/data/{}.csv'.format(folder , 'exposure_info.csv') - - # if os.path.isfile(INT_FILENAME): - # output_data = pds.read_csv(INT_FILENAME, index_col=0) - # output_data = output_data.append(temp_data, ignore_index=True) - # output_data.to_csv(INT_FILENAME) - # else: - # temp_data.to_csv(INT_FILENAME) - if handlefile == True: for detector in get_beamline().detector: self.handle_file(detector, extra=extra, verbosity=verbosity, **md) diff --git a/startup/user_collection/user_HighVoltage.py b/startup/user_collection/user_HighVoltage.py index 4d24780..31933f5 100644 --- a/startup/user_collection/user_HighVoltage.py +++ b/startup/user_collection/user_HighVoltage.py @@ -789,7 +789,7 @@ def waxs_on_inner(): # for inner-outer stitching # cms.SAXS.setCalibration([748, 1680-590], 2.01, [-64, -73]) # 13.5 keV cms.SAXS.setCalibration([761, 1680 - 606], 5.0, [-65, -73]) -RE.md["experiment_alias_directory"] = "/nsls2/data/cms/legacy/xf11bm/data/2022_1/LZhu2/" +RE.md["experiment_alias_directory"] = proposal_path() + "experiments/LZhu2/" if True: cali = CapillaryHolder(base=stg) diff --git a/startup/user_collection/user_LinkamTensile.py b/startup/user_collection/user_LinkamTensile.py index 3a8b173..08aed26 100644 --- a/startup/user_collection/user_LinkamTensile.py +++ b/startup/user_collection/user_LinkamTensile.py @@ -39,7 +39,7 @@ # RE.install_suspender(sus) -RE.md["experiment_alias_directory"] = "/nsls2/data/cms/legacy/xf11bm/data/2023_1/beamline" +RE.md["experiment_alias_directory"] = proposal_path() + "experiments/beamline" # cms.SAXS.setCalibration([737, 1680-582], 3, [-65, -73]) #3m, 13.5kev # cms.SAXS.setCalibration([738, 1097], 3.0, [-65, -73]) #3m,13.5kev # cms.SAXS.setCalibration([738, 1680-590], 2, [-65, -73]) diff --git a/startup/user_collection/user_LinkamThermal.py b/startup/user_collection/user_LinkamThermal.py index d5c7cbc..c3f570d 100644 --- a/startup/user_collection/user_LinkamThermal.py +++ b/startup/user_collection/user_LinkamThermal.py @@ -25,7 +25,7 @@ # RE.install_suspender(sus) -RE.md["experiment_alias_directory"] = "/nsls2/data/cms/legacy/xf11bm/data/2023_1/beamline/Commissioning" +RE.md["experiment_alias_directory"] = proposal_path() + "experiments/beamline/Commissioning" # cms.SAXS.setCalibration([737, 1680-582], 3, [-65, -73]) #3m, 13.5kev # cms.SAXS.setCalibration([738, 1097], 3.0, [-65, -73]) #3m,13.5kev # cms.SAXS.setCalibration([738, 1680-590], 2, [-65, -73]) diff --git a/startup/user_collection/user_TSAXSWAXS_stitching.py b/startup/user_collection/user_TSAXSWAXS_stitching.py index 6ef33d0..ea7cb57 100755 --- a/startup/user_collection/user_TSAXSWAXS_stitching.py +++ b/startup/user_collection/user_TSAXSWAXS_stitching.py @@ -787,7 +787,7 @@ def waxs_on_inner(): # for inner-outer stitching # cms.SAXS.setCalibration([748, 1680-590], 2.01, [-64, -73]) # 13.5 keV cms.SAXS.setCalibration([761, 1680 - 606], 5.0, [-65, -73]) -RE.md["experiment_alias_directory"] = "/nsls2/data/cms/legacy/xf11bm/data/2022_1/LZhu/" +RE.md["experiment_alias_directory"] = proposal_path() + "experiments/LZhu/" if True: cali = CapillaryHolder(base=stg) diff --git a/startup/user_collection/user_openMAXS.py.LZhu2022C1 b/startup/user_collection/user_openMAXS.py.LZhu2022C1 index eed9dea..8c9cf0c 100644 --- a/startup/user_collection/user_openMAXS.py.LZhu2022C1 +++ b/startup/user_collection/user_openMAXS.py.LZhu2022C1 @@ -766,7 +766,7 @@ def waxs_on_inner(): #for inner-outer stitching #cms.SAXS.setCalibration([748, 1680-590], 2.01, [-64, -73]) # 13.5 keV cms.SAXS.setCalibration([761, 1680-606], 5.8, [-65, -73]) -RE.md['experiment_alias_directory'] = '/nsls2/data/cms/legacy/xf11bm/data/2022_1/LZhu3/' +RE.md['experiment_alias_directory'] = proposal_path() + "experiments/LZhu3/" if True: From c5915c77032528747490b83f3e6c94d9bacaaf14 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 16 May 2025 17:17:12 -0400 Subject: [PATCH 07/23] Prefect testing --- .gitignore | 1 + startup/02-data_security.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5e04c34..f0f67c5 100755 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ doc/source/_generated_images/* # Backups *.pybak +.ipynb_checkpoints/ \ No newline at end of file diff --git a/startup/02-data_security.py b/startup/02-data_security.py index 3b64749..4fb2bb0 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -7,3 +7,6 @@ RE.md.pop('experiment_project', None) RE.md.pop('experiment_group', None) RE.md.pop('experiment_cycle', None) + +RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delte this +RE.md['savename'] = "testname" \ No newline at end of file From 4c8a9d6650c0745c7bae7726ae905136043ba7bd Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Mon, 28 Jul 2025 10:08:34 -0400 Subject: [PATCH 08/23] MNT: modified gitignore --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f0f67c5..aea5894 100755 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,8 @@ doc/source/_generated_images/* # Backups *.pybak -.ipynb_checkpoints/ \ No newline at end of file +.ipynb_checkpoints/ +*.visionenv + +# Audio Files +*.wav \ No newline at end of file From ada43007e9a86a8fe1dee9d0973583ceb35812f4 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Mon, 28 Jul 2025 15:41:55 -0400 Subject: [PATCH 09/23] ENH: Data Security 07/28/2025 --- startup/.cms_config | 3 +- .../94-sample-checkpoint.py | 104 +++++++++--------- startup/00-startup.py | 9 +- startup/20-area-detectors.py | 13 ++- startup/43-endstation-ioLogik.py | 2 + startup/81-beam.py | 16 +-- startup/82-beamstop.py | 8 +- startup/90-bluesky.py | 2 +- startup/94-sample.py | 89 ++++++++------- startup/94-sample.py.save | 26 ++--- startup/95-sample-custom.py | 2 +- startup/992-PTA-mobile.py | 2 +- startup/beamstop_config.cfg | 8 ++ startup/user_collection/user_LinkamThermal.py | 6 +- 14 files changed, 159 insertions(+), 131 deletions(-) diff --git a/startup/.cms_config b/startup/.cms_config index a967cfe..6a7ca25 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1231,4 +1231,5 @@ 1229,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.701,Mon May 12 02:42:31 2025 1230,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.701,Tue May 13 21:47:30 2025 1231,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-15.600142,Tue May 13 21:55:17 2025 -1232,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-15.600674999999999,Tue May 13 21:56:00 2025 +1232,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-15.600675,Tue May 13 21:56:00 2025 +1233,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Mon Jul 28 11:38:49 2025 diff --git a/startup/.ipynb_checkpoints/94-sample-checkpoint.py b/startup/.ipynb_checkpoints/94-sample-checkpoint.py index 8206e4c..15677a2 100644 --- a/startup/.ipynb_checkpoints/94-sample-checkpoint.py +++ b/startup/.ipynb_checkpoints/94-sample-checkpoint.py @@ -1746,9 +1746,9 @@ def get_measurement_md(self, prefix=None, **md): # md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV') # md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{}:cam1:FileNumber_RBV'.format(pilatus_Epicsname)) - if get_beamline().detector[0].name is "pilatus300": + if get_beamline().detector[0].name is "pilatus300k-1": md_current["detector_sequence_ID"] = caget("XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV") - elif get_beamline().detector[0].name is "pilatus2M": + elif get_beamline().detector[0].name is "pilatus2m-1": md_current["detector_sequence_ID"] = caget("XF:11BMB-ES{Det:PIL2M}:cam1:FileNumber_RBV") md_current.update(get_beamline().get_md()) @@ -1778,10 +1778,10 @@ def _expose_manual(self, exposure_time=None, verbosity=3, poling_period=0.1, **m # caput('XF:11BMB-ES{}:cam1:AcquireTime'.format(pilatus_Epicsname), exposure_time) # caput('XF:11BMB-ES{}:cam1:AcquirePeriod'.format(pilatus_Epicsname), exposure_time+0.1) - if get_beamline().detector[0].name is "pilatus300": + if get_beamline().detector[0].name is "pilatus300k-1": caput("XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime", exposure_time) caput("XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod", exposure_time + 0.1) - elif get_beamline().detector[0].name is "pilatus2M": + elif get_beamline().detector[0].name is "pilatus2m-1": caput("XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime", exposure_time) caput("XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod", exposure_time + 0.1) @@ -1826,9 +1826,9 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus800' and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus300' and exposure_time != detector.cam.acquire_time.get(): + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) @@ -1864,23 +1864,23 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name is "pilatus300": + if detector.name is "pilatus300k-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus800" or detector.name is "pilatus8002": + elif detector.name is "pilatus800k-1" or detector.name is "pilatus800k-2": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - # if detector.name is 'pilatus300': + # if detector.name is "pilatus300k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus2M': + # elif detector.name is "pilatus2m-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) # elif detector.name is 'PhotonicSciences_CMS': @@ -1927,13 +1927,13 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name is "pilatus300": + if detector.name is "pilatus300k-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus800" or detector.name is "pilatus8002": + elif detector.name is "pilatus800k-1" or detector.name is "pilatus800k-2": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) @@ -1967,9 +1967,9 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus800' and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus300' and exposure_time != detector.cam.acquire_time.get(): + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) @@ -2009,13 +2009,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name is "pilatus300": + if detector.name is "pilatus300k-1": current_exposure_time = caget("XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": current_exposure_time = caget("XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is "pilatus800": + elif detector.name is "pilatus800k-1": current_exposure_time = caget("XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) # elif detector.name is 'PhotonicSciences_CMS': @@ -2038,13 +2038,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit status = 1 for detector in get_beamline().detector: - if detector.name is "pilatus300": + if detector.name is "pilatus300k-1": if caget("XF:11BMB-ES{Det:SAXS}:cam1:Acquire") == 1: status *= 0 - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": if caget("XF:11BMB-ES{Det:PIL2M}:cam1:Acquire") == 1: status *= 0 - elif detector.name is "pilatus800": + elif detector.name is "pilatus800k-1": if caget("XF:11BMB-ES{Det:PIL800K}:cam1:Acquire") == 1: status *= 0 # elif detector.name is 'PhotonicSciences_CMS': @@ -2073,13 +2073,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" if subdirs: - if detector.name is "pilatus300" or detector.name is "pilatus8002": + if detector.name is "pilatus300k-1" or detector.name is "pilatus800k-2": subdir = "/maxs/raw/" detname = "maxs" - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": subdir = "/saxs/raw/" detname = "saxs" - elif detector.name is "pilatus800": + elif detector.name is "pilatus800k-1": subdir = "/waxs/raw/" detname = "waxs" else: @@ -2125,7 +2125,7 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" - if detector.name is "pilatus300" or detector.name is "pilatus8002": + if detector.name is "pilatus300k-1" or detector.name is "pilatus800k-2": # chars = caget('XF:11BMB-ES{Det:SAXS}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] filename = detector.tiff.full_file_name.get() # RL, 20210831 @@ -2165,7 +2165,7 @@ def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, link if verbosity >= 3: print(" Data linked as: {}".format(link_name)) - elif detector.name is "pilatus2M": + elif detector.name is "pilatus2m-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL2M}:TIFF1:FullFileName_RBV') @@ -2212,7 +2212,7 @@ def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, link if verbosity >= 3: print(" Data linked as: {}".format(link_name)) - elif detector.name is "pilatus800": + elif detector.name is "pilatus800k-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') @@ -2763,7 +2763,7 @@ def measureRock( exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus300' and exposure_time != detector.cam.acquire_time.get(): + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) @@ -2800,16 +2800,16 @@ def measureRock( # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name is "pilatus300" or "pilatus800" or "pilatus2M" or "pilatus8002": + if detector.name is "pilatus300k-1" or "pilatus800k-1" or "pilatus2m-1" or "pilatus800k-2": max_exposure_time = detector.cam.acquire_time.get() - # if detector.name is 'pilatus300': + # if detector.name is "pilatus300k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus2M': + # elif detector.name is "pilatus2m-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) # elif detector.name is 'PhotonicSciences_CMS': @@ -2834,13 +2834,13 @@ def measureRock( if detector.cam.acquire.get(): status *= 0 - # if detector.name is 'pilatus300': + # if detector.name is "pilatus300k-1": # if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: # status *= 0 - # elif detector.name is 'pilatus2M': + # elif detector.name is "pilatus2m-1": # if caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: # status *= 0 - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # if caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: # status *= 0 # elif detector.name is 'PhotonicSciences_CMS': @@ -3015,7 +3015,7 @@ def _test_expose( # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name is "pilatus300" or "pilatus2M": + if detector.name is "pilatus300k-1" or "pilatus2m-1": current_exposure_time = caget("XF:11BMB-ES{}:cam1:AcquireTime".format(pilatus_Epicsname)) max_exposure_time = max(max_exposure_time, current_exposure_time) elif detector.name is "PhotonicSciences_CMS": @@ -3044,7 +3044,7 @@ def _test_expose( status = 1 for detector in get_beamline().detector: - if detector.name is "pilatus300" or "pilatus2M": + if detector.name is "pilatus300k-1" or "pilatus2m-1": print("status2.5 = ", status) if caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: status = 0 @@ -3548,28 +3548,28 @@ def series_measure( print("handling the file names") self.handle_fileseries(detector, num_frames=num_frames, extra=extra, verbosity=verbosity, **md) - # if detector.name is 'pilatus2M': + # if detector.name is "pilatus2m-1": # caput('XF:11BMB-ES{Det:PIL2M}:cam1:NumImages', 1) - # if detector.name is 'pilatus300' : + # if detector.name is "pilatus300k-1" : # caput('XF:11BMB-ES{Det:SAXS}:cam1:NumImages', 1) - # if detector.name is 'pilatus800' : + # if detector.name is "pilatus800k-1" : # caput('XF:11BMB-ES{Det:PIL800K}:cam1:NumImages', 1) def initialDetector(self): # reset the num_frame back to 1 for detector in get_beamline().detector: detector.cam.num_images.put(1) - # if detector.name is 'pilatus2M': + # if detector.name is "pilatus2m-1": # caput('XF:11BMB-ES{Det:PIL2M}:cam1:NumImages', 1) - # if detector.name is 'pilatus300' : + # if detector.name is "pilatus300k-1" : # caput('XF:11BMB-ES{Det:SAXS}:cam1:NumImages', 1) - # if detector.name is 'pilatus800' : + # if detector.name is "pilatus800k-1" : # caput('XF:11BMB-ES{Det:PIL800K}:cam1:NumImages', 1) def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, subdirs=True, **md): subdir = "" - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": # chars = caget('XF:11BMB-ES{Det:SAXS}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] # filename_part1 = ''.join(chr(char) for char in chars)[:-13] @@ -3622,7 +3622,7 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit if num_frame == 0 or num_frame == np.max(num_frames): print(" Data {} linked as: {}".format(filename_new, link_name_new)) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": # chars = caget('XF:11BMB-ES{Det:PIL2M}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] # filename_part1 = ''.join(chr(char) for char in chars)[:-13] @@ -3679,12 +3679,12 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit if num_frame == 0 or num_frame == np.max(num_frames): print(" Data {} linked as: {}".format(filename_new, link_name_new)) - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] # filename_part1 = ''.join(chr(char) for char in chars)[:-13] - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') @@ -3751,15 +3751,15 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, subdirs=True, **md): subdir = "" if subdirs: - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": subdir = "/maxs/raw/" detname = "maxs" print("{} data handling".format(detector.name)) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": subdir = "/saxs/raw/" detname = "saxs" print("pilatus2M data handling") - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": subdir = "/waxs/raw/" detname = "waxs" print("pilatus800k data handling") diff --git a/startup/00-startup.py b/startup/00-startup.py index 4427948..0543680 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -3,6 +3,8 @@ import nslsii import os +os.environ.pop('TILED_API_KEY') + from tiled.client import from_profile from databroker import Broker from redis_json_dict import RedisJSONDict @@ -24,8 +26,8 @@ def in_prompt_tokens(self, cli=None): ip = get_ipython() ip.prompts = ProposalIDPrompt(ip) -# # Configure a Tiled writing client -# tiled_writing_client = from_profile("nsls2", api_key=os.environ["TILED_BLUESKY_WRITING_API_KEY_CMS"])["cms"]["raw"] +# Configure a Tiled writing client +tiled_writing_client = from_profile("nsls2", api_key=os.environ["TILED_BLUESKY_WRITING_API_KEY_CMS"])["cms"]["raw"] class TiledInserter: @@ -47,7 +49,6 @@ def insert(self, name, doc): raise error tiled_inserter = TiledInserter() -tiled_inserter = "cms" nslsii.configure_base(get_ipython().user_ns, tiled_inserter, @@ -55,7 +56,7 @@ def insert(self, name, doc): redis_url="info.cms.nsls2.bnl.gov") print("Initializing Tiled reading client...\nMake sure you check for duo push.") -tiled_reading_client = from_profile("nsls2", username=None, include_data_sources=True)["cms"]["raw"] +tiled_reading_client = cat = from_profile("nsls2", username=None)["cms"]["raw"] # db = Broker(tiled_reading_client) diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index 7f73ef9..3fe5438 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -151,6 +151,15 @@ def stage(self, *args, **kwargs): self.tiff.reg_root = assets_path() + f'{self.name}' return super().stage(*args, **kwargs) + def setExposureTime(self, exposure_time, verbosity=3): + yield from mv(self.cam.acquire_time, self.cam.acquire_time.get()) # noop + + def setExposurePeriod(self, exposure_period, verbosity=3): + yield from mv(self.cam.acquire_period, self.cam.acquire_period.get()) # noop + + def setExposureNumber(self, exposure_number, verbosity=3): + yield from mv(self.cam.num_images, self.cam.num_images.get()) # noop + class PilatusDetectorCamV33(PilatusDetectorCam): """This is used to update the standard prosilica to AD33.""" @@ -514,8 +523,8 @@ def stage(self): # pilatus300 section # if True: if Pilatus300_on == True: - pilatus300 = Pilatus300V33("XF:11BMB-ES{Det:SAXS}:", name="pilatus300") - # pilatus300 = PilatusV33('XF:11BMB-ES{Det:SAXS}:', name='pilatus300') + pilatus300 = Pilatus300V33("XF:11BMB-ES{Det:SAXS}:", name="pilatus300k-1") + # pilatus300 = PilatusV33('XF:11BMB-ES{Det:SAXS}:', name="pilatus300k-1") pilatus300.tiff.read_attrs = [] pilatus300.stats3.total.kind = "hinted" pilatus300.stats4.total.kind = "hinted" diff --git a/startup/43-endstation-ioLogik.py b/startup/43-endstation-ioLogik.py index 3e7fc00..c5b6b21 100644 --- a/startup/43-endstation-ioLogik.py +++ b/startup/43-endstation-ioLogik.py @@ -1,3 +1,5 @@ +print(f'Loading {__file__}') + import time from ophyd import Device diff --git a/startup/81-beam.py b/startup/81-beam.py index 785870b..234ea5f 100644 --- a/startup/81-beam.py +++ b/startup/81-beam.py @@ -3350,13 +3350,13 @@ def setDirectBeamROI(self, size=[10, 4], verbosity=3): The size is changed to [10, 4] for possible beam drift during a user run (changed at 08/16/17) """ - if pilatus_name.name == "pilatus2M": + if pilatus_name.name == "pilatus2m-1": detector = self.SAXS # These positions are updated based on current detector position det_md = detector.get_md() x0 = det_md["detector_SAXS_x0_pix"] y0 = det_md["detector_SAXS_y0_pix"] - if pilatus_name.name == "pilatus800": + if pilatus_name.name == "pilatus800k-1": detector = self.WAXS # These positions are updated based on current detector position @@ -3385,13 +3385,13 @@ def setReflectedBeamROI(self, total_angle=0.16, size=[10, 2], verbosity=3): The size argument controls the size (in pixels) of the ROI itself (in the format [width, height]). A size=[6,2] is reasonable.""" - if pilatus_name.name == "pilatus2M": + if pilatus_name.name == "pilatus2m-1": detector = self.SAXS # These positions are updated based on current detector position det_md = detector.get_md() x0 = det_md["detector_SAXS_x0_pix"] y0 = det_md["detector_SAXS_y0_pix"] - if pilatus_name.name == "pilatus800": + if pilatus_name.name == "pilatus800k-1": detector = self.WAXS # These positions are updated based on current detector position @@ -3438,13 +3438,13 @@ def setROI2ReflectBeamROI(self, total_angle=0.16, size=[10, 100], verbosity=3): The size argument controls the size (in pixels) of the ROI itself (in the format [width, height]). A size=[6,2] is reasonable.""" - if pilatus_name.name == "pilatus2M": + if pilatus_name.name == "pilatus2m-1": detector = self.SAXS # These positions are updated based on current detector position det_md = detector.get_md() x0 = det_md["detector_SAXS_x0_pix"] y0 = det_md["detector_SAXS_y0_pix"] - if pilatus_name.name == "pilatus800": + if pilatus_name.name == "pilatus800k-1": detector = self.WAXS # These positions are updated based on current detector position @@ -3521,14 +3521,14 @@ def setSpecularReflectivityROI(self, total_angle=0.16, size=[10, 10], default_SA y_offset_pix = y_offset_mm / pixel_size # for pilatus800k - if pilatus_name.name == "pilatus800": + if pilatus_name.name == "pilatus800k-1": y_pos = int(y0 - size[1] / 2 - y_offset_pix) # for pilatus2M, placed up-side down # y_pos = int( y0 - size[1]/2 + y_offset_pix ) # for pilatus2M, with pattern rotated 180deg. changed at 052918 - if pilatus_name.name == "pilatus2M": + if pilatus_name.name == "pilatus2m-1": y_pos = int(y0 - size[1] / 2 - y_offset_pix) # y pixels for intermodule gaps, for pilatus2M (195 pixels high module, 17 pixels high gap) diff --git a/startup/82-beamstop.py b/startup/82-beamstop.py index ac0c3c5..fc20475 100644 --- a/startup/82-beamstop.py +++ b/startup/82-beamstop.py @@ -5,8 +5,10 @@ from pathlib import Path from datetime import datetime +folder = '/home/xf11bm/.ipython/profile_collection/startup/' + class Beamstop: - def __init__(self, name, config_file='beamstop_config.cfg'): + def __init__(self, name, config_file = folder + 'beamstop_config.cfg'): self.name = name self.config_file = Path(config_file) self.bsx = bsx.position @@ -16,12 +18,12 @@ def __init__(self, name, config_file='beamstop_config.cfg'): self.load() @classmethod - def get(cls, name, config_file='beamstop_config.cfg'): + def get(cls, name, config_file = folder + 'beamstop_config.cfg'): print(f"Set current beamstop to '{name}' without moving.") return cls(name, config_file=config_file) @classmethod - def goto(cls, name, config_file='beamstop_config.cfg'): + def goto(cls, name, config_file = folder + 'beamstop_config.cfg'): bs = cls(name, config_file=config_file) # bs._move() RE(bs._move()) diff --git a/startup/90-bluesky.py b/startup/90-bluesky.py index d6c3728..d23c338 100644 --- a/startup/90-bluesky.py +++ b/startup/90-bluesky.py @@ -369,7 +369,7 @@ def config_update(): current_config = { "bsx_pos": cms.bsx_pos, - #'armr_absorber_o':beam.armr_absorber_o, + 'armr_absorber_o':beam.armr_absorber_o, "_delta_y_hover": robot._delta_y_hover, "_delta_y_slot": robot._delta_y_slot, "_delta_garage_x": robot._delta_garage_x, diff --git a/startup/94-sample.py b/startup/94-sample.py index 5a21263..dc70300 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1782,9 +1782,9 @@ def get_measurement_md(self, prefix=None, **md): # md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV') # md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{}:cam1:FileNumber_RBV'.format(pilatus_Epicsname)) - if get_beamline().detector[0].name == "pilatus300": + if get_beamline().detector[0].name == "pilatus300k-1": md_current["detector_sequence_ID"] = caget("XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV") - elif get_beamline().detector[0].name == "pilatus2M": + elif get_beamline().detector[0].name == "pilatus2m-1": md_current["detector_sequence_ID"] = caget("XF:11BMB-ES{Det:PIL2M}:cam1:FileNumber_RBV") md_current.update(get_beamline().get_md()) @@ -1814,10 +1814,10 @@ def _expose_manual(self, exposure_time=None, verbosity=3, poling_period=0.1, **m # caput('XF:11BMB-ES{}:cam1:AcquireTime'.format(pilatus_Epicsname), exposure_time) # caput('XF:11BMB-ES{}:cam1:AcquirePeriod'.format(pilatus_Epicsname), exposure_time+0.1) - if get_beamline().detector[0].name == "pilatus300": + if get_beamline().detector[0].name == "pilatus300k-1": caput("XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime", exposure_time) caput("XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod", exposure_time + 0.1) - elif get_beamline().detector[0].name == "pilatus2M": + elif get_beamline().detector[0].name == "pilatus2m-1": caput("XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime", exposure_time) caput("XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod", exposure_time + 0.1) @@ -1862,9 +1862,9 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus800' and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus300' and exposure_time != detector.cam.acquire_time.get(): + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) @@ -1900,23 +1900,28 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name == "pilatus300": + print('here in expose:', detector.name) + if detector.name == "pilatus300k-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus800" or detector.name == "pilatus8002": + elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - # if detector.name is 'pilatus300': + elif 'webcam' in detector.name: + current_exposure_time = detector.cam.acquire_time.get() + max_exposure_time = max(max_exposure_time, current_exposure_time) + + # if detector.name is "pilatus300k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus2M': + # elif detector.name is "pilatus2m-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # current_exposure_time = caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime') # max_exposure_time = max(max_exposure_time, current_exposure_time) # elif detector.name is 'PhotonicSciences_CMS': @@ -1963,26 +1968,26 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name == "pilatus300": + if detector.name == "pilatus300k-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus800" or detector.name == "pilatus8002": + elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) percentage = 100 * (time.time() - start_time) / max_exposure_time print("After re-exposing .... percentage = {} ".format(percentage)) - # if detector.name is 'pilatus300': + # if detector.name is "pilatus300k-1": # if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: # status *= 0 - # elif detector.name is 'pilatus2M': + # elif detector.name is "pilatus2m-1": # if caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: # status *= 0 - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # if caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: # status *= 0 # elif detector.name is 'PhotonicSciences_CMS': @@ -2024,9 +2029,9 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus800' and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is 'pilatus300' and exposure_time != detector.cam.acquire_time.get(): + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) @@ -2066,13 +2071,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - if detector.name == "pilatus300": + if detector.name == "pilatus300k-1": current_exposure_time = caget("XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": current_exposure_time = caget("XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": current_exposure_time = caget("XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime") max_exposure_time = max(max_exposure_time, current_exposure_time) # elif detector.name is 'PhotonicSciences_CMS': @@ -2095,13 +2100,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit status = 1 for detector in get_beamline().detector: - if detector.name == "pilatus300": + if detector.name == "pilatus300k-1": if caget("XF:11BMB-ES{Det:SAXS}:cam1:Acquire") == 1: status *= 0 - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": if caget("XF:11BMB-ES{Det:PIL2M}:cam1:Acquire") == 1: status *= 0 - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": if caget("XF:11BMB-ES{Det:PIL800K}:cam1:Acquire") == 1: status *= 0 # elif detector.name is 'PhotonicSciences_CMS': @@ -2130,13 +2135,13 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" if subdirs: - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": subdir = "/maxs/raw/" detname = "maxs" - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": subdir = "/saxs/raw/" detname = "saxs" - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": subdir = "/waxs/raw/" detname = "waxs" else: @@ -2191,7 +2196,7 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": # chars = caget('XF:11BMB-ES{Det:SAXS}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] filename = detector.tiff.full_file_name.get() # RL, 20210831 @@ -2231,7 +2236,7 @@ def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, link if verbosity >= 3: print(" Data linked as: {}".format(link_name)) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL2M}:TIFF1:FullFileName_RBV') @@ -2278,7 +2283,7 @@ def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, link if verbosity >= 3: print(" Data linked as: {}".format(link_name)) - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') @@ -2851,7 +2856,7 @@ def measureRock( # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name in {"pilatus300", "pilatus800", "pilatus2M", "pilatus8002"}: + if detector.name in {"pilatus300k-1", "pilatus800k-1", "pilatus2m-1", "pilatus800k-2"}: max_exposure_time = detector.cam.acquire_time.get() else: if verbosity >= 1: @@ -3016,7 +3021,7 @@ def _test_expose( # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name in {"pilatus300", "pilatus2M"}: + if detector.name in {"pilatus300k-1", "pilatus2m-1"}: current_exposure_time = caget("XF:11BMB-ES{}:cam1:AcquireTime".format(pilatus_Epicsname)) max_exposure_time = max(max_exposure_time, current_exposure_time) elif detector.name == "PhotonicSciences_CMS": @@ -3045,7 +3050,7 @@ def _test_expose( status = 1 for detector in get_beamline().detector: - if detector.name == "pilatus300" or "pilatus2M": + if detector.name == "pilatus300k-1" or "pilatus2m-1": print("status2.5 = ", status) if caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: status = 0 @@ -3524,7 +3529,7 @@ def initialDetector(self): def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, subdirs=True, **md): subdir = "" - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": filename = detector.tiff.full_file_name.get() # RL, 20210831 print("pilatus300k data handling") @@ -3573,7 +3578,7 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit if num_frame == 0 or num_frame == np.max(num_frames): print(" Data {} linked as: {}".format(filename_new, link_name_new)) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": # chars = caget('XF:11BMB-ES{Det:PIL2M}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] # filename_part1 = ''.join(chr(char) for char in chars)[:-13] @@ -3630,12 +3635,12 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit if num_frame == 0 or num_frame == np.max(num_frames): print(" Data {} linked as: {}".format(filename_new, link_name_new)) - # elif detector.name is 'pilatus800': + # elif detector.name is "pilatus800k-1": # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') # filename = ''.join(chr(char) for char in chars)[:-1] # filename_part1 = ''.join(chr(char) for char in chars)[:-13] - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": foldername = "/nsls2/xf11bm/" # chars = caget('XF:11BMB-ES{Det:PIL800K}:TIFF1:FullFileName_RBV') @@ -3702,15 +3707,15 @@ def _old_handle_fileseries(self, detector, num_frames=None, extra=None, verbosit def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, subdirs=True, **md): subdir = "" if subdirs: - if detector.name == "pilatus300" or detector.name == "pilatus8002": + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": subdir = "/maxs/raw/" detname = "maxs" print("{} data handling".format(detector.name)) - elif detector.name == "pilatus2M": + elif detector.name == "pilatus2m-1": subdir = "/saxs/raw/" detname = "saxs" print("pilatus2M data handling") - elif detector.name == "pilatus800": + elif detector.name == "pilatus800k-1": subdir = "/waxs/raw/" detname = "waxs" print("pilatus800k data handling") diff --git a/startup/94-sample.py.save b/startup/94-sample.py.save index e8f62b6..cf515c2 100755 --- a/startup/94-sample.py.save +++ b/startup/94-sample.py.save @@ -1294,9 +1294,9 @@ class Sample_Generic(CoordinateSystem): #md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV') #md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{}:cam1:FileNumber_RBV'.format(pilatus_Epicsname)) - if get_beamline().detector[0].name is 'pilatus300': + if get_beamline().detector[0].name is "pilatus300k-1": md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{Det:SAXS}:cam1:FileNumber_RBV') - elif get_beamline().detector[0].name is 'pilatus2M': + elif get_beamline().detector[0].name is "pilatus2m-1": md_current['detector_sequence_ID'] = caget('XF:11BMB-ES{Det:PIL2M}:cam1:FileNumber_RBV') md_current.update(get_beamline().get_md()) @@ -1327,10 +1327,10 @@ class Sample_Generic(CoordinateSystem): #caput('XF:11BMB-ES{}:cam1:AcquireTime'.format(pilatus_Epicsname), exposure_time) #caput('XF:11BMB-ES{}:cam1:AcquirePeriod'.format(pilatus_Epicsname), exposure_time+0.1) - if get_beamline().detector[0].name is 'pilatus300': + if get_beamline().detector[0].name is "pilatus300k-1": caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime', exposure_time) caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod', exposure_time+0.1) - elif get_beamline().detector[0].name is 'pilatus2M': + elif get_beamline().detector[0].name is "pilatus2m-1": caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime', exposure_time) caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod', exposure_time+0.1) @@ -1367,7 +1367,7 @@ class Sample_Generic(CoordinateSystem): if exposure_time is not None: #for detector in gs.DETS: for detector in get_beamline().detector: - if detector.name is 'pilatus2M' and exposure_time != caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + if detector.name is "pilatus2m-1" and exposure_time != caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): detector.setExposureTime(exposure_time, verbosity=verbosity) #extra wait time when changing the exposure time. time.sleep(2) @@ -1398,10 +1398,10 @@ class Sample_Generic(CoordinateSystem): # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name is 'pilatus300': + if detector.name is "pilatus300k-1": current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name is 'pilatus2M': + elif detector.name is "pilatus2m-1": current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') max_exposure_time = max(max_exposure_time, current_exposure_time) elif detector.name is 'PhotonicSciences_CMS': @@ -1420,10 +1420,10 @@ class Sample_Generic(CoordinateSystem): status = 1 for detector in get_beamline().detector: - if detector.name is 'pilatus300': + if detector.name is "pilatus300k-1": if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: status *= 0 - elif detector.name is 'pilatus2M': + elif detector.name is "pilatus2m-1": if caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: status *= 0 elif detector.name is 'PhotonicSciences_CMS': @@ -1454,7 +1454,7 @@ class Sample_Generic(CoordinateSystem): subdir = '' - if detector.name is 'pilatus300': + if detector.name is "pilatus300k-1": chars = caget('XF:11BMB-ES{Det:SAXS}:TIFF1:FullFileName_RBV') filename = ''.join(chr(char) for char in chars)[:-1] @@ -1489,7 +1489,7 @@ class Sample_Generic(CoordinateSystem): if verbosity>=3: print(' Data linked as: {}'.format(link_name)) - elif detector.name is 'pilatus2M': + elif detector.name is "pilatus2m-1": chars = caget('XF:11BMB-ES{Det:PIL2M}:TIFF1:FullFileName_RBV') filename = ''.join(chr(char) for char in chars)[:-1] @@ -1754,7 +1754,7 @@ class Sample_Generic(CoordinateSystem): # Wait for detectors to be ready max_exposure_time = 0 for detector in get_beamline().detector: - if detector.name is 'pilatus300' or 'pilatus2M': + if detector.name is "pilatus300k-1" or "pilatus2m-1": current_exposure_time = caget('XF:11BMB-ES{}:cam1:AcquireTime'.format(pilatus_Epicsname)) max_exposure_time = max(max_exposure_time, current_exposure_time) elif detector.name is 'PhotonicSciences_CMS': @@ -1780,7 +1780,7 @@ class Sample_Generic(CoordinateSystem): status = 1 for detector in get_beamline().detector: - if detector.name is 'pilatus300' or 'pilatus2M': + if detector.name is "pilatus300k-1" or "pilatus2m-1": print('status2.5 = ', status) if caget('XF:11BMB-ES{}:cam1:Acquire'.format(pilatus_Epicsname))==1: status = 0 diff --git a/startup/95-sample-custom.py b/startup/95-sample-custom.py index 155f30c..eb4eff0 100644 --- a/startup/95-sample-custom.py +++ b/startup/95-sample-custom.py @@ -973,7 +973,7 @@ def do(self, step=0, align_step=0, **md): # detselect(pilatus300) detselect(pilatus2M) for detector in get_beamline().detector: - if detector.name == "pilatus2M": + if detector.name == "pilatus2m-1": RE(detector.setExposureTime(self.md["exposure_time"])) else: detector.setExposureTime(self.md["exposure_time"]) diff --git a/startup/992-PTA-mobile.py b/startup/992-PTA-mobile.py index 1646fb6..65edaab 100644 --- a/startup/992-PTA-mobile.py +++ b/startup/992-PTA-mobile.py @@ -150,7 +150,7 @@ # The SmarAct module is broken. Need to change to a SPARE_S for armr # changed from spareM to spareS by RL at 2011/07/23 -armr = EpicsMotor("XF:11BMB-ES{Spare:L-Ax:S}Mtr", name="armr") +# armr = EpicsMotor("XF:11BMB-ES{Spare:L-Ax:S}Mtr", name="armr") ## stages for detectors diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 1eae207..7045aeb 100644 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -30,5 +30,13 @@ "bsphi": -64.001781, "timestamp": "2025-04-22 10:01:03" } + ], + "test": [ + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.995626999999992, + "timestamp": "2025-07-28 12:55:37" + } ] } \ No newline at end of file diff --git a/startup/user_collection/user_LinkamThermal.py b/startup/user_collection/user_LinkamThermal.py index c3f570d..ca24da9 100644 --- a/startup/user_collection/user_LinkamThermal.py +++ b/startup/user_collection/user_LinkamThermal.py @@ -249,20 +249,20 @@ def series_measure( RE(detector.setExposurePeriod(exposure_period)) RE(detector.setExposureNumber(num_frames)) - # if detector.name is 'pilatus2M': + # if detector.name is "pilatus2m-1": # if exposure_time != caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime', exposure_time) # caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod', exposure_period) # caput('XF:11BMB-ES{Det:PIL2M}:cam1:NumImages', num_frames) - # if detector.name is 'pilatus800': + # if detector.name is "pilatus800k-1": # if exposure_time != caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime'): # caput('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime', exposure_time) # caput('XF:11BMB-ES{Det:PIL800K}:cam1:AcquirePeriod', exposure_period) # caput('XF:11BMB-ES{Det:PIL800K}:cam1:NumImages', num_frames) - # if detector.name is 'pilatus300' : + # if detector.name is "pilatus300k-1" : # if exposure_time != caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime'): # caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime', exposure_time) # caput('XF:11BMB-ES{Det:SAXS}:cam1:AcquirePeriod', exposure_period) From 117e170b92bed94ceab5ea3ccbe74de1fe6dc066 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Mon, 28 Jul 2025 17:21:25 -0400 Subject: [PATCH 10/23] Removed local symlink gnerator --- startup/02-data_security.py | 3 +- startup/94-sample.py | 109 ++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 57 deletions(-) diff --git a/startup/02-data_security.py b/startup/02-data_security.py index 4fb2bb0..e0d291f 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -8,5 +8,4 @@ RE.md.pop('experiment_group', None) RE.md.pop('experiment_cycle', None) -RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delte this -RE.md['savename'] = "testname" \ No newline at end of file +RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delte this \ No newline at end of file diff --git a/startup/94-sample.py b/startup/94-sample.py index dc70300..ce018ae 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1900,7 +1900,7 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Wait for detectors to be ready max_exposure_time = 0.1 for detector in get_beamline().detector: - print('here in expose:', detector.name) + # print('here in expose:', detector.name) if detector.name == "pilatus300k-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) @@ -2150,9 +2150,6 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= return filename = detector.tiff.full_file_name.get() # RL, 20210831 - if not os.path.isfile(filename): - print("File does not exist") - return # Alternate method to get the last filename # filename = '{:s}/{:s}.tiff'.format( detector.tiff.file_path.get(), detector.tiff.file_name.get() ) @@ -2171,27 +2168,28 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= # savename = self.get_savename(savename_extra=extra) savename = md["filename"] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) - - if os.path.isfile(link_name): - i = 1 - while os.path.isfile("{}.{:d}".format(link_name, i)): - i += 1 - os.rename(link_name, "{}.{:d}".format(link_name, i)) - os.symlink(filename, link_name) - - - #debug the losing data issue on pil2m. suggested by T. Caswell - # with open(link_name, 'rb') as fin: - # h = hashlib.md5(fin.read(1024)).hexdigest() - # with open(link_name + '.md5', 'w') as fout: - # fout.write(h) - - - if verbosity >= 3: - print(" Data linked as: {}".format(link_name)) - if not os.path.isfile(os.readlink(link_name)): #added by RL, 20231109 - raise ValueError('NO IMAGE OUTPUT.') + link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) + print(f" A symlink will be created at: {proposal_path()}experiments/{link_name}") + + # if os.path.isfile(link_name): + # i = 1 + # while os.path.isfile("{}.{:d}".format(link_name, i)): + # i += 1 + # os.rename(link_name, "{}.{:d}".format(link_name, i)) + # os.symlink(filename, link_name) + + + # #debug the losing data issue on pil2m. suggested by T. Caswell + # # with open(link_name, 'rb') as fin: + # # h = hashlib.md5(fin.read(1024)).hexdigest() + # # with open(link_name + '.md5', 'w') as fout: + # # fout.write(h) + + + # if verbosity >= 3: + # print(" Data linked as: {}".format(link_name)) + # if not os.path.isfile(os.readlink(link_name)): #added by RL, 20231109 + # raise ValueError('NO IMAGE OUTPUT.') def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" @@ -3742,42 +3740,43 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # savename = md['filename'][:-5] savename = self.get_savename(savename_extra=extra) - link_name = "{}/{}{}_{:06d}_{}.tiff".format( - RE.md["experiment_alias_directory"], - subdir, - savename, - RE.md["scan_id"] - 1, - detname, - ) - link_name_part1 = "{}/{}{}_{:06d}".format( - RE.md["experiment_alias_directory"], - subdir, - savename, - RE.md["scan_id"] - 1, - ) + # link_name = "{}/{}{}_{:06d}_{}.tiff".format( + # RE.md["experiment_alias_directory"], + # subdir, + # savename, + # RE.md["scan_id"] - 1, + # detname, + # ) + # link_name_part1 = "{}/{}{}_{:06d}".format( + # RE.md["experiment_alias_directory"], + # subdir, + # savename, + # RE.md["scan_id"] - 1, + # ) # link_name = '{}/{}{}_{:06d}_{}.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id'], detname) # link_name_part1 = '{}/{}{}_{:06d}'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']) - if os.path.isfile(link_name): - i = 1 - while os.path.isfile("{}.{:d}".format(link_name, i)): - i += 1 - os.rename(link_name, "{}.{:d}".format(link_name, i)) - - for num_frame in range(num_frames): - filename_new = "{}_{:06d}.tiff".format(filename_part1, num_frame) - if os.path.isfile(filename_new) == False: - return print("File number {} does not exist.".format(num_frame)) - - link_name_new = "{}_{:06d}_{}.tiff".format(link_name_part1, num_frame, detname) - os.symlink(filename_new, link_name_new) - if verbosity >= 3: - if num_frame == 0 or num_frame == np.max(num_frames): - print(" Data {} linked as: {}".format(filename_new, link_name_new)) + # if os.path.isfile(link_name): + # i = 1 + # while os.path.isfile("{}.{:d}".format(link_name, i)): + # i += 1 + # os.rename(link_name, "{}.{:d}".format(link_name, i)) + + # for num_frame in range(num_frames): + # filename_new = "{}_{:06d}.tiff".format(filename_part1, num_frame) + # if os.path.isfile(filename_new) == False: + # return print("File number {} does not exist.".format(num_frame)) + + # link_name_new = "{}_{:06d}_{}.tiff".format(link_name_part1, num_frame, detname) + # os.symlink(filename_new, link_name_new) + # if verbosity >= 3: + # if num_frame == 0 or num_frame == np.max(num_frames): + # print(" Data {} linked as: {}".format(filename_new, link_name_new)) savename = self.get_savename(savename_extra=extra) # savename = md['filename'] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) + link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) + print(f" Symlinks will be created at: {proposal_path()}experiments/{link_name}") # Control methods ######################################## From 8badbe168ba7a79c961ca82c70b01cdc5a046f2f Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Tue, 29 Jul 2025 17:06:08 -0400 Subject: [PATCH 11/23] Fixed use of file paths and names --- .gitignore | 5 +- startup/00-startup.py | 2 +- startup/02-data_security.py | 2 +- startup/81-beam.py | 100 ++++++++++++++++++++++++++++++++++++ startup/94-sample.py | 36 ++++++------- 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index aea5894..be59cc8 100755 --- a/.gitignore +++ b/.gitignore @@ -143,4 +143,7 @@ doc/source/_generated_images/* *.visionenv # Audio Files -*.wav \ No newline at end of file +*.wav + +# Users scripts +users/ diff --git a/startup/00-startup.py b/startup/00-startup.py index 0543680..abb26d2 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -3,7 +3,7 @@ import nslsii import os -os.environ.pop('TILED_API_KEY') +os.environ.pop('TILED_API_KEY') # Make sure no user-defined API key is set from tiled.client import from_profile from databroker import Broker diff --git a/startup/02-data_security.py b/startup/02-data_security.py index e0d291f..1392b81 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -8,4 +8,4 @@ RE.md.pop('experiment_group', None) RE.md.pop('experiment_cycle', None) -RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delte this \ No newline at end of file +RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delete this \ No newline at end of file diff --git a/startup/81-beam.py b/startup/81-beam.py index 234ea5f..1955db0 100644 --- a/startup/81-beam.py +++ b/startup/81-beam.py @@ -3130,6 +3130,106 @@ def setMetadata(self, verbosity=3): "/n The folder ::: {} ::: has been made for users. /n".format(RE.md["experiment_alias_directory"]) ) + def setMetadata_new(self, verbosity=3): + """Guides the user through setting some of the required and recommended + meta-data fields.""" + + if verbosity >= 3: + print("This will guide you through adding some meta-data for the upcoming experiment.") + if verbosity >= 4: + print( + "You can accept default values (shown in square [] brackets) by pressing enter. You can leave a value blank (or put a space) to skip that entry." + ) + + # Set some values automatically + month = int(time.strftime("%m")) + if month <= 4: + cycle = 1 + elif month <= 8: + cycle = 2 + else: + cycle = 3 + RE.md["experiment_cycle"] = "{:s}_{:d}".format(time.strftime("%Y"), cycle) + + RE.md["calibration_energy_keV"] = float(round(self.beam.energy(verbosity=0), 3)) + RE.md["calibration_wavelength_A"] = float(round(self.beam.wavelength(verbosity=0), 5)) + + # TODO: + # RE.md['calibration_detector_distance_m'] = + # RE.md['calibration_detector_x0'] = + # RE.md['calibration_detector_y0'] = + + # Ask the user some questions + + questions = [ + ["experiment_proposal_number", "Proposal number"], + ["experiment_SAF_number", "SAF number"], + ["experiment_group", "User group (e.g. PI)"], + ["experiment_user", "The specific user/person running the experiment"], + ["experiment_project", "Project name/code"], + ["userpy_alias_directory", "Alias directory"], + ["experiment_alias_directory", "Alias directory"], + [ + "experiment_type", + "Type of experiments/measurements (SAXS, GIWAXS, etc.)", + ], + ] + + # TBD: + # Path where data will be stored? + + self._dialog_total_questions = len(questions) + self._dialog_question_number = 1 + + for key, text in questions: + try: + self._ask_question(key, text) + except KeyboardInterrupt: + return + + if verbosity >= 4: + print("You can also add/edit metadata directly using the RE.md object.") + + #RE.md["userpy_alias_directory"] = '/home/xf11bm/.ipython/profile_collection/users/2025-2/TKoga' + if os.path.exists(RE.md["userpy_alias_directory"]): + print("/n The folder has existed. Please change folder name if necessary./n") + else: + os.makedirs(RE.md["userpy_alias_directory"], exist_ok=True) + + #for double-checking folders + #/nsls2//data/cms/shared/config/bluesky/profile_collection/users/ + print('user.py will be saved in folder: {}'.format(RE.md["userpy_alias_directory"])) + print('data will be saved in folder: {}'.format(RE.md["experiment_alias_directory"])) + print('redo cms.setMeadata() if any folder is wrong! ') + + # if os.path.exists(RE.md["experiment_alias_directory"]): + # print("/n The folder has existed. Please change folder name if necessary./n") + # else: + # os.makedirs(RE.md["experiment_alias_directory"], exist_ok=True) + # os.makedirs(os.path.join(RE.md["experiment_alias_directory"], "waxs"), exist_ok=True) + # os.makedirs( + # os.path.join(RE.md["experiment_alias_directory"], "waxs/raw"), + # exist_ok=True, + # ) + # os.makedirs( + # os.path.join(RE.md["experiment_alias_directory"], "waxs/analysis"), + # exist_ok=True, + # ) + # os.makedirs(os.path.join(RE.md["experiment_alias_directory"], "saxs"), exist_ok=True) + # os.makedirs( + # os.path.join(RE.md["experiment_alias_directory"], "saxs/raw"), + # exist_ok=True, + # ) + # os.makedirs( + # os.path.join(RE.md["experiment_alias_directory"], "saxs/analysis"), + # exist_ok=True, + # ) + # os.makedirs(os.path.join(RE.md["experiment_alias_directory"], "data"), exist_ok=True) + # os.makedirs(os.path.join(RE.md['experiment_alias_directory'], 'saxs'), exist_ok=True) + # print( + # "/n The folder ::: {} ::: has been made for users. /n".format(RE.md["experiment_alias_directory"]) + # ) + def _ask_question(self, key, text, default=None): if default is None and key in RE.md: default = RE.md[key] diff --git a/startup/94-sample.py b/startup/94-sample.py index ce018ae..7c7ad79 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -2168,7 +2168,7 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= # savename = self.get_savename(savename_extra=extra) savename = md["filename"] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) + link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') print(f" A symlink will be created at: {proposal_path()}experiments/{link_name}") # if os.path.isfile(link_name): @@ -2809,7 +2809,7 @@ def measureRock( md_current["sample_savename"] = savename md_current["measure_type"] = measure_type # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, md_current['detector_sequence_ID']) - md_current["filename"] = "{:s}_{:04d}.tiff".format(savename, RE.md["scan_id"]) + md_current["filename"] = "{:s}_{:04d}".format(savename, RE.md["scan_id"]) md_current["beam_int_bim3"] = beam.bim3.flux(verbosity=0) md_current["beam_int_bim4"] = beam.bim4.flux(verbosity=0) md_current["beam_int_bim5"] = beam.bim5.flux(verbosity=0) @@ -2919,8 +2919,8 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", md_current.update(self.get_measurement_md()) md_current["sample_savename"] = savename md_current["measure_type"] = measure_type - # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, md_current['detector_sequence_ID']) - # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, RE.md['scan_id']) + # md_current['filename'] = '{:s}_{:04d}'.format(savename, md_current['detector_sequence_ID']) + # md_current['filename'] = '{:s}_{:04d}'.format(savename, RE.md['scan_id']) md_current["filename"] = "{:s}_{:06d}".format(savename, RE.md["scan_id"]) md_current.update(md) @@ -2968,8 +2968,8 @@ def _test_measure_single( md_current["measure_type"] = measure_type md_current.update(self.get_measurement_md()) - # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, md_current['detector_sequence_ID']) - md_current["filename"] = "{:s}_{:04d}.tiff".format(savename, RE.md["scan_id"]) + # md_current['filename'] = '{:s}_{:04d}'.format(savename, md_current['detector_sequence_ID']) + md_current["filename"] = "{:s}_{:04d}".format(savename, RE.md["scan_id"]) md_current.update(md) self._test_expose(exposure_time, shutteronoff=shutteronoff, extra=extra, verbosity=verbosity, **md_current) self.md["measurement_ID"] += 1 @@ -3383,7 +3383,7 @@ def scan_measure( md_current["scan"] = "scan_measure" md_current.update(self.get_measurement_md()) md_current["measure_series_num_frames"] = num_frames - md_current["filename"] = "{:s}_{:04d}.tiff".format(savename, RE.md["scan_id"]) + md_current["filename"] = "{:s}_{:04d}".format(savename, RE.md["scan_id"]) md_current["measure_series_motor"] = motor.name md_current["measure_series_positions"] = [start, stop] md_current["exposure_time"] = exposure_time @@ -3488,16 +3488,16 @@ def series_measure( md_current["measure_type"] = measure_type md_current["series"] = "series_measure" md_current.update(self.get_measurement_md()) - # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, md_current['detector_sequence_ID']) + # md_current['filename'] = '{:s}_{:04d}'.format(savename, md_current['detector_sequence_ID']) md_current["measure_series_num_frames"] = num_frames - md_current["filename"] = "{:s}_{:04d}.tiff".format(savename, RE.md["scan_id"]) - # md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, RE.md['scan_id']+1) + md_current["filename"] = "{:s}_{:04d}".format(savename, RE.md["scan_id"]) + # md_current['filename'] = '{:s}_{:04d}'.format(savename, RE.md['scan_id']+1) md_current["exposure_time"] = exposure_time md_current["exposure_period"] = exposure_period # md_current['measure_series_motor'] = motor.name # md_current['measure_series_positions'] = [start, stop] - # md_current['fileno'] = '{:s}_{:04d}.tiff'.format(savename, RE.md['scan_id']) + # md_current['fileno'] = '{:s}_{:04d}'.format(savename, RE.md['scan_id']) md_current.update(md) print(RE.md["scan_id"]) @@ -3722,8 +3722,8 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, print("WARNING: Can't do file handling for detector '{}'.".format(detector.name)) return - filename = detector.tiff.full_file_name.get() # RL, 20210831 - filename_part1 = "{:s}/{:s}".format(detector.tiff.file_path.get(), detector.tiff.file_name.get()) + # filename = detector.tiff.full_file_name.get() # RL, 20210831 + # filename_part1 = "{:s}/{:s}".format(detector.tiff.file_path.get(), detector.tiff.file_name.get()) # Alternate method to get the last filename # filename = '{:s}/{:s}.tiff'.format( detector.tiff.file_path.get(), detector.tiff.file_name.get() ) @@ -3740,6 +3740,7 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # savename = md['filename'][:-5] savename = self.get_savename(savename_extra=extra) + # savename = RE.md["filename"] # link_name = "{}/{}{}_{:06d}_{}.tiff".format( # RE.md["experiment_alias_directory"], # subdir, @@ -3756,12 +3757,6 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # link_name = '{}/{}{}_{:06d}_{}.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id'], detname) # link_name_part1 = '{}/{}{}_{:06d}'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']) - # if os.path.isfile(link_name): - # i = 1 - # while os.path.isfile("{}.{:d}".format(link_name, i)): - # i += 1 - # os.rename(link_name, "{}.{:d}".format(link_name, i)) - # for num_frame in range(num_frames): # filename_new = "{}_{:06d}.tiff".format(filename_part1, num_frame) # if os.path.isfile(filename_new) == False: @@ -3772,10 +3767,9 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # if verbosity >= 3: # if num_frame == 0 or num_frame == np.max(num_frames): # print(" Data {} linked as: {}".format(filename_new, link_name_new)) - savename = self.get_savename(savename_extra=extra) # savename = md['filename'] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname) + link_name = "{}/{}{}_{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, RE.md["scan_id"] - 1, detname).replace('//', '/') print(f" Symlinks will be created at: {proposal_path()}experiments/{link_name}") # Control methods From ca46e0174bdfb3f9278d325bef2e879eaed0ae53 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 30 Jul 2025 14:21:49 -0400 Subject: [PATCH 12/23] FIX: configure writable path for specfiles --- startup/02-data_security.py | 1 + startup/86-live-spec.py | 2 +- startup/94-sample.py | 92 ++++++++++++++++++++----------------- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/startup/02-data_security.py b/startup/02-data_security.py index 1392b81..9687124 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -7,5 +7,6 @@ RE.md.pop('experiment_project', None) RE.md.pop('experiment_group', None) RE.md.pop('experiment_cycle', None) +RE.md.pop('userpy_alias_directory', None) RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delete this \ No newline at end of file diff --git a/startup/86-live-spec.py b/startup/86-live-spec.py index 86d09f3..d68c761 100644 --- a/startup/86-live-spec.py +++ b/startup/86-live-spec.py @@ -10,7 +10,7 @@ def spec_factory(name, doc): - directory = proposal_path() + "experiments/data/ScanFiles" + directory = "/nsls2/data/cms/shared/config/operations/specfiles/" file_prefix = "cms_scan_" + time.strftime("%Y_%m_%d") # # skip multiple motor scans diff --git a/startup/94-sample.py b/startup/94-sample.py index 7c7ad79..119d827 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1931,55 +1931,55 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p if verbosity >= 1: print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) - if verbosity >= 2: - status = 0 - while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): - percentage = 100 * (time.time() - start_time) / max_exposure_time - print( - "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), - end="", - ) - - time.sleep(poling_period) - - status = 1 - for detector in get_beamline().detector: - if detector.cam.acquire.get() == 1: - status *= 0 + # if verbosity >= 2: + # status = 0 + # while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): + # percentage = 100 * (time.time() - start_time) / max_exposure_time + # print( + # "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), + # end="", + # ) + + # time.sleep(poling_period) + + # status = 1 + # for detector in get_beamline().detector: + # if detector.cam.acquire.get() == 1: + # status *= 0 # print('counting .... percentage = {}'.format(percentage)) else: time.sleep(max_exposure_time) - # special solution for 2022_1/TKoga2 - if verbosity >= 5: - print("verbosity = {}.".format(verbosity)) - pct_threshold = 90 - while percentage < pct_threshold: - print("sth is wrong .... percentage = {} < {}%".format(percentage, pct_threshold)) - start_time = time.time() - uids = RE(count(get_beamline().detector), **md) - # yield from (count(get_beamline().detector), **md) - - # get_beamline().beam.off() - # print('shutter is off') - - # Wait for detectors to be ready - max_exposure_time = 0.1 - for detector in get_beamline().detector: - if detector.name == "pilatus300k-1": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus2m-1": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - - percentage = 100 * (time.time() - start_time) / max_exposure_time - print("After re-exposing .... percentage = {} ".format(percentage)) + # # special solution for 2022_1/TKoga2 + # if verbosity >= 5: + # print("verbosity = {}.".format(verbosity)) + # pct_threshold = 90 + # while percentage < pct_threshold: + # print("sth is wrong .... percentage = {} < {}%".format(percentage, pct_threshold)) + # start_time = time.time() + # uids = RE(count(get_beamline().detector), **md) + # # yield from (count(get_beamline().detector), **md) + + # # get_beamline().beam.off() + # # print('shutter is off') + + # # Wait for detectors to be ready + # max_exposure_time = 0.1 + # for detector in get_beamline().detector: + # if detector.name == "pilatus300k-1": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "pilatus2m-1": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + + # percentage = 100 * (time.time() - start_time) / max_exposure_time + # print("After re-exposing .... percentage = {} ".format(percentage)) # if detector.name is "pilatus300k-1": # if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: @@ -2144,6 +2144,10 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= elif detector.name == "pilatus800k-1": subdir = "/waxs/raw/" detname = "waxs" + elif 'webcam' in detector.name: + subdir = "/camera/" + detname = detector.name + else: if verbosity >= 1: print("WARNING: Can't do file handling for detector '{}'.".format(detector.name)) @@ -2169,6 +2173,8 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= savename = md["filename"] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') + # if 'camera' in detector.name: + # link_name = "{}/{}{}_000000_{}.png".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') print(f" A symlink will be created at: {proposal_path()}experiments/{link_name}") # if os.path.isfile(link_name): From 41bfa657e029a0b545bafc00a8f48d5e92cb062d Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 30 Jul 2025 18:25:10 -0400 Subject: [PATCH 13/23] ENH: use databroker and fix legacy paths --- startup/.cms_config | 5 +++-- startup/00-startup.py | 2 +- startup/10-motors.py | 4 ++-- startup/91-fit_scan.py | 17 +++++++++-------- startup/94-sample.py | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/startup/.cms_config b/startup/.cms_config index 66d6966..53348dc 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1312,5 +1312,6 @@ 1310,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900451,Thu Jul 24 16:37:48 2025 1311,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 24 16:38:26 2025 1312,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 24 16:40:16 2025 -1233,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Mon Jul 28 11:38:49 2025 -1313,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900600999999998,Tue Jul 29 17:14:22 2025 +1313,44.45,62.8,5.0,4.0,"[-96.3, -198.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Mon Jul 28 11:38:49 2025 +1314,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900600999999998,Tue Jul 29 17:14:22 2025 +1315,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-18.400622,Wed Jul 30 15:37:23 2025 diff --git a/startup/00-startup.py b/startup/00-startup.py index abb26d2..e43b2d6 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -58,7 +58,7 @@ def insert(self, name, doc): print("Initializing Tiled reading client...\nMake sure you check for duo push.") tiled_reading_client = cat = from_profile("nsls2", username=None)["cms"]["raw"] -# db = Broker(tiled_reading_client) +db = Broker(tiled_reading_client) # Keep for backcompatibility with older code that uses databroker from pyOlog.ophyd_tools import * diff --git a/startup/10-motors.py b/startup/10-motors.py index cfeee2a..fcd1a00 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -8,8 +8,8 @@ # class Slits(Device): # top = Cpt(EpicsMotor, '-Ax:T}Mtr') # bottom = Cpt(EpicsMotor, '-Ax:B}Mtr') -#beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 -beamline_stage = 'open_MAXS' +beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 +# beamline_stage = 'open_MAXS' # beamline_stage = 'BigHuber' print('Beamline_stage = {}'.format(beamline_stage)) diff --git a/startup/91-fit_scan.py b/startup/91-fit_scan.py index 3eb7423..f7d59a8 100644 --- a/startup/91-fit_scan.py +++ b/startup/91-fit_scan.py @@ -951,7 +951,7 @@ def fit_scan( detectors = get_beamline().detector plot_y = get_beamline().PLOT_Y - # plot_y = pilatus2M.stats4.total + # plot_y = pilatus2m-1.stats4.total # print("plot_y is {}".format(plot_y)) else: @@ -1037,7 +1037,8 @@ def fit_scan( # md['exposure_time'] = exposure_time_last # if plot_y=='pilatus300_stats4_total' or plot_y=='pilatus300_stats3_total': - if plot_y == "pilatus2M_stats4_total" or plot_y == "pilatus2M_stats3_total": + if plot_y == "pilatus2m-1_stats4_total" or plot_y == "pilatus2m-1_stats3_total": + #TODO: if plot_y == detector.name + '_stats4_total': remove_last_Pilatus_series() # check save_flg and save the scan data thru databroker @@ -1162,7 +1163,7 @@ def fit_edge( bec.enable_table() # if plot_y=='pilatus300_stats4_total' or plot_y=='pilatus300_stats3_total': - if plot_y == "pilatus2M_stats4_total" or plot_y == "pilatus2M_stats3_total": + if plot_y == "pilatus2m-1_stats4_total" or plot_y == "pilatus2m-1_stats3_total": remove_last_Pilatus_series() x0_guess = np.average(livetable.xdata) @@ -1398,7 +1399,7 @@ def _fit_scan(): # RE(scan(list(detectors), motor, start, stop, num, per_step=per_step, md=md), subs ) # if plot_y=='pilatus300_stats4_total' or plot_y=='pilatus300_stats3_total': - if plot_y == "pilatus2M_stats4_total" or plot_y == "pilatus2M_stats3_total": + if plot_y == "pilatus2m-1_stats4_total" or plot_y == "pilatus2m-1_stats3_total": remove_last_Pilatus_series() if fit is None: @@ -1518,17 +1519,17 @@ def ps( if uid == "-1": uid = -1 if det == "default": - if db[uid].start['detectors'][0] == "pilatus2M" and suffix == "default": - intensity_field = "pilatus2M_stats4_total" + if db[uid].start['detectors'][0] == "pilatus2m-1" and suffix == "default": + intensity_field = "pilatus2m-1_stats4_total" # intensity_field = "elm_sum_all" - elif db[uid].start['detectors'][0] == "pilatus2M": + elif db[uid].start['detectors'][0] == "pilatus2m-1": intensity_field = "elm" + suffix elif suffix == "default": intensity_field = db[uid].start['detectors'][0] + "_stats1_total" else: intensity_field = db[uid].start['detectors'][0] + suffix else: - if det == "pilatus2M" and suffix == "default": + if det == "pilatus2m-1" and suffix == "default": intensity_field = "elm_sum_all" elif det == "elm": intensity_field = "elm" + suffix diff --git a/startup/94-sample.py b/startup/94-sample.py index 23e808a..cb30ec6 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -4048,7 +4048,7 @@ def transmission_data_output(self): # beam.absorber_transmission_list = [1, 0.041, 0.0017425, 0.00007301075, 0.00000287662355, 0.000000122831826, 0.00000000513437] scan_id = h.start["scan_id"] I_bim5 = 0 # beam intensity from bim5 - I0 = dtable.pilatus2M_stats4_total + I0 = dtable['pilatus2m-1_stats4_total'] filename = h.start["sample_name"] exposure_time = h.start["sample_exposure_time"] From a8612408e03b71bc725611c69ec641c886eb3877 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 15 Aug 2025 10:17:16 -0400 Subject: [PATCH 14/23] UPD: configuration changes --- startup/.cms_config | 11 +++++++++++ startup/10-motors.py | 4 ++-- startup/94-sample.py | 3 +++ startup/beamstop_config.cfg | 30 ++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/startup/.cms_config b/startup/.cms_config index cb7dd27..f3fe37b 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1313,3 +1313,14 @@ 1311,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 24 16:38:26 2025 1312,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 24 16:40:16 2025 1313,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900600999999998,Tue Jul 29 17:14:22 2025 +1314,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-16.300307999999998,Wed Jul 30 19:32:07 2025 +1315,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-16.900647,Wed Jul 30 19:33:32 2025 +1316,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 31 10:21:45 2025 +1317,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.800403,Thu Jul 31 10:22:06 2025 +1318,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.000816,Thu Jul 31 10:23:39 2025 +1319,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.000816,Thu Jul 31 10:23:50 2025 +1320,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.000816,Thu Jul 31 12:07:26 2025 +1321,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-17.900453,Thu Jul 31 12:07:39 2025 +1322,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.200816,Thu Jul 31 15:23:43 2025 +1323,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.000816,Thu Jul 31 15:33:26 2025 +1324,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-16.800351,Mon Aug 4 17:20:11 2025 diff --git a/startup/10-motors.py b/startup/10-motors.py index cfeee2a..fcd1a00 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -8,8 +8,8 @@ # class Slits(Device): # top = Cpt(EpicsMotor, '-Ax:T}Mtr') # bottom = Cpt(EpicsMotor, '-Ax:B}Mtr') -#beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 -beamline_stage = 'open_MAXS' +beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 +# beamline_stage = 'open_MAXS' # beamline_stage = 'BigHuber' print('Beamline_stage = {}'.format(beamline_stage)) diff --git a/startup/94-sample.py b/startup/94-sample.py index b07fa09..4a8c938 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1887,6 +1887,9 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p md["beam_int_bim3"] = beam.bim3.flux(verbosity=0) md["beam_int_bim4"] = beam.bim4.flux(verbosity=0) md["beam_int_bim5"] = beam.bim5.flux(verbosity=0) + + if 'temperature_Linkam' in self.naming_scheme: + md["temperature_Linkam"] = LThermal.temperature() # md['trigger_time'] = self.clock() # md.update(md_current) diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 39ac60c..14b199b 100755 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -35,6 +35,30 @@ "bsy": -12.799769, "bsphi": -61.001744, "timestamp": "2025-06-26 17:04:33" + }, + { + "bsx": -18.000815999999997, + "bsy": -12.599539, + "bsphi": -61.003749, + "timestamp": "2025-07-31 10:23:39" + }, + { + "bsx": -18.000815999999997, + "bsy": -12.599539, + "bsphi": -61.003749, + "timestamp": "2025-07-31 10:23:50" + }, + { + "bsx": -18.200816, + "bsy": -12.599538999999998, + "bsphi": -61.00361100000001, + "timestamp": "2025-07-31 12:07:26" + }, + { + "bsx": -18.000816, + "bsy": -12.399296, + "bsphi": -61.005459, + "timestamp": "2025-07-31 15:33:26" } ], "round_17": [ @@ -117,6 +141,12 @@ "bsy": 17.001509, "bsphi": -181.005665, "timestamp": "2025-06-17 09:46:10" + }, + { + "bsx": -16.900647, + "bsy": 17.001970999999998, + "bsphi": -181.008503, + "timestamp": "2025-07-30 19:33:32" } ], "round_3m": [ From 1b0c040893d873919e8e799c54b8054b709ea4f9 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Tue, 7 Oct 2025 11:11:31 -0400 Subject: [PATCH 15/23] live updates on Oct 7 --- startup/.cms_config | 34 + startup/00-startup.py | 6 +- startup/02-data_security.py | 2 +- startup/03-async.py | 24 + startup/10-motors.py | 4 +- startup/15-optics-utilities.py | 10 + startup/20-area-detectors.py | 199 +++- startup/26-IonChamber.py | 152 +++ startup/27-Xspress3.py | 300 ++++++ startup/27-Xspress3.pyQAS | 512 ++++++++++ startup/51-linkam-stages.py | 3 + startup/51-linkam-stages_new.py | 952 ++++++++++++++++++ startup/55-archiver.py | 100 ++ startup/81-beam.py | 18 +- startup/94-sample.py | 312 ++++-- startup/95-sample-custom.py | 16 +- startup/beamstop_config.cfg | 88 ++ startup/cfg/LinkamTensile_step.csv | 3 + startup/cfg/LinkamTrans_stage.cfg | 9 + startup/cfg/LinkamTrans_step.csv | 3 + startup/cfg/linkam_stage_pos.cfg | 60 ++ .../user_collection/user_TemperarureRamp.py | 2 +- startup/user_collection/user_XR.py | 2 +- 23 files changed, 2686 insertions(+), 125 deletions(-) create mode 100644 startup/03-async.py create mode 100644 startup/26-IonChamber.py create mode 100644 startup/27-Xspress3.py create mode 100644 startup/27-Xspress3.pyQAS create mode 100644 startup/51-linkam-stages_new.py create mode 100644 startup/cfg/LinkamTensile_step.csv create mode 100644 startup/cfg/LinkamTrans_stage.cfg create mode 100644 startup/cfg/LinkamTrans_step.csv create mode 100644 startup/cfg/linkam_stage_pos.cfg diff --git a/startup/.cms_config b/startup/.cms_config index f3fe37b..bf03ad1 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1324,3 +1324,37 @@ 1322,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.200816,Thu Jul 31 15:23:43 2025 1323,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-18.000816,Thu Jul 31 15:33:26 2025 1324,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,,-16.800351,Mon Aug 4 17:20:11 2025 +1325,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.900647,Fri Aug 15 11:35:29 2025 +1326,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.900647,Fri Aug 15 11:36:42 2025 +1327,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.500706,Thu Sep 18 15:13:01 2025 +1328,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-0.0001609999999999,Thu Sep 18 17:36:57 2025 +1329,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-18.000816,Thu Sep 18 17:37:10 2025 +1330,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.722915,Thu Sep 18 17:38:40 2025 +1331,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.722913,Thu Sep 18 17:38:48 2025 +1332,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Thu Sep 18 20:00:15 2025 +1333,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.900776,Thu Sep 18 20:01:48 2025 +1334,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.900771,Thu Sep 18 20:01:56 2025 +1335,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-18.000816,Thu Sep 18 20:09:29 2025 +1336,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.701234,Thu Sep 18 20:10:28 2025 +1337,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.701234,Fri Sep 19 14:21:31 2025 +1338,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.701234,Fri Sep 19 14:21:42 2025 +1339,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.901198,Mon Sep 22 10:17:39 2025 +1340,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.403015,Mon Sep 22 11:59:54 2025 +1341,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.403015,Mon Sep 22 12:00:01 2025 +1342,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.402984,Mon Sep 22 13:29:11 2025 +1343,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.402986,Mon Sep 22 13:29:20 2025 +1344,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.900647,Wed Sep 24 10:02:37 2025 +1345,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-11.382811,Wed Sep 24 10:08:17 2025 +1346,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-11.382811,Mon Sep 29 09:29:40 2025 +1347,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-8.382811,Mon Sep 29 09:47:26 2025 +1348,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.182811,Mon Sep 29 09:50:10 2025 +1349,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.183133,Mon Sep 29 09:51:29 2025 +1350,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.183132,Mon Sep 29 09:51:38 2025 +1351,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.701234,Mon Sep 29 09:52:17 2025 +1352,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-13.201614,Mon Sep 29 09:56:41 2025 +1353,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.182811,Mon Sep 29 09:56:57 2025 +1354,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.000162,Fri Oct 3 10:09:18 2025 +1355,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.000372,Fri Oct 3 15:58:36 2025 +1356,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Sun Oct 5 10:49:57 2025 +1357,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.402984,Sun Oct 5 10:55:51 2025 +1358,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.403476,Sun Oct 5 10:59:14 2025 diff --git a/startup/00-startup.py b/startup/00-startup.py index e43b2d6..6a41f55 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -3,7 +3,11 @@ import nslsii import os -os.environ.pop('TILED_API_KEY') # Make sure no user-defined API key is set +try: + os.environ.pop('TILED_API_KEY') # Make sure no user-defined API key is set +except KeyError: + pass + from tiled.client import from_profile from databroker import Broker diff --git a/startup/02-data_security.py b/startup/02-data_security.py index 9687124..0e2fd08 100644 --- a/startup/02-data_security.py +++ b/startup/02-data_security.py @@ -9,4 +9,4 @@ RE.md.pop('experiment_cycle', None) RE.md.pop('userpy_alias_directory', None) -RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delete this \ No newline at end of file +# RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delete this \ No newline at end of file diff --git a/startup/03-async.py b/startup/03-async.py new file mode 100644 index 0000000..fe5e461 --- /dev/null +++ b/startup/03-async.py @@ -0,0 +1,24 @@ +# Trying to introduce async function for asynchronous beamline control +# Add by Siyu Wu 2025/08/21 + +import asyncio +import threading +import contextlib +import queue +from concurrent.futures import ThreadPoolExecutor + +def async_run(func, *args, **kwargs): + """ + Run a synchronous function in an asynchronous context. + """ + try: + return asyncio.run(func(*args, **kwargs)) + except RuntimeError: + try: + import nest_asyncio + nest_asyncio.apply() + except Exception: + pass + loop = asyncio.get_event_loop() + return loop.run_in_executor(None, func, *args, **kwargs) + diff --git a/startup/10-motors.py b/startup/10-motors.py index fcd1a00..a958129 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -8,8 +8,8 @@ # class Slits(Device): # top = Cpt(EpicsMotor, '-Ax:T}Mtr') # bottom = Cpt(EpicsMotor, '-Ax:B}Mtr') -beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 -# beamline_stage = 'open_MAXS' +# beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 +beamline_stage = 'open_MAXS' # beamline_stage = 'BigHuber' print('Beamline_stage = {}'.format(beamline_stage)) diff --git a/startup/15-optics-utilities.py b/startup/15-optics-utilities.py index 741f8fd..8a41118 100644 --- a/startup/15-optics-utilities.py +++ b/startup/15-optics-utilities.py @@ -122,6 +122,16 @@ def ave_mir_x(): return ave_x +def get_mir_yaw(): + '''calculate the yaw angle of the toroidal mirror in [mrad]''' + usx = mir_usx.user_readback.value + dsx = mir_dsx.user_readback.value + alpha_rad = np.arctan((dsx-usx)/mir_us_to_ds/1000) + alpah_deg = np.rad2deg(alpha_rad) + print(f"The yaw angle of the toroidal mirror is {alpha_rad*1e3: .4f} mrad, equal to {alpah_deg: .4f} deg.") + return alpha_rad*1e3 + + def get_mir_angle(): '''calculate the incident angle of the toroidal mirror in [mrad]''' usy = mir_usy.user_readback.value diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index 3fe5438..e6c93d6 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -29,9 +29,11 @@ from ophyd.areadetector.plugins import HDF5Plugin #,register_plugin,PluginBase import time +import asyncio Pilatus2M_on = True +# Pilatus2M_on = 'h5' # 'h5' for h5 mode Camera_on=True Pilatus300_on = False # ONLY 1 Pilatus800 will be turned on at the same time. changed by RL, 20210831 @@ -316,7 +318,7 @@ def setExposurePeriod(self, exposure_period, verbosity=3): def setExposureNumber(self, exposure_number, verbosity=3): yield from mv(self.cam.num_images, exposure_number) -class Pilatus2MV33_h5(SingleTriggerV33, PilatusDetector): +class PilatusV33_h5(SingleTriggerV33, PilatusDetector): cam = Cpt(PilatusDetectorCamV33, "cam1:") image = Cpt(ImagePlugin, "image1:") stats1 = Cpt(StatsPluginV33, "Stats1:") @@ -379,6 +381,68 @@ def stage(self): raise error return super().stage(*args, **kwargs) +class Pilatus800V33_h5(SingleTriggerV33, PilatusDetector): + cam = Cpt(PilatusDetectorCamV33, "cam1:") + image = Cpt(ImagePlugin, "image1:") + stats1 = Cpt(StatsPluginV33, "Stats1:") + stats2 = Cpt(StatsPluginV33, "Stats2:") + stats3 = Cpt(StatsPluginV33, "Stats3:") + stats4 = Cpt(StatsPluginV33, "Stats4:") + stats5 = Cpt(StatsPluginV33, "Stats5:") + roi1 = Cpt(ROIPlugin, "ROI1:") + roi2 = Cpt(ROIPlugin, "ROI2:") + roi3 = Cpt(ROIPlugin, "ROI3:") + roi4 = Cpt(ROIPlugin, "ROI4:") + proc1 = Cpt(ProcessPlugin, "Proc1:") + trans1 = Cpt(TransformPlugin, "Trans1:") + + h5 = Cpt( + HDF5PluginWithFileStore, + suffix="HDF1:", + write_path_template = "", + ) + + def setExposureTime(self, exposure_time, verbosity=3): + yield from mv( + self.cam.acquire_time, + exposure_time, + # self.cam.acquire_period, + # exposure_time + 0.1, + ) + # self.cam.acquire_time.put(exposure_time) + # self.cam.acquire_period.put(exposure_time+.1) + # caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime', exposure_time) + # caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquirePeriod', exposure_time+0.1) + + def setExposurePeriod(self, exposure_period, verbosity=3): + yield from mv(self.cam.acquire_period, exposure_period) + + def setExposureNumber(self, exposure_number, verbosity=3): + yield from mv(self.cam.num_images, exposure_number) + + def stage(self): + self.h5.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.h5.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + self.h5.reg_root = assets_path() + f'{self.name}' + + # wrap the staging process in a retry loop + error = None + for retry in range(5): + try: + return super().stage() + except TimeoutError as err: + # Staging failed becasue the IOC did not answer + # some request in a resonable time + # Stash the exception as the variable 'error' + error = err + else: + # Staging worked. Stop retyring. + break + else: + # We exhausted all retires and none worked. + # Raise the error captured above to produce a useful error message. + raise error + return super().stage(*args, **kwargs) if Camera_on==True: @@ -395,6 +459,7 @@ def stage(self): # off-axis camera fs6 = StandardProsilicaV33("XF:11BMB-BI{OffAxis-Cam:1}", name="webcam-6") # on-axis camera + fs8 = StandardProsilicaV33("XF:11BMB-BI{Cam:08}", name="webcam-8") fs9 = StandardProsilicaV33("XF:11BMB-BI{OnAxis-Cam:2}", name="webcam-9") all_standard_pros = [fs2, fs3, fs4] @@ -453,7 +518,7 @@ def stage(self): # item_check = getattr(fs1.cam, item) # item_check.kind = "omitted" - all_standard_pros = [fs1, fs2, fs3, fs4, fs5, fs6, fs9] + all_standard_pros = [fs1, fs2, fs3, fs4, fs5, fs6, fs8, fs9] for camera in all_standard_pros: camera.read_attrs = ['stats1', 'stats2', 'stats3', 'stats4', 'stats5'] # camera.tiff.read_attrs = [] # leaving just the 'image' @@ -669,52 +734,130 @@ def stage(self): item_check = getattr(pilatus2M.cam, item) item_check.kind = "omitted" -elif Pilatus2M_on == 'h5': +else: + pilatus2M = "Pil2MISNOTWORKING" - pilatus2M = Pilatus2MV33_h5("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2m-1") - pilatus2M.h5.read_attrs = [] +if Pilatus2M_on == True: + + pilatus2M_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2m-1") + pilatus2M_h5.h5.read_attrs = [] STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] - pilatus2M.read_attrs = ["h5"] + STATS_NAMES2M + pilatus2M_h5.read_attrs = ["h5"] + STATS_NAMES2M for stats_name in STATS_NAMES2M: - stats_plugin = getattr(pilatus2M, stats_name) + stats_plugin = getattr(pilatus2M_h5, stats_name) stats_plugin.read_attrs = ["total"] - pilatus2M.cam.ensure_nonblocking() - pilatus2M.h5.ensure_blocking() - pilatus2M.stats3.total.kind = "hinted" - pilatus2M.stats4.total.kind = "hinted" + pilatus2M_h5.cam.ensure_nonblocking() + pilatus2M_h5.h5.ensure_blocking() + pilatus2M_h5.stats3.total.kind = "hinted" + pilatus2M_h5.stats4.total.kind = "hinted" - for item in pilatus2M.stats1.configuration_attrs: - item_check = getattr(pilatus2M.stats1, item) + for item in pilatus2M_h5.stats1.configuration_attrs: + item_check = getattr(pilatus2M_h5.stats1, item) item_check.kind = "omitted" - for item in pilatus2M.stats2.configuration_attrs: - item_check = getattr(pilatus2M.stats2, item) + for item in pilatus2M_h5.stats2.configuration_attrs: + item_check = getattr(pilatus2M_h5.stats2, item) item_check.kind = "omitted" - for item in pilatus2M.stats3.configuration_attrs: - item_check = getattr(pilatus2M.stats3, item) + for item in pilatus2M_h5.stats3.configuration_attrs: + item_check = getattr(pilatus2M_h5.stats3, item) item_check.kind = "omitted" - for item in pilatus2M.stats4.configuration_attrs: - item_check = getattr(pilatus2M.stats4, item) + for item in pilatus2M_h5.stats4.configuration_attrs: + item_check = getattr(pilatus2M_h5.stats4, item) item_check.kind = "omitted" - for item in pilatus2M.stats5.configuration_attrs: - item_check = getattr(pilatus2M.stats5, item) + for item in pilatus2M_h5.stats5.configuration_attrs: + item_check = getattr(pilatus2M_h5.stats5, item) item_check.kind = "omitted" - # for item in pilatus2M.tiff.configuration_attrs: - # item_check = getattr(pilatus2M.tiff, item) + # for item in pilatus2M_h5.tiff.configuration_attrs: + # item_check = getattr(pilatus2M_h5.tiff, item) # item_check.kind = "omitted" - for item in pilatus2M.cam.configuration_attrs: - item_check = getattr(pilatus2M.cam, item) + for item in pilatus2M_h5.cam.configuration_attrs: + item_check = getattr(pilatus2M_h5.cam, item) + item_check.kind = "omitted" + +if Pilatus800_on == True: + pilatus800_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL800K}:", name="pilatus800k-1") + pilatus800_h5.h5.read_attrs = [] + + STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] + pilatus800_h5.read_attrs = ["h5"] + STATS_NAMES2M + + for stats_name in STATS_NAMES: + stats_plugin = getattr(pilatus800_h5, stats_name) + stats_plugin.read_attrs = ["total"] + + for item in pilatus800_h5.stats1.configuration_attrs: + item_check = getattr(pilatus800_h5.stats1, item) + item_check.kind = "omitted" + + for item in pilatus800_h5.stats2.configuration_attrs: + item_check = getattr(pilatus800_h5.stats2, item) item_check.kind = "omitted" -else: - pilatus2M = "Pil2MISNOTWORKING" + for item in pilatus800_h5.stats3.configuration_attrs: + item_check = getattr(pilatus800_h5.stats3, item) + item_check.kind = "omitted" + + for item in pilatus800_h5.stats4.configuration_attrs: + item_check = getattr(pilatus800_h5.stats4, item) + item_check.kind = "omitted" + + for item in pilatus800_h5.stats5.configuration_attrs: + item_check = getattr(pilatus800_h5.stats5, item) + item_check.kind = "omitted" + + # for item in pilatus800_h5.tiff.configuration_attrs: + # item_check = getattr(pilatus800_h5.tiff, item) + # item_check.kind = "omitted" + + for item in pilatus800_h5.cam.configuration_attrs: + item_check = getattr(pilatus800_h5.cam, item) + item_check.kind = "omitted" + + +if Pilatus800_2_on == True: + pilatus8002_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL800K2}:", name="pilatus800k-2") + pilatus8002_h5.h5.read_attrs = [] + STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] + pilatus8002_h5.read_attrs = ["h5"] + STATS_NAMES2M + + for stats_name in STATS_NAMES: + stats_plugin = getattr(pilatus8002_h5, stats_name) + stats_plugin.read_attrs = ["total"] + + for item in pilatus8002_h5.stats1.configuration_attrs: + item_check = getattr(pilatus8002_h5.stats1, item) + item_check.kind = "omitted" + + for item in pilatus8002_h5.stats2.configuration_attrs: + item_check = getattr(pilatus8002_h5.stats2, item) + item_check.kind = "omitted" + + for item in pilatus8002_h5.stats3.configuration_attrs: + item_check = getattr(pilatus8002_h5.stats3, item) + item_check.kind = "omitted" + + for item in pilatus8002_h5.stats4.configuration_attrs: + item_check = getattr(pilatus8002_h5.stats4, item) + item_check.kind = "omitted" + + for item in pilatus8002_h5.stats5.configuration_attrs: + item_check = getattr(pilatus8002_h5.stats5, item) + item_check.kind = "omitted" + + # for item in pilatus8002_h5.tiff.configuration_attrs: + # item_check = getattr(pilatus8002_h5.tiff, item) + # item_check.kind = "omitted" + + for item in pilatus8002_h5.cam.configuration_attrs: + item_check = getattr(pilatus8002_h5.cam, item) + item_check.kind = "omitted" # define the current pilatus detector: pilatus_name and _Epicsname, instead of # pilatus300 or pilatus2M # pilatus_name = pilatus800 @@ -796,4 +939,4 @@ def count_no_save_plan(det): -print(f'Loading {__file__}DONEEEEEEEEEEEEEEEEEEEEEEEEEEEE') +print(f'Loading {__file__}') diff --git a/startup/26-IonChamber.py b/startup/26-IonChamber.py new file mode 100644 index 0000000..586bd88 --- /dev/null +++ b/startup/26-IonChamber.py @@ -0,0 +1,152 @@ +# from nslsii.ad33 import QuadEMV33 +# # from ophyd import Signal +# # from ophyd import Component as Cpt +# #from ophyd.quadem import TetrAMM + +# class new_qm(QuadEMV33): +# corrected_signal = Cpt(Signal,name='corrected_signal',value=0.0,kind='hinted') + +# def __init__(self,*args,**kwargs): +# super().__init__(*args, **kwargs) +# self.current2.mean_value.subscribe(self.update_monitor) + +# def update_monitor(self,old_value,value,**kwargs): + +# atten_2 = int(S3.absorber1.user_readback.value) +# attenuator_name_signal.set(f'att{atten_2}') +# attenuation_factor_signal.set(att_fact_selected[f'att{atten_2}']) +# newvalue = (value) * attenuation_factor_signal.get() +# # print(newvalue) +# self.corrected_signal.set(newvalue) + + + +# # quadem = new_qm("XF:11BMB-BI{IM:3}:IC2_MON", name="quadem") +# # quadem = new_qm("XF:11BMB-BI{IM:3}:IC", name="quadem") +# quadem = QuadEMV33("XF:11BMB-BI{IM:3}:IC2_MON", name="quadem") +# quadem.conf.port_name.put("EM180") +# quadem.stage_sigs["acquire_mode"] = 2 + + +# for i in [1, 2, 3, 4]: +# getattr(quadem, f"{i}_MON").mean_value.kind = "normal" + +# # for i in [1,2,3]: +# # for i in [2,3]: +# # getattr(quadem, f"current{i}").mean_value.kind = "hinted" + + +# quadem.integration_time.put(0.0004) +# quadem.values_per_read.put(500) +# #for continuous reading +# quadem.acquire_mode.put(2) +# #for diamond mode +# quadem.geometry.put(0) + + +# quadem1_cl = EpicsSignal("XF:12ID1-BI{EM:1}EM1:ComputeCurrentOffset1.PROC", name="quadem1_clear") +# quadem2_cl = EpicsSignal("XF:12ID1-BI{EM:1}EM1:ComputeCurrentOffset2.PROC", name="quadem2_clear") +# quadem3_cl = EpicsSignal("XF:12ID1-BI{EM:1}EM1:ComputeCurrentOffset3.PROC", name="quadem3_clear") + +from datetime import datetime, timezone +from typing import Any, Dict, TypeVar +A, B = TypeVar("A"), TypeVar("B") + +class OrderedDictType(Dict[A, B]): + ... + +def quadem_clear(): + yield from bps.mv(quadem1_cl,1) + yield from bps.mv(quadem2_cl,1) + yield from bps.mv(quadem3_cl,1) + + +class IonChamber(Device): + """ + IonChamber is operated by Oxford 400, including 4 channels. + ch4 is used independently on the beam intensity monitoring at s4. + """ + ch4 = Cpt(EpicsSignal, 'IC4_MON') + period_setpoint = Cpt(EpicsSignal, 'PERIOD_SP') + period_readback = Cpt(EpicsSignal, 'PERIOD_MON') + # period_readback = Cpt(EpicsSignal, 'ITIME_MON') + count = Cpt(EpicsSignal, 'GETCS') + + def setExposureTime(self, exptime): + while self.period_readback.get() != exptime: + time.sleep(0.1) + self.period_setpoint.set(exptime) + print("Set Ion Chamber exposure time to {} s".format(self.period_readback.get())) + # return time + + # from bluesky.plan_stubs import trigger_and_read + + def read(self) -> OrderedDictType[str, Dict[str, Any]]: + + print("Triggering Ion Chamber...") + self.count.put(0) + + print("Reading Ion Chamber...") + time.sleep(self.period_readback.get()+0.1) + print(f"Exposed {self.period_readback.get()} s") + + now = datetime.now(timezone.utc).timestamp() + + return { + f"{self.name}_ch4": { + 'value': np.log10(self.ch4.get()), + 'timestamp': now + }, + f"{self.name}_period_setpoint": { + 'value': self.period_setpoint.get(), + 'timestamp': now + }, + f"{self.name}_period_readback": { + 'value': self.period_readback.get(), + 'timestamp': now + }, + f"{self.name}_count": { + 'value': self.count.get(), + 'timestamp': now + } + } + + def trigger_and_read(self): + # return self.count.get() + print("Triggering Ion Chamber...") + yield from bps.mv(self.count,1) + # return self.ch4.get() + + def expose(self): + # return self.count.get() + RE(bps.mv(self.count,1)) + return self.ch4.get() + + # def setExposureTime(self, time): + # self.period_setpoint.put(time) + # print("Set Ion Chamber exposure time to {} s".format(self.period_readback.get())) + + + # ch2 = Cpt(EpicsSignal, 'Current2:MeanValue_RBV') + # ch3 = Cpt(EpicsSignal, 'Current3:MeanValue_RBV') + # ch4 = Cpt(EpicsSignal, 'Current4:MeanValue_RBV') + # sumX = Cpt(EpicsSignal, 'SumX:MeanValue_RBV') + # sumY = Cpt(EpicsSignal, 'SumY:MeanValue_RBV') + # posX = Cpt(EpicsSignal, 'PosX:MeanValue_RBV') + # posY = Cpt(EpicsSignal, 'PosY:MeanValue_RBV') + + +#ben commented this out on 9/20/21 due to errors +ic = IonChamber("XF:11BMB-BI{IM:3}:", name="ic") + + +# bpm.ch1.kind = 'hinted' +# bpm.ch2.kind = 'hinted' +# bpm.ch3.kind = 'hinted' +ic.ch4.kind = 'hinted' + +# bpm.sumX.kind = 'hinted' +# bpm.sumY.kind = 'hinted' +# bpm.posX.kind = 'normal' +# bpm.posY.kind = 'normal' + diff --git a/startup/27-Xspress3.py b/startup/27-Xspress3.py new file mode 100644 index 0000000..daa99d3 --- /dev/null +++ b/startup/27-Xspress3.py @@ -0,0 +1,300 @@ +import os +import h5py +import sys +import numpy as np +import time as ttime +from ophyd.areadetector.plugins import PluginBase +from ophyd import Signal, DeviceStatus +from ophyd import Component as Cpt +from ophyd.areadetector.filestore_mixins import FileStorePluginBase +from ophyd.device import Staged +from enum import Enum +from collections import OrderedDict +from nslsii.detectors.xspress3 import ( + XspressTrigger, + Xspress3Detector, + Xspress3Channel, + Xspress3FileStore, +) + + +class ScanMode(Enum): + step = 1 + fly = 2 + + +class Xspress3FileStoreFlyable(Xspress3FileStore): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @property + def filestore_res(self): + raise Exception("don't want to be here") + return self._filestore_res + + @property + def filestore_spec(self): + if self.parent._mode is ScanMode.fly: + return 'XPS3_FLY' + return 'XSP3' + + def generate_datum(self, key, timestamp, datum_kwargs): + if self.parent._mode is ScanMode.step: + return super().generate_datum(key, timestamp, datum_kwargs) + elif self.parent._mode is ScanMode.fly: + # we are doing something _very_ dirty here to skip a level + # of the inheritance + # this is brittle is if the MRO changes we may not hit all + # the level we expect to + return FileStorePluginBase.generate_datum( + self, key, timestamp, datum_kwargs + ) + + def warmup(self): + """ + A convenience method for 'priming' the plugin. + The plugin has to 'see' one acquisition before it is ready to capture. + This sets the array size, etc. + NOTE : this comes from: + https://github.com/NSLS-II/ophyd/blob/master/ophyd/areadetector/plugins.py + We had to replace "cam" with "settings" here. + Also modified the stage sigs. + """ + print("Warming up the hdf5 plugin...", end="") + set_and_wait(self.enable, 1) + sigs = OrderedDict( + [ + (self.parent.settings.array_callbacks, 1), + (self.parent.settings.image_mode, "Single"), + (self.parent.settings.trigger_mode, "Internal"), + # In case the acquisition time is set very long + (self.parent.settings.acquire_time, 1), + # (self.parent.settings.acquire_period, 1), + (self.parent.settings.acquire, 1), + ] + ) + + original_vals = {sig: sig.get() for sig in sigs} + + for sig, val in sigs.items(): + ttime.sleep(0.1) # abundance of caution + set_and_wait(sig, val) + + # ttime.sleep(2) # wait for acquisition + + for sig, val in reversed(list(original_vals.items())): + ttime.sleep(0.1) + set_and_wait(sig, val) + print("done") + + def describe(self): + desc = super().describe() + + if self.parent._mode is ScanMode.fly: + spec = { + "external": "FileStore:", + "dtype": "array", + # TODO do not hard code + "shape": (self.parent.settings.num_images.get(), 3, 4096), + "source": self.prefix, + } + return {self.parent._f_key: spec} + else: + return super().describe() + + +class XspressTriggerFlyable(XspressTrigger): + def trigger(self): + if self._staged != Staged.yes: + raise RuntimeError("not staged") + + self._status = DeviceStatus(self) + self.settings.erase.put(1) + self._acquisition_signal.put(1, wait=False) + trigger_time = ttime.time() + if self._mode is ScanMode.step: + for sn in self.read_attrs: + if sn.startswith("channel") and "." not in sn: + ch = getattr(self, sn) + self.dispatch(ch.name, trigger_time) + elif self._mode is ScanMode.fly: + self.dispatch(self._f_key, trigger_time) + else: + raise Exception(f"unexpected mode {self._mode}") + self._abs_trigger_count += 1 + return self._status + + +class OPLSXspress3Detector(XspressTriggerFlyable, Xspress3Detector): + # TODO: garth, the ioc is missing some PVs? + # det_settings.erase_array_counters + # (XF:05IDD-ES{Xsp:1}:ERASE_ArrayCounters) + # det_settings.erase_attr_reset (XF:05IDD-ES{Xsp:1}:ERASE_AttrReset) + # det_settings.erase_proc_reset_filter + # (XF:05IDD-ES{Xsp:1}:ERASE_PROC_ResetFilter) + # det_settings.update_attr (XF:05IDD-ES{Xsp:1}:UPDATE_AttrUpdate) + # det_settings.update (XF:05IDD-ES{Xsp:1}:UPDATE) + roi_data = Cpt(PluginBase, "ROIDATA:") + + # Channels: Uncomment these to enable more + channel1 = Cpt(Xspress3Channel, "C1_", channel_num=1, read_attrs=["rois"]) + # channel2 = Cpt(Xspress3Channel, 'C2_', channel_num=2, read_attrs=['rois']) + # channel3 = Cpt(Xspress3Channel, 'C3_', channel_num=3, read_attrs=['rois']) + acquisition_time = Cpt(EpicsSignal, "AcquireTime") + capture_mode = Cpt(EpicsSignal, "HDF5:Capture") + + + erase = Cpt(EpicsSignal, "ERASE") + array_counter = Cpt(EpicsSignal, "ArrayCounter_RBV") + create_dir = Cpt(EpicsSignal, "HDF5:FileCreateDir") + + hdf5 = Cpt(Xspress3FileStoreFlyable, 'HDF1:',write_path_template='',) + + # # TODO: Change file locations for OPLS + # hdf5 = Cpt( + # Xspress3FileStoreFlyable, + # "HDF5:", + # # # read_path_template="/nsls2/xf12id1/data/xpress3", + # # read_path_template="/nsls2/xf12id1/data/xpress3/%Y/%m/%d/", + # # # write_path_template="/nsls2/xf12id1/data/xpress3", + # # write_path_template="/nsls2/xf12id1/data/xpress3/%Y/%m/%d/", + + + # # read_path_template="/nsls2/data/smi/legacy/xf12id1/data/xpress3/%Y/%m/%d/", + # # write_path_template="/nsls2/data/smi/legacy/xf12id1/data/xpress3/%Y/%m/%d/", + # # write_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + # # read_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + # # root="/", + # root='/nsls2/data/smi/legacy/xf12id1/data', + # # root="/home/xspress3/data", + # ) + + # this is used as a latch to put the xspress3 into 'bulk' mode + # for fly scanning. Do this is a signal (rather than as a local variable + # or as a method so we can modify this as part of a plan + fly_next = Cpt(Signal, value=False) + + def __init__( + self, + prefix, + *, + f_key="fluor", + configuration_attrs=None, + read_attrs=None, + **kwargs, + ): + self._f_key = f_key + if configuration_attrs is None: + configuration_attrs = [ + "external_trig", + "total_points", + "spectra_per_point", + "settings", + "rewindable", + ] + if read_attrs is None: + read_attrs = ["channel1", "hdf5"] + super().__init__( + prefix, + configuration_attrs=configuration_attrs, + read_attrs=read_attrs, + **kwargs, + ) + # this is possiblely one too many places to store this + # in the parent class it looks at if the extrenal_trig signal is high + self._mode = ScanMode.step + + # self.create_dir.put(-3) + + # Step-scan interface methods. + def stage(self, *args, **kwargs): + if self.spectra_per_point.get() != 1: + raise NotImplementedError( + "multi spectra per point not supported yet") + self.hdf5.write_path_template = assets_path() + f'{xs.name}/%Y/%m/%d/' + self.hdf5.read_path_template = assets_path() + f'{xs.name}/%Y/%m/%d/' + self.hdf5.reg_root = assets_path() + f'{xs.name}' + ret = super().stage(*args, **kwargs) + self._datum_counter = itertools.count() + return ret + + def trigger(self): + + self._status = DeviceStatus(self) + self.settings.erase.put(1) + self._acquisition_signal.put(1, wait=False) + trigger_time = ttime.time() + + for sn in self.read_attrs: + if sn.startswith('channel') and '.' not in sn: + ch = getattr(self, sn) + self.generate_datum(ch.name, trigger_time) + + self._abs_trigger_count += 1 + return self._status + + def unstage(self): + self.settings.trigger_mode.put(1) # 'Software' + super().unstage() + self._datum_counter = None + + def stop(self): + ret = super().stop() + self.hdf5.stop() + return ret + + # def stop(self, *, success=False): + # ret = super().stop() + # # todo move this into the stop method of the settings object? + # self.settings.acquire.put(0) + # self.hdf5.stop(success=success) + # return ret + + # def stage(self): + # self.hdf5.write_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + # self.hdf5.read_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + # self.hdf5.reg_root = assets_path() + f'{xsx.name}' + # # do the latching + # if self.fly_next.get(): + # self.fly_next.put(False) + # self._mode = ScanMode.fly + # return super().stage() + + # def unstage(self): + # try: + # ret = super().unstage() + # finally: + # self._mode = ScanMode.step + # return ret + + +try: + xs = OPLSXspress3Detector("XF:11BM-ES{Xsp:1}:", + name="xs") + xs.channel1.rois.read_attrs = ["roi{:02}".format(j) + for j in [1, 2, 3, 4]] + + + xs.acquisition_time.set(1) + xs.total_points.set(1) + xs.hdf5.num_extra_dims.put(0) + xs.hdf5.warmup() + + # TODO: Does this need to be done on startup? Better to init manually? + # if os.getenv("TOUCHBEAMLINE", "0") == "1": + # xs.settings.num_channels.put(1) + # xs.channel1.vis_enabled.put(1) + + +except TimeoutError: + xs = None + print("\nCannot connect to Xspress3. Continuing without device.\n") +except Exception as ex: + xs = None + print("\nUnexpected error connecting to Xspress3.\n", ex, end="\n\n") + +# def det_exposure_time_xs(detector, exp_t, meas_t=1): +# yield from bps.mov( +# xs.settings.acquire_time, exp_t, +# # c exp_t+0.2, +# xs.settings.num_images.value, int(meas_t/exp_t)) \ No newline at end of file diff --git a/startup/27-Xspress3.pyQAS b/startup/27-Xspress3.pyQAS new file mode 100644 index 0000000..2cd1e8a --- /dev/null +++ b/startup/27-Xspress3.pyQAS @@ -0,0 +1,512 @@ +from ophyd.areadetector import (AreaDetector, PixiradDetectorCam, ImagePlugin, + TIFFPlugin, StatsPlugin, HDF5Plugin, + ProcessPlugin, ROIPlugin, TransformPlugin, + OverlayPlugin, DetectorBase, ADBase) +from ophyd.areadetector.plugins import PluginBase +from ophyd.areadetector.cam import AreaDetectorCam +from ophyd.device import BlueskyInterface +from ophyd.areadetector.trigger_mixins import SingleTrigger +from ophyd.areadetector.filestore_mixins import (FileStoreIterativeWrite, + FileStoreHDF5IterativeWrite, + FileStoreTIFFSquashing, + FileStoreTIFF) +from ophyd import Signal, EpicsSignal, EpicsSignalRO +from ophyd.status import SubscriptionStatus +from ophyd.sim import NullStatus # TODO: remove after complete/collect are defined +from ophyd import Component as Cpt +from ophyd import DynamicDeviceComponent as DDC +from ophyd.status import SubscriptionStatus, DeviceStatus + +from pathlib import PurePath +from nslsii.detectors.xspress3 import (XspressTrigger, Xspress3Detector, Xspress3DetectorSettings, make_rois, + Xspress3Channel, Xspress3FileStore, Xspress3ROI, logger) + +# from isstools.trajectory.trajectory import trajectory_manager + +import bluesky.plans as bp +import bluesky.plan_stubs as bps + +import numpy as np +import itertools +import time as ttime +from collections import deque, OrderedDict +import warnings +from databroker.assets.handlers import XS3_XRF_DATA_KEY as XRF_DATA_KEY + + +class Xspress3FileStoreFlyable(Xspress3FileStore): + def warmup(self): + """ + A convenience method for 'priming' the plugin. + The plugin has to 'see' one acquisition before it is ready to capture. + This sets the array size, etc. + NOTE : this comes from: + https://github.com/NSLS-II/ophyd/blob/master/ophyd/areadetector/plugins.py + We had to replace "cam" with "settings" here. + Also modified the stage sigs. + """ + print("warming up the hdf5 plugin...") + self.enable.set(1).wait() + sigs = OrderedDict([(self.parent.settings.array_callbacks, 1), + (self.parent.settings.trigger_mode, 'Internal'), + # just in case the acquisition time is set very long... + (self.parent.settings.acquire_time, 1), + # (self.capture, 1), + (self.parent.settings.acquire, 1)]) + + original_vals = {sig: sig.get() for sig in sigs} + + # Remove the hdf5.capture item here to avoid an error as it should reset back to 0 itself + # del original_vals[self.capture] + + for sig, val in sigs.items(): + ttime.sleep(0.1) # abundance of caution + sig.set(val).wait() + + ttime.sleep(2) # wait for acquisition + + for sig, val in reversed(list(original_vals.items())): + ttime.sleep(0.1) + sig.set(val).wait() + print("done") + +class Xspress3XDetector(DetectorBase): + settings = Cpt(Xspress3DetectorSettings, 'det1:') + + external_trig = Cpt(Signal, value=False, + doc='Use external triggering') + total_points = Cpt(Signal, value=-1, + doc='The total number of points to acquire overall') + spectra_per_point = Cpt(Signal, value=1, + doc='Number of spectra per point') + make_directories = Cpt(Signal, value=False, + doc='Make directories on the DAQ side') + rewindable = Cpt(Signal, value=False, + doc='Xspress3 cannot safely be rewound in bluesky') + + # channel1 = Cpt(Xspress3Channel, 'C1_', channel_num=1) + # XF:11BM-ES{Xsp:1} + + data_key = XRF_DATA_KEY + + def __init__(self, prefix, *, read_attrs=None, configuration_attrs=None, + name=None, parent=None, + # to remove? + file_path='', ioc_file_path='', default_channels=None, + channel_prefix=None, + roi_sums=False, + # to remove? + **kwargs): + + if read_attrs is None: + read_attrs = ['channel1', ] + + if configuration_attrs is None: + configuration_attrs = ['channel1.rois', 'settings'] + + super().__init__(prefix, read_attrs=read_attrs, + configuration_attrs=configuration_attrs, + name=name, parent=parent, **kwargs) + + # get all sub-device instances + sub_devices = {attr: getattr(self, attr) + for attr in self._sub_devices} + + # filter those sub-devices, just giving channels + channels = {dev.channel_num: dev + for attr, dev in sub_devices.items() + if isinstance(dev, Xspress3Channel) + } + + # make an ordered dictionary with the channels in order + self._channels = OrderedDict(sorted(channels.items())) + + @property + def channels(self): + return self._channels.copy() + + @property + def all_rois(self): + for ch_num, channel in self._channels.items(): + for roi in channel.all_rois: + yield roi + + @property + def enabled_rois(self): + for roi in self.all_rois: + if roi.enable.get(): + yield roi + + def read_hdf5(self, fn, *, rois=None, max_retries=2): + '''Read ROI data from an HDF5 file using the current ROI configuration + Parameters + ---------- + fn : str + HDF5 filename to load + rois : sequence of Xspress3ROI instances, optional + ''' + if rois is None: + rois = self.enabled_rois + + num_points = self.settings.num_images.get() + if isinstance(fn, h5py.File): + hdf = fn + else: + hdf = h5py.File(fn, 'r') + + RoiTuple = Xspress3ROI.get_device_tuple() + + handler = Xspress3HDF5Handler(hdf, key=self.data_key) + for roi in self.enabled_rois: + roi_data = handler.get_roi(chan=roi.channel_num, + bin_low=roi.bin_low.get(), + bin_high=roi.bin_high.get(), + max_points=num_points) + + roi_info = RoiTuple(bin_low=roi.bin_low.get(), + bin_high=roi.bin_high.get(), + ev_low=roi.ev_low.get(), + ev_high=roi.ev_high.get(), + value=roi_data, + value_sum=None, + enable=None) + + yield roi.name, roi_info + + +class Xspress3XChannel(ADBase): + roi_name_format = 'Det{self.channel_num}_{roi_name}' + roi_sum_name_format = 'Det{self.channel_num}_{roi_name}_sum' + + rois = DDC(make_rois(range(1, 4))) + vis_enabled = Cpt(EpicsSignal, 'EnableCallbacks') + # extra_rois_enabled = Cpt(EpicsSignal, 'PluginControlValExtraROI') + + def __init__(self, prefix, *, channel_num=None, **kwargs): + self.channel_num = int(channel_num) + + super().__init__(prefix, **kwargs) + + @property + def all_rois(self): + for roi in range(1, self.rois.num_rois.get() + 1): + yield getattr(self.rois, 'roi{:02d}'.format(roi)) + + def set_roi(self, index, ev_low, ev_high, *, name=None): + '''Set specified ROI to (ev_low, ev_high) + Parameters + ---------- + index : int or Xspress3ROI + The roi index or instance to set + ev_low : int + low eV setting + ev_high : int + high eV setting + name : str, optional + The unformatted ROI name to set. Each channel specifies its own + `roi_name_format` and `roi_sum_name_format` in which the name + parameter will get expanded. + ''' + if isinstance(index, Xspress3ROI): + roi = index + else: + if index <= 0: + raise ValueError('ROI index starts from 1') + roi = list(self.all_rois)[index - 1] + + roi.configure(ev_low, ev_high) + if name is not None: + roi_name = self.roi_name_format.format(self=self, roi_name=name) + roi.name = roi_name + roi.value.name = roi_name + roi.value_sum.name = self.roi_sum_name_format.format(self=self, + roi_name=name) + + def clear_all_rois(self): + '''Clear all ROIs''' + for roi in self.all_rois: + roi.clear() + +xsx = QASXspress3XDetector('XF:11BM-ES{Xsp:1}:', name='xspress3-1') + +class QASXspress3XDetector(XspressTrigger, Xspress3XDetector): + roi_data = Cpt(PluginBase, 'ROIDATA:') + channel1 = Cpt(Xspress3XChannel, 'C1_', channel_num=1, read_attrs=['rois']) + # create_dir = Cpt(EpicsSignal, 'HDF5:FileCreateDir') + + mca1_sum = Cpt(EpicsSignal, 'ARRSUM1:ArrayData') + + mca1 = Cpt(EpicsSignal, 'ARR1:ArrayData') + + cnt_time = Cpt(EpicsSignal, 'C1_SCA0:Value_RBV') + + # channel6 = Cpt(Xspress3Channel, 'C6_', channel_num=6) + + #TODO change folder to xspress3 + # hdf5 = Cpt(Xspress3FileStoreFlyable, 'HDF1:', + # read_path_template='/nsls2/data/qas-new/legacy/raw/x3x/%Y/%m/%d/', + # root='/nsls2/data/qas-new/legacy/raw/', + # write_path_template='/nsls2/data/qas-new/legacy/raw/x3x/%Y/%m/%d/', + # ) + hdf5 = Cpt(Xspress3FileStoreFlyable, 'HDF1:',write_path_template='',) + + def __init__(self, prefix, *, configuration_attrs=None, read_attrs=None, + **kwargs): + if configuration_attrs is None: + configuration_attrs = ['external_trig', 'total_points', + 'spectra_per_point', 'settings', + 'rewindable'] + if read_attrs is None: + read_attrs = ['channel1', 'hdf5', 'settings.acquire_time'] + super().__init__(prefix, configuration_attrs=configuration_attrs, + read_attrs=read_attrs, **kwargs) + self.set_channels_for_hdf5() + + self._asset_docs_cache = deque() + self._datum_counter = None + + self.channel1.rois.roi01.configuration_attrs.append('bin_low') + + # Step-scan interface methods. + def stage(self, *args, **kwargs): + if self.spectra_per_point.get() != 1: + raise NotImplementedError( + "multi spectra per point not supported yet") + self.hdf5.write_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + self.hdf5.read_path_template = assets_path() + f'{xsx.name}/%Y/%m/%d/' + self.hdf5.reg_root = assets_path() + f'{xsx.name}' + ret = super().stage(*args, **kwargs) + self._datum_counter = itertools.count() + return ret + + def trigger(self): + + self._status = DeviceStatus(self) + self.settings.erase.put(1) + self._acquisition_signal.put(1, wait=False) + trigger_time = ttime.time() + + for sn in self.read_attrs: + if sn.startswith('channel') and '.' not in sn: + ch = getattr(self, sn) + self.generate_datum(ch.name, trigger_time) + + self._abs_trigger_count += 1 + return self._status + + def unstage(self): + self.settings.trigger_mode.put(1) # 'Software' + super().unstage() + self._datum_counter = None + + def stop(self): + ret = super().stop() + self.hdf5.stop() + return ret + + def collect(self): + # TODO: try to separate it from the xspress3 class + collected_frames = self.settings.array_counter.get() + + # This is a hack around the issue with .NORD (number of elements to # + # read) that does not match .NELM (number of elements to that the array + # will hold) + dpb_sec_nelm_count = int(dpb_sec_nelm.get()) + dpb_nsec_nelm_count = int(dpb_nsec_nelm.get()) + dpb_sec_values = np.array(dpb_sec.get(count=dpb_sec_nelm_count), + dtype='float128')[:collected_frames * 2: 2] + dpb_nsec_values = np.array(dpb_nsec.get(count=dpb_nsec_nelm_count), + dtype='float128')[:collected_frames * 2: 2] + + di_timestamps = dpb_sec_values + dpb_nsec_values * 1e-9 + + len_di_timestamps = len(di_timestamps) + len_datum_ids = len(self._datum_ids) + + if len_di_timestamps != len_datum_ids: + warnings.warn(f'The length of "di_timestamps" ({len_di_timestamps}) ' + f'does not match the length of "self._datum_ids" ({len_datum_ids})') + + num_frames = min(len_di_timestamps, len_datum_ids) + num_frames = len_datum_ids + for frame_num in range(num_frames): + datum_id = self._datum_ids[frame_num] + # ts = di_timestamps[frame_num] + ts = di_timestamps + + data = {self.name: datum_id} + # TODO: fix the lost precision as pymongo complained about np.float128. + ts = float(ts) + + yield {'data': data, + 'timestamps': {key: ts for key in data}, + 'time': ts, # TODO: use the proper timestamps from the mono start and stop times + 'filled': {key: False for key in data}} + + def set_channels_for_hdf5(self, channels=(1)): + """ + Configure which channels' data should be saved in the resulted hdf5 file. + Parameters + ---------- + channels: tuple, optional + the channels to save the data for + """ + # The number of channel + for n in channels: + getattr(self, f'channel{n}').rois.read_attrs = ['roi{:02}'.format(j) for j in [1]] + self.hdf5.num_extra_dims.put(0) + self.settings.num_channels.put(8) + + +def initialize_Xspress3X(xsx, hdf5_warmup=True): + # TODO: do not put on startup or do it conditionally, if on beamline. + xsx.channel1.vis_enabled.put(1) + xsx.total_points.put(1) + + # This is necessary for when the ioc restarts + # we have to trigger one image for the hdf5 plugin to work correctly + # else, we get file writing errors + if hdf5_warmup: + xsx.hdf5.warmup() + + # Hints: + for n in [1, 2]: # TODO: 8? + getattr(xsx, f'channel{n}').rois.roi01.value.kind = 'hinted' + + xsx.settings.configuration_attrs = ['acquire_period', + 'acquire_time', + 'gain', + 'image_mode', + 'manufacturer', + 'model', + 'num_exposures', + 'num_images', + 'temperature', + 'temperature_actual', + 'trigger_mode', + 'config_path', + 'config_save_path', + 'invert_f0', + 'invert_veto', + 'xsp_name', + 'num_channels', + 'num_frames_config', + 'run_flags', + 'trigger_signal'] + + for n, d in xsx.channels.items(): + roi_names = ['roi{:02}'.format(j) for j in [1]] + d.rois.read_attrs = roi_names + d.rois.configuration_attrs = roi_names + for roi_n in roi_names: + getattr(d.rois, roi_n).value_sum.kind = 'omitted' + + + + +def xsx_count(acq_time: float = 1, num_frames: int = 1): + yield from bps.mv(xsx.settings.erase, 0) + yield from bp.count([xsx], acq_time) + + +class QASXspress3XDetectorStream(QASXspress3XDetector): + + def stage(self, acq_rate, traj_time, *args, **kwargs): + self.hdf5.file_write_mode.put(2) # put it to Stream |||| IS ALREADY STREAMING + self.external_trig.put(True) + self.set_expected_number_of_points(acq_rate, traj_time) + self.spectra_per_point.put(1) + self.settings.trigger_mode.put(3) # put the trigger mode to TTL in + + super().stage(*args, **kwargs) + # note, hdf5 is already capturing at this point + self.settings.acquire.put(1) # start recording data + + def set_expected_number_of_points(self, acq_rate, traj_time): + self._num_points = int(acq_rate * (traj_time + 1)) + self.total_points.put(self._num_points) + + def describe_collect(self): + return_dict = {self.name: + {f'{self.name}': {'source': 'XS', + 'dtype': 'array', + 'shape': [self.settings.num_images.get(), + self.hdf5.array_size.height.get(), + self.hdf5.array_size.width.get()], + 'filename': f'{self.hdf5.full_file_name.get()}', + 'external': 'FILESTORE:'}}} + return return_dict + + def collect(self): + num_frames = len(self._datum_ids) + + # break num_frames up and yield in sections? + + for frame_num in range(num_frames): + datum_id = self._datum_ids[frame_num] + data = {self.name: datum_id} + + ts = ttime.time() + + yield {'data': data, + 'timestamps': {key: ts for key in data}, + 'time': ts, # TODO: use the proper timestamps from the mono start and stop times + 'filled': {key: False for key in data}} + # print(f"-------------------{ts}-------------------------------------") + + def collect_asset_docs(self): + items = list(self._asset_docs_cache) + self._asset_docs_cache.clear() + for item in items: + yield item + +from itertools import product +import pandas as pd +from databroker.assets.handlers import HandlerBase, Xspress3HDF5Handler + + +class QASXspress3XHDF5Handler(Xspress3HDF5Handler): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._roi_data = None + self._num_channels = None + + def _get_dataset( + self): # readpout of the following stuff should be done only once, this is why I redefined _get_dataset method - Denis Leshchev Feb 9, 2021 + # dealing with parent + super()._get_dataset() + + # finding number of channels + if self._num_channels is not None: + return + print('determining number of channels') + shape = self.dataset.shape + if len(shape) != 3: + raise RuntimeError(f'The ndim of the dataset is not 3, but {len(shape)}') + self._num_channels = shape[1] + + if self._roi_data is not None: + return + print('reading ROI data') + self.chanrois = [f'CHAN{c}ROI{r}' for c, r in product([1], [1])] + _data_columns = [self._file['/entry/instrument/detector/NDAttributes'][chanroi][()] for chanroi in + self.chanrois] + data_columns = np.vstack(_data_columns).T + self._roi_data = pd.DataFrame(data_columns, columns=self.chanrois) + + def __call__(self, *args, frame=None, **kwargs): + self._get_dataset() + return_dict = {f'ch_{i + 1}': self._dataset[frame, i, :] for i in range(self._num_channels)} + return_dict_rois = {chanroi: self._roi_data[chanroi][frame] for chanroi in self.chanrois} + return {**return_dict, **return_dict_rois} + +# initialize_Xspress3X(xsx) + +# xsx_stream = QASXspress3XDetectorStream('XF:11BM-ES{Xsp:1}:', name='xsx_stream') +# initialize_Xspress3X(xsx_stream, hdf5_warmup=True) + + +# heavy-weight file handler +# db.reg.register_handler(QASXspress3XHDF5Handler.HANDLER_NAME, +# QASXspress3XHDF5Handler, overwrite=True) \ No newline at end of file diff --git a/startup/51-linkam-stages.py b/startup/51-linkam-stages.py index 5c6ef63..4ac3ba5 100644 --- a/startup/51-linkam-stages.py +++ b/startup/51-linkam-stages.py @@ -1,5 +1,8 @@ # EPICS interface developed by Jakub # Bsui code adopted from BMM/Bruce Ravel and modified by Ruipeng Li +# 2025-08-19: Modified by Siyu Wu + +import pandas as pd class LinkamThermal(Device): diff --git a/startup/51-linkam-stages_new.py b/startup/51-linkam-stages_new.py new file mode 100644 index 0000000..8fcd7bb --- /dev/null +++ b/startup/51-linkam-stages_new.py @@ -0,0 +1,952 @@ +# EPICS interface developed by Jakub +# Bsui code adopted from BMM/Bruce Ravel and modified by Ruipeng Li +# 2025-08-19: Modified by Siyu Wu + +import asyncio +import pandas as pd +import time +import json +from pathlib import Path +from datetime import datetime + +class LinkamThermal(Device): + """ + Device interface and experiment orchestration for the Linkam thermal stage. + Supports step programming, temperature/rate control, async experiment runs, + and automated data archiving. + """ + + # Set-and-read signals + cmd = Cpt(EpicsSignal, "STARTHEAT") + temperature_setpoint = Cpt(EpicsSignal, "SETPOINT:SET") + temperature_rate_setpoint = Cpt(EpicsSignal, "RAMPRATE:SET") + + # Read-Only signals + status_power = Cpt(EpicsSignalRO, "STARTHEAT") + status_code = Cpt(EpicsSignalRO, "STATUS") + # status_code = Cpt(EpicsSignal, 'STATUS') + # done = Cpt(AtSetpoint, parent_attr = 'status_code') + temperature_current = Cpt(EpicsSignalRO, "TEMP") + temperature_rate_current = Cpt(EpicsSignalRO, "RAMPRATE") + power = Cpt(EpicsSignalRO, "POWER") + + # Not commonly used signals + init = Cpt(EpicsSignal, "INIT") + model_array = Cpt(EpicsSignal, "MODEL") + serial_array = Cpt(EpicsSignal, "SERIAL") + stage_model_array = Cpt(EpicsSignal, "STAGE:MODEL") + stage_serial_array = Cpt(EpicsSignal, "STAGE:SERIAL") + firm_ver = Cpt(EpicsSignal, "FIRM:VER") + hard_ver = Cpt(EpicsSignal, "HARD:VER") + ctrllr_err = Cpt(EpicsSignal, "CTRLLR:ERR") + config = Cpt(EpicsSignal, "CONFIG") + stage_config = Cpt(EpicsSignal, "STAGE:CONFIG") + disable = Cpt(EpicsSignal, "DISABLE") + dsc = Cpt(EpicsSignal, "DSC") + # RR_set = Cpt(EpicsSignal, 'RAMPRATE:SET') + # RR = Cpt(EpicsSignal, 'RAMPRATE') + ramptime = Cpt(EpicsSignal, "RAMPTIME") + # startheat = Cpt(EpicsSignal, 'STARTHEAT') + holdtime_set = Cpt(EpicsSignal, "HOLDTIME:SET") + holdtime = Cpt(EpicsSignal, "HOLDTIME") + lnp_speed = Cpt(EpicsSignal, "LNP_SPEED") + lnp_mode_set = Cpt(EpicsSignal, "LNP_MODE:SET") + lnp_speed_set = Cpt(EpicsSignal, "LNP_SPEED:SET") + + # Stage origin position logging functions + # Add by Siyu Wu 2025-08-19 + + # Default step columns and PVs for thermal mode + step_columns = ['stepNo', 'temperature', 'rate', 'wait_time'] + pv_list = [ + 'XF:11BM-ES:{LINKAM}:TEMP', + 'XF:11BM-ES:{LINKAM}:RAMPRATE', + 'XF:11BM-ES:{LINKAM}:SETPOINT', + 'XF:11BM-ES:{LINKAM}:STATUS', + 'XF:11BM-ES:{LINKAM}:POWER' + ] + + def __init__(self, prefix, name=None, step_columns=None, pv_list=None, **kwargs): + """ + Initialize LinkamThermal device, step configuration, and position logging. + """ + super().__init__(prefix, name=name, **kwargs) + self.folder = '/nsls2/data3/cms/shared/config/bluesky/profile_collection/startup/cfg/' + self.config_file = Path(self.folder + 'linkam_stage_pos.cfg') + self.csv_path = Path(self.folder + f'{name}_step.csv') + self.step_columns = step_columns or self.step_columns + self.pv_list = pv_list or self.pv_list + self.step_config = pd.DataFrame(columns=self.step_columns) + self._sync() + self.positions = self._load_config() + self.loadOrigin() + + def _sync(self): + """ + Sync current motor positions to internal state. + """ + self.xo = smx.position + self.yo = smy.position + + def _save_config(self): + """Save current positions to JSON config file.""" + with open(self.config_file, 'w') as f: + json.dump(self.positions, f, indent=2) + + def _load_config(self): + """Load positions from JSON config file.""" + if self.config_file.exists(): + with open(self.config_file, 'r') as f: + return json.load(f) + return {} + + def setOrigin(self): + """Save current origin position (xo, yo) with timestamp to config file.""" + if self.config_file.exists(): + with open(self.config_file, 'r') as f: + self.positions = json.load(f) + else: + self.positions = {} + + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + entry = {'xo': self.xo, 'yo': self.yo, 'timestamp': timestamp} + self.positions.setdefault(self.name, []).append(entry) + self._save_config() + print(f"Saved current position for '{self.name}' at {timestamp}.") + + def loadOrigin(self): + """Load the last saved origin position (xo, yo) from the config file.""" + if self.config_file.exists(): + with open(self.config_file, 'r') as f: + self.positions = json.load(f) + entries = self.positions.get(self.name) + if entries: + latest = entries[-1] + self.xo = latest['xo'] + self.yo = latest['yo'] + print(f"Loaded last position for '{self.name}' from {latest.get('timestamp', 'unknown')}") + else: + print(f"No saved position found for '{self.name}'.") + else: + print(f"No config file found for '{self.name}'.") + + def on(self): + """Turn the stage heater on.""" + while self.cmd.get() != 1: + time.sleep(0.2) + self.cmd.put(1) + return self.cmd.get() + + def _on(self): + """ + Internal method to turn the stage on. + """ + yield from bps.mv(self.cmd, 1) + + def off(self): + """Turn the stage heater off.""" + while self.cmd.get() != 0: + time.sleep(0.2) + self.cmd.put(0) + return self.cmd.get() + + def _off(self): + """ + Internal method to turn the stage off. + """ + yield from bps.mv(self.cmd, 0) + + def setTemperature(self, temperature): + """ + Sets the temperature setpoint for the stage. + """ + while self.temperature_setpoint.get() != temperature: + time.sleep(0.2) + self.temperature_setpoint.put(temperature) + return self.temperature_setpoint.get() + + def setTemperatureRate(self, temperature_rate: float) -> float: + """Set the temperature ramp rate for the stage.""" + while self.temperature_rate_setpoint.get() != temperature_rate: + time.sleep(0.2) + self.temperature_rate_setpoint.put(temperature_rate) + return self.temperature_rate_setpoint.get() + + def temperature(self) -> float: + """Get the current temperature of the stage.""" + return self.temperature_current.get() + + def temperatureRate(self) -> float: + """Get the current temperature ramp rate of the stage.""" + return self.temperature_rate_current.get() + + @property + def serial(self): + return self.arr2word(self.serial_array.get()) + + @property + def model(self): + return self.arr2word(self.model_array.get()) + + @property + def stage_model(self): + return self.arr2word(self.stage_model_array.get()) + + @property + def stage_serial(self): + return self.arr2word(self.stage_serial_array.get()) + + @property + def firmware_version(self): + return self.arr2word(self.firm_ver.get()) + + @property + def hardware_version(self): + return self.arr2word(self.hard_ver.get()) + + def status(self): + """ + Print a summary of the current stage status. + Shows temperature, setpoint, heater, pump, and error states. + """ + text = f"\nCurrent temperature = {self.temperature():.1f}, setpoint = {self.temperature_setpoint.get():.1f}\n\n" + code = int(self.status_code.get()) + text += f"Error : {'yes' if code & 1 else 'no'}\n" + text += f"At setpoint : {'yes' if code & 2 else 'no'}\n" + text += f"Heater : {'on' if code & 4 else 'off'}\n" + text += f"Pump : {'on' if code & 8 else 'off'}\n" + text += f"Pump Auto : {'yes' if code & 16 else 'no'}\n" + print(text) + + # Linkam Stage Step Configuration Read/Load functions + # Added by Siyu Wu 2025-08-19 + + def load_step_config(self) -> pd.DataFrame: + """ + Load step configuration from CSV. + Uses self.step_columns for required columns. + Returns: pandas DataFrame + """ + df = pd.read_csv(self.csv_path) + required_cols = set(self.step_columns) + if not required_cols.issubset(df.columns): + raise ValueError(f"CSV must contain columns: {required_cols}") + self.step_config = df + return df + + def save_step_config(self) -> None: + """Save step configuration DataFrame to CSV.""" + self.step_config.to_csv(self.csv_path, index=False) + + def show_step_config(self) -> None: + """Display the current step configuration.""" + if hasattr(self, 'step_config'): + print(self.step_config) + else: + print("No step configuration loaded.") + def add_step(self, *args, **kwargs) -> None: + """ + Add a new step to the step configuration. + You can use positional arguments (in order of self.step_columns, skipping 'stepNo') + or keyword arguments (column=value). + Example: + add_step(25, 10, 15) # temperature, rate, wait_time + add_step(25, 10, 15, -1000, 100) # for tensile: temperature, rate, wait_time, position, velocity + add_step(temperature=25, rate=10, wait_time=15) + """ + if not hasattr(self, 'step_config'): + self.step_config = pd.DataFrame(columns=self.step_columns) + stepNo = len(self.step_config) + new_step = {'stepNo': stepNo} + # Fill from positional args + cols = [col for col in self.step_columns if col != 'stepNo'] + for i, col in enumerate(cols): + if i < len(args): + new_step[col] = args[i] + else: + new_step[col] = kwargs.get(col, None) + self.step_config = pd.concat([self.step_config, pd.DataFrame([new_step])], ignore_index=True) + self.save_step_config() + print(f"Added step {stepNo}: " + ", ".join(f"{k}={v}" for k, v in new_step.items() if k != 'stepNo')) + + def set_step(self, stepNo: int, *args, **kwargs) -> None: + """ + Update a specific step in the step configuration. + You can use positional arguments (in order of self.step_columns, skipping 'stepNo') + or keyword arguments (column=value). + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + current_data = {'stepNo': stepNo} + cols = [col for col in self.step_columns if col != 'stepNo'] + for i, col in enumerate(cols): + if i < len(args): + current_data[col] = args[i] + else: + current_data[col] = kwargs.get(col, self.step_config.loc[stepNo, col]) + self.step_config.loc[stepNo] = current_data + self.save_step_config() + print(f"Updated step {stepNo}: " + ", ".join(f"{k}={v}" for k, v in current_data.items() if k != 'stepNo')) + + def delete_step(self, stepNo: int) -> None: + """ + Delete a specific step from the step configuration. + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + self.step_config = self.step_config[self.step_config['stepNo'] != stepNo] + self.step_config.reset_index(drop=True, inplace=True) + self.step_config['stepNo'] = range(len(self.step_config)) + self.save_step_config() + print(f"Deleted step {stepNo}.") + + def insert_step(self, stepNo: int, *args, **kwargs) -> None: + """ + Insert a new step at a specific position in the step configuration. + You can use positional arguments (in order of self.step_columns, skipping 'stepNo') + or keyword arguments (column=value). + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo > len(self.step_config): + raise IndexError("Step number out of range.") + new_step = {'stepNo': stepNo} + cols = [col for col in self.step_columns if col != 'stepNo'] + for i, col in enumerate(cols): + if i < len(args): + new_step[col] = args[i] + else: + new_step[col] = kwargs.get(col, None) + upper_half = self.step_config[self.step_config['stepNo'] >= stepNo].copy() + upper_half['stepNo'] += 1 + self.step_config = pd.concat([ + self.step_config[self.step_config['stepNo'] < stepNo], + pd.DataFrame([new_step]), + upper_half + ], ignore_index=True) + self.save_step_config() + print(f"Inserted step at position {stepNo}: " + ", ".join(f"{k}={v}" for k, v in new_step.items() if k != 'stepNo')) + + # --- Step Execution --- + + def run_step(self, stepNo: int = 0) -> None: + """ + Run the thermal stage from a specific step (blocking). + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + for i in range(stepNo, len(self.step_config)): + self.run_single_step(i) + self.off() + + def run_single_step(self, stepNo: int, stop_evt: threading.Event = None) -> None: + """ + Run a single step (blocking), responsive to stop_evt. + Prints step info before running. + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + if stop_evt and stop_evt.is_set(): + return + step = self.step_config.iloc[stepNo] + print(f"[LINKAM] Running step {stepNo}: Temperature={step['temperature']}, Rate={step['rate']}, Wait={step['wait_time']}s") + self.on() + self.setTemperature(step['temperature']) + self.setTemperatureRate(step['rate']) + wait_s = float(step.get('wait_time', 0)) + end = time.monotonic() + wait_s + while time.monotonic() < end: + if stop_evt and stop_evt.is_set(): + break + time.sleep(min(0.1, end - time.monotonic())) + + async def run_single_step_async(self, stepNo: int, stop_event: asyncio.Event = None) -> None: + """ + Async wrapper for run_single_step that bridges to a threading.Event. + """ + thread_stop = threading.Event() + async def _bridge(): + if stop_event is None: + return + await stop_event.wait() + thread_stop.set() + bridge_task = asyncio.create_task(_bridge()) + try: + await asyncio.to_thread(self.run_single_step, stepNo, thread_stop) + finally: + if not bridge_task.done(): + bridge_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await bridge_task + + async def run_step_async(self, stepNo: int = 0, stop_event: asyncio.Event = None) -> None: + """ + Run the thermal stage from a specific step asynchronously. + """ + if stop_event is None: + stop_event = asyncio.Event() + try: + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + for i in range(stepNo, len(self.step_config)): + if stop_event.is_set(): + print("[LINKAM] Stop event detected, aborting step run.") + break + await self.run_single_step_async(i, stop_event=stop_event) + finally: + self.off() + + async def run_and_archive(self, output_file=None, stepNo=0, verbose=True, stop_event=None): + """ + Run Linkam steps and archive PVs asynchronously. + Responds to external asyncio.Event stop signal. + + Parameters: + output_file (str, optional): Path to save archived data. If None, uses timestamped default. + stepNo (int, optional): Step number to start from. Defaults to 0. + verbose (bool, optional): If True, prints progress information. + stop_event (asyncio.Event, optional): External stop signal. If set, aborts run. + + This method wraps the Linkam step runner in an archiver context, recording all PVs in pv_list + to output_file while running the steps. If interrupted, turns off the heater and exits cleanly. + """ + if output_file is None: + now = datetime.now().strftime("%Y%m%d-%H-%M-%S") + output_file = self.folder + f'linkam_archiver_record_{now}.csv' + pv_list = self.pv_list + if stop_event is None: + stop_event = asyncio.Event() + @archived(output_file, pv_list, verbose=verbose) + async def run_steps_async(): + await self.run_step_async(stepNo, stop_event=stop_event) + try: + await run_steps_async() + except KeyboardInterrupt: + print("[LINKAM] Interrupted! Turning off heater.") + stop_event.set() + self.off() + +################################################################################## + + + # def set(self, value): + # if value == 'Open': + # return self.full.set('Open') #& self.soft.set('Open') + # elif value == 'Soft': + # return self.soft.set('Open') & self.full.set('Close') + # elif value == 'Close': + # return self.full.set('Close') & self.soft.set('Close') + # else: + # raise ValueError("value must be in {'Open', 'Close', 'Soft'}") + + +################################################################################## + +# Legacy Fuctions (Not Used) + +# def setLinkamOn(self): +# caput('XF:11BM-ES:{LINKAM}:STARTHEAT', 1) +# return 1 + +# def setLinkamOff(self): +# caput('XF:11BM-ES:{LINKAM}:STARTHEAT', 0) +# return 0 + +# def linkamTemperature(self): +# return caget('XF:11BM-ES:{LINKAM}:TEMP') +# def setLinkamTemperature(self,temperature ): +# caput('XF:11BM-ES:{LINKAM}:SETPOINT:SET', temperature) +# return temperature + + +# def setLinkamRate(self, rate): +# caput('XF:11BM-ES:{LINKAM}:RAMPRATE:SET', rate) +# return rate + +# def linkamStatus(self): +# return caget('XF:11BM-ES:{LINKAM}:STATUS') + + +# def linkamTensilePos(self): +# return caget('XF:11BM-ES:{LINKAM}:TST_MOTOR_POS') + + +class LinkamTensile(LinkamThermal): + """ + Device interface and experiment orchestration for the Linkam tensile stage. + + Supports step programming with temperature, ramp rate, wait time, position, velocity, + and automated data archiving of all relevant PVs (temperature, force, position, velocity, etc). + """ + + # cmd = Cpt(EpicsSignal, 'STARTHEAT') + # temperature_setpoint = Cpt(EpicsSignal, 'SETPOINT:SET') + # temperature_rate_setpoint = Cpt(EpicsSignal, 'RAMPRATE:SET') + + # status = Cpt(EpicsSignalRO, 'STATUS') + # temperature = Cpt(EpicsSignalRO, 'TEMP') + # rampRate = Cpt(EpicsSignalRO, 'RAMPRATE') + + # Read-Only signals for the states of the stage + status_code_Tensile = Cpt(EpicsSignalRO, "TST_STATUS") + POS = Cpt(EpicsSignalRO, "TST_MOTOR_POS") + POS_RAW = Cpt(EpicsSignalRO, "TST_RAW_MOTOR_POS") + + FORCE = Cpt(EpicsSignalRO, "TST_FORCE") + STRAIN = Cpt(EpicsSignalRO, "TST_STRAIN") + STRESS = Cpt(EpicsSignalRO, "TST_STRESS") + + tensile_maxs_force = Cpt(EpicsSignalRO, "TST_MAX_FORCE") + tensile_remain_cycles = Cpt(EpicsSignalRO, "TST_CYCLES_REMAINING") + + # Read-Only signals of the set points + direction = Cpt(EpicsSignalRO, "TST_TABLE_DIR") + force = Cpt(EpicsSignal, "TST_FORCE_SETPOINT") + distance = Cpt(EpicsSignal, "TST_MTR_DIST_SP") # relative distance + mode = Cpt(EpicsSignalRO, "TST_TABLE_MODE") + velocity = Cpt(EpicsSignal, "TST_MTR_VEL") + + # Set-and-read signals + run_cmd = Cpt(EpicsSignal, "TST_START_MOTOR") # start/stop the motor + + direction_setpoint = Cpt(EpicsSignal, "TST_TABLE_DIR:SET") + force_setpoint = Cpt(EpicsSignal, "TST_FORCE_SETPOINT:SET") + distance_setpoint = Cpt(EpicsSignal, "TST_MTR_DIST_SP:SET") # relative distance + mode_setpoint = Cpt(EpicsSignal, "TST_TABLE_MODE:SET") + velocity_setpoint = Cpt(EpicsSignal, "TST_MTR_VEL:SET") + + # not commonly used ones + J2J_distance = Cpt(EpicsSignal, "TST_JAW_TO_JAW_SIZE") + J2J_distance_setpoint = Cpt(EpicsSignal, "TST_JAW_TO_JAW_SIZE:SET") + + # PVs to archive for tensile experiments + step_columns = ['stepNo', 'temperature', 'rate', 'wait_time', 'position', 'velocity'] + pv_list = [ + 'XF:11BM-ES:{LINKAM}:TEMP', # Temperature + 'XF:11BM-ES:{LINKAM}:RAMPRATE', # Ramp rate + 'XF:11BM-ES:{LINKAM}:TST_FORCE', # Force + 'XF:11BM-ES:{LINKAM}:TST_RAW_MOTOR_POS', # Position + # 'XF:11BM-ES:{LINKAM}:TST_MTR_VEL', # Velocity + # 'XF:11BM-ES:{LINKAM}:SETPOINT', # Setpoint + # 'XF:11BM-ES:{LINKAM}:TST_STATUS', # Status + 'XF:11BM-ES:{LINKAM}:POWER' # Power + ] + + def __init__(self, prefix, name=None, **kwargs): + super().__init__(prefix, name=name, **kwargs) + self.step_setters = { + 'temperature': self.setTemperature, + 'rate': self.setTemperatureRate, + 'position': self.setPosition, + 'velocity': self.setVelocity, + } + + def statusTensile(self, verbosity=3): + text = f"\nCurrent temperature = {self.temperature():.1f}, setpoint = {self.temperature_setpoint.get():.1f}\n\n" + + # mode_value = self. + text += f"\nCurrent mode = {self.getMode(verbosity=5):}\n\n" + code = int(self.status_code_Tensile.get()) + + if code & 1: # Zero Limit + text += "Zero Limit : yes" + "\n" + else: + text += "Zero Limit : no\n" + if code & 2: # ref Limit + text += "ref Limit : yes" + "\n" + else: + text += "ref Limit : no\n" + if code & 4: # Move Done + text += "Move Done : on" + "\n" + else: + text += "Move Done : off\n" + if code & 8: # Direction + text += "Direction : on" + "\n" + else: + text += "Direction : off\n" + if code & 16: # Force + text += "Force : yes" + "\n" + else: + text += "Force : no\n" + if code & 32: # Cycle mode + text += "Cycle mode : yes" + "\n" + else: + text += "Cycle mode : no\n" + if code & 64: # Cycle dir open + text += "Cycle dir open : yes" + "\n" + else: + text += "Cycle dir open : no\n" + + if verbosity >= 3: + print(text) + return code + + def start(self): + return self.run_cmd.put(1) + + def _start(self): + yield from bps.mv(self.run_cmd, 1) + + def stop(self): + return self.run_cmd.put(0) + + def _stop(self): + yield from bps.mv(self.run_cmd, 0) + + def setMode(self, mode): + """ + mode = 0 : 'velocity' + mode = 1 : 'step' + mode = 2 : 'cycle' + mode = 3 : 'force' + mode = 4 : 'relax' + mode = 5 : 'stop' + """ + + if type(mode) == str: + if mode == "velocity": + mode = 0 + elif mode == "step": + mode = 1 + elif mode == "cycle": + mode = 2 + elif mode == "force": + mode = 3 + elif mode == "relax": + mode = 4 + elif mode == "stop": + mode = 5 + else: + return print("Wrong mode.") + + if mode == 0: + print("Mode: velocity.") + print("Constant velocity is expected.") + print("Inputs are limited to velocity and distance.") + + elif mode == 1: + print("Mode: step.") + print("Distance is expected.") + print("Inputs are limited to velocity and distance.") + + elif mode == 2: + print("Mode: cycle.") + print("SWITCH TO setp MODE!") + # print('Inputs are limited to velocity and distance.') + + elif mode == 3: + print("Mode: force.") + print("Constant force is expected.") + print("Inputs are limited to force and distance.") + + elif mode == 4: + print("Mode: relax.") + print("Nothing is expected except time.") + + elif mode == 5: + print("Mode: stop.") + + else: + return print("[LINKAM-TENSILE] Wrong mode. Please choose from 0-velocity, 1-step, 3-force, 4-relax and 5-stop") + + return self.mode_setpoint.put(mode) + + def getMode(self, verbosity=0): + value = self.mode_setpoint.get() + if value == 0: + mode_value = "velocity" + elif value == 1: + mode_value = "step" + elif value == 2: + mode_value = "cycle" + elif value == 3: + mode_value = "force" + elif value == 4: + mode_value = "relax" + elif value == 5: + mode_value = "stop" + + if verbosity >= 3: + return mode_value + + return value + + def mov(self, position, velocity=None, verbosity=3): + # move to the absolute position + + if position < 0: + return "[LINKAM-TENSILE] Error: position < 0. " + + # set distance + relative_pos = position - self.POS.get() + if verbosity >= 3: + print("[LINKAM-TENSILE] The motor will move {:1f} mm.".format(relative_pos)) + + if relative_pos > 0: + self.setDirection(0) + elif relative_pos < 0: + self.setDirection(1) + else: + return self.POS.get() + + self.distance_setpoint.put(abs(relative_pos)) + + # set velocity + if velocity == None and self.velocity.get() == 0: + return print("[LINKAM-TENSILE] The velocity is 0. No movement.") + # self.velocity_setpoint.put(self.velocity.get()) + elif velocity == None and self.velocity.get() != 0: + pass + else: + self.velocity_setpoint.put(velocity) + + self.run_cmd.put(1) + if verbosity >= 1: + while int(LTensile.status_code_Tensile.get()) & 4: + return self.POS.get() + + if verbosity >= 3: + print(self.POS.get()) + return self.POS.get() + + def movr(self, distance, velocity=None, verbosity=3): + # move to the absolute position + relative_pos = distance + if relative_pos > 0: # open + self.setDirection(0) + elif relative_pos < 0: # close + self.setDirection(1) + else: + return self.POS.get() + self.distance_setpoint.put(abs(relative_pos)) + + if velocity == None and self.velocity.get() == 0: + return print("[LINKAM-TENSILE] The velocity is 0. No movement.") + # self.velocity_setpoint.put(self.velocity.get()) + elif velocity == None and self.velocity.get() != 0: + pass + else: + self.velocity_setpoint.put(velocity) + + self.run_cmd.put(1) + if verbosity >= 1: + while int(LTensile.status_code_Tensile.get()) & 4: + return self.POS.get() + + if verbosity >= 3: + print(self.POS.get()) + return self.POS.get() + + def _mov(self, position, velocity=None, verbosity=3): + # move to the absolute position + # YF version + + if position < 0: + return "Error: position < 0. " + + # set distance + relative_pos = position - self.POS.get() + if verbosity >= 3: + print("[LINKAM-TENSILE] The motor will move {:1f} mm.".format(relative_pos)) + + if relative_pos > 0: + yield from bps.mv(self.direction_setpoint, 0) + # self.setDirection(0) + elif relative_pos < 0: + yield from bps.mv(self.direction_setpoint, 1) + else: + return self.POS.get() + + yield from bps.mv(self.distance_setpoint, abs(relative_pos)) + + # set velocity + if velocity == None and self.velocity.get() == 0: + return print("[LINKAM-TENSILE] The velocity is 0. No movement.") + # self.velocity_setpoint.put(self.velocity.get()) + elif velocity == None and self.velocity.get() != 0: + pass + else: + yield from bps.mv(self.velocity_setpoint, velocity) + # self.velocity_setpoint.put(velocity) + + yield from bps.mv(self.run_cmd, 1) + # self.run_cmd.put(1) + if verbosity >= 1: + while int(LTensile.status_code_Tensile.get()) & 4: + return self.POS.get() + + if verbosity >= 3: + print(self.POS.get()) + return self.POS.get() + + def setDirection(self, direction, wait_time=0.1, verbosity=3): + # 0 = Open, 1 = close + if direction == 0 or direction == "open": + self.direction_setpoint.put(0) + # print("test0") + + elif direction == 1 or direction == "close": + # print("test1") + self.direction_setpoint.put(1) + else: + print("[LINKAM-TENSILE] Wrong input.") + + time.sleep(wait_time) + if verbosity >= 3: + if self.direction.get() == 0: + text_direction = "open" + else: + text_direction = "close" + print("[LINKAM-TENSILE] Current direction : {}".format(text_direction)) + + return self.direction.get() + + def _setDirection(self, direction, verbosity=3): + # 0 = Open, 1 = close + if direction == 0 or direction == "open": + yield from bps.mv(self.direction_setpoint, 0) + elif direction == 1 or direction == "close": + yield from bps.mv(self.direction_setpoint, 1) + else: + print("[LINKAM-TENSILE] Wrong input.") + + if verbosity >= 3: + if self.direction.get() == 0: + text_direction = "open" + else: + text_direction = "close" + print("[LINKAM-TENSILE] Current direction : {}".format(text_direction)) + + return self.direction.get() + + def states(self): + # show the current states of the tensile stage, including + # T, motor position and direction, force, velocity and mode + + # from Temperature sensor, independent from the tensile + text = f"\nCurrent temperature = {self.temperature():.1f}, setpoint = {self.temperature_setpoint.get():.1f}\n\n" + + # stage states, RO + text += f"\nSTAGE POSITION = {self.POS.get():1f}\n\n" + text += f"\nSTAGE FORCE = {self.FORCE.get():1f}\n\n" + text += f"\nSTAGE STRAIN = {self.STRAIN.get():1f}\n\n" + text += f"\nSTAGE STRESS = {self.STRESS.get():1f}\n\n" + + # setting for the tensile part + text += f"\nCurrent mode = {self.getMode(verbosity=5)}\n\n" + text += f"\nCurrent distance = {self.distance.get():1f}, setpoint={self.distance_setposition.get():1f}\n\n" + text += f"\nCurrent velocity = {self.velocity.get():1f}, setpoint={self.velocity_setposition.get():1f}\n\n" + text += f"\nCurrent force = {self.force.get():1f}, setpoint={self.force_setposition.get():1f}\n\n" + + # force = Cpt(EpicsSignal, 'TST_FORCE_SETPOINT') + # distance = Cpt(EpicsSignal, 'TST_MTR_DIST_SP') #relative distance + # mode = Cpt(EpicsSignalRO, 'TST_TABLE_MODE') + # velocity = Cpt(EpicsSignal, 'TST_MTR_VEL') + + # Linkam Stage Tensile Step Configuration Read/Load functions + # Added by Siyu Wu 2025-09-01 + + def setPosition(self, position: float) -> float: + """Set the tensile stage position.""" + while self.POS.get() != position: + time.sleep(0.2) + self.distance_setpoint.put(position) + return self.POS.get() + + def setVelocity(self, velocity: float) -> float: + """Set the tensile stage velocity.""" + while self.velocity_current.get() != velocity: + time.sleep(0.2) + self.velocity_setpoint.put(velocity) + return self.velocity_current.get() + + def force(self) -> float: + """Get the current tensile stage force.""" + return self.FORCE.get() + + def run_single_step(self, stepNo: int, stop_evt: threading.Event = None) -> None: + """ + Run a single tensile step (blocking), responsive to stop_evt. + Sets all step PVs, triggers motor start, waits, and stops motor if interrupted. + """ + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + step = self.step_config.iloc[stepNo] + print(f"[LINKAM-TENSILE] Running step {stepNo}: " + + ", ".join(f"{col}={step[col]}" for col in self.step_columns if col != 'stepNo')) + + # Set all relevant PVs + self.setTemperature(step['temperature']) + self.setTemperatureRate(step['rate']) + + self.on() + + # Use movr to trigger tensile movement + self.movr(step['position'], step['velocity']) + + try: + wait_s = float(step.get('wait_time', 0)) + end = time.monotonic() + wait_s + while time.monotonic() < end: + if stop_evt and stop_evt.is_set(): + print("[LINKAM-TENSILE] Stop event detected, stopping motor.") + break + time.sleep(min(0.1, end - time.monotonic())) + finally: + self.stop() + + async def run_single_step_async(self, stepNo: int, stop_event: asyncio.Event = None) -> None: + """ + Async wrapper for run_single_step that bridges to a threading.Event. + """ + thread_stop = threading.Event() + async def _bridge(): + if stop_event is None: + return + await stop_event.wait() + thread_stop.set() + bridge_task = asyncio.create_task(_bridge()) + try: + await asyncio.to_thread(self.run_single_step, stepNo, thread_stop) + finally: + if not bridge_task.done(): + bridge_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await bridge_task + + async def run_step_async(self, stepNo: int = 0, stop_event: asyncio.Event = None) -> None: + """ + Run the tensile stage from a specific step asynchronously. + """ + if stop_event is None: + stop_event = asyncio.Event() + try: + if not hasattr(self, 'step_config'): + raise ValueError("No step configuration loaded. Use load_step_config first.") + if stepNo < 0 or stepNo >= len(self.step_config): + raise IndexError("Step number out of range.") + for i in range(stepNo, len(self.step_config)): + if stop_event.is_set(): + print("[LINKAM-TENSILE] Stop event detected, aborting step run.") + break + await self.run_single_step_async(i, stop_event=stop_event) + finally: + self.off() + self.stop() + + +LThermal = LinkamThermal("XF:11BM-ES:{LINKAM}:", name="LinkamTrans") +# LThermal = LinkamThermal("XF:11BM-ES:{LINKAM}:", name="LinkamGI") +LTensile = LinkamTensile("XF:11BM-ES:{LINKAM}:", name="LinkamTensile") diff --git a/startup/55-archiver.py b/startup/55-archiver.py index 0676a3b..dc6b7cf 100644 --- a/startup/55-archiver.py +++ b/startup/55-archiver.py @@ -255,3 +255,103 @@ def get_archived_pvs_from_uid(pv_list,uid,pre=0,post=0,verbose=True): # plt.title('uid: %s sample: %s'%(md['uid'][:8],md['sample']),fontsize=14) # plt.ylim(-2,1) # plt.legend(loc='upper left',bbox_to_anchor=(1.2,.98)) + +#### CMS customized Archiver decorators +#### Added by Siyu Wu, 2025/08 + +from functools import wraps + +arvReader = ArchiverReader({"url": "http://epics-services-cms.nsls2.bnl.local:11168", + "timezone": "US/Eastern"}) + +def archived(output_file, pv_list=None, dt=0.1, verbose=True): + """ + Decorator to archive PVs using Archiver between function start and end. + """ + + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + t_start = time.time() + t_start_str = datetime.fromtimestamp(t_start).strftime("%Y-%m-%d %H:%M:%S") + if verbose: + print(f"[Archiver] Recording PVs from {t_start_str}") + + try: + result = await func(*args, **kwargs) + except KeyboardInterrupt: + print("[Archiver] Interrupted! Archiving up to this point.") + raise + + finally: + t_end = time.time() + t_end_str = datetime.fromtimestamp(t_end).strftime("%Y-%m-%d %H:%M:%S") + if verbose: + print(f"[Archiver] Recording PVs until {t_end_str}") + + if dt is not None and dt > 0: + # Build fixed time grid + n_points = int((t_end - t_start) / dt) + 1 + time_grid = np.linspace(t_start, t_end, n_points) + df_sync = pd.DataFrame({'timestamp': time_grid}) + df_sync['time_elapsed'] = df_sync['timestamp'] - t_start + + # For each PV, interpolate or forward-fill to time grid + for pv in pv_list: + try: + df = arvReader.get(pv, t_start_str, t_end_str) + # Convert archiver times to epoch seconds + pv_times = pd.to_datetime(df['time']).astype(np.int64) / 1e9 + pv_data = df['data'].to_numpy() + # Interpolate to grid (linear, or nearest if only one point) + if len(pv_times) > 1: + interp_data = np.interp(time_grid, pv_times, pv_data) + elif len(pv_times) == 1: + interp_data = np.full_like(time_grid, pv_data[0]) + else: + interp_data = np.full_like(time_grid, np.nan) + df_sync[f"{pv}_data"] = interp_data + if verbose: + print(f"[Archiver] {pv}: {len(pv_data)} points, interpolated to {len(interp_data)}") + except Exception as e: + print(f"[Archiver] Skipping PV '{pv}': {e}") + df_sync[f"{pv}_data"] = np.full_like(time_grid, np.nan) + + # Move time_elapsed to first column + cols = ['time_elapsed'] + [c for c in df_sync.columns if c != 'time_elapsed'] + df_sync = df_sync[cols] + + df_sync.to_csv(output_file, index=False) + print(f"[Archiver] Saved {len(df_sync)} rows to {output_file}") + + else: + since = t_start_str + until = t_end_str + df_all = pd.DataFrame() + for pv in pv_list: + try: + df = arvReader.get(pv, since, until) + ep_time = [pd.Timestamp(t).timestamp() for t in df['time']] + pv_data = df['data'].to_numpy() + df_pv = pd.DataFrame({f"{pv}_time": ep_time, f"{pv}_data": pv_data}) + df_all = pd.concat([df_all, df_pv], axis=1) + if verbose: + print(f"[Archiver] {pv}: {len(df_pv)} points") + except Exception as e: + print(f"[Archiver] Skipping PV '{pv}': {e}") + df_all.to_csv(output_file, index=False) + print(f"[Archiver] Saved {len(df_all)} rows to {output_file}") + return result + return wrapper + return decorator + +# Example Usage: + +# linkam_pvs = [ +# "XF:11BM-ES:{LINKAM}:TEMP", # Temperature +# "XF:11BM-ES:{LINKAM}:TST_FORCE", # Force +# "XF:11BM-ES:{LINKAM}:TST_STRESS", # Stress +# ] +# @archived("linkam_archiver_record.csv", pv_list=linkam_pvs, archiver_instance=ARV) +# def run_linkam_steps(): +# LThermal.run_step(0) diff --git a/startup/81-beam.py b/startup/81-beam.py index 1955db0..135a9ce 100644 --- a/startup/81-beam.py +++ b/startup/81-beam.py @@ -3043,7 +3043,7 @@ def get_md(self, prefix=None, **md): return md_current - def setMetadata(self, verbosity=3): + def _setMetadata(self, verbosity=3): """Guides the user through setting some of the required and recommended meta-data fields.""" @@ -3079,7 +3079,7 @@ def setMetadata(self, verbosity=3): ["experiment_SAF_number", "SAF number"], ["experiment_group", "User group (e.g. PI)"], ["experiment_user", "The specific user/person running the experiment"], - ["experiment_project", "Project name/code"], + # ["experiment_project", "Project name/code"], ["experiment_alias_directory", "Alias directory"], [ "experiment_type", @@ -3130,7 +3130,7 @@ def setMetadata(self, verbosity=3): "/n The folder ::: {} ::: has been made for users. /n".format(RE.md["experiment_alias_directory"]) ) - def setMetadata_new(self, verbosity=3): + def setMetadata(self, verbosity=3): """Guides the user through setting some of the required and recommended meta-data fields.""" @@ -3162,13 +3162,13 @@ def setMetadata_new(self, verbosity=3): # Ask the user some questions questions = [ - ["experiment_proposal_number", "Proposal number"], + # ["experiment_proposal_number", "Proposal number"], ["experiment_SAF_number", "SAF number"], ["experiment_group", "User group (e.g. PI)"], ["experiment_user", "The specific user/person running the experiment"], - ["experiment_project", "Project name/code"], - ["userpy_alias_directory", "Alias directory"], - ["experiment_alias_directory", "Alias directory"], + # ["experiment_project", "Project name/code"], + ["userpy_alias_directory", "UserPy Alias directory"], + ["experiment_alias_directory", "Experiment Alias directory"], [ "experiment_type", "Type of experiments/measurements (SAXS, GIWAXS, etc.)", @@ -3202,6 +3202,10 @@ def setMetadata_new(self, verbosity=3): print('data will be saved in folder: {}'.format(RE.md["experiment_alias_directory"])) print('redo cms.setMeadata() if any folder is wrong! ') + # sam=Sample('test') + # detSelect(fs6) + # sam.measure(.1) + # if os.path.exists(RE.md["experiment_alias_directory"]): # print("/n The folder has existed. Please change folder name if necessary./n") # else: diff --git a/startup/94-sample.py b/startup/94-sample.py index 23140d5..2cd93d6 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1861,7 +1861,11 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p if ( exposure_time != detector.cam.acquire_time.get() ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): - RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + detector.cam.acquire_time.set(exposure_time) + if detector.cam.num_images.get() != 1: + detector.cam.num_images.set(1) + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): @@ -1895,7 +1899,7 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # uids = RE(count(get_beamline().detector, 1), **md) uids = RE(count(get_beamline().detector), **md) - # yield from (count(get_beamline().detector), **md) + # yield from RE(count(get_beamline().detector), **md) # get_beamline().beam.off() # print('shutter is off') @@ -1904,10 +1908,10 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p max_exposure_time = 0.1 for detector in get_beamline().detector: # print('here in expose:', detector.name) - if detector.name == "pilatus300k-1": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus2m-1": + # if detector.name == "pilatus300k-1": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + if detector.name == "pilatus2m-1": current_exposure_time = detector.cam.acquire_time.get() max_exposure_time = max(max_exposure_time, current_exposure_time) elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": @@ -1934,68 +1938,30 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p if verbosity >= 1: print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) - # if verbosity >= 2: - # status = 0 - # while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): - # percentage = 100 * (time.time() - start_time) / max_exposure_time - # print( - # "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), - # end="", - # ) + if verbosity >= 2: + status = 0 + while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): + percentage = 100 * (time.time() - start_time) / max_exposure_time + print( + "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), + end="", + ) - # time.sleep(poling_period) + time.sleep(poling_period) - # status = 1 - # for detector in get_beamline().detector: - # if detector.cam.acquire.get() == 1: - # status *= 0 + status = 1 + for detector in get_beamline().detector: + if detector.cam.acquire.get() == 1: + status *= 0 + print(' ') - # print('counting .... percentage = {}'.format(percentage)) + get_beamline().beam.off() + + print( + "Exposing {:6.2f} s ".format((time.time() - start_time)) + ) - else: - time.sleep(max_exposure_time) - # # special solution for 2022_1/TKoga2 - # if verbosity >= 5: - # print("verbosity = {}.".format(verbosity)) - # pct_threshold = 90 - # while percentage < pct_threshold: - # print("sth is wrong .... percentage = {} < {}%".format(percentage, pct_threshold)) - # start_time = time.time() - # uids = RE(count(get_beamline().detector), **md) - # # yield from (count(get_beamline().detector), **md) - - # # get_beamline().beam.off() - # # print('shutter is off') - - # # Wait for detectors to be ready - # max_exposure_time = 0.1 - # for detector in get_beamline().detector: - # if detector.name == "pilatus300k-1": - # current_exposure_time = detector.cam.acquire_time.get() - # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name == "pilatus2m-1": - # current_exposure_time = detector.cam.acquire_time.get() - # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": - # current_exposure_time = detector.cam.acquire_time.get() - # max_exposure_time = max(max_exposure_time, current_exposure_time) - - # percentage = 100 * (time.time() - start_time) / max_exposure_time - # print("After re-exposing .... percentage = {} ".format(percentage)) - - # if detector.name is "pilatus300k-1": - # if caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is "pilatus2m-1": - # if caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is "pilatus800k-1": - # if caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: - # status *= 0 - # elif detector.name is 'PhotonicSciences_CMS': - # if not detector.detector_is_ready(verbosity=0): - # status *= 0 # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: # print('Warning: Detector pilatus300 still not done acquiring.') @@ -2006,7 +1972,7 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: # print('Warning: Detector pilatus2M still not done acquiring.') - get_beamline().beam.off() + if handlefile == True: for detector in get_beamline().detector: @@ -2938,6 +2904,142 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", self.md["measurement_ID"] += 1 + def runPotentiostats_Ext(self, num_frames, exposure_time=0.995, exposure_period=1, detectors=None, trigger_mode='ExtTrigger', extra=None, wait_time=None, verbosity=3, **md): + """ + Continueous shots with internal trigger of detectors . + + Parameters + ---------- + speeds: list + The list of spinner speeds in multiple steps. + speeds = [500, 2000, 500] + periods: list + The lasting time for corresponding spinner speed. + periods = [5, 60, 20] + num_frames : int + The number of data points. + exposure_time: float + The exposure time for single point + exposure_period: float + The exposure period for single point. should be at least 0.05s longer than exposure_time + md : dict, optional + metadata + """ + + # print(speeds) + + # speeds = tuple(speeds) + # times = tuple(times) + # print(type(speeds)) + if exposure_period < exposure_time+0.005: + return print('Error: exposure period should be at least 0.005s more than exposure time.') + + if detectors==None: + detectors = cms.detector + + if exposure_time is not None: + self.set_attribute('exposure_time', exposure_time) + + # Set exposure time + for detector in get_beamline().detector: + yield from detector.setExposureTime(exposure_time) + yield from detector.setExposurePeriod(exposure_period) + yield from detector.setExposureNumber(num_frames) + + #bec.disable_plots() + #bec.disable_table() + if trigger_mode=='ExtTrigger': + detector.cam.trigger_mode.put(2) + if trigger_mode=='ExtMultiple': + detector.cam.trigger_mode.put(3) + if trigger_mode=='Internal': + detector.cam.trigger_mode.put(0) + + yield from bps.sleep(2) + + savename = self.get_savename(savename_extra=extra) + + #caput('XF:11BMB-ES{Det:SAXS}:cam1:FileName', savename) + + if verbosity>=2 and (get_beamline().current_mode != 'measurement'): + print("WARNING: Beamline is not in measurement mode (mode is '{}')".format(get_beamline().current_mode)) + + if verbosity>=1 and len(cms.detector)<1: + print("ERROR: No detectors defined in cms.detector") + return + + md_current = self.get_md() + md_current['sample_savename'] = savename + md_current['series'] = 'series_measure' + md_current.update(self.get_measurement_md()) + #md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, md_current['detector_sequence_ID']) + md_current['measure_series_num_frames'] = num_frames + md_current['filename'] = '{:s}_{:04d}.tiff'.format(savename, RE.md['scan_id']) + md_current['exposure_time'] = exposure_time + #md_current['measure_series_motor'] = motor.name + #md_current['measure_series_positions'] = [start, stop] + + + #md_current['fileno'] = '{:s}_{:04d}.tiff'.format(savename, RE.md['scan_id']) + md_current.update(md) + md = md_current + # print (md_current) + + # turn on shutter + yield from shutter_on() + + # Perform the scan + @bpp.stage_decorator(detectors) + + def inner(): + + for detector in detectors: + status = yield from bps.trigger(detector, group='det') + + # status = yield from bps.trigger(pilatus2M, group='det') + + # for index, period in enumerate(periods): + + # print('Step {} : speed = {}V for {}seconds'.format(index, speeds[index], period)) + yield from bps.sleep(1) + + print('Trigger ON') + if wait_time != None: + yield from bps.sleep(wait_time) + + + yield from bps.mv(TTL2, 1) #TLL on + + yield from bps.wait(group='det') + + #total_time = num_frames*exposure_period + #moving_interval = 1 + # for index in range(int(total_time/moving_interval)): + # yield from bps.mov(smx, smx.position+0.1*index) + # yeild from sleep(1) + for detector in detectors: + yield from bps.read(detector) + + + yield from bpp.run_wrapper( + inner(), md=md#{'potentiostats': 'TTL2'} + ) + + #get_beamline().beam._test_off(wait_time=0.1) + yield from bps.mv(TTL2, 0) #TLL off + yield from shutter_off() + + self.md['measurement_ID'] += 1 + + detector.cam.trigger_mode.put(0) + #data collected, link uid to file name + for detector in cms.detector: + self.handle_fileseries(detector, num_frames=num_frames, extra=extra, verbosity=verbosity, **md) + + #reset the num_frame back to 1 + for detector in get_beamline().detector: + yield from detector.setExposureNumber(1) + def _test_time(self): print(time.time()) time.time() @@ -3508,13 +3610,13 @@ def series_measure( # md_current['fileno'] = '{:s}_{:04d}'.format(savename, RE.md['scan_id']) md_current.update(md) - + md = md_current print(RE.md["scan_id"]) # Perform the scan + get_beamline().beam.on() # get_beamline().beam._test_on(wait_time=0.1) try: - get_beamline().beam.on() RE(count(get_beamline().detector, md=md_current)) except: print('Series Scan Interupted! Handling Files.') @@ -3835,14 +3937,14 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, print("WARNING: Can't do file handling for detector '{}'.".format(detector.name)) return - # filename = detector.tiff.full_file_name.get() # RL, 20210831 + filename = detector.tiff.full_file_name.get() # RL, 20210831 # filename_part1 = "{:s}/{:s}".format(detector.tiff.file_path.get(), detector.tiff.file_name.get()) # Alternate method to get the last filename # filename = '{:s}/{:s}.tiff'.format( detector.tiff.file_path.get(), detector.tiff.file_name.get() ) - # if verbosity>=3: - # print(' Data saved to: {}'.format(filename)) + if verbosity>=3: + print(' Data saved to: {}'.format(filename)) # if md['measure_type'] is not 'snap': if True: @@ -3852,8 +3954,9 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # link_name = '{}/{}{}'.format(RE.md['experiment_alias_directory'], subdir, md['filename']) # savename = md['filename'][:-5] - savename = self.get_savename(savename_extra=extra) - # savename = RE.md["filename"] + # savename = self.get_savename(savename_extra=extra) + savename = md["filename"] + # savename = md["filename"][:-5] # link_name = "{}/{}{}_{:06d}_{}.tiff".format( # RE.md["experiment_alias_directory"], # subdir, @@ -3882,7 +3985,7 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, # print(" Data {} linked as: {}".format(filename_new, link_name_new)) # savename = md['filename'] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, RE.md["scan_id"] - 1, detname).replace('//', '/') + link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//', '/') print(f" Symlinks will be created at: {proposal_path()}experiments/{link_name}") # Control methods @@ -3890,6 +3993,9 @@ def handle_fileseries(self, detector, num_frames=None, extra=None, verbosity=3, def setTemperature(self, temperature, output_channel="1", verbosity=3): # if verbosity>=1: # print('Temperature functions not implemented in {}'.format(self.__class__.__name__)) + # temperature_set_RBV = caget("XF:11BM-ES{Env:01-Out:1}T-RB") + + # while abs((temperature_set_RBV-273.15) - temperature) > 5.0: if output_channel == "1": if verbosity >= 2: print( @@ -3926,6 +4032,8 @@ def setTemperature(self, temperature, output_channel="1", verbosity=3): ) caput("XF:11BM-ES{Env:01-Out:4}T-SP", temperature + 273.15) + # temperature_set_RBV = caget("XF:11BM-ES{Env:01-Out:1}T-RB") + def temperature(self, temperature_probe="A", output_channel="1", RTDchan=2, verbosity=3): # if verbosity>=1: # print('Temperature functions not implemented in {}'.format(self.__class__.__name__)) @@ -4458,6 +4566,10 @@ def gotoSample(self, sample_number): def setTemperature(self, temperature, output_channel="1", verbosity=3): # if verbosity>=1: # print('Temperature functions not implemented in {}'.format(self.__class__.__name__)) + + # temperature_set_RBV = caget("XF:11BM-ES{Env:01-Out:1}T-RB") + + # while abs((temperature_set_RBV-273.15) - temperature) > 5.0: if output_channel == "1": if verbosity >= 2: print( @@ -4494,6 +4606,8 @@ def setTemperature(self, temperature, output_channel="1", verbosity=3): ) caput("XF:11BM-ES{Env:01-Out:4}T-SP", temperature + 273.15) + # temperature_set_RBV = caget("XF:11BM-ES{Env:01-Out:1}T-RB") + def temperature(self, temperature_probe="A", output_channel="1", verbosity=3): # if verbosity>=1: # print('Temperature functions not implemented in {}'.format(self.__class__.__name__)) @@ -4806,3 +4920,57 @@ def get_default_stage(): hol.addSampleSlot(SampleGISAXS_Generic("test_sample_03"), 5.0) sam = hol.getSample(1) + + +# manually handle file under data security +# log in as user. create folder /profile_collection/users/2025-3/beamline/experiment/waxs/raw +# ss = handlefilename(range(2118770, 2118860)) +# ss = handlefilename(range(2118903, 2118952)) + +def handlefilename(uids): + """Given a list of uids, return a list of filenames.""" + filenames = [] + for uid in uids: + h = db[uid] + link_name = h.start['filename'] + '_000000_waxs.tiff' + for name, doc in h.documents(): + if name == "resource": + rdoc = doc + break + filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_000000.tiff' + try: + h = db[uid] + fname = h.start["sample_name"] + filenames.append(fname) + except: + pass + + os.symlink(filename, link_name) + + filenames.append(link_name) + return filenames + +def handlefiles_names(): + """Given a list of uids, return a list of filenames.""" + filenames = [] + uid = -1 + h = db[uid] + + for ii in range(h.start['measure_series_num_frames']): + link_name = h.start['filename'] + '_' + f'{ii:06d}' + '_waxs.tiff' + for name, doc in h.documents(): + if name == "resource": + rdoc = doc + break + filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_' + f'{ii:06d}' + '.tiff' + # try: + # h = db[uid] + # fname = h.start["sample_name"] + # filenames.append(fname) + # except: + # pass + + os.symlink(filename, link_name) + + filenames.append(link_name) + return filenames \ No newline at end of file diff --git a/startup/95-sample-custom.py b/startup/95-sample-custom.py index 2ea8f6d..8bd91e9 100644 --- a/startup/95-sample-custom.py +++ b/startup/95-sample-custom.py @@ -2386,16 +2386,8 @@ def __init__(self, name="CapillaryHolder", base=None, **kwargs): # slot 15; smx = -61.94 # Set the x and y origin to be the center of slot 8 - # self.xsetOrigin(-16.7) - # self.ysetOrigin(-2.36985) - # self.ysetOrigin(-2.36985) - # self.xsetOrigin(-16.7+-0.3) - # self.ysetOrigin(-1.8) - # self.xsetOrigin(-17.2) - - - self.ysetOrigin(-5) - self.xsetOrigin(-16) #as of 06/15/2025 --Siyu + self.ysetOrigin(-4.2) + self.xsetOrigin(-16) #as of 09/19/2025 --Siyu self.mark("right edge", x=+54.4) @@ -2577,8 +2569,8 @@ def __init__(self, name="CapillaryHolderThreeRows", base=None, **kwargs): self._positional_axis = ["x", "y"] - self._axes["y"].origin = -1.8 # The origin is the #8 hole in the top row - self._axes["x"].origin = -16.9 + self._axes["y"].origin = -4.2 # The origin is the #8 hole in the top row + self._axes["x"].origin = -16.558 # as of 09/19/2025 -- Siyu self.x_spacing = 6.342 # 3.5 inches / 14 spaces self.y_spacing = 0.25 * 25.4 # 2.5 inches / 14 spaces diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 14b199b..0bba640 100755 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -59,6 +59,36 @@ "bsy": -12.399296, "bsphi": -61.005459, "timestamp": "2025-07-31 15:33:26" + }, + { + "bsx": -18.000816, + "bsy": -12.399296, + "bsphi": -61.005459, + "timestamp": "2025-09-18 17:38:48" + }, + { + "bsx": -18.000816, + "bsy": -12.399296, + "bsphi": -61.005459, + "timestamp": "2025-09-18 20:10:28" + }, + { + "bsx": -16.701234, + "bsy": -12.299123, + "bsphi": -61.00727400000001, + "timestamp": "2025-09-19 14:21:31" + }, + { + "bsx": -16.701234, + "bsy": -12.299123, + "bsphi": -61.00727400000001, + "timestamp": "2025-09-19 14:21:42" + }, + { + "bsx": -13.201614, + "bsy": -12.298904, + "bsphi": -61.009099000000006, + "timestamp": "2025-09-29 09:56:41" } ], "round_17": [ @@ -115,6 +145,18 @@ "bsy": -10.098904000000001, "bsphi": 26.99588899999999, "timestamp": "2025-07-24 16:40:16" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-09-18 20:01:48" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-09-18 20:01:56" } ], "rod": [ @@ -147,6 +189,30 @@ "bsy": 17.001970999999998, "bsphi": -181.008503, "timestamp": "2025-07-30 19:33:32" + }, + { + "bsx": -11.382811, + "bsy": 17.002456, + "bsphi": -181.011339, + "timestamp": "2025-09-24 10:08:17" + }, + { + "bsx": -12.182811000000001, + "bsy": 17.003025, + "bsphi": -181.011203, + "timestamp": "2025-09-29 09:47:26" + }, + { + "bsx": -12.182811, + "bsy": 17.003025, + "bsphi": -181.011203, + "timestamp": "2025-09-29 09:51:29" + }, + { + "bsx": -12.182811, + "bsy": 17.003025, + "bsphi": -181.011203, + "timestamp": "2025-09-29 09:51:38" } ], "round_3m": [ @@ -184,5 +250,27 @@ "bsphi": -181.00277, "timestamp": "2025-06-06 16:23:38" } + ], + "round_2m_17kev": [ + { + "bsx": -15.402984, + "bsy": -13.300077, + "bsphi": 27.000339999999994, + "timestamp": "2025-09-22 13:29:11" + }, + { + "bsx": -15.402984, + "bsy": -13.300077, + "bsphi": 27.000339999999994, + "timestamp": "2025-09-22 13:29:20" + } + ], + "round_3m_17kev": [ + { + "bsx": -14.403476, + "bsy": -15.099765999999999, + "bsphi": 27.00034099999999, + "timestamp": "2025-10-05 10:59:14" + } ] } \ No newline at end of file diff --git a/startup/cfg/LinkamTensile_step.csv b/startup/cfg/LinkamTensile_step.csv new file mode 100644 index 0000000..272bea6 --- /dev/null +++ b/startup/cfg/LinkamTensile_step.csv @@ -0,0 +1,3 @@ +stepNo,temperature,rate,wait_time,position,velocity +0,25,10,1000,10000,50 +1,25,10,1000,10000,50 \ No newline at end of file diff --git a/startup/cfg/LinkamTrans_stage.cfg b/startup/cfg/LinkamTrans_stage.cfg new file mode 100644 index 0000000..45761e4 --- /dev/null +++ b/startup/cfg/LinkamTrans_stage.cfg @@ -0,0 +1,9 @@ +{ + "LinkamTrans": [ + { + "xo": -16.300050000000002, + "yo": 3.51425, + "timestamp": "2025-09-01 17:13:28" + } + ] +} \ No newline at end of file diff --git a/startup/cfg/LinkamTrans_step.csv b/startup/cfg/LinkamTrans_step.csv new file mode 100644 index 0000000..4ff7069 --- /dev/null +++ b/startup/cfg/LinkamTrans_step.csv @@ -0,0 +1,3 @@ +stepNo,temperature,rate,wait_time +0,100,30,180 +1,100,30,1800 \ No newline at end of file diff --git a/startup/cfg/linkam_stage_pos.cfg b/startup/cfg/linkam_stage_pos.cfg new file mode 100644 index 0000000..2f43178 --- /dev/null +++ b/startup/cfg/linkam_stage_pos.cfg @@ -0,0 +1,60 @@ +{ + "LinkamTensile": [ + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-08-19 17:32:56" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-08-19 17:33:49" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-09-21 10:16:49" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-09-21 10:24:42" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-09-21 14:59:56" + } + ], + "LinkamTrans": [ + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-08-19 17:46:56" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-09-01 17:15:31" + } + ], + "LinkamGI": [ + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-08-19 17:48:28" + } + ], + "Baixu_MT": [ + { + "xo": 56.239000000000004, + "yo": 7.800000000000001, + "timestamp": "2025-10-05 11:15:14" + }, + { + "xo": 56.239000000000004, + "yo": 7.800000000000001, + "timestamp": "2025-10-05 12:25:02" + } + ] +} \ No newline at end of file diff --git a/startup/user_collection/user_TemperarureRamp.py b/startup/user_collection/user_TemperarureRamp.py index 99bf3e4..96217ef 100644 --- a/startup/user_collection/user_TemperarureRamp.py +++ b/startup/user_collection/user_TemperarureRamp.py @@ -858,7 +858,7 @@ def swaxs_on(): RE.md["experiment_SAF_number"] = "304391" RE.md["experiment_user"] = "various" RE.md["experiment_type"] = "TSAXS" # TSAXS, GISAXS, GIWAXS, etc. -RE.md["experiment_project"] = "Crystallization at L/L interface" +# RE.md["experiment_project"] = "Crystallization at L/L interface" if False: diff --git a/startup/user_collection/user_XR.py b/startup/user_collection/user_XR.py index 52f199b..c9607ad 100644 --- a/startup/user_collection/user_XR.py +++ b/startup/user_collection/user_XR.py @@ -460,7 +460,7 @@ def doTemperature_series_heat( RE.md["experiment_SAF_number"] = "304276" RE.md["experiment_user"] = "Loo et al" RE.md["experiment_type"] = "XR" # TSAXS, GISAXS, GIWAXS, etc. -RE.md["experiment_project"] = "polymeric semiconductor thin films" +# RE.md["experiment_project"] = "polymeric semiconductor thin films" # this is the 2nd try to run only RT and 70deg to check the radiation damage. From fb3f0de00a9e0a52dd81021374bdf30e47bcfa3b Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Thu, 13 Nov 2025 17:16:16 -0500 Subject: [PATCH 16/23] commit as of Nov 13 for h5 plugin --- startup/.cms_config | 45 ++ startup/10-motors.py | 9 +- startup/20-area-detectors.py | 93 +++- startup/26-IonChamber.py | 48 +- startup/90-bluesky.py | 5 +- startup/91-fit_scan.py | 584 +++++++++++++++++++ startup/94-sample-AIR.py.dev | 648 ++++++++++++++++++++++ startup/94-sample.py | 893 +++++++++++++++++++++--------- startup/95-sample-custom.py | 29 +- startup/beamstop_config.cfg | 96 ++++ startup/cfg/LinkamTrans_step.csv | 5 +- startup/cfg/LinkamTrans_step2.csv | 24 + startup/cfg/linkam_stage_pos.cfg | 10 + 13 files changed, 2200 insertions(+), 289 deletions(-) create mode 100644 startup/94-sample-AIR.py.dev create mode 100644 startup/cfg/LinkamTrans_step2.csv diff --git a/startup/.cms_config b/startup/.cms_config index bf03ad1..c464d02 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1358,3 +1358,48 @@ 1356,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Sun Oct 5 10:49:57 2025 1357,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.402984,Sun Oct 5 10:55:51 2025 1358,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.403476,Sun Oct 5 10:59:14 2025 +1359,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-13.201614,Wed Oct 8 18:40:52 2025 +1360,44.45,62.8,5.0,4.0,"[-96.3, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.79981875, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-13.201614,Thu Oct 9 09:39:25 2025 +1361,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.401841999999998,Thu Oct 9 09:56:42 2025 +1362,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.182811,Sat Oct 11 13:12:56 2025 +1363,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-13.983231,Sat Oct 11 13:15:09 2025 +1364,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Sat Oct 11 19:11:39 2025 +1365,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.500931,Sat Oct 11 19:12:55 2025 +1366,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.182811,Sat Oct 11 20:54:24 2025 +1367,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.183085,Sat Oct 11 20:55:22 2025 +1368,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-13.201614,Wed Oct 15 11:06:20 2025 +1369,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Wed Oct 15 11:09:42 2025 +1370,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Mon Oct 20 11:51:26 2025 +1371,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.831741999999998,Mon Oct 20 11:55:27 2025 +1372,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Wed Oct 22 09:39:14 2025 +1373,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.802302999999998,Wed Oct 22 09:40:50 2025 +1374,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Fri Oct 24 10:18:59 2025 +1375,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.900412,Fri Oct 24 11:16:07 2025 +1376,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.900401,Fri Oct 24 11:16:37 2025 +1377,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.100424,Fri Oct 24 11:27:03 2025 +1378,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.100424999999998,Fri Oct 24 11:27:09 2025 +1379,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.100426,Fri Oct 24 11:27:12 2025 +1380,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-12.182811,Sun Oct 26 20:47:26 2025 +1381,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Sun Oct 26 20:49:16 2025 +1382,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Nov 3 09:59:51 2025 +1383,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Nov 3 10:00:15 2025 +1384,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.499976,Mon Nov 3 10:09:16 2025 +1385,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Nov 3 13:23:51 2025 +1386,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.683580999999998,Mon Nov 3 13:24:37 2025 +1387,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.183626,Mon Nov 3 13:26:21 2025 +1388,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683832,Mon Nov 3 13:46:23 2025 +1389,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683833,Mon Nov 3 13:46:30 2025 +1390,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Thu Nov 6 11:10:30 2025 +1391,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.001988,Thu Nov 6 12:56:49 2025 +1392,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Nov 10 09:06:55 2025 +1393,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983498,Mon Nov 10 09:10:23 2025 +1394,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983504,Mon Nov 10 09:10:29 2025 +1395,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Wed Nov 12 13:09:49 2025 +1396,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202365999999998,Wed Nov 12 13:10:58 2025 +1397,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202372999999998,Wed Nov 12 13:11:04 2025 +1398,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Wed Nov 12 18:23:53 2025 +1399,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183478,Wed Nov 12 18:24:52 2025 +1400,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183481,Wed Nov 12 18:24:57 2025 +1401,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Thu Nov 13 09:11:47 2025 +1402,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.00233,Thu Nov 13 09:12:29 2025 +1403,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.002333999999998,Thu Nov 13 09:12:33 2025 diff --git a/startup/10-motors.py b/startup/10-motors.py index a958129..90896eb 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -8,8 +8,8 @@ # class Slits(Device): # top = Cpt(EpicsMotor, '-Ax:T}Mtr') # bottom = Cpt(EpicsMotor, '-Ax:B}Mtr') -# beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 -beamline_stage = 'open_MAXS' +beamline_stage = "default" #for AB, please also change Smpl2-Y from 3... to -5 +# beamline_stage = 'open_MAXS' # beamline_stage = 'BigHuber' print('Beamline_stage = {}'.format(beamline_stage)) @@ -148,6 +148,11 @@ class Filter(Device): # # smx = EpicsMotor('XF:11BMB-ES{ESP:3-Ax:C1}Mtr', name='smx') sprayy = EpicsMotor("XF:11BMB-ES{Chm:Smpl2-Ax:X}Mtr", name="sprayy") +#added in 10-27-2025 for telescoping flight path +fpn = EpicsMotor("XF:11BM-ES{Mdrive-Ax:1}Mtr", name="fpn") +fpr = EpicsMotor("XF:11BM-ES{Mdrive-Ax:2}Mtr", name="fpr") + + # goniometer smy2 = EpicsMotor("XF:11BMB-ES{Chm:Smpl-Ax:Y}Mtr", name="smy2") diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index e6c93d6..51e8745 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -277,7 +277,7 @@ def stage(self, *args, **kwargs): return super().stage(*args, **kwargs) -class Pilatus2MV33(SingleTriggerV33, PilatusDetector): +class Pilatus2MV33(SingleTriggerV33, PilatusDetector):#, h5=False): cam = Cpt(PilatusDetectorCamV33, "cam1:") image = Cpt(ImagePlugin, "image1:") stats1 = Cpt(StatsPluginV33, "Stats1:") @@ -292,11 +292,24 @@ class Pilatus2MV33(SingleTriggerV33, PilatusDetector): proc1 = Cpt(ProcessPlugin, "Proc1:") trans1 = Cpt(TransformPlugin, "Trans1:") + # self.h5 = h5 + # if h5: + # h5 = Cpt( + # HDF5PluginWithFileStore, + # suffix="HDF1:", + # write_path_template = "", + # ) + # else: + # tiff = Cpt( + # TIFFPluginWithFileStore, + # suffix="TIFF1:", + # write_path_template = "", + # ) tiff = Cpt( - TIFFPluginWithFileStore, - suffix="TIFF1:", - write_path_template = "", - ) + TIFFPluginWithFileStore, + suffix="TIFF1:", + write_path_template = "", + ) def stage(self, *args, **kwargs): self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' @@ -304,6 +317,38 @@ def stage(self, *args, **kwargs): self.tiff.reg_root = assets_path() + f'{self.name}' return super().stage(*args, **kwargs) + + # def stage(self, *args, **kwargs): + # if h5: + # self.h5.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + # self.h5.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + # self.h5.reg_root = assets_path() + f'{self.name}' + + # # wrap the staging process in a retry loop + # error = None + # for retry in range(5): + # try: + # return super().stage() + # except TimeoutError as err: + # # Staging failed becasue the IOC did not answer + # # some request in a resonable time + # # Stash the exception as the variable 'error' + # error = err + # else: + # # Staging worked. Stop retyring. + # break + # else: + # # We exhausted all retires and none worked. + # # Raise the error captured above to produce a useful error message. + # raise error + # return super().stage(*args, **kwargs) + + # else: + # self.tiff.write_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + # self.tiff.read_path_template = assets_path() + f'{self.name}/%Y/%m/%d/' + # self.tiff.reg_root = assets_path() + f'{self.name}' + # return super().stage(*args, **kwargs) + def setExposureTime(self, exposure_time, verbosity=3): yield from mv( self.cam.acquire_time, @@ -741,16 +786,17 @@ def stage(self): pilatus2M_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL2M}:", name="pilatus2m-1") pilatus2M_h5.h5.read_attrs = [] + pilatus2M_h5.cam.ensure_nonblocking() STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] pilatus2M_h5.read_attrs = ["h5"] + STATS_NAMES2M - for stats_name in STATS_NAMES2M: - stats_plugin = getattr(pilatus2M_h5, stats_name) - stats_plugin.read_attrs = ["total"] - pilatus2M_h5.cam.ensure_nonblocking() - pilatus2M_h5.h5.ensure_blocking() - pilatus2M_h5.stats3.total.kind = "hinted" - pilatus2M_h5.stats4.total.kind = "hinted" + # for stats_name in STATS_NAMES2M: + # stats_plugin = getattr(pilatus2M_h5, stats_name) + # stats_plugin.read_attrs = ["total"] + # pilatus2M_h5.cam.ensure_nonblocking() + # pilatus2M_h5.h5.ensure_blocking() + # pilatus2M_h5.stats3.total.kind = "hinted" + # pilatus2M_h5.stats4.total.kind = "hinted" for item in pilatus2M_h5.stats1.configuration_attrs: item_check = getattr(pilatus2M_h5.stats1, item) @@ -783,6 +829,7 @@ def stage(self): if Pilatus800_on == True: pilatus800_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL800K}:", name="pilatus800k-1") pilatus800_h5.h5.read_attrs = [] + pilatus800.cam.ensure_nonblocking() STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] pilatus800_h5.read_attrs = ["h5"] + STATS_NAMES2M @@ -826,6 +873,7 @@ def stage(self): STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] pilatus8002_h5.read_attrs = ["h5"] + STATS_NAMES2M + pilatus8002.cam.ensure_nonblocking() for stats_name in STATS_NAMES: stats_plugin = getattr(pilatus8002_h5, stats_name) @@ -940,3 +988,24 @@ def count_no_save_plan(det): print(f'Loading {__file__}') + + + +def pil2M_test(): + for ii in range(10): + # pilatus2M.cam.acquire_time.set(5) + yield from pilatus2M.setExposureTime(10) + yield from count([pilatus2M]) + yield from pilatus2M.setExposureTime(5) + yield from count([pilatus2M]) + + +def pil2M_test2(): + for ii in range(10): + pilatus2M.cam.acquire_time.set(10) + RE(count([pilatus2M])) + pilatus2M.cam.acquire_time.set(5) + for jj in range(300): + print(pilatus2M.cam.acquire_time.get()) + time.sleep(0.01) + RE(count([pilatus2M])) diff --git a/startup/26-IonChamber.py b/startup/26-IonChamber.py index 586bd88..fd2533d 100644 --- a/startup/26-IonChamber.py +++ b/startup/26-IonChamber.py @@ -66,13 +66,16 @@ class IonChamber(Device): IonChamber is operated by Oxford 400, including 4 channels. ch4 is used independently on the beam intensity monitoring at s4. """ + ch1 = Cpt(EpicsSignal, 'IC1_MON') + ch2 = Cpt(EpicsSignal, 'IC2_MON') + ch3 = Cpt(EpicsSignal, 'IC3_MON') ch4 = Cpt(EpicsSignal, 'IC4_MON') period_setpoint = Cpt(EpicsSignal, 'PERIOD_SP') period_readback = Cpt(EpicsSignal, 'PERIOD_MON') # period_readback = Cpt(EpicsSignal, 'ITIME_MON') count = Cpt(EpicsSignal, 'GETCS') - def setExposureTime(self, exptime): + def setExposureTime(self, exptime,**kwargs): while self.period_readback.get() != exptime: time.sleep(0.1) self.period_setpoint.set(exptime) @@ -83,20 +86,36 @@ def setExposureTime(self, exptime): def read(self) -> OrderedDictType[str, Dict[str, Any]]: - print("Triggering Ion Chamber...") + # print("Triggering Ion Chamber...") self.count.put(0) - print("Reading Ion Chamber...") + # print("Reading Ion Chamber...") time.sleep(self.period_readback.get()+0.1) - print(f"Exposed {self.period_readback.get()} s") + # print(f"Exposed {self.period_readback.get()} s") now = datetime.now(timezone.utc).timestamp() return { + # f"{self.name}_ch_all": { + # 'value': np.log10(self.ch1.get()/2+self.ch2.get()/2), + # 'timestamp': now + # }, f"{self.name}_ch4": { 'value': np.log10(self.ch4.get()), 'timestamp': now }, + f"{self.name}_ch3": { + 'value': np.log10(self.ch3.get()), + 'timestamp': now + }, + f"{self.name}_ch2": { + 'value': np.log10(abs(self.ch2.get())), + 'timestamp': now + }, + f"{self.name}_ch1": { + 'value': np.log10(self.ch1.get()), + 'timestamp': now + }, f"{self.name}_period_setpoint": { 'value': self.period_setpoint.get(), 'timestamp': now @@ -117,13 +136,24 @@ def trigger_and_read(self): yield from bps.mv(self.count,1) # return self.ch4.get() - def expose(self): + def expose(self, monitor_type='all'): # return self.count.get() RE(bps.mv(self.count,1)) - return self.ch4.get() + + if monitor_type=='ch4': + return self.ch4.get() + elif monitor_type=='ch1': + return self.ch1.get() + elif monitor_type=='ch2': + return self.ch2.get() + elif monitor_type=='ch3': + return self.ch3.get() + elif monitor_type=='all': + return (self.ch1.get()+self.ch2.get())/2 + # def setExposureTime(self, time): - # self.period_setpoint.put(time) + # self.period_setpoint.ut(time) # print("Set Ion Chamber exposure time to {} s".format(self.period_readback.get())) @@ -143,7 +173,9 @@ def expose(self): # bpm.ch1.kind = 'hinted' # bpm.ch2.kind = 'hinted' # bpm.ch3.kind = 'hinted' -ic.ch4.kind = 'hinted' +ic.ch1.kind = 'hinted' +ic.ch2.kind = 'hinted' +# ic.ch4.kind = 'hinted' # bpm.sumX.kind = 'hinted' # bpm.sumY.kind = 'hinted' diff --git a/startup/90-bluesky.py b/startup/90-bluesky.py index d23c338..a1a2df3 100644 --- a/startup/90-bluesky.py +++ b/startup/90-bluesky.py @@ -571,7 +571,10 @@ def rock(current=current): def inner_rock_and_read(): # yield from trigger(detector) # status = yield from trigger(detector[0]) - status = detector[0].trigger() + + for _detector in detector: + status = _detector.trigger() ## need to trigger each detector separately, HZ, 20251024; Need to check both status + # status = detector[0].trigger() while not status.done: yield from rock() yield from mv(rock_motor, current) diff --git a/startup/91-fit_scan.py b/startup/91-fit_scan.py index f7d59a8..ef4ecc5 100644 --- a/startup/91-fit_scan.py +++ b/startup/91-fit_scan.py @@ -1423,8 +1423,447 @@ def setMonitor(monitor=["stats1", "stats2", "stats3", "stats4"]): pilatus2M.read_attrs = ["tiff"] + monitor +#software flyscan +def flyscan(det, mtr, start, stop, step, exp_time): + # motors: smy (+/- 2), sth (+/- 1) + # det = pilatus2M + + # It takes 0.4 to 0.7 s longer to complete motion, so let's add 1 s for now + # It should be computed/estimated more accurately + num = int(np.abs(stop - start) / step) + total_time = num * exp_time + velocity = np.abs(stop - start) / total_time + + det.tiff.kind = "omitted" + det.tiff.disable_on_stage() + det.stats4.total.kind = "hinted" + + frame_numbers = [] + frame_timestamps = [] + frame_mtr_pos = [] + frame_roi2_ts = [] + frame_roi3_ts = [] + frame_roi4_ts = [] + + frame_roi2_total_ts = [] + frame_roi3_total_ts = [] + frame_roi4_total_ts = [] + frame_roi2_total = [] + frame_roi3_total = [] + frame_roi4_total = [] + + def accumulate(value, old_value, timestamp, **kwargs): + frame_numbers.append(value) + frame_timestamps.append(timestamp) + frame_mtr_pos.append(mtr.position) + + def accumulate_roi2(value, old_value, timestamp, **kwargs): + frame_roi2_ts.append(timestamp) + + def accumulate_roi3(value, old_value, timestamp, **kwargs): + frame_roi3_ts.append(timestamp) + + def accumulate_roi4(value, old_value, timestamp, **kwargs): + frame_roi4_ts.append(timestamp) + + def accumulate_roi2_total(value, old_value, timestamp, **kwargs): + frame_roi2_total_ts.append(timestamp) + frame_roi2_total.append(value) + + def accumulate_roi3_total(value, old_value, timestamp, **kwargs): + frame_roi3_total_ts.append(timestamp) + frame_roi3_total.append(value) + + def accumulate_roi4_total(value, old_value, timestamp, **kwargs): + frame_roi4_total_ts.append(timestamp) + frame_roi4_total.append(value) + + def inner(): + # if det == None: + # det = cms.detector[0] + detector = pilatus2M + cid = detector.cam.array_counter.subscribe(accumulate) + cid2 = detector.stats2.array_counter.subscribe(accumulate_roi2) + cid3 = detector.stats3.array_counter.subscribe(accumulate_roi3) + cid4 = detector.stats4.array_counter.subscribe(accumulate_roi4) + cid2a = detector.stats2.total.subscribe(accumulate_roi2_total) + cid3a = detector.stats3.total.subscribe(accumulate_roi3_total) + cid4a = detector.stats4.total.subscribe(accumulate_roi4_total) + try: + yield from bps.trigger(detector, group="fake_fly") + yield from bps.abs_set(mtr, stop, group="fake_fly") + yield from bps.wait(group="fake_fly") + finally: + detector.cam.array_counter.unsubscribe(cid) + detector.stats2.array_counter.unsubscribe(cid2) + detector.stats3.array_counter.unsubscribe(cid3) + detector.stats4.array_counter.unsubscribe(cid4) + detector.stats2.total.unsubscribe(cid2a) + detector.stats3.total.unsubscribe(cid3a) + detector.stats4.total.unsubscribe(cid4a) + + @bpp.reset_positions_decorator( + [det.cam.num_images, det.cam.acquire_time, det.cam.acquire_period, mtr.velocity] + ) + # @bpp.reset_positions_decorator( + # [det.cam.num_images, det.cam.acquire_time, det.cam.acquire_period] + # ) + def inner2(): + print(f"setting motor start position") + yield from bps.abs_set(mtr, start, wait=True) + print(f"setting motor start velocity: {velocity}") + # yield from bps.mv(mtr.velocity, velocity) + # if mtr.name == "smx" or mtr.name == "smy": + # mtr.velocity.set(velocity) + # yield from bps.sleep(1) + # if mtr.velocity.get() != velocity: + # print(f"WARNING: motor velocity {mtr.velocity.get()} is different from the set value {velocity}") + # else: + yield from bps.mv(mtr.velocity, velocity) + + print(f"Number of acquired images: {num}. Exposure time: {exp_time}") + + yield from bps.mv(det.cam.acquire_time, exp_time - 0.005) + yield from bps.mv(det.cam.acquire_period, exp_time) + yield from bps.mv(det.cam.num_images, num) + yield from inner() + + yield from inner2() + ## for testing plot + # lp = LivePlot(det.stats4.total, mtr.name, ax=lambda: plt.figure(num='flyscan').gca()) + # cbs = [lp] + # yield from bpp.subs_wrapper(inner2, cbs) + + def trim_list(v, num): + n_first = max(len(v) - num, 0) + return v[n_first:] + + def set_total_values(frame_roi_ts, total_ts, total, dt=0.25 * exp_time): + total_ts = [_ - dt for _ in total_ts] + vals = [0] * len(frame_roi_ts) + n_current, v_current = 0, 0 + for n in range(len(vals)): + if n_current < len(total_ts) and total_ts[n_current] < frame_roi_ts[n]: + v_current = total[n_current] + n_current += 1 + vals[n] = v_current + return vals + + # 0-th frame is discarded during trimming + _ = frame_mtr_pos + frame_mtr_pos = [_[0]] + [(_[n] + _[n - 1]) / 2 for n in range(1, len(_))] + + total_roi2 = set_total_values(frame_roi2_ts, frame_roi2_total_ts, frame_roi2_total) + total_roi3 = set_total_values(frame_roi3_ts, frame_roi3_total_ts, frame_roi3_total) + total_roi4 = set_total_values(frame_roi4_ts, frame_roi4_total_ts, frame_roi4_total) + + num = num - 1 # Discard the 1st point + + frame_numbers = trim_list(frame_numbers, num) + frame_timestamps = trim_list(frame_timestamps, num) + frame_mtr_pos = trim_list(frame_mtr_pos, num) + frame_roi2_ts = trim_list(frame_roi2_ts, num) + frame_roi3_ts = trim_list(frame_roi3_ts, num) + frame_roi4_ts = trim_list(frame_roi4_ts, num) + total_roi2 = trim_list(total_roi2, num) + total_roi3 = trim_list(total_roi3, num) + total_roi4 = trim_list(total_roi4, num) + + print("**********************************************************************") + + print(f"POSITION, TOTAL_ROI2, TOTAL_ROI3, TOTAL_ROI4") + for n, pos in enumerate(frame_mtr_pos): + print(f"{pos:11.5f} {total_roi2[n]:11.1f} {total_roi3[n]:11.1f} {total_roi4[n]:11.1f}") + print("**********************************************************************") + + return frame_mtr_pos, total_roi2, total_roi3, total_roi4 + + + +def align_motor(det, mtr, start, stop, step, exp_time, select_roi=4, mtr_max_velocity=0.08, fitting=True, model_type='peak', plot=True): + mtr_current = mtr.position + # start, stop = mtr_current + start_rel, mtr_current + stop_rel + + max_step = exp_time * mtr_max_velocity + step = min(step, max_step) + print(f"Y-scan step: {step}") + # exp_time_min = step / mtr_max_velocity + # exp_time = max(exp_time, exp_time_min) + # print(f"Y-scan exposure time: {exp_time}") + + @bpp.finalize_decorator(final_plan=shutter_off) + def inner(): + yield from shutter_on() + pos, roi2, roi3, roi4 = yield from flyscan(det, mtr, start, stop, step, exp_time) + + # print(pos, roi) + cen_return = None + roi = roi4 if select_roi == 4 else roi3 if select_roi == 3 else roi2 + print(pos, roi) + # Get axes for plotting + # if plot: + # print("Plotting...") + # # print(roi, pos) + # title = "fly_scan: {} vs. {}".format(det.name, mtr.name) + + # fig = None + # for i in plt.get_fignums(): + # title_cur = plt.figure(i).canvas.manager.window.windowTitle() + # if title_cur == title: + # fig = plt.figure(i) + # break + + # if fig is None: + # # New figure + # # fig, ax = plt.subplots() + # fig = plt.figure(figsize=(11, 7), facecolor="white") + # fig.canvas.manager.toolbar.pan() + + # fig.canvas.manager.set_window_title(title) + + # # ax = fig.gca() + # # ax.cla() + # fig.clear() + # plt.plot(pos, roi, 'b.-') + # plt.xlabel(f"{mtr.name} position") + # plt.ylabel(f"ROI{select_roi} counts") + # plt.title(f"Fly scan: {det.name} vs. {mtr.name}") + # plt.show(block=False) + # fig.tight_layout() + # fig.canvas.draw() + # last_data = pos, roi + # subs.append(liveplot) + + + if fitting: + try: + print("Fitting...") + print("Fitting...") + print("Fitting...") + print("Fitting...") + print("Fitting...") + # yield from bps.sleep(3) # Let the plot update + # cen, _, _ = do_fitting(pos, roi, model_type=model_type) + print(model_type) + if model_type == 'step': + print('step fitting') + cen, fwhm = ps_xy(pos, roi, der=True) + else: + print('peak fitting') + cen, fwhm = ps_xy(pos, roi, der=False) + print(model_type, cen, fwhm) + print(f"Center: {cen}") + # plt.axvline(cen, color='r', linestyle='--') + # plt.show(block=False) + if cen < min(pos) or cen > max(pos): + print(f"WARNING: The center {cen} is outside of the scan range.") + cen = mtr_current + + cen_return = cen + + except Exception as ex: + cen = mtr_current + print(f"ERROR: Failed to find the edge: {ex}") + + + else: + cen = mtr_current + print(f"NO fittig: Go back to original postion {cen}") + + yield from bps.abs_set(mtr, cen, wait=True) + yield from bps.mv(det.cam.num_images, 1) + yield from bps.trigger(det, group="fake_fly") + yield from bps.wait(group="fake_fly") + # plt.show(block=False) + + return cen_return + + + return (yield from inner()) + + +def _fly_scan(mtr, start, stop, step, exp_time, select_roi, det=None, fitting=True, model_type='peak'): + if det is None: + det = cms.detector[0] + + @bpp.stage_decorator([det]) + def inner(): + yield from align_motor(mtr=mtr, start=start, stop=stop, step=step, exp_time=exp_time, det=det, select_roi=select_roi, fitting=fitting, model_type=model_type) + + yield from inner() + + # return results + +def fly_scan( + motor, + span, + step=0.1, + select_roi=None, + detectors=None, + detector_suffix="", + exposure_time=0.3, + toggle_beam=True, + plot=True, + fit=None, + wait_time=None, + fitting=True, + model_type='peak', + save_flg=0, +): + """ + Scans the specified motor, and attempts to fit the data as requested. + + Parameters + ---------- + motor : motor + The axis/stage/motor that you want to move. + span : float + The total size of the scan range (centered about the current position). + If a two-element list is instead specified, this is interpreted as the + distances relative to the current position for the start and end. + num : int + The number of scan points. + fit : None or string + If None, then fitting is not done. Otherwise, the model specified by the + supplied string is used. + peaks: gauss, lorentz, doublesigmoid, square + edges: sigmoid, step + stats: max, min, COM (center-of-mass), HM (half-max) + background : None or string + A baseline/background underlying the fit function can be specified. + (In fact, a sequence of summed background functions can be supplied.) + constant, linear + md : dict, optional + metadata + """ + # TODO: Normalize per ROI pixel and per count_time? + # TODO: save scan data with save_flg=1. + if toggle_beam: + beam.on() + + if not beam.is_on(): + print("WARNING: Experimental shutter is not open.") + + initial_position = motor.user_readback.value + + if type(span) is list: + start = initial_position + span[0] + stop = initial_position + span[1] + else: + start = initial_position - span / 2.0 + stop = initial_position + span / 2.0 + span = abs(stop - start) + # positions, dp = np.linspace(start, stop, num, endpoint=True, retstep=True) + + if detectors is None: + # detselect(pilatus_name, suffix='_stats4_total') + detectors = get_beamline().detector[0] + + else: + plot_y = "{}{}".format(detectors[0].name, detector_suffix) + + subs = [] + + if exposure_time is None: + exposure_time = 0.3 + + if select_roi is None: + select_roi = 4 + + # md["plan_header_override"] = "fit_scan" + # md["scan"] = "fit_scan" + # md["measure_type"] = "fit_scan_{}".format(motor.name) + # md["fit_function"] = fit + # md["fit_background"] = background + + + # Perform the scan + RE(_fly_scan(mtr=motor, start=start, stop=stop, step=step, exp_time=exposure_time, select_roi=select_roi, det=detectors, fitting=fitting, model_type=model_type)) + + + # # Get axes for plotting + # if plot is True: + # title = "fit_scan: {} vs. {}".format(detectors[0].name, motor.name) + # # if len(plt.get_fignums())>0: + # # Try to use existing figure + # # fig = plt.gcf() # Most recent figure + + # fig = None + # for i in plt.get_fignums(): + # title_cur = plt.figure(i).canvas.manager.window.windowTitle() + # if title_cur == title: + # fig = plt.figure(i) + # break + + # if fig is None: + # # New figure + # # fig, ax = plt.subplots() + # fig = plt.figure(figsize=(11, 7), facecolor="white") + # fig.canvas.manager.toolbar.pan() + + # fig.canvas.manager.set_window_title(title) + # ax = fig.gca() + + # livetable = LiveTable([motor] + list(detectors)) + # # subs.append(livetable) + # # liveplot = LivePlot_Custom(plot_y, motor.name, ax=ax) + # liveplot = LivePlot(plot_y, motor.name, ax=ax) + # subs.append(liveplot) + + # if wait_time is not None: + # subs.append(MotorWait(motor, wait_time)) + + + # if fit in ["max", "min", "COM", "HM", "HMi"] or type(fit) is list: + # livefit = LiveStat(fit, plot_y, motor.name) + + # livefitplot = LiveStatPlot(livefit, ax=ax, scan_range=[start, stop]) + + # subs.append(livefitplot) + + # elif fit is not None: + # # Perform a fit + + # # livefit = LiveFit(lm_model, plot_y, {'x': motor.name}, init_guess) + # livefit = LiveFit_Custom( + # fit, + # plot_y, + # {"x": motor.name}, + # scan_range=[start, stop], + # background=background, + # ) + + # # livefitplot = LiveFitPlot(livefit, color='k') + # livefitplot = LiveFitPlot_Custom(livefit, ax=ax, scan_range=[start, stop]) + + # subs.append(livefitplot) + + + # if plot_y == "pilatus2m-1_stats4_total" or plot_y == "pilatus2m-1_stats3_total": + # remove_last_Pilatus_series() + + # # check save_flg and save the scan data thru databroker + # if save_flg == 1: + # header = db[-1] + # dtable = header.table() + # filename = "{}/{}".format(RE.md["experiment_alias_directory"], header.start["scan_id"]) + # dtable.to_csv(filename) + + if toggle_beam: + beam.off() + + # if fit is None: + + # motor.move(initial_position) + + # else: + # print(livefit.result.values) + # x0 = livefit.result.values["x0"] + # # mov(motor, x0) + # motor.move(x0) + # return livefit.result from bluesky.plan_stubs import one_1d_step @@ -1667,6 +2106,151 @@ def err_func(x, x0, k=2, A=1, base=0): #### erf fit from Yugang +def ps_xy(x, y, + shift=0.5, + logplot="off", + der=True, + plot=True, +): + """ + YG Copied from CHX beamline@March 18, 2018 + function to determine statistic on line profile (assumes either peak or erf-profile) + calling sequence: uid='-1',det='default',suffix='default',shift=.5) + det='default' -> get detector from metadata, otherwise: specify, e.g. det='eiger4m_single' + suffix='default' -> _stats1_total / _sum_all, otherwise: specify, e.g. suffix='_stats2_total' + shift: scale for peak presence (0.5 -> peak has to be taller factor 2 above background) + """ + # import datetime + # import time + # import numpy as np + # from PIL import Image + # from databroker import db, get_fields, get_images, get_table + # from matplotlib import pyplot as pltfrom + # from lmfit import Model + # from lmfit import minimize, Parameters, Parameter, report_fit + # from scipy.special import erf + + # field='dcm_b';intensity_field='elm_sum_all' + x = np.array(x) + y = np.array(y) + # print(t) + if der: + y = np.diff(y) + x = x[1:] + + PEAK = x[np.argmax(y)] + PEAK_y = np.max(y) + COM = np.sum(x * y) / np.sum(y) + + ### from Maksim: assume this is a peak profile: + def is_positive(num): + return True if num > 0 else False + + # Normalize values first: + ym = (y - np.min(y)) / (np.max(y) - np.min(y)) - shift # roots are at Y=0 + + positive = is_positive(ym[0]) + list_of_roots = [] + for i in range(len(y)): + current_positive = is_positive(ym[i]) + if current_positive != positive: + list_of_roots.append( + x[i - 1] + + (x[i] - x[i - 1]) / (abs(ym[i]) + abs(ym[i - 1])) * abs(ym[i - 1]) + ) + positive = not positive + if len(list_of_roots) >= 2: + FWHM = abs(list_of_roots[-1] - list_of_roots[0]) + CEN = list_of_roots[0] + 0.5 * (list_of_roots[1] - list_of_roots[0]) + ps.fwhm = FWHM + ps.cen = CEN + # return { + # 'fwhm': abs(list_of_roots[-1] - list_of_roots[0]), + # 'x_range': list_of_roots, + # } + else: # ok, maybe it's a step function.. + print("no peak...trying step function...") + ym = ym + shift + + def err_func(x, x0, k=2, A=1, base=0): #### erf fit from Yugang + return base - A * erf(k * (x - x0)) + + mod = Model(err_func) + ### estimate starting values: + x0 = np.mean(x) + # k=0.1*(np.max(x)-np.min(x)) + pars = mod.make_params(x0=x0, k=200, A=1.0, base=0.0) + result = mod.fit(ym, pars, x=x) + CEN = result.best_values["x0"] + FWHM = result.best_values["k"] + ps.cen = CEN + ps.fwhm = FWHM + + ### re-plot results: + if plot: + if logplot == "on": + plt.close(999) + plt.figure(999) + plt.semilogy([PEAK, PEAK], [np.min(y), np.max(y)], "k--", label="PEAK") + # plt.hold(True) + plt.semilogy([CEN, CEN], [np.min(y), np.max(y)], "r-.", label="CEN") + plt.semilogy([COM, COM], [np.min(y), np.max(y)], "g.-.", label="COM") + plt.semilogy(x, y, "bo-") + plt.xlabel('xlabel') + plt.ylabel('ylabel') + plt.legend() + plt.title( + "uid: " + # + str(uid) + # + " @ " + # + str(t) + + "\nPEAK: " + + str(PEAK_y)[:8] + + " @ " + + str(PEAK)[:8] + + " COM @ " + + str(COM)[:8] + + "\n FWHM: " + + str(FWHM)[:8] + + " @ CEN: " + + str(CEN)[:8], + size=9, + ) + plt.show() + else: + plt.close(999) + plt.figure(999) + plt.plot([PEAK, PEAK], [np.min(y), np.max(y)], "k--", label="PEAK") + # plt.hold(True) + plt.plot([CEN, CEN], [np.min(y), np.max(y)], "r-.", label="CEN") + plt.plot([COM, COM], [np.min(y), np.max(y)], "g.-.", label="COM") + plt.plot(x, y, "bo-") + plt.xlabel('xlabel') + plt.ylabel('ylabel') + plt.legend() + plt.title( + "uid: " + # + str(uid) + # + " @ " + # + str(t) + + "\nPEAK: " + + str(PEAK_y)[:8] + + " @ " + + str(PEAK)[:8] + + " COM @ " + + str(COM)[:8] + + "\n FWHM: " + + str(FWHM)[:8] + + " @ CEN: " + + str(CEN)[:8], + size=9, + ) + plt.show() + + ### assign values of interest as function attributes: + ps.peak = PEAK + ps.com = COM + return CEN, FWHM #ps.peak, ps.com, ps.cen, ps.fwhm # TODO: diff --git a/startup/94-sample-AIR.py.dev b/startup/94-sample-AIR.py.dev new file mode 100644 index 0000000..84cdd3c --- /dev/null +++ b/startup/94-sample-AIR.py.dev @@ -0,0 +1,648 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vi: ts=4 sw=4 + +""" +AI-Ready Sample Measurement Infrastructure +Modular, standardized, and future-proof measurement system +""" + +import time +import asyncio +from pathlib import Path +from datetime import datetime, timezone +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Any, Union +from abc import ABC, abstractmethod +import json +import pandas as pd +from enum import Enum + +# ============================================================================ +# Core Data Structures +# ============================================================================ + +class MeasurementType(Enum): + SINGLE = "single" + SERIES = "series" + TILED = "tiled" + TRIGGERED = "triggered" + SCAN = "scan" + +class DetectorType(Enum): + PILATUS = "pilatus" + CAMERA = "camera" + SENSOR = "sensor" + ION_CHAMBER = "ion_chamber" + +@dataclass +class MeasurementConfig: + """Standardized measurement configuration""" + measurement_type: MeasurementType + exposure_time: float + num_frames: int = 1 + exposure_period: Optional[float] = None + wait_time: Optional[float] = None + extra: Optional[str] = None + tiling_config: Optional[Dict] = None + trigger_config: Optional[Dict] = None + +@dataclass +class DetectorConfig: + """Standardized detector configuration""" + name: str + detector_type: DetectorType + exposure_time: float + num_images: int = 1 + trigger_mode: str = "Internal" + file_path: Optional[str] = None + ready: bool = False + +@dataclass +class MetadataPackage: + """Standardized metadata package""" + # Core identification + scan_id: int + timestamp: float + measurement_type: str + sample_name: str + filename: str + + # Measurement parameters + exposure_time: float + num_frames: int + detector_configs: List[DetectorConfig] + + # Sample state + sample_position: Dict[str, float] + sample_metadata: Dict[str, Any] + + # Beamline state + beam_energy: float + beam_intensity: Dict[str, float] + detector_positions: Dict[str, float] + + # Environment + temperature: Dict[str, float] + humidity: Optional[float] = None + pressure: Optional[float] = None + + # Custom metadata + custom: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class MeasurementResult: + """Standardized measurement result""" + success: bool + scan_id: int + timestamp: float + execution_time: float + + # File information + raw_files: List[str] + processed_files: List[str] + symlinks: List[str] + + # Validation results + files_validated: bool + data_quality: Dict[str, Any] + + # Statistics + detector_stats: Dict[str, Dict[str, float]] + measurement_stats: Dict[str, Any] + + # Errors/warnings + errors: List[str] = field(default_factory=list) + warnings: List[str] = field(default_factory=list) + +# ============================================================================ +# Abstract Base Classes +# ============================================================================ + +class DetectorManager(ABC): + """Abstract base class for detector management""" + + @abstractmethod + def prepare(self, config: DetectorConfig) -> bool: + """Prepare detector for measurement""" + pass + + @abstractmethod + def is_ready(self) -> bool: + """Check if detector is ready""" + pass + + @abstractmethod + def get_status(self) -> Dict[str, Any]: + """Get current detector status""" + pass + +class FilenameGenerator(ABC): + """Abstract base class for filename generation""" + + @abstractmethod + def generate(self, metadata: MetadataPackage) -> str: + """Generate filename from metadata""" + pass + +class FileHandler(ABC): + """Abstract base class for file handling""" + + @abstractmethod + def handle_files(self, detector_config: DetectorConfig, metadata: MetadataPackage) -> List[str]: + """Handle detector files after measurement""" + pass + + @abstractmethod + def validate_files(self, file_paths: List[str]) -> bool: + """Validate that files exist and are valid""" + pass + +# ============================================================================ +# Concrete Implementations +# ============================================================================ + +class PilatusDetectorManager(DetectorManager): + """Pilatus detector management""" + + def __init__(self, detector): + self.detector = detector + + def prepare(self, config: DetectorConfig) -> bool: + """Prepare Pilatus detector""" + try: + # Set exposure parameters + if config.exposure_time != self.detector.cam.acquire_time.get(): + self.detector.cam.acquire_time.set(config.exposure_time) + + if config.num_images != self.detector.cam.num_images.get(): + self.detector.cam.num_images.set(config.num_images) + + # Set trigger mode if needed + if hasattr(self.detector.cam, 'trigger_mode'): + trigger_modes = {'Internal': 0, 'ExtTrigger': 2, 'ExtMultiple': 3} + if config.trigger_mode in trigger_modes: + self.detector.cam.trigger_mode.put(trigger_modes[config.trigger_mode]) + + config.ready = True + return True + + except Exception as e: + print(f"Error preparing {self.detector.name}: {e}") + return False + + def is_ready(self) -> bool: + """Check if Pilatus is ready""" + return self.detector.cam.acquire.get() == 0 + + def get_status(self) -> Dict[str, Any]: + """Get Pilatus status""" + return { + 'name': self.detector.name, + 'acquiring': self.detector.cam.acquire.get(), + 'exposure_time': self.detector.cam.acquire_time.get(), + 'num_images': self.detector.cam.num_images.get(), + 'detector_state': self.detector.cam.detector_state.get() + } + +class StandardFilenameGenerator(FilenameGenerator): + """Standard filename generation based on naming scheme""" + + def generate(self, metadata: MetadataPackage) -> str: + """Generate standardized filename""" + timestamp_str = datetime.fromtimestamp(metadata.timestamp).strftime("%Y%m%d-%H%M%S") + + # Basic format: samplename_scanid_timestamp + base_name = f"{metadata.sample_name}_{metadata.scan_id:06d}_{timestamp_str}" + + # Add measurement type if not single + if metadata.measurement_type != "single": + base_name += f"_{metadata.measurement_type}" + + # Clean filename + base_name = base_name.replace(" ", "_").replace("/", "-") + + return base_name + +class ModularFileHandler(FileHandler): + """Modular file handler for different detector types""" + + def __init__(self, base_path: str): + self.base_path = Path(base_path) + + def handle_files(self, detector_config: DetectorConfig, metadata: MetadataPackage) -> List[str]: + """Handle files based on detector type""" + detector_map = { + 'pilatus2m-1': 'saxs', + 'pilatus800k-1': 'waxs', + 'pilatus800k-2': 'maxs', + 'pilatus300k-1': 'maxs' + } + + det_name = detector_map.get(detector_config.name, detector_config.name) + + # Create directory structure + raw_dir = self.base_path / det_name / 'raw' + raw_dir.mkdir(parents=True, exist_ok=True) + + symlinks = [] + + if detector_config.num_images == 1: + # Single file + symlinks.append(self._create_single_symlink(detector_config, metadata, det_name, raw_dir)) + else: + # Series files + symlinks.extend(self._create_series_symlinks(detector_config, metadata, det_name, raw_dir)) + + return symlinks + + def _create_single_symlink(self, detector_config, metadata, det_name, raw_dir): + """Create symlink for single file""" + # Get source file from detector + if hasattr(detector_config, 'detector'): + source_file = detector_config.detector.tiff.full_file_name.get() + else: + source_file = detector_config.file_path + + # Create symlink + link_name = f"{metadata.filename}_000000_{det_name}.tiff" + link_path = raw_dir / link_name + + if source_file and Path(source_file).exists(): + link_path.symlink_to(source_file) + print(f" Symlink created: {link_path}") + return str(link_path) + + return None + + def _create_series_symlinks(self, detector_config, metadata, det_name, raw_dir): + """Create symlinks for series files""" + symlinks = [] + + # Base path for series files + if hasattr(detector_config, 'detector'): + base_path = detector_config.detector.tiff.file_path.get() + base_name = detector_config.detector.tiff.file_name.get() + else: + base_path = str(Path(detector_config.file_path).parent) + base_name = Path(detector_config.file_path).stem + + for i in range(detector_config.num_images): + source_file = f"{base_path}/{base_name}_{i:06d}.tiff" + link_name = f"{metadata.filename}_{i:06d}_{det_name}.tiff" + link_path = raw_dir / link_name + + if Path(source_file).exists(): + link_path.symlink_to(source_file) + symlinks.append(str(link_path)) + + print(f" Created {len(symlinks)} symlinks for {det_name}") + return symlinks + + def validate_files(self, file_paths: List[str]) -> bool: + """Validate that all files exist and are readable""" + for file_path in file_paths: + if not Path(file_path).exists(): + return False + return True + +# ============================================================================ +# Core Measurement System +# ============================================================================ + +class MeasurementSystem: + """Core measurement orchestration system""" + + def __init__(self, sample): + self.sample = sample + self.detector_managers = {} + self.filename_generator = StandardFilenameGenerator() + self.file_handler = ModularFileHandler(RE.md.get("userpy_alias_directory", "/tmp")) + + # Initialize detector managers + self._initialize_detector_managers() + + def _initialize_detector_managers(self): + """Initialize detector managers for available detectors""" + for detector in get_beamline().detector: + if 'pilatus' in detector.name: + self.detector_managers[detector.name] = PilatusDetectorManager(detector) + # Add other detector types as needed + + def measure(self, config: MeasurementConfig, **custom_md) -> MeasurementResult: + """Execute measurement with standardized flow""" + start_time = time.time() + + # Step 1: Prepare detectors + detector_configs = self._prepare_detectors(config) + + # Step 2: Prepare metadata + metadata = self._prepare_metadata(config, detector_configs, custom_md) + + # Step 3: Prepare filename + metadata.filename = self.filename_generator.generate(metadata) + + # Step 4: Double check readiness + if not self._check_readiness(detector_configs): + return MeasurementResult( + success=False, + scan_id=metadata.scan_id, + timestamp=metadata.timestamp, + execution_time=0, + raw_files=[], + processed_files=[], + symlinks=[], + files_validated=False, + data_quality={}, + detector_stats={}, + measurement_stats={}, + errors=["Detectors not ready"] + ) + + # Step 5: Execute exposure + exposure_success = self._execute_exposure(config, metadata) + + # Step 6: Handle files + symlinks = self._handle_files(detector_configs, metadata) + + # Step 7: Validate files + files_validated = self._validate_files(symlinks) + + # Step 8: Return stats/logging/info + execution_time = time.time() - start_time + result = self._compile_results( + metadata, detector_configs, symlinks, + files_validated, execution_time, exposure_success + ) + + # Update sample measurement ID + self.sample.md['measurement_ID'] += 1 + + return result + + def _prepare_detectors(self, config: MeasurementConfig) -> List[DetectorConfig]: + """Step 1: Prepare all detectors""" + detector_configs = [] + + for detector in get_beamline().detector: + det_config = DetectorConfig( + name=detector.name, + detector_type=DetectorType.PILATUS if 'pilatus' in detector.name else DetectorType.CAMERA, + exposure_time=config.exposure_time, + num_images=config.num_frames, + trigger_mode=config.trigger_config.get('mode', 'Internal') if config.trigger_config else 'Internal' + ) + + # Apply sample-specific exposure times + if hasattr(self.sample, 'md'): + if detector.name == "pilatus2m-1" and self.sample.md.get("SAXS_time"): + det_config.exposure_time = self.sample.md["SAXS_time"] + elif detector.name == "pilatus800k-1" and self.sample.md.get("WAXS_time"): + det_config.exposure_time = self.sample.md["WAXS_time"] + elif detector.name == "pilatus800k-2" and self.sample.md.get("MAXS_time"): + det_config.exposure_time = self.sample.md["MAXS_time"] + + # Prepare detector + if detector.name in self.detector_managers: + self.detector_managers[detector.name].prepare(det_config) + + detector_configs.append(det_config) + + return detector_configs + + def _prepare_metadata(self, config: MeasurementConfig, detector_configs: List[DetectorConfig], custom_md: Dict) -> MetadataPackage: + """Step 2: Prepare standardized metadata""" + timestamp = datetime.now(timezone.utc).timestamp() + + # Get sample position + sample_position = {} + if hasattr(self.sample, '_axes'): + for axis_name, axis in self.sample._axes.items(): + sample_position[axis_name] = axis.get_position(verbosity=0) + + # Get beam intensities + beam_intensity = {} + try: + beam_intensity['bim3'] = beam.bim3.flux(verbosity=0) + beam_intensity['bim4'] = beam.bim4.flux(verbosity=0) + beam_intensity['bim5'] = beam.bim5.flux(verbosity=0) + except: + pass + + # Get detector positions + detector_positions = {} + try: + detector_positions['SAXSx'] = SAXSx.position + detector_positions['SAXSy'] = SAXSy.position + detector_positions['WAXSx'] = WAXSx.position + detector_positions['WAXSy'] = WAXSy.position + except: + pass + + # Get temperature readings + temperature = {} + try: + temperature['A'] = self.sample.temperature(temperature_probe='A', verbosity=0) + temperature['B'] = self.sample.temperature(temperature_probe='B', verbosity=0) + temperature['C'] = self.sample.temperature(temperature_probe='C', verbosity=0) + if 'temperature_Linkam' in getattr(self.sample, 'naming_scheme', []): + temperature['Linkam'] = LThermal.temperature() + except: + pass + + metadata = MetadataPackage( + scan_id=RE.md['scan_id'], + timestamp=timestamp, + measurement_type=config.measurement_type.value, + sample_name=self.sample.name, + filename="", # Will be set in step 3 + exposure_time=config.exposure_time, + num_frames=config.num_frames, + detector_configs=detector_configs, + sample_position=sample_position, + sample_metadata=getattr(self.sample, 'md', {}), + beam_energy=beam.energy(verbosity=0) if 'beam' in globals() else 0, + beam_intensity=beam_intensity, + detector_positions=detector_positions, + temperature=temperature, + custom=custom_md + ) + + return metadata + + def _check_readiness(self, detector_configs: List[DetectorConfig]) -> bool: + """Step 4: Check if all systems are ready""" + # Check beam + if not get_beamline().beam.is_on(): + print("WARNING: Beam shutter is not open") + + # Check detectors + for det_config in detector_configs: + if det_config.name in self.detector_managers: + if not self.detector_managers[det_config.name].is_ready(): + print(f"ERROR: Detector {det_config.name} not ready") + return False + + return True + + def _execute_exposure(self, config: MeasurementConfig, metadata: MetadataPackage) -> bool: + """Step 5: Execute the actual measurement""" + try: + # Turn on beam + get_beamline().beam.on() + + # Build Bluesky metadata + bluesky_md = { + 'plan_header_override': config.measurement_type.value, + 'sample_name': self.sample.name, + 'filename': metadata.filename, + 'measurement_type': config.measurement_type.value, + 'sample_exposure_time': config.exposure_time, + **metadata.beam_intensity, + **metadata.custom + } + + # Execute measurement based on type + if config.measurement_type == MeasurementType.SINGLE: + uids = RE(count(get_beamline().detector), **bluesky_md) + elif config.measurement_type == MeasurementType.SERIES: + uids = RE(count(get_beamline().detector), **bluesky_md) + # Add other measurement types as needed + + # Turn off beam + get_beamline().beam.off() + + return True + + except Exception as e: + print(f"Error during exposure: {e}") + get_beamline().beam.off() + return False + + def _handle_files(self, detector_configs: List[DetectorConfig], metadata: MetadataPackage) -> List[str]: + """Step 6: Handle all detector files""" + all_symlinks = [] + + for det_config in detector_configs: + # Add detector reference for file handling + for detector in get_beamline().detector: + if detector.name == det_config.name: + det_config.detector = detector + break + + symlinks = self.file_handler.handle_files(det_config, metadata) + all_symlinks.extend(symlinks) + + return all_symlinks + + def _validate_files(self, symlinks: List[str]) -> bool: + """Step 7: Validate all files""" + return self.file_handler.validate_files(symlinks) + + def _compile_results(self, metadata: MetadataPackage, detector_configs: List[DetectorConfig], + symlinks: List[str], files_validated: bool, execution_time: float, + exposure_success: bool) -> MeasurementResult: + """Step 8: Compile comprehensive results""" + + # Get detector statistics + detector_stats = {} + for det_config in detector_configs: + if det_config.name in self.detector_managers: + detector_stats[det_config.name] = self.detector_managers[det_config.name].get_status() + + return MeasurementResult( + success=exposure_success and files_validated, + scan_id=metadata.scan_id, + timestamp=metadata.timestamp, + execution_time=execution_time, + raw_files=[], # Would be populated from detector + processed_files=[], + symlinks=symlinks, + files_validated=files_validated, + data_quality={}, # Could add quality metrics + detector_stats=detector_stats, + measurement_stats={ + 'total_files': len(symlinks), + 'measurement_type': metadata.measurement_type, + 'exposure_time': metadata.exposure_time, + 'num_frames': metadata.num_frames + } + ) + +# ============================================================================ +# Enhanced Sample Class +# ============================================================================ + +class Sample_AIR(Sample_Generic): + """AI-Ready Sample class with modular measurement system""" + + def __init__(self, name, base=None, **md): + super().__init__(name, base=base, **md) + self.measurement_system = MeasurementSystem(self) + + def measure(self, exposure_time=None, extra=None, **md) -> MeasurementResult: + """Modern single measurement""" + config = MeasurementConfig( + measurement_type=MeasurementType.SINGLE, + exposure_time=exposure_time or self.md.get('exposure_time', 1.0), + extra=extra + ) + + return self.measurement_system.measure(config, **md) + + def measure_series(self, exposure_time=None, num_frames=10, exposure_period=None, + extra=None, **md) -> MeasurementResult: + """Modern series measurement""" + config = MeasurementConfig( + measurement_type=MeasurementType.SERIES, + exposure_time=exposure_time or self.md.get('exposure_time', 1.0), + num_frames=num_frames, + exposure_period=exposure_period, + extra=extra + ) + + return self.measurement_system.measure(config, **md) + + def measure_tiled(self, exposure_time=None, tiling='ygaps', extra=None, **md) -> MeasurementResult: + """Modern tiled measurement""" + config = MeasurementConfig( + measurement_type=MeasurementType.TILED, + exposure_time=exposure_time or self.md.get('exposure_time', 1.0), + extra=extra, + tiling_config={'mode': tiling} + ) + + return self.measurement_system.measure(config, **md) + + def measure_triggered(self, exposure_time=None, num_frames=1, trigger_mode='ExtTrigger', + extra=None, **md) -> MeasurementResult: + """Modern triggered measurement""" + config = MeasurementConfig( + measurement_type=MeasurementType.TRIGGERED, + exposure_time=exposure_time or self.md.get('exposure_time', 1.0), + num_frames=num_frames, + extra=extra, + trigger_config={'mode': trigger_mode} + ) + + return self.measurement_system.measure(config, **md) + +# ============================================================================ +# Usage Examples +# ============================================================================ + +if __name__ == "__main__": + # Example usage + sam = Sample_AIR("test_sample") + + # Single measurement + result = sam.measure(exposure_time=1.0, extra="test") + print(f"Measurement success: {result.success}") + print(f"Files created: {len(result.symlinks)}") + + # Series measurement + result = sam.measure_series(exposure_time=0.5, num_frames=10, extra="series_test") + + # Access comprehensive results + print(f"Execution time: {result.execution_time:.2f}s") + print(f"Detector stats: {result.detector_stats}") \ No newline at end of file diff --git a/startup/94-sample.py b/startup/94-sample.py index 2cd93d6..f60b604 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -1,3 +1,4 @@ + #!/usr/bin/python # -*- coding: utf-8 -*- # vi: ts=4 sw=4 @@ -1386,6 +1387,9 @@ def __init__(self, name, base=None, **md): self.md = { "exposure_time": 1.0, "measurement_ID": 1, + "SAXS_time": 0.5, + "WAXS_time": 2, + "MAXS_time": None, } self.md.update(md) @@ -1845,140 +1849,6 @@ def _expose_manual(self, exposure_time=None, verbosity=3, poling_period=0.1, **m get_beamline().beam.off() - def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, poling_period=0.1, **md): - """Internal function that is called to actually trigger a measurement.""" - """TODO: **md doesnot work in RE(count). """ - - if "measure_type" not in md: - md["measure_type"] = "expose" - # self.log('{} for {}.'.format(md['measure_type'], self.name), **md) - - # Set exposure time - if exposure_time is not None: - exposure_time = abs(exposure_time) - # for detector in gs.DETS: - for detector in get_beamline().detector: - if ( - exposure_time != detector.cam.acquire_time.get() - ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): - # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - detector.cam.acquire_time.set(exposure_time) - if detector.cam.num_images.get() != 1: - detector.cam.num_images.set(1) - - # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): - # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): - # detector.setExposureTime(exposure_time, verbosity=verbosity) - ##extra wait time when changing the exposure time. - ##time.sleep(2) - ############################################# - ##extra wait time for adjusting pilatus2M - ##this extra wait time has to be added. Otherwise, the exposure will be skipped when the exposure time is increased - ##Note by 091918 - ############################################# - # time.sleep(2) - # elif detector.name is 'PhotonicSciences_CMS': - # detector.setExposureTime(exposure_time, verbosity=verbosity) - - # Do acquisition - get_beamline().beam.on() - - md["plan_header_override"] = md["measure_type"] - start_time = time.time() - - # md_current = self.get_md() - md["beam_int_bim3"] = beam.bim3.flux(verbosity=0) - md["beam_int_bim4"] = beam.bim4.flux(verbosity=0) - md["beam_int_bim5"] = beam.bim5.flux(verbosity=0) - - if 'temperature_Linkam' in self.naming_scheme: - md["temperature_Linkam"] = LThermal.temperature() - # md['trigger_time'] = self.clock() - # md.update(md_current) - - # uids = RE(count(get_beamline().detector, 1), **md) - uids = RE(count(get_beamline().detector), **md) - # yield from RE(count(get_beamline().detector), **md) - - # get_beamline().beam.off() - # print('shutter is off') - - # Wait for detectors to be ready - max_exposure_time = 0.1 - for detector in get_beamline().detector: - # print('here in expose:', detector.name) - # if detector.name == "pilatus300k-1": - # current_exposure_time = detector.cam.acquire_time.get() - # max_exposure_time = max(max_exposure_time, current_exposure_time) - if detector.name == "pilatus2m-1": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - - elif 'webcam' in detector.name: - current_exposure_time = detector.cam.acquire_time.get() - max_exposure_time = max(max_exposure_time, current_exposure_time) - - # if detector.name is "pilatus300k-1": - # current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') - # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is "pilatus2m-1": - # current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') - # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is "pilatus800k-1": - # current_exposure_time = caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime') - # max_exposure_time = max(max_exposure_time, current_exposure_time) - # elif detector.name is 'PhotonicSciences_CMS': - # current_exposure_time = detector.exposure_time - # max_exposure_time = max(max_exposure_time, current_exposure_time) - else: - if verbosity >= 1: - print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) - - if verbosity >= 2: - status = 0 - while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): - percentage = 100 * (time.time() - start_time) / max_exposure_time - print( - "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), - end="", - ) - - time.sleep(poling_period) - - status = 1 - for detector in get_beamline().detector: - if detector.cam.acquire.get() == 1: - status *= 0 - print(' ') - - get_beamline().beam.off() - - print( - "Exposing {:6.2f} s ".format((time.time() - start_time)) - ) - - - - # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: - # print('Warning: Detector pilatus300 still not done acquiring.') - - # #if verbosity>=3 and caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: - # #print('Warning: Detector pilatus300 still not done acquiring.') - - # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: - # print('Warning: Detector pilatus2M still not done acquiring.') - - - - if handlefile == True: - for detector in get_beamline().detector: - self.handle_file(detector, extra=extra, verbosity=verbosity, **md) - # self.handle_file(detector, extra=extra, verbosity=verbosity) - def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, poling_period=0.1, **md): """Internal function that is called to actually trigger a measurement.""" """TODO: **md doesnot work in RE(count). """ @@ -2101,6 +1971,296 @@ def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosit # self.handle_file(detector, extra=extra, verbosity=verbosity, **md) ##self.handle_file(detector, extra=extra, verbosity=verbosity) + def set_detector_exposure_time(self, detector, exposure_time=None, md=None, verbosity=3): + """ + Set the exposure time for a detector, handling detector-specific logic. + Legacy comments and code preserved for reference. + """ + if md is None: + md = self.md + # --- Legacy logic preserved below --- + if 'webcam' in detector.name: + # print(f"Setting exposure time for {detector.name} to {exposure_time} s.") + return detector.cam.acquire_time.get() + # Detector-specific mapping + det_time_map = { + "pilatus2m-1": "SAXS_time", + "pilatus800k-1": "WAXS_time", + "pilatus800k-2": "MAXS_time", + } + key = det_time_map.get(detector.name) + # Priority: explicit exposure_time > detector-specific time > None + chosen_time = None + if exposure_time is not None: + chosen_time = exposure_time + elif key and md.get(key) is not None: + chosen_time = md[key] + # Set if needed + if chosen_time is not None and chosen_time != detector.cam.acquire_time.get(): + detector.cam.acquire_time.set(chosen_time) + # print(f"Setting exposure time for {detector.name} to {chosen_time} s.") + return chosen_time + # --- End legacy logic --- + return detector.cam.acquire_time.get() + + def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, poling_period=0.1, **md): + """Internal function that is called to actually trigger a measurement.""" + """TODO: **md doesnot work in RE(count). """ + + if "measure_type" not in md: + md["measure_type"] = "expose" + # self.log('{} for {}.'.format(md['measure_type'], self.name), **md) + + + # Modularized exposure time setting + for detector in get_beamline().detector: + md["exposure_time"] = self.set_detector_exposure_time(detector, exposure_time, self.md, verbosity=verbosity) + # Ensure number of images is 1 + if detector.cam.num_images.get() != 1: + detector.cam.num_images.set(1) + + + # --- Legacy comments/code preserved below --- + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): + # detector.setExposureTime(exposure_time, verbosity=verbosity) + ##extra wait time when changing the exposure time. + ##time.sleep(2) + ############################################# + ##extra wait time for adjusting pilatus2M + ##this extra wait time has to be added. Otherwise, the exposure will be skipped when the exposure time is increased + ##Note by 091918 + ############################################# + # time.sleep(2) + # elif detector.name is 'PhotonicSciences_CMS': + # detector.setExposureTime(exposure_time, verbosity=verbosity) + + # Do acquisition + get_beamline().beam.on() + + md["plan_header_override"] = md["measure_type"] + start_time = time.time() + + # md_current = self.get_md() + md["beam_int_bim3"] = beam.bim3.flux(verbosity=0) + md["beam_int_bim4"] = beam.bim4.flux(verbosity=0) + md["beam_int_bim5"] = beam.bim5.flux(verbosity=0) + + if 'temperature_Linkam' in self.naming_scheme: + md["temperature_Linkam"] = LThermal.temperature() + # md['trigger_time'] = self.clock() + # md.update(md_current) + + # uids = RE(count(get_beamline().detector, 1), **md) + uids = RE(count(get_beamline().detector), **md) + # yield from RE(count(get_beamline().detector), **md) + + # print(md['filename']) + # get_beamline().beam.off() + # print('shutter is off') + + # Wait for detectors to be ready + # max_exposure_time = 0.1 + # for detector in get_beamline().detector: + # # print('here in expose:', detector.name) + # # if detector.name == "pilatus300k-1": + # # current_exposure_time = detector.cam.acquire_time.get() + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # if detector.name == "pilatus2m-1": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "pilatus800k-1" or detector.name == "pilatus800k-2": + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + + # elif 'webcam' in detector.name: + # current_exposure_time = detector.cam.acquire_time.get() + # max_exposure_time = max(max_exposure_time, current_exposure_time) + + # # if detector.name is "pilatus300k-1": + # # current_exposure_time = caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime') + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # # elif detector.name is "pilatus2m-1": + # # current_exposure_time = caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime') + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # # elif detector.name is "pilatus800k-1": + # # current_exposure_time = caget('XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime') + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # # elif detector.name is 'PhotonicSciences_CMS': + # # current_exposure_time = detector.exposure_time + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # else: + # if verbosity >= 1: + # print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) + + # if verbosity >= 2: + # status = 0 + # while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): + # percentage = 100 * (time.time() - start_time) / max_exposure_time + # print( + # "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), + # end="", + # ) + + # time.sleep(poling_period) + + # status = 1 + # for detector in get_beamline().detector: + # if detector.cam.acquire.get() == 1: + # status *= 0 + # print(' ') + + get_beamline().beam.off() + + # print("Exposing {:6.2f} s ".format((time.time() - start_time))) + + + + # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL800K}:cam1:Acquire')==1: + # print('Warning: Detector pilatus300 still not done acquiring.') + + # #if verbosity>=3 and caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: + # #print('Warning: Detector pilatus300 still not done acquiring.') + + # if verbosity>=3 and caget('XF:11BMB-ES{Det:PIL2M}:cam1:Acquire')==1: + # print('Warning: Detector pilatus2M still not done acquiring.') + + + + if handlefile == True: + for detector in get_beamline().detector: + self.md["exposure_time"] = detector.cam.acquire_time.get() + md["exposure_time"] = detector.cam.acquire_time.get() + md["filename"] = self.get_savename() + self.handle_file(detector, extra=extra, verbosity=verbosity, **md) + # self.handle_file(detector, extra=extra, verbosity=verbosity) + # self.handle_file_datasecurity(detector, extra=extra, verbosity=verbosity, **md) + + ''' + # def _expose_test(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, poling_period=0.1, **md): + # """Internal function that is called to actually trigger a measurement.""" + # """TODO: **md doesnot work in RE(count). """ + + # if "measure_type" not in md: + # md["measure_type"] = "expose" + # # self.log('{} for {}.'.format(md['measure_type'], self.name), **md) + + # # Set exposure time + # start_time = time.time() + # print("1", time.time() - start_time) + # if exposure_time is not None: + # exposure_time = abs(exposure_time) + # # for detector in gs.DETS: + # for detector in get_beamline().detector: + # if ( + # exposure_time != detector.cam.acquire_time.get() + # ): # caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + # # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + # # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): + # # detector.setExposureTime(exposure_time, verbosity=verbosity) + # ##extra wait time when changing the exposure time. + # ##time.sleep(2) + # ############################################# + # ##extra wait time for adjusting pilatus2M + # ##this extra wait time has to be added. Otherwise, the exposure will be skipped when the exposure time is increased + # ##Note by 091918 + # ############################################# + # # time.sleep(2) + # # elif detector.name is 'PhotonicSciences_CMS': + # # detector.setExposureTime(exposure_time, verbosity=verbosity) + # print("2", time.time() - start_time) + + # # Do acquisition + # get_beamline().beam.on() + # print("beamon", time.time() - start_time) + + # md["plan_header_override"] = md["measure_type"] + # # start_time = time.time() + + # # md_current = self.get_md() + # md["beam_int_bim3"] = beam.bim3.flux(verbosity=0) + # md["beam_int_bim4"] = beam.bim4.flux(verbosity=0) + # md["beam_int_bim5"] = beam.bim5.flux(verbosity=0) + # # md['trigger_time'] = self.clock() + # # md.update(md_current) + + # print("3", time.time() - start_time) + # # uids = RE(count(get_beamline().detector, 1), **md) + # uids = RE(count(get_beamline().detector), **md) + # # yield from (count(get_beamline().detector), **md) + # print("4", time.time() - start_time) + + # # get_beamline().beam.off() + # # print('shutter is off') + + # # Wait for detectors to be ready + # max_exposure_time = 0.1 + # for detector in get_beamline().detector: + # if detector.name == "pilatus300k-1": + # current_exposure_time = caget("XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime") + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "pilatus2m-1": + # current_exposure_time = caget("XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime") + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "pilatus800k-1": + # current_exposure_time = caget("XF:11BMB-ES{Det:PIL800K}:cam1:AcquireTime") + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # # elif detector.name is 'PhotonicSciences_CMS': + # # current_exposure_time = detector.exposure_time + # # max_exposure_time = max(max_exposure_time, current_exposure_time) + # else: + # if verbosity >= 1: + # print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) + + # print("5", time.time() - start_time) + # if verbosity >= 2: + # status = 0 + # while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): + # percentage = 100 * (time.time() - start_time) / max_exposure_time + # print( + # "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), + # end="", + # ) + # time.sleep(poling_period) + + # status = 1 + # for detector in get_beamline().detector: + # if detector.name == "pilatus300k-1": + # if caget("XF:11BMB-ES{Det:SAXS}:cam1:Acquire") == 1: + # status *= 0 + # elif detector.name == "pilatus2m-1": + # if caget("XF:11BMB-ES{Det:PIL2M}:cam1:Acquire") == 1: + # status *= 0 + # elif detector.name == "pilatus800k-1": + # if caget("XF:11BMB-ES{Det:PIL800K}:cam1:Acquire") == 1: + # status *= 0 + # # elif detector.name is 'PhotonicSciences_CMS': + # # if not detector.detector_is_ready(verbosity=0): + # # status *= 0 + # print("6", time.time() - start_time) + + # else: + # time.sleep(max_exposure_time) + # print("7", time.time() - start_time) + + # # if verbosity>=3 and caget('XF:11BMB-ES{Det:SAXS}:cam1:Acquire')==1: + # # print('Warning: Detector pilatus300 still not done acquiring.') + + # if verbosity >= 3 and caget("XF:11BMB-ES{Det:PIL2M}:cam1:Acquire") == 1: + # print("Warning: Detector pilatus2M still not done acquiring.") + + # get_beamline().beam.off() + # print("8", time.time() - start_time) + + # # if handlefile == True: + # # for detector in get_beamline().detector: + # # self.handle_file(detector, extra=extra, verbosity=verbosity, **md) + # ##self.handle_file(detector, extra=extra, verbosity=verbosity) + ''' + def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" if subdirs: @@ -2133,15 +2293,21 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= if True: # self.set_attribute('exposure_time', caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime')) self.set_attribute("exposure_time", detector.cam.acquire_time.get()) # RL, 20210831 + print("Exposure time:", detector.cam.acquire_time.get()) # Create symlink # link_name = '{}/{}{}'.format(RE.md['experiment_alias_directory'], subdir, md['filename']) # savename = md['filename'][:-5] # savename = self.get_savename(savename_extra=extra) + # print(md) savename = md["filename"] # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) - link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') + link_name = "{}/{}{}_{}_000000_{}.tiff".format(RE.md["experiment_alias_directory"], + subdir, + savename, + RE.md['scan_id']-1, + detname).replace('//','/') # if 'camera' in detector.name: # link_name = "{}/{}{}_000000_{}.png".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') print(f" A symlink will be created at: {proposal_path()}experiments/{link_name}") @@ -2165,7 +2331,87 @@ def handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave= # print(" Data linked as: {}".format(link_name)) # if not os.path.isfile(os.readlink(link_name)): #added by RL, 20231109 # raise ValueError('NO IMAGE OUTPUT.') - + + def handle_file_datasecurity(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): + subdir = "" + if subdirs: + if detector.name == "pilatus300k-1" or detector.name == "pilatus800k-2": + subdir = "/maxs/raw/" + detname = "maxs" + elif detector.name == "pilatus2m-1": + subdir = "/saxs/raw/" + detname = "saxs" + elif detector.name == "pilatus800k-1": + subdir = "/waxs/raw/" + detname = "waxs" + elif 'webcam' in detector.name: + subdir = "/camera/" + detname = detector.name + + else: + if verbosity >= 1: + print("WARNING: Can't do file handling for detector '{}'.".format(detector.name)) + return + + filename = detector.tiff.full_file_name.get() # RL, 20210831 + # Alternate method to get the last filename + # filename = '{:s}/{:s}.tiff'.format( detector.tiff.file_path.get(), detector.tiff.file_name.get() ) + + if verbosity >= 3: + print(" Data saved to: {}".format(filename)) + + # if md['measure_type'] is not 'snap': + if True: + # self.set_attribute('exposure_time', caget('XF:11BMB-ES{Det:SAXS}:cam1:AcquireTime')) + self.set_attribute("exposure_time", detector.cam.acquire_time.get()) # RL, 20210831 + # print("Exposure time:", detector.cam.acquire_time.get()) + # print("Filename:", md["filename"]) + # Create symlink + # link_name = '{}/{}{}'.format(RE.md['experiment_alias_directory'], subdir, md['filename']) + # savename = md['filename'][:-5] + + # savename = self.get_savename(savename_extra=extra) + savename = md["filename"] + + link_name = savename + '_000000_' + detname + '.tiff' + link_folder = RE.md["userpy_alias_directory"] + '/' + detname + '/raw/' + if os.path.exists(link_folder) == False: + os.makedirs(link_folder) + + os.symlink(filename, link_folder+link_name) + + # # link_name = '{}/{}{}_{:04d}_maxs.tiff'.format(RE.md['experiment_alias_directory'], subdir, savename, RE.md['scan_id']-1) + # link_name = "{}/{}{}_000000_{}.tiff".format(RE.md["userpy_alias_directory"], subdir, savename, detname).replace('//','/') + # if 'camera' in detector.name: + # link_name = "{}/{}{}_000000_{}.png".format(RE.md["experiment_alias_directory"], subdir, savename, detname).replace('//','/') + print(f" A symlink will be created at: {link_folder}experiments/{link_name}") + + + + + + # if os.path.isfile(link_name): + # i = 1 + # while os.path.isfile("{}.{:d}".format(link_name, i)): + # i += 1 + # os.rename(link_name, "{}.{:d}".format(link_name, i)) + + + # #debug the losing data issue on pil2m. suggested by T. Caswell + # # with open(link_name, 'rb') as fin: + # # h = hashlib.md5(fin.read(1024)).hexdigest() + # # with open(link_name + '.md5', 'w') as fout: + # # fout.write(h) + + + # if verbosity >= 3: + # print(" Data linked as: {}".format(link_name)) + # if not os.path.isfile(os.readlink(link_name)): #added by RL, 20231109 + # raise ValueError('NO IMAGE OUTPUT.') + + # return filenames + + def _old_handle_file(self, detector, extra=None, verbosity=3, subdirs=True, linksave=True, **md): subdir = "" @@ -2623,6 +2869,10 @@ def measure( WAXSx_o = WAXSx.user_readback.value # MAXSy_o = MAXSy.user_readback.value + RE.md["tiling"] = "xygaps" + self.pos1_scanID = RE.md["scan_id"]-1 + RE.md["pos1_scanID"] = self.pos1_scanID + extra_current = "pos1" if extra is None else "{}_pos1".format(extra) md["detector_position"] = "lower_left" self.measure_single( @@ -2634,6 +2884,9 @@ def measure( **md, ) + self.pos1_scanID = RE.md["scan_id"]-1 + self.pos1_scanID = RE.md["scan_id"]-1 + # pos2 if [pilatus2M] in cms.detector: SAXSy.move(SAXSy_o + 5.16) @@ -2874,12 +3127,11 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", included in the savename/filename). """ - if exposure_time is not None: - self.set_attribute("exposure_time", exposure_time) + # if exposure_time is not None: + # self.set_attribute("exposure_time", exposure_time) # else: # exposure_time = self.get_attribute('exposure_time') - savename = self.get_savename(savename_extra=extra) if verbosity >= 2 and (get_beamline().current_mode != "measurement"): print( @@ -2890,6 +3142,12 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", print("ERROR: No detectors defined in cms.detector") return + for detector in get_beamline().detector: + if exposure_time != detector.cam.acquire_time.get(): + RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + + savename = self.get_savename(savename_extra=extra) + md_current = self.get_md() md_current.update(self.get_measurement_md()) md_current["sample_savename"] = savename @@ -2904,6 +3162,41 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", self.md["measurement_ID"] += 1 + def prepare_detector(self, mode, exposure_time=None, md=None, verbosity=3): + """ + Prepare all detectors: set exposure time and number of images. + Uses set_detector_exposure_time for modular logic. + Legacy comments preserved. + """ + if md is None: + md = self.md + for detector in get_beamline().detector: + md["exposure_time"] = self.set_detector_exposure_time(detector, exposure_time, md, verbosity=verbosity) + # Ensure number of images is 1 + if mode in ('measure', 'snap', 'expose'): + if detector.cam.num_images.get() != 1: + detector.cam.num_images.set(1) + if mode == 'series_measure': + if detector.cam.num_images.get() != md.get('num_frames', 1): + detector.cam.num_images.set(md.get('num_frames', 1)) + + # --- Legacy comments/code preserved below --- + # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): + # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): + # detector.setExposureTime(exposure_time, verbosity=verbosity) + ##extra wait time when changing the exposure time. + ##time.sleep(2) + ############################################# + ##extra wait time for adjusting pilatus2M + ##this extra wait time has to be added. Otherwise, the exposure will be skipped when the exposure time is increased + ##Note by 091918 + ############################################# + # time.sleep(2) + # elif detector.name is 'PhotonicSciences_CMS': + # detector.setExposureTime(exposure_time, verbosity=verbosity) + + def runPotentiostats_Ext(self, num_frames, exposure_time=0.995, exposure_period=1, detectors=None, trigger_mode='ExtTrigger', extra=None, wait_time=None, verbosity=3, **md): """ Continueous shots with internal trigger of detectors . @@ -3086,116 +3379,116 @@ def _test_measure_single( self.md["measurement_ID"] += 1 - def _test_expose( - self, exposure_time=None, extra=None, verbosity=3, poling_period=0.1, shutteronoff=True, **md - ): - """Internal function that is called to actually trigger a measurement.""" - - if "measure_type" not in md: - md["measure_type"] = "expose" - # self.log('{} for {}.'.format(md['measure_type'], self.name), **md) - - # Set exposure time - if exposure_time is not None: - for detector in get_beamline().detector: - detector.setExposureTime(exposure_time, verbosity=verbosity) - - # print('1') #5e-5 - # print(self.clock()) - - # Do acquisition - # check shutteronoff, if - if shutteronoff == True: - get_beamline().beam.on() - else: - print("shutter is disabled") - - # print('2') #3.0 - # print(self.clock()) - - md["plan_header_override"] = md["measure_type"] - start_time = time.time() - print("2") # 3.0 - print(self.clock()) - - # uids = RE(count(get_beamline().detector, 1), **md) - # uids = RE(count(get_beamline().detector), **md) - yield from (count(get_beamline().detector)) - print("3") # 4.3172 - print(self.clock()) - - # get_beamline().beam.off() - # print('shutter is off') + # def _test_expose( + # self, exposure_time=None, extra=None, verbosity=3, poling_period=0.1, shutteronoff=True, **md + # ): + # """Internal function that is called to actually trigger a measurement.""" - # Wait for detectors to be ready - max_exposure_time = 0 - for detector in get_beamline().detector: - if detector.name in {"pilatus300k-1", "pilatus2m-1"}: - current_exposure_time = caget("XF:11BMB-ES{}:cam1:AcquireTime".format(pilatus_Epicsname)) - max_exposure_time = max(max_exposure_time, current_exposure_time) - elif detector.name == "PhotonicSciences_CMS": - current_exposure_time = detector.exposure_time - max_exposure_time = max(max_exposure_time, current_exposure_time) - else: - if verbosity >= 1: - print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) + # if "measure_type" not in md: + # md["measure_type"] = "expose" + # # self.log('{} for {}.'.format(md['measure_type'], self.name), **md) - print("4") # 4.3193 - print(self.clock()) + # # Set exposure time + # if exposure_time is not None: + # for detector in get_beamline().detector: + # detector.setExposureTime(exposure_time, verbosity=verbosity) - if verbosity >= 2: - status = 0 - print("status1 = ", status) + # # print('1') #5e-5 + # # print(self.clock()) - while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): - percentage = 100 * (time.time() - start_time) / max_exposure_time - print( - "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), - end="", - ) - print("status2 = ", status) - - time.sleep(poling_period) - - status = 1 - for detector in get_beamline().detector: - if detector.name == "pilatus300k-1" or "pilatus2m-1": - print("status2.5 = ", status) - if caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: - status = 0 - print("status3 = ", status) - print("status3.5 = ", status) - - elif detector.name == "PhotonicSciences_CMS": - if not detector.detector_is_ready(verbosity=0): - status = 0 - print("5") # 3.0 - print(self.clock()) - print("6") # 3.0 - print(self.clock()) + # # Do acquisition + # # check shutteronoff, if + # if shutteronoff == True: + # get_beamline().beam.on() + # else: + # print("shutter is disabled") + + # # print('2') #3.0 + # # print(self.clock()) + + # md["plan_header_override"] = md["measure_type"] + # start_time = time.time() + # print("2") # 3.0 + # print(self.clock()) + + # # uids = RE(count(get_beamline().detector, 1), **md) + # # uids = RE(count(get_beamline().detector), **md) + # yield from (count(get_beamline().detector)) + # print("3") # 4.3172 + # print(self.clock()) + + # # get_beamline().beam.off() + # # print('shutter is off') + + # # Wait for detectors to be ready + # max_exposure_time = 0 + # for detector in get_beamline().detector: + # if detector.name in {"pilatus300k-1", "pilatus2m-1"}: + # current_exposure_time = caget("XF:11BMB-ES{}:cam1:AcquireTime".format(pilatus_Epicsname)) + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # elif detector.name == "PhotonicSciences_CMS": + # current_exposure_time = detector.exposure_time + # max_exposure_time = max(max_exposure_time, current_exposure_time) + # else: + # if verbosity >= 1: + # print("WARNING: Didn't recognize detector '{}'.".format(detector.name)) + + # print("4") # 4.3193 + # print(self.clock()) + + # if verbosity >= 2: + # status = 0 + # print("status1 = ", status) + + # while (status == 0) and (time.time() - start_time) < (max_exposure_time + 20): + # percentage = 100 * (time.time() - start_time) / max_exposure_time + # print( + # "Exposing {:6.2f} s ({:3.0f}%) \r".format((time.time() - start_time), percentage), + # end="", + # ) + # print("status2 = ", status) + + # time.sleep(poling_period) + + # status = 1 + # for detector in get_beamline().detector: + # if detector.name == "pilatus300k-1" or "pilatus2m-1": + # print("status2.5 = ", status) + # if caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: + # status = 0 + # print("status3 = ", status) + # print("status3.5 = ", status) + + # elif detector.name == "PhotonicSciences_CMS": + # if not detector.detector_is_ready(verbosity=0): + # status = 0 + # print("5") # 3.0 + # print(self.clock()) + # print("6") # 3.0 + # print(self.clock()) - else: - time.sleep(max_exposure_time) + # else: + # time.sleep(max_exposure_time) - # print('5') #4.4193 - # print(self.clock()) + # # print('5') #4.4193 + # # print(self.clock()) - if verbosity >= 3 and caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: - print("Warning: Detector still not done acquiring.") + # if verbosity >= 3 and caget("XF:11BMB-ES{}:cam1:Acquire".format(pilatus_Epicsname)) == 1: + # print("Warning: Detector still not done acquiring.") - if shutteronoff == True: - get_beamline().beam.off() - else: - print("shutter is disabled") + # if shutteronoff == True: + # get_beamline().beam.off() + # else: + # print("shutter is disabled") - # print('6') #4.9564 - # print(self.clock()) + # # print('6') #4.9564 + # # print(self.clock()) - for detector in get_beamline().detector: - self.handle_file(detector, extra=extra, verbosity=verbosity, **md) + # for detector in get_beamline().detector: + # self.handle_file(detector, extra=extra, verbosity=verbosity, **md) - # print('7') #4.9589 - # print(self.clock()) + # # print('7') #4.9589 + # # print(self.clock()) def _test_measureSpots( self, @@ -3429,7 +3722,9 @@ def do(self, step=0, verbosity=3, **md): if step <= 10: if verbosity >= 5: print(" step 10: measuring") - self.measure(self.SAXS_time) + self.measure(**md) + + def scan_measure( self, @@ -4084,6 +4379,7 @@ def temperature(self, temperature_probe="A", output_channel="1", RTDchan=2, verb def humidity(self, AI_chan=7, temperature=25, verbosity=3): return ioL.readRH(AI_chan=AI_chan, temperature=temperature, verbosity=verbosity) + ''' # def transmission_data_output(self, slot_pos): # """Output the tranmission of direct beam""" # h = db[-1] @@ -4149,6 +4445,7 @@ def humidity(self, AI_chan=7, temperature=25, verbosity=3): # output_data.to_csv(INT_FILENAME) # else: # temp_data.to_csv(INT_FILENAME) + ''' #updated on April 1st, 2025 def transmission_data_output(self): @@ -4927,31 +5224,109 @@ def get_default_stage(): # ss = handlefilename(range(2118770, 2118860)) # ss = handlefilename(range(2118903, 2118952)) -def handlefilename(uids): +def handlefilename(uids, detector=None, output_folder=None): """Given a list of uids, return a list of filenames.""" + # single uid filenames = [] + # if detector.name == 'pilatus800k-1': + # detector_name = 'waxs' + # elif detector.name == 'pilatus2m-1': + # detector_name = 'saxs' + # elif detector.name == 'pilatus800k-2': + # detector_name = 'maxs' + # else: + # return print('Please specify the detector name: pilatus800 (WAXS), pilatus2M (SAXS), pilatus8002 (MAXS)') + for uid in uids: h = db[uid] - link_name = h.start['filename'] + '_000000_waxs.tiff' - for name, doc in h.documents(): - if name == "resource": - rdoc = doc - break - filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_000000.tiff' - try: - h = db[uid] - fname = h.start["sample_name"] - filenames.append(fname) - except: - pass - os.symlink(filename, link_name) + for detector_i in h.start['detectors']: + if detector_i == 'pilatus800k-1': + detector_name = 'waxs' + + link_name = h.start['filename'] + '_000000_' + detector_name + '.tiff' + for name, doc in h.documents(): + if name == "resource": + rdoc = doc + break + filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_000000.tiff' + try: + h = db[uid] + fname = h.start["sample_name"] + filenames.append(fname) + except: + pass + if output_folder is not None: + link_name = output_folder + '/raw/' + link_name + if os.path.exists(output_folder + '/raw/') == False: + os.makedirs(output_folder + '/raw/') + else: + output_folder = RE.md["userpy_alias_directory"] + link_name = output_folder + '/' + detector_name + '/raw/' + link_name + if os.path.exists(output_folder + '/' + detector_name + '/raw/') == False: + os.makedirs(output_folder + '/' + detector_name + '/raw/') + + os.symlink(filename, link_name) + + elif detector_i == 'pilatus2m-1': + detector_name = 'saxs' + + link_name = h.start['filename'] + '_000000_' + detector_name + '.tiff' + for name, doc in h.documents(): + if name == "resource": + rdoc = doc + break + filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_000000.tiff' + try: + h = db[uid] + fname = h.start["sample_name"] + filenames.append(fname) + except: + pass + if output_folder is not None: + link_name = output_folder + '/raw/' + link_name + if os.path.exists(output_folder + '/raw/') == False: + os.makedirs(output_folder + '/raw/') + else: + output_folder = RE.md["userpy_alias_directory"] + link_name = output_folder + '/' + detector_name + '/raw/' + link_name + if os.path.exists(output_folder + '/' + detector_name + '/raw/') == False: + os.makedirs(output_folder + '/' + detector_name + '/raw/') + + os.symlink(filename, link_name) + + elif detector == 'pilatus800k-2': + detector_i = 'maxs' + link_name = h.start['filename'] + '_000000_' + detector_name + '.tiff' + for name, doc in h.documents(): + if name == "resource": + rdoc = doc + break + filename = rdoc['root'] + '/' + rdoc['resource_path'] + '/' + rdoc['resource_kwargs']['filename'] + '_000000.tiff' + try: + h = db[uid] + fname = h.start["sample_name"] + filenames.append(fname) + except: + pass + if output_folder is not None: + link_name = output_folder + '/raw/' + link_name + if os.path.exists(output_folder + '/raw/') == False: + os.makedirs(output_folder + '/raw/') + else: + output_folder = RE.md["userpy_alias_directory"] + link_name = output_folder + '/' + detector_name + '/raw/' + link_name + if os.path.exists(output_folder + '/' + detector_name + '/raw/') == False: + os.makedirs(output_folder + '/' + detector_name + '/raw/') + + os.symlink(filename, link_name) filenames.append(link_name) return filenames def handlefiles_names(): """Given a list of uids, return a list of filenames.""" + # burst mode filenames = [] uid = -1 h = db[uid] @@ -4973,4 +5348,14 @@ def handlefiles_names(): os.symlink(filename, link_name) filenames.append(link_name) - return filenames \ No newline at end of file + return filenames + +#20251011 +#np.arange(2152960, 2153049+1) + +#np.arange(2152864,2152873+1) + + +# sam = Sample_Generic("test") + + diff --git a/startup/95-sample-custom.py b/startup/95-sample-custom.py index 8bd91e9..9fabda1 100644 --- a/startup/95-sample-custom.py +++ b/startup/95-sample-custom.py @@ -1291,18 +1291,23 @@ def XR_scan( output_data = self.XR_data_output(direct_beam_slot, exposure_time) # output_data = output_data.iloc[0:0] + # create data directory if not exist + (Path(os.path.dirname(__file__))/'data').mkdir(exist_ok=True) # create a data file to save the XRR data if output_file is None: header = db[-1] # XR_FILENAME='{}/data/{}.csv'.format(os.path.dirname(__file__) , header.get('start').get('scan_id')+1) # XR_FILENAME='{}/data/{}.csv'.format(header.start['experiment_alias_directory'], header.get('start').get('scan_id')+1) # XR_FILENAME='{}/data/{}_{}.csv'.format(header.start['experiment_alias_directory'],header.start['sample_name'], header.get('start').get('scan_id')+1) + # XR_FILENAME = "{}/data/{}.csv".format( + # header.start["experiment_alias_directory"], header.start["filename"] + # ) XR_FILENAME = "{}/data/{}.csv".format( - header.start["experiment_alias_directory"], header.start["filename"] + os.path.dirname(__file__), header.start["filename"] ) print("FILENAME= {}".format(XR_FILENAME)) else: - XR_FILENAME = "{}/data/{}.csv".format(header.start["experiment_alias_directory"], output_file) + XR_FILENAME = "{}/data/{}.csv".format(os.path.dirname(__file__), output_file) if verbosity>=5: theta_output_data = pds.DataFrame([]) @@ -1390,7 +1395,7 @@ def XR_scan( print("The theta is {}\n".format(theta)) if verbosity>=5: THETA_FILENAME = "{}/data/Theta_profile_{}.csv".format( - header.start["experiment_alias_directory"], header.start["filename"] + os.path.dirname(__file__), header.start["filename"] ) theta_temp_output = {'a_scanID':db[-1].start["scan_id"]+1, "b_theta": theta } @@ -1458,7 +1463,7 @@ def XR_abort(self): bec.enable_plots() bec.enable_table() - pilatus_name.hints = {"fields": ["pilatus800_stats3_total", "pilatus800_stats4_total"]} + pilatus_name.hints = {"fields": ["pilatus800k-1_stats3_total", "pilatus800k-1_stats4_total"]} def XR_data_output(self, slot_pos, exposure_time): """XRR data output in DataFrame format, including: @@ -1496,9 +1501,12 @@ def XR_data_output(self, slot_pos, exposure_time): qz = 4 * np.pi * np.sin(np.deg2rad(sth_pos)) / h.start["calibration_wavelength_A"] scan_id = h.start["scan_id"] I0 = h.start["beam_int_bim5"] # beam intensity from bim5 - I1 = dtable.pilatus800_stats1_total - I2 = dtable.pilatus800_stats2_total - I3 = 2 * dtable.pilatus800_stats1_total - dtable.pilatus800_stats2_total + # I1 = dtable.pilatus800_stats1_total + # I2 = dtable.pilatus800_stats2_total + # I3 = 2 * dtable.pilatus800_stats1_total - dtable.pilatus800_stats2_total + I1 = dtable['pilatus800k-1_stats1_total'] + I2 = dtable['pilatus800k-1_stats2_total'] + I3 = 2 * I1 - I2 In = I3 / beam.absorber_transmission_list[slot_pos] / exposure_time current_data = { @@ -1764,12 +1772,13 @@ def th2thscan( # XR_FILENAME='{}/data/{}.csv'.format(os.path.dirname(__file__) , header.get('start').get('scan_id')+1) # XR_FILENAME='{}/data/{}.csv'.format(header.start['experiment_alias_directory'], header.get('start').get('scan_id')+1) th2th_FILENAME = "{}/data/{}_{}.csv".format( - header.start["experiment_alias_directory"], + # header.start["experiment_alias_directory"], + os.path.dirname(__file__), header.start["sample_name"], header.get("start").get("scan_id") + 1, ) else: - th2th_FILENAME = "{}/data/{}.csv".format(header.start["experiment_alias_directory"], output_file) + th2th_FILENAME = "{}/data/{}.csv".format(os.path.dirname(__file__), output_file) # load theta positions in scan if scan_type == "theta_scan": @@ -3347,7 +3356,7 @@ def tscan( # dump all the (seconds, degC) data into a file; use filename like "_tscan_-.csv" under *user/tscan directory self.tscan_filename = "{}/tscan/{}_tscan_{}.csv".format( - RE.md["experiment_alias_directory"], self.name, RE.md["scan_id"] + os.path.dirname(__file__), self.name, RE.md["scan_id"] ) self.tscan_data = pds.DataFrame(columns=["scan_id", "degC", "seconds"]) # f=open(filename,'w') diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 0bba640..016f1f0 100755 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -89,6 +89,30 @@ "bsy": -12.298904, "bsphi": -61.009099000000006, "timestamp": "2025-09-29 09:56:41" + }, + { + "bsx": -15.301903, + "bsy": -12.198667, + "bsphi": -61.011183, + "timestamp": "2025-10-15 11:09:42" + }, + { + "bsx": -15.301903, + "bsy": -12.198666999999999, + "bsphi": -61.011183, + "timestamp": "2025-10-22 09:40:50" + }, + { + "bsx": -15.301903, + "bsy": -12.198666999999999, + "bsphi": -61.011182999999996, + "timestamp": "2025-11-12 13:11:04" + }, + { + "bsx": -15.301903, + "bsy": -12.198666999999999, + "bsphi": -61.011182999999996, + "timestamp": "2025-11-13 09:12:33" } ], "round_17": [ @@ -157,6 +181,36 @@ "bsy": -10.098904, "bsphi": 26.99588899999999, "timestamp": "2025-09-18 20:01:56" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-10-20 11:55:27" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-10-24 11:16:07" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-10-24 11:16:37" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-10-24 11:27:03" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.99588899999999, + "timestamp": "2025-10-24 11:27:12" } ], "rod": [ @@ -213,6 +267,48 @@ "bsy": 17.003025, "bsphi": -181.011203, "timestamp": "2025-09-29 09:51:38" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-10-26 20:49:16" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-03 10:09:16" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-03 13:24:37" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-03 13:26:21" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-03 13:46:30" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-10 09:10:29" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-12 18:24:57" } ], "round_3m": [ diff --git a/startup/cfg/LinkamTrans_step.csv b/startup/cfg/LinkamTrans_step.csv index 4ff7069..1bb36b1 100644 --- a/startup/cfg/LinkamTrans_step.csv +++ b/startup/cfg/LinkamTrans_step.csv @@ -1,3 +1,4 @@ stepNo,temperature,rate,wait_time -0,100,30,180 -1,100,30,1800 \ No newline at end of file +0,20,5,120 +1,-90,5,1620 +2,20,5,1500 \ No newline at end of file diff --git a/startup/cfg/LinkamTrans_step2.csv b/startup/cfg/LinkamTrans_step2.csv new file mode 100644 index 0000000..21485cd --- /dev/null +++ b/startup/cfg/LinkamTrans_step2.csv @@ -0,0 +1,24 @@ +stepNo,temperature,rate,wait_time +0,20,5,180 +1,10,5,240 +2,0,5,240 +3,-10,5,240 +4,-20,5,240 +5,-30,5,240 +6,-40,5,240 +7,-50,5,240 +8,-60,5,240 +9,-70,5,240 +10,-80,5,240 +11,-90,5,240 +12,-80,5,240 +13,-70,5,240 +14,-60,5,240 +15,-50,5,240 +16,-40,5,240 +17,-30,5,240 +18,-20,5,240 +19,-10,5,240 +20,0,5,240 +21,10,5,240 +22,20,5,240 diff --git a/startup/cfg/linkam_stage_pos.cfg b/startup/cfg/linkam_stage_pos.cfg index 2f43178..5c1e809 100644 --- a/startup/cfg/linkam_stage_pos.cfg +++ b/startup/cfg/linkam_stage_pos.cfg @@ -36,6 +36,16 @@ "xo": -16.8, "yo": 3.51425, "timestamp": "2025-09-01 17:15:31" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-10-15 11:35:42" + }, + { + "xo": -16.8, + "yo": 3.51425, + "timestamp": "2025-10-20 14:41:24" } ], "LinkamGI": [ From 1d8468b1bbff9724ae817bb46b7efd115fde57fe Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Tue, 16 Dec 2025 13:05:28 -0500 Subject: [PATCH 17/23] live update on Dec 16 --- .vscode/settings.json | 5 + startup/.cms_config | 26 + startup/20-area-detectors.py | 30 +- startup/beamstop_config.cfg | 24 + startup/user_collection/user_static.py | 1362 ++++++++++++++++++++++++ 5 files changed, 1443 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json create mode 100755 startup/user_collection/user_static.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a8c2003 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:conda", + "python-envs.defaultPackageManager": "ms-python.python:conda", + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/startup/.cms_config b/startup/.cms_config index c464d02..ddd5a43 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1403,3 +1403,29 @@ 1401,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Thu Nov 13 09:11:47 2025 1402,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.00233,Thu Nov 13 09:12:29 2025 1403,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.002333999999998,Thu Nov 13 09:12:33 2025 +1404,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Sat Nov 15 09:01:21 2025 +1405,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183411,Sat Nov 15 09:02:09 2025 +1406,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183414,Sat Nov 15 09:02:11 2025 +1407,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Mon Nov 17 18:24:43 2025 +1408,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Nov 17 18:42:12 2025 +1409,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183432,Mon Nov 17 18:43:19 2025 +1410,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Mon Nov 17 22:54:01 2025 +1411,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.302394,Mon Nov 17 23:03:03 2025 +1412,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Thu Nov 20 18:19:43 2025 +1413,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.100848,Thu Nov 20 18:22:36 2025 +1414,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.400367,Mon Dec 1 09:59:00 2025 +1415,44.45,62.8,5.0,4.0,"[-96.8, -197.0, -122.5, -1.0]","[0.0, -101.80002499999999, -93.79981875, 89.2]","[0.0, -104.9, 0.0, 90.0]","[-97.5121875, -100.80002499999999, -93.3, 89.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.400367,Mon Dec 1 10:05:59 2025 +1416,44.45,62.8,5.0,4.0,"(-99.799775, -195.99989374999998, -122.00016875, -1.0)","(0, -100.7999, -91.3003, 88.2)","[0.0, -104.9, 0.0, 90.0]","(-96.00009374999999, -100.7999, -91.3003, 88.2)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.400367,Mon Dec 1 17:13:41 2025 +1417,44.45,62.8,5.0,4.0,"[-99.799775, -195.99989374999998, -122.00016875, -1.0]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.00009374999999, -100.7999, -91.3003, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Dec 1 17:39:20 2025 +1418,44.45,62.8,5.0,4.0,"[-99.799775, -195.99989374999998, -122.00016875, -1.0]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.00009374999999, -100.7999, -91.3003, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983448999999998,Mon Dec 1 17:40:27 2025 +1419,44.45,62.8,5.0,4.0,"(-99.8, -195.99989374999998, -123.00029375, -1.5)","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.00009374999999, -100.7999, -91.3003, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983448999999998,Tue Dec 2 15:48:16 2025 +1420,44.45,62.8,5.0,4.0,"(-99.3, -195.99989374999998, -122.50023125, -1.2999999999999972)","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.00009374999999, -100.7999, -91.3003, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983448999999998,Wed Dec 3 09:42:54 2025 +1421,44.45,62.8,5.0,4.0,"[-99.799775, -195.99989374999998, -122.50023125, -1.2999999999999972]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.00009374999999, -100.7999, -91.3003, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983448999999998,Wed Dec 3 09:46:59 2025 +1422,45.1,62.8,5.0,4.0,"(-99.5, -197.5, -122.50023125, -1.35)","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96., -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983448999999998,Wed Dec 3 09:55:54 2025 +1423,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.499962999999997,Sun Dec 14 21:09:54 2025 +1424,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Sun Dec 14 22:56:19 2025 +1425,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.183381,Sun Dec 14 22:57:15 2025 +1426,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.301903,Mon Dec 15 10:13:08 2025 +1427,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Mon Dec 15 10:15:44 2025 +1428,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Dec 15 12:11:19 2025 +1429,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Mon Dec 15 12:13:28 2025 diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index 51e8745..2627bd8 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -102,6 +102,19 @@ def ensure_nonblocking(self): if hasattr(cpt, "ensure_nonblocking"): cpt.ensure_nonblocking() + #added by RL + def ensure_blocking(self): + self.stage_sigs["wait_for_plugins"] = "Yes" + for c in self.parent.component_names: + cpt = getattr(self.parent, c) + if cpt is self: + continue + if hasattr(cpt, "ensure_blocking"): + try: + cpt.ensure_blocking() + except: + pass + class StandardProsilica(SingleTrigger, ProsilicaDetector): tiff = Cpt(TIFFPluginWithFileStore, suffix='TIFF1:', @@ -250,6 +263,7 @@ def setExposureTime(self, exposure_time, verbosity=3): # self.cam.acquire_period, # exposure_time + 0.1, ) + # self.cam.acquire_time.put(exposure_time) # self.cam.acquire_period.put(exposure_time+.1) # caput('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime', exposure_time) @@ -356,6 +370,11 @@ def setExposureTime(self, exposure_time, verbosity=3): # self.cam.acquire_period, # exposure_time + 0.1, ) + print('Setting exposure time to ', exposure_time) + while self.cam.acquire_time.get() != exposure_time: + #yield from asyncio.sleep(0.1) + yield from bps.sleep(0.1) + print('Waiting for exposure time to be set correctly...') def setExposurePeriod(self, exposure_period, verbosity=3): yield from mv(self.cam.acquire_period, exposure_period) @@ -574,7 +593,7 @@ def stage(self): camera.read_attrs.append('tiff') camera.tiff.read_attrs = [] - camera.cam.ensure_nonblocking() + # camera.cam.ensure_nonblocking() # class StandardsimDetectorV33(SingleTriggerV33, ProsilicaDetector): @@ -656,6 +675,8 @@ def stage(self): pilatus800.stats4.total.kind = "hinted" STATS_NAMES = ["stats1", "stats2", "stats3", "stats4", "stats5"] pilatus800.read_attrs = ["tiff"] + STATS_NAMES + # pilatus800.cam.ensure_blocking() + for stats_name in STATS_NAMES: stats_plugin = getattr(pilatus800, stats_name) stats_plugin.read_attrs = ["total"] @@ -746,7 +767,8 @@ def stage(self): for stats_name in STATS_NAMES2M: stats_plugin = getattr(pilatus2M, stats_name) stats_plugin.read_attrs = ["total"] - pilatus2M.cam.ensure_nonblocking() + # pilatus2M.cam.ensure_nonblocking() #changed by RL 25-11-15, + # pilatus2M.cam.ensure_blocking() #changed by RL 25-11-15, pilatus2M.tiff.ensure_blocking() pilatus2M.stats3.total.kind = "hinted" pilatus2M.stats4.total.kind = "hinted" @@ -829,7 +851,7 @@ def stage(self): if Pilatus800_on == True: pilatus800_h5 = PilatusV33_h5("XF:11BMB-ES{Det:PIL800K}:", name="pilatus800k-1") pilatus800_h5.h5.read_attrs = [] - pilatus800.cam.ensure_nonblocking() + pilatus800_h5.cam.ensure_nonblocking() STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] pilatus800_h5.read_attrs = ["h5"] + STATS_NAMES2M @@ -873,7 +895,7 @@ def stage(self): STATS_NAMES2M = ["stats1", "stats2", "stats3", "stats4"] pilatus8002_h5.read_attrs = ["h5"] + STATS_NAMES2M - pilatus8002.cam.ensure_nonblocking() + pilatus8002_h5.cam.ensure_nonblocking() for stats_name in STATS_NAMES: stats_plugin = getattr(pilatus8002_h5, stats_name) diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 016f1f0..124f98e 100755 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -113,6 +113,12 @@ "bsy": -12.198666999999999, "bsphi": -61.011182999999996, "timestamp": "2025-11-13 09:12:33" + }, + { + "bsx": -16.202255, + "bsy": -12.798437999999999, + "bsphi": -61.013161, + "timestamp": "2025-12-15 10:15:44" } ], "round_17": [ @@ -211,6 +217,12 @@ "bsy": -10.098904, "bsphi": 26.99588899999999, "timestamp": "2025-10-24 11:27:12" + }, + { + "bsx": -17.900453, + "bsy": -10.098904, + "bsphi": 26.995889, + "timestamp": "2025-11-20 18:22:36" } ], "rod": [ @@ -309,6 +321,18 @@ "bsy": 17.003504, "bsphi": -181.01414499999998, "timestamp": "2025-11-12 18:24:57" + }, + { + "bsx": -14.683067, + "bsy": 17.003504, + "bsphi": -181.01414499999998, + "timestamp": "2025-11-15 09:02:09" + }, + { + "bsx": -15.283414999999998, + "bsy": 17.003998, + "bsphi": -181.016052, + "timestamp": "2025-12-15 12:13:28" } ], "round_3m": [ diff --git a/startup/user_collection/user_static.py b/startup/user_collection/user_static.py new file mode 100755 index 0000000..1584dd6 --- /dev/null +++ b/startup/user_collection/user_static.py @@ -0,0 +1,1362 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vi: ts=4 sw=4 + + + + +################################################################################ +# Short-term settings (specific to a particular user/experiment) can +# be placed in this file. You may instead wish to make a copy of this file in +# the user's data directory, and use that as a working copy. +################################################################################ + + +#logbooks_default = ['User Experiments'] +#tags_default = ['CFN Soft-Bio'] + +import pickle +import os +from shutil import copyfile +from pathlib import Path + + +from ophyd import EpicsSignal +from bluesky.suspenders import SuspendFloor, SuspendCeil + +if True: + ring_current = EpicsSignal('SR:OPS-BI{DCCT:1}I:Real-I') + sus = SuspendFloor(ring_current, 100, resume_thresh=300, sleep=600) + RE.install_suspender(sus) + +# cms.SAXS.setCalibration([758, 1077], 5.03, [-65, -73]) #20201021, 13.5 keV +# RE.md['experiment_alias_directory'] = '/nsls2/data/cms/legacy/xf11bm/data/2021_3/AAmassian10' +# cms.SAXS.setCalibration([738, 1097], 3.0, [-65, -73]) #3m,13.5kev + +#absorber_pos = EpicsSignal( 'XF:11BMB-ES{SM:1-Ax:ArmR}Mtr.RBV') +#sus_abs_low = SuspendFloor(absorber_pos, -56, resume_thresh=-55) +#sus_abs_hi = SuspendCeil(absorber_pos, -54, resume_thresh=-55) +#RE.install_suspender(sus_abs_low) +#RE.install_suspender(sus_abs_hi) +#from ophyd import EpicsSignal +#from bluesky.suspenders import SuspendFloor + +#beam_current = EpicsSignal('SR:OPS-BI{DCCT:1}I:Real-I') +#sus = SuspendFloor(beam_current, 100, resume_thresh=101) +#RE.install_suspender(sus) +#RE.clear_suspenders() + +### DEFINE YOUR PARENT DATA FOLDER HERE + + + +RE.md['experiment_alias_directory'] = '0_Static' +RE.md["userpy_alias_directory"] = '/nsls2/data/cms/shared/config/bluesky/profile_collection/users/2025-3/UserName/' + +# cms.SAXS.setCalibration([732, 1680-582], 3.8, [-65, -73]) # +# cms.SAXS.setCalibration([759, 1680-606], 5, [-65, -73]) #2022Jul +# cms.SAXS.setCalibration([754, 1078], 5.0, [-65, -73]) # +# cms.SAXS.setCalibration([732, 1096], 3, [-65, -73]) #2023 Oct 3m +# cms.SAXS.setCalibration([753, 1080], 5, [-65, -73]) +# cms.SAXS.setCalibration([753, 1080], 5, [-65, -73]) #2024 March 5m +# cms.SAXS.setCalibration([754, 1081], 5.0, [-65, -73]) #2024 June 5m +# cms.SAXS.setCalibration([750, 1080], 5.03, [-65, -73]) #5m,13.5kev, Sept2025 +cms.SAXS.setCalibration([751, 1081], 5.03, [-65, -73]) #vacuum + + + + +# def saxs_on(): +# detselect(pilatus2M) +# WAXSx.move(-192) +# WAXSy.move(24) + +# def saxs_on_det(): +# detselect(pilatus2M) +# WAXSx.move(-225) +# WAXSy.move(30) + +# def waxs_on(): +# detselect(pilatus800) +# WAXSx.move(-192) +# WAXSy.move(16) + + +# def waxs_on_outer(): +# detselect(pilatus800) +# WAXSx.move(-210) +# WAXSy.move(16) + + +# def swaxs_on(): +# detselect([pilatus2M, pilatus800]) +# WAXSx.move(-210) +# WAXSy.move(30) + + + + +def saxs_on(): + detselect(pilatus2M) + # WAXSx.move(-200) + # WAXSy.move(24) + WAXSx.move(-195) + WAXSy.move(24) + +def saxs_on_det(): + detselect(pilatus2M) + WAXSx.move(-225) + WAXSy.move(30) + +def waxs_on(): + detselect(pilatus800) + WAXSx.move(-195) + WAXSy.move(24) + +def swaxs_on(): + detselect([pilatus2M, pilatus800]) + WAXSx.move(-210) + WAXSy.move(30) + +if False: + # The following shortcuts can be used for unit conversions. For instance, + # for a motor operating in 'mm' units, one could instead do: + # sam.xr( 10*um ) + # To move it by 10 micrometers. HOWEVER, one must be careful if using + # these conversion parameters, since they make implicit assumptions. + # For instance, they assume linear axes are all using 'mm' units. Conversely, + # you will not receive an error if you try to use 'um' for a rotation axis! + m = 1e3 + cm = 10.0 + mm = 1.0 + um = 1e-3 + nm = 1e-6 + + inch = 25.4 + pixel = 0.172 # Pilatus + + deg = 1.0 + rad = np.degrees(1.0) + mrad = np.degrees(1e-3) + urad = np.degrees(1e-6) + + + + +def get_default_stage(): + return stg + + +class SampleTSAXS(SampleTSAXS_Generic): + + def __init__(self, name, base=None, **md): + super().__init__(name=name, base=base, **md) + self.naming_scheme = ['name', 'extra', 'exposure_time'] + + +class SampleGISAXS(SampleGISAXS_Generic): + + def __init__(self, name, base=None, **md): + super().__init__(name=name, base=base, **md) + self.naming_scheme = ['name', 'extra', 'th', 'exposure_time'] + self.naming_scheme = ['name', 'extra', 'clock', 'temperature', 'exposure_time'] + + +#class Sample(SampleTSAXS): +class Sample(SampleGISAXS): + + def __init__(self, name, base=None, **md): + + super().__init__(name=name, base=base, **md) + + + #self.naming_scheme = ['name', 'extra', 'clock', 'temperature', 'th', 'exposure_time'] + #self.naming_scheme = ['name', 'extra', 'th', 'exposure_time'] + #self.naming_scheme = ['name', 'extra', 'th', 'exposure_time'] + #self.naming_scheme = ['name', 'extra', 'y', 'th', 'clock', 'exposure_time'] + self.naming_scheme = ['name', 'extra', 'x', 'th', 'exposure_time'] + #self.naming_scheme = ['name', 'extra', 'clock', 'temperature', 'exposure_time'] + + self._axes['y'].origin = 9 + self._axes['th'].origin = 0 + + self.md['exposure_time'] = 1 + #self.SAXS_time = 10 + #self.WAXS_time = 10 + self.SAXS_time = 10 + self.WAXS_time = 10 + self.WAXS_time_rock = 3 + + #self.incident_angles_default = [0.08, 0.10, 0.12, 0.15, 0.20] + #self.incident_angles_default = [0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20] + #self.incident_angles_default = [0.08, 0.12, 0.14, 0.20, 0.26, 0.32] #for 17kev/15kev + #self.incident_angles_default = [0.10, 0.16, 0.20] #for 17kev/15kev + #self.incident_angles_default = [0.08, 0.12, 0.15, 0.18, 0.20] #for 10kev + #self.incident_angles_default = [0.08, 0.12, 0.15, 0.18] #for 10kev LJR + #self.incident_angles_default = [0.12, 0.16, 0.20, 0.24] #for 10kev, Perovskites + self.incident_angles_default = [0.12, 0.16, 0.20, 0.24] #for 10kev, Perovskites + #self.incident_angles_default = [0.02, 0.04, 0.05, 0.06, 0.08, 0.09, 0.1, 0.12, 0.15] + #self.incident_angles_default = [0.02, 0.04, 0.06, 0.08, 0.1, 0.12, 0.15] + #self.incident_angles_default = [0.0] + + self.x_pos_default = [-1, 0, 1] + + self.total_flow = 20 + self.wetflow_default = self.total_flow*np.arange(.1, .51, .1) + self.wetwait_default = [1200,1200,1200,1200,1200] + + # def _set_axes_definitions(self): + # '''Internal function which defines the axes for this stage. This is kept + # as a separate function so that it can be over-ridden easily.''' + # super()._set_axes_definitions() + + # self._axes_definitions.append( {'name': 'phi', + # 'motor': srot, + # 'enabled': True, + # 'scaling': +1.0, + # 'units': 'deg', + # 'hint': None, + # } ) + # self._axes_definitions.append( {'name': 'trans2', + # 'motor': strans2, + # 'enabled': True, + # 'scaling': +1.0, + # 'units': 'deg', + # 'hint': None, + # } ) + + def _measureTimeSeries(self, exposure_time=None, num_frames=10, wait_time=None, extra=None, measure_type='measureTimeSeries', verbosity=3, **md): + + self.naming_scheme_hold = self.naming_scheme + self.naming_scheme = ['name', 'extra', 'clock', 'exposure_time'] + super().measureTimeSeries(exposure_time=exposure_time, num_frames=num_frames, wait_time=wait_time, extra=extra, measure_type=measure_type, verbosity=verbosity, **md) + self.naming_scheme = self.naming_scheme_hold + + def goto(self, label, verbosity=3, **additional): + super().goto(label, verbosity=verbosity, **additional) + # You can add customized 'goto' behavior here + + def scan_SAXSdet(self, exposure_time=None) : + SAXS_pos=[-73, 0, 73] + #SAXSx_pos=[-65, 0, 65] + + RE.md['stitchback'] = True + + for SAXSx_pos in SAXS_pos: + for SAXSy_pos in SAXS_pos: + mov(SAXSx, SAXSx_pos) + mov(SAXSy, SAXSy_pos) + self.measure(10) + + + + + def do(self, step=0, align_step=0, **md): + + #NOTE: if align_step =8 is not working, try align_step=4 + + if step<=1: + saxs_on() + get_beamline().modeAlignment() + + if step<=2: + self.xo() # goto origin + + + if step<=4: + self.yo() + self.tho() + + if step<=5: + self.align(step=align_step, reflection_angle=0.15) + #self.setOrigin(['y','th']) # This is done within align + + #if step<=7: + #self.xr(0.2) + + if step<=8: + get_beamline().modeMeasurement() + + if step<=10: + + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + + swaxs_on() + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.SAXS_time, tiling='ygaps', **md) + #waxs_on() + #self._test2_measureIncidentAngles(self.incident_angles_default, exposure_time=self.WAXS_time, tiling='ygaps', **md) + + self.thabs(0.0) + + + def x_scan(self, samth=0.12, exposure_time=10): + + self.gotoOrigin() + self.thabs(samth) + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + # saxs_on() + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=exposure_time, tiling='ygaps', **md) + for x in np.arange(-1, 1.01, 0.2): + self.xabs(x) + self.measure(exposure_time, extra='location1') + + if pilatus2M in cms.detector: + SAXSy.move(SAXSy.position+5.16) + elif pilatus800 in cms.detector: + WAXSy.move(WAXSy.position+5.16) + + for x in np.arange(-1, 1.01, 0.2): + self.xabs(x) + self.measure(exposure_time, extra='location2') + + if pilatus2M in cms.detector: + SAXSy.move(SAXSy.position-5.16) + elif pilatus800 in cms.detector: + WAXSy.move(WAXSy.position-5.16) + + + def IC_int(self): + + ion_chamber_readout1=caget('XF:11BMB-BI{IM:3}:IC1_MON') + ion_chamber_readout2=caget('XF:11BMB-BI{IM:3}:IC2_MON') + ion_chamber_readout3=caget('XF:11BMB-BI{IM:3}:IC3_MON') + ion_chamber_readout4=caget('XF:11BMB-BI{IM:3}:IC4_MON') + + ion_chamber_readout=ion_chamber_readout1+ion_chamber_readout2+ion_chamber_readout3+ion_chamber_readout4 + + return ion_chamber_readout>1*5e-08 + + + def do_SAXS(self, step=0, align_step=0, **md): + + if step<=1: + saxs_on() + get_beamline().modeAlignment() + + if step<=2: + self.xo() # goto origin + + + if step<=4: + self.yo() + self.tho() + + if step<=5: + self.align(step=align_step, reflection_angle=0.12) + #self.setOrigin(['y','th']) # This is done within align + + #if step<=7: + #self.xr(0.2) + + if step<=8: + get_beamline().modeMeasurement() + + if step<=10: + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + # if 'ME_TCTA&Ir(ppy)3' in self.name: + # self.SAXS_time = 60 + # if 'ES' in self.name: + # swaxs_on() + # self.measureIncidentAngles_Stitch(incident_angles, exposure_time=120, tiling='ygaps', **md) + # else: + # saxs_on() + # self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.SAXS_time, tiling='ygaps', **md) + # # if 'thin' in self.name: + # # self.measureIncidentAngle(0.12, exposure_time=120) + saxs_on() + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.SAXS_time, tiling='ygaps', **md) + + + + #if self.exposure_time_SAXS==None: + #self.measureIncidentAngles(incident_angles, exposure_time=self.SAXS_time, tiling=self.tiling, **md) + #else: + #self.measureIncidentAngles(incident_angles, exposure_time=self.exposure_time_SAXS, tiling=self.tiling, **md) + + self.thabs(0.0) + + + def align(self, step=0, reflection_angle=0.12, verbosity=3): + '''Align the sample with respect to the beam. GISAXS alignment involves + vertical translation to the beam center, and rocking theta to get the + sample plane parralel to the beam. Finally, the angle is re-optimized + in reflection mode. + + The 'step' argument can optionally be given to jump to a particular + step in the sequence.''' + start_time = time.time() + alignment = 'Success' + initial_y = smy.position + initial_th = sth.position + + align_crazy = self.swing(reflection_angle=reflection_angle) + crazy_y = smy.position + crazy_th = sth.position + cms.setDirectBeamROI() + + if align_crazy[0] == False: + alignment = 'Failed' + if step<=4: + if verbosity>=4: + print(' align: fitting') + + fit_scan(smy, 1.2, 21, fit='HMi') + ##time.sleep(2) + fit_scan(sth, 1.5, 21, fit='max') + ##time.sleep(2) + + #if step<=5: + # #fit_scan(smy, 0.6, 17, fit='sigmoid_r') + # fit_edge(smy, 0.6, 17) + # fit_scan(sth, 1.2, 21, fit='max') + + + if step<=8: + #fit_scan(smy, 0.3, 21, fit='sigmoid_r') + + fit_edge(smy, 0.6, 21) + #time.sleep(2) + #fit_edge(smy, 0.4, 21) + fit_scan(sth, 0.8, 21, fit='COM') + #time.sleep(2) + self.setOrigin(['y', 'th']) + + + if step<=9 and reflection_angle is not None: + # Final alignment using reflected beam + if verbosity>=4: + print(' align: reflected beam') + get_beamline().setReflectedBeamROI(total_angle=reflection_angle*2.0) + #get_beamline().setReflectedBeamROI(total_angle=reflection_angle*2.0, size=[12,2]) + + self.thabs(reflection_angle) + + result = fit_scan(sth, 0.4, 41, fit='max') + #result = fit_scan(sth, 0.2, 81, fit='max') #it's useful for alignment of SmarAct stage + sth_target = result.values['x_max']-reflection_angle + + if result.values['y_max']>50: + th_target = self._axes['th'].motor_to_cur(sth_target) + self.thsetOrigin(th_target) + + #fit_scan(smy, 0.2, 21, fit='max') + #self.setOrigin(['y']) + + if step<=10: + self.thabs(0.0) + beam.off() + + ### save the alignment information + align_time = time.time() - start_time + + current_data = {'a_sample': self.name, + 'b_quick_alignment': alignment, + 'c_align_time': align_time, + 'd_offset_y': smy.position - initial_y, + 'e_offset_th': sth.position - initial_th, + 'f_crazy_offset_y': smy.position - crazy_y, + 'g_crazy_offset_th': sth.position - crazy_th, + 'h_search_no': align_crazy[1]} + + temp_data = pds.DataFrame([current_data]) + + + # {proposal_path()}experiments/ + # INT_FILENAME='{}/data/{}.csv'.format(proposal_path()+'/experiments/'+RE.md['experiment_alias_directory'], 'alignment_results.csv') + # os.mkdir(os.path.dirname(__file__)+'/data/',exist_ok=True) + (Path(os.path.dirname(__file__))/'data').mkdir(exist_ok=True) + INT_FILENAME='{}/data/{}.csv'.format(os.path.dirname(__file__) , 'alignment_results') + + if os.path.isfile(INT_FILENAME): + output_data = pds.read_csv(INT_FILENAME, index_col=0) + output_data = pds.concat([output_data, temp_data]) + output_data.to_csv(INT_FILENAME) + else: + temp_data.to_csv(INT_FILENAME) + + def swing(self, step=0, reflection_angle=0.12, ROI_size=[10, 180], th_range=0.3, int_threshold=10, verbosity=3): + + #setting parameters + rel_th = 1 + ct = 0 + cycle = 0 + intenisty_threshold = 10 + + #re-assure the 3 ROI positon + get_beamline().setDirectBeamROI() + get_beamline().setReflectedBeamROI(total_angle=reflection_angle*2) + + #set ROI2 as a fixed area + get_beamline().setROI2ReflectBeamROI(total_angle=reflection_angle*2, size=ROI_size) + pilatus2M.roi2.size.y.set(190) + pilatus2M.roi2.min_xyz.min_y.set(852) + + + #def ROI3 in 160pixels with the center located at reflection beam + # get_beamline().setReflectedBeamROI(total_angle = reflection_angle*2, size=ROI_size) #set ROI3 + + # self.thabs(reflection_angle) + if verbosity>=4: + print(' Aligning {}'.format(self.name)) + + # if step<=0: + # # Prepare for alignment + + # if RE.state!='idle': + # RE.abort() + + # if get_beamline().current_mode!='alignment': + # #if verbosity>=2: + # #print("WARNING: Beamline is not in alignment mode (mode is '{}')".format(get_beamline().current_mode)) + # print("Switching to alignment mode (current mode is '{}')".format(get_beamline().current_mode)) + # get_beamline().modeAlignment() + + + get_beamline().setDirectBeamROI() + + beam.on() + + + if step<=2: + # if verbosity>=4: + # print(' align: searching') + + # Estimate full-beam intensity 2025-01-24 + value = None + if True: + # You can eliminate this, in which case RE.md['beam_intensity_expected'] is used by default + self.yr(-2) + #detector = gs.DETS[0] + detector = get_beamline().detector[0] + # value_name = get_beamline().TABLE_COLS[0] + beam.on() + RE(count([detector])) + value = detector.read()['pilatus2m-1_stats4_total']['value'] + self.yr(2) + + # if 'beam_intensity_expected' in RE.md: + # if value intenisty_threshold: + + #continue the fast alignment + print('The reflective beam is found! Continue the fast alignment') + + while abs(rel_th) > 0.005 and ct < 5: + # while detector.roi3.max_value.get() > 50 and ct < 5: + + #absolute beam position + refl_beam = detector.roi2.min_xyz.min_y.get() + detector.stats2.max_xy.y.get() + + #roi3 position + roi3_beam = detector.roi3.min_xyz.min_y.get() + detector.roi3.size.y.get()/2 + + #distance from current postion to the center of roi2 (the disired rel beam position) + # rel_ypos = detector.stats2.max_xy.get().y - detector.stats2.size.get().y + rel_ypos = refl_beam - roi3_beam + + rel_th = rel_ypos/get_beamline().SAXS.distance/1000*0.172/np.pi*180/2 + + print('The th offset is {}'.format(rel_th)) + self.thr(rel_th) + + ct += 1 + RE(count([detector])) + + if detector.stats3.total.get()>50: + + print('The fast alignment works!') + self.thr(-reflection_angle) + + + self.setOrigin(['y', 'th']) + + beam.off() + + return True, ii + + else: + print('Alignment Error: Cannot Locate the reflection beam') + self.thr(-reflection_angle) + beam.off() + + return False, ii + + + elif abs(detector.stats2.max_xy.get().y - detector.stats2.centroid.get().y) > 5: + print('Max and Centroid dont Match!') + + #perform the full alignment + print('Alignment Error: No reflection beam is found!') + self.thr(-reflection_angle) + beam.off() + return False, ii + + else: + print('Intensiy < threshold!') + + #perform the full alignment + print('Alignment Error: No reflection beam is found!') + self.thr(-reflection_angle) + beam.off() + return False, ii + + def search_plan(self, motor=smy, step_size=1.0, min_step=0.05, intensity=None, target=0.5, detector=None, detector_suffix=None, polarity=+1, verbosity=3): + '''Moves this axis, searching for a target value. + + Parameters + ---------- + step_size : float + The initial step size when moving the axis + min_step : float + The final (minimum) step size to try + intensity : float + The expected full-beam intensity readout + target : 0.0 to 1.0 + The target ratio of full-beam intensity; 0.5 searches for half-max. + The target can also be 'max' to find a local maximum. + detector, detector_suffix + The beamline detector (and suffix, such as '_stats4_total') to trigger to measure intensity + polarity : +1 or -1 + Positive motion assumes, e.g. a step-height 'up' (as the axis goes more positive) + ''' + print("HERE!!") + + if detector is None: + #detector = gs.DETS[0] + detector = get_beamline().detector[0] + if detector_suffix is None: + #value_name = gs.TABLE_COLS[0] + value_name = get_beamline().TABLE_COLS[0] + else: + value_name = detector.name + detector_suffix + + print(f"detector={detector}") + + @bpp.stage_decorator([detector]) + @bpp.run_decorator(md={}) + def inner_search(): + nonlocal intensity, target, step_size + + if not get_beamline().beam.is_on(): + print('WARNING: Experimental shutter is not open.') + + + if intensity is None: + intensity = RE.md['beam_intensity_expected'] + + # bec.disable_table() + + + # Check current value + vv = yield from bps.trigger_and_read([detector, motor]) + value = vv[value_name]['value'] + # RE(count([detector])) + # value = detector.read()[value_name]['value'] + + + if target == 'max': + + if verbosity>=5: + print("Performing search on axis '{}' target is 'max'".format(self.name)) + + max_value = value + # max_position = self.get_position(verbosity=0) + + + direction = +1*polarity + + while step_size>=min_step: + if verbosity>=4: + print(" move {} by {} × {}".format(self.name, direction, step_size)) + + # pos = yield from bps.rd(motor) + yield from bps.mvr(motor, direction*step_size) + # self.move_relative(move_amount=direction*step_size, verbosity=verbosity-2) + + prev_value = value + yield from bps.trigger_and_read([detector, motor]) + # RE(count([detector])) + + value = detector.read()[value_name]['value'] + # if verbosity>=3: + # print(" {} = {:.3f} {}; value : {}".format(self.name, self.get_position(verbosity=0), self.units, value)) + + if value>max_value: + max_value = value + # max_position = self.get_position(verbosity=0) + + if value>prev_value: + # Keep going in this direction... + pass + else: + # Switch directions! + direction *= -1 + step_size *= 0.5 + + + elif target == 'min': + + if verbosity>=5: + print("Performing search on axis '{}' target is 'min'".format(self.name)) + + direction = +1*polarity + + while step_size>=min_step: + if verbosity>=4: + print(" move {} by {} × {}".format(self.name, direction, step_size)) + + # pos = yield from bps.rd(motor) + yield from bps.mvr(motor, direction*step_size) + # self.move_relative(move_amount=direction*step_size, verbosity=verbosity-2) + + prev_value = value + yield from bps.trigger_and_read([detector, motor]) + # RE(count([detector])) + value = detector.read()[value_name]['value'] + if verbosity>=3: + print(" {} = {:.3f} {}; value : {}".format(self.name, self.get_position(verbosity=0), self.units, value)) + + if value=5: + print("Performing search on axis '{}' target {} × {} = {}".format(self.name, target_rel, intensity, target)) + if verbosity>=4: + print(" value : {} ({:.1f}%)".format(value, 100.0*value/intensity)) + + + # Determine initial motion direction + if value>target: + direction = -1*polarity + else: + direction = +1*polarity + + while step_size>=min_step: + + if verbosity>=4: + print(" move {} by {} × {}".format(self.name, direction, step_size)) + + # pos = yield from bps.rd(motor) + yield from bps.mvr(motor, direction*step_size) + # self.move_relative(move_amount=direction*step_size, verbosity=verbosity-2) + + yield from bps.trigger_and_read([detector, motor]) + # RE(count([detector])) + value = detector.read()[value_name]['value'] + #if verbosity>=3: + # print(" {} = {:.3f} {}; value : {} ({:.1f}%)".format(self.name, self.get_position(verbosity=0), self.units, value, 100.0*value/intensity)) + + # Determine direction + if value>target: + new_direction = -1.0*polarity + else: + new_direction = +1.0*polarity + + if abs(direction-new_direction)<1e-4: + # Same direction as we've been going... + # ...keep moving this way + pass + else: + # Switch directions! + direction *= -1 + step_size *= 0.5 + + # bec.enable_table() + + yield from inner_search() + ############### + def do_WAXS_only(self, step=0, align_step=0, **md): + if step<5: + self.xo() + self.yo() + self.tho() + get_beamline().modeMeasurement() + if step<=10: + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + waxs_on() + #for detector in get_beamline().detector: + #detector.setExposureTime(self.MAXS_time) + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.WAXS_time, tiling='ygaps', **md) + #self.xabs(0.5) + #self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.WAXS_time, tiling='ygaps', **md) + + + ############### + def do_SAXS_measure(self, step=0, align_step=0, **md): + if step<5: + self.xo() + self.yo() + self.tho() + get_beamline().modeMeasurement() + if step<=10: + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + saxs_on() + #for detector in get_beamline().detector: + #detector.setExposureTime(self.MAXS_time) + #self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.SAXS_time, tiling='ygaps', **md) + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=self.SAXS_time, tiling='ygaps',**md) + self.thabs(0.0) + + ##### For checking one sample + def do_WAXS(self, step=0, align_step=0, reflection_angle=0.12, **md): + + if step<=1: + saxs_on() + get_beamline().modeAlignment() + + if step<=2: + self.xo() # goto origin + + + if step<=4: + self.yo() + self.tho() + + if step<=5: + self.align(step=align_step, reflection_angle=reflection_angle ) + #self.setOrigin(['y','th']) # This is done within align + + #if step<=7: + #self.xr(0.2) + + if step<=8: + get_beamline().modeMeasurement() + + if step<=10: + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + + if self.measure_setting['exposure_time']==None: + exposure_time = self.WAXS_time + else: + exposure_time = self.measure_setting['exposure_time'] + + waxs_on() + #for detector in get_beamline().detector: + #detector.setExposureTime(self.MAXS_time) + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=exposure_time, tiling='ygaps', **md) + + self.thabs(0.0) + + ##### For checking one sample + def do_WAXS_align(self, step=0, align_step=0, reflection_angle=0.12, **md): + + if step<=1: + saxs_on() + get_beamline().modeAlignment() + + if step<=2: + self.xo() # goto origin + + + if step<=4: + self.yo() + self.tho() + + if step<=5: + self.align(step=align_step, reflection_angle=reflection_angle ) + #self.setOrigin(['y','th']) # This is done within align + + #if step<=7: + #self.xr(0.2) + + #if step<=8: + #get_beamline().modeMeasurement() + + def do_WAXS_measure(self, step=0, **md): + + if step<=1: + self.gotoOrigin() + + if step<=10: + if self.incident_angles==None: + incident_angles = self.incident_angles_default + else: + incident_angles = self.incident_angles + + incident_angle_large = [angle for angle in incident_angles if angle>5] + + if self.measure_setting['exposure_time']==None: + exposure_time = self.WAXS_time + else: + exposure_time = self.measure_setting['exposure_time'] + + get_beamline().modeMeasurement() + waxs_on() + self.measureIncidentAngles_Stitch(incident_angles, exposure_time=exposure_time, tiling='ygaps', **md) + + + #for detector in get_beamline().detector: + #detector.setExposureTime(self.MAXS_time) + + # for xpos in np.arange(-5, 5.1, .5): + # self.xabs(xpos) + # self.measureIncidentAngles_Stitch(incident_angles, exposure_time=exposure_time, tiling='ygaps', **md) + + #rock + # if 1: #'PBTTT' in self.name: + # rock_angle = 1.16 + # rock_motor_limits = 0.46 + # elif 'PCD' in self.name: + # rock_angle = 0.98 + # rock_motor_limits = 0.68 + # self.measureRock(rock_angle, exposure_time=self.WAXS_time_rock, rock_motor=sth, rock_motor_limits=rock_motor_limits, extra='rock', **md) + + + # self.xabs(1) + # self.measureIncidentAngles_Stitch(incident_angles, exposure_time=exposure_time, tiling='ygaps', **md) + + + self.thabs(0.0) + + + +class GIBar_Custom(GIBar): + def __init__(self, name='GIBarCustom', base=None, **kwargs): + super().__init__(name=name, base=base, **kwargs) + + #self._axes['x'].origin = -59.3 + ##position for calibration + self._axes['x'].origin = -71.35 + self._axes['y'].origin = 7 + self._axes['th'].origin = 0.1 + self.setPosition() + + # CREATE DIRECTORIES TO SAVE DATA + #holder_data_folder = os.path.join(parent_data_folder, self.name) + #os.makedirs(holder_data_folder, exist_ok=True) + #os.makedirs(os.path.join(holder_data_folder, 'waxs'), exist_ok=True) + #os.makedirs(os.path.join(holder_data_folder, 'saxs'), exist_ok=True) + #os.makedirs(os.path.join(holder_data_folder, 'waxs/raw'), exist_ok=True) + #os.makedirs(os.path.join(holder_data_folder, 'saxs/raw'), exist_ok=True) + #RE.md['experiment_alias_directory'] = holder_data_folder + + #### COPY CURRENT STATE OF user.py TO SAMPLE DIRECTORY + #copyfile(os.path.join(parent_data_folder, 'user.py'), os.path.join(holder_data_folder,'user.py')) + + + def doSamples(self, verbosity=3): + + #maxs_on() + for sample in self.getSamples(): + if verbosity>=3: + print('Doing sample {}...'.format(sample.name)) + if sample.detector=='SAXS' or sample.detector=='BOTH': + sample.do_SAXS() + + for sample in self.getSamples(): + if verbosity>=3: + print('Doing sample {}...'.format(sample.name)) + if sample.detector=='WAXS': + sample.do_WAXS_align() + + for sample in self.getSamples(): + if verbosity>=3: + print('Doing sample {}...'.format(sample.name)) + if sample.detector=='BOTH' or sample.detector=='WAXS': + sample.do_WAXS_measure() + + + ###############The new alignement procedure. + #def doSamples(self, step=0, verbosity=3): + + #if step<10: + #self.alignSamples_Custom(step=step) + + #if step<15: + #for sample in self.getSamples(): + #if verbosity>=3: + #print('Doing sample {}...'.format(sample.name)) + #if sample.detector=='SAXS' or sample.detector=='SAXS': + #sample.do_SAXS_only() + + #for sample in self.getSamples(): + #if verbosity>=3: + #print('Doing sample {}...'.format(sample.name)) + #if sample.detector=='WAXS' or sample.detector=='SAXS': + #sample.do_WAXS_only() + + def doTemperatures(self, step=0, reset_clock=True, temperature_heat_list=[100], temperature_cool_list=[100, 45],temperature_list=None, tiling=None, output_file='Transmission_output', int_measure=False, wait_time=600, temperature_probe='A', output_channel='1', temperature_tolerance=1, temp_tolerance=1, poling_period=2.0, verbosity=3,**md): + + #cms.modeMeasurement() + if temperature_list==None: + temperature_list = self.temperature_list + + if reset_clock==True: + for sample in self.getSamples(): + sample.reset_clock() + #RT + if step < 1: + + self.doSamples() + + #heating + if step < 5: + for index, temperature in enumerate(temperature_heat_list): + # Set new temperature + self.setTemperature(temperature, output_channel=output_channel, verbosity=verbosity) + + # Wait until we reach the temperature + start_time = time.time() + while abs(self.temperature(temperature_probe=temperature_probe, verbosity=0) - temperature)>temperature_tolerance and time.time()-start_time<1000: + if verbosity>=3: + print(' setpoint = {:.3f}°C, Temperature = {:.3f}°C \r'.format(self.temperature_setpoint()-273.15, self.temperature(temperature_probe=temperature_probe, verbosity=0)), end='') + time.sleep(poling_period) + + # Allow for additional equilibration at this temperature + sam=self.gotoSample(1) + sam.xr(0.2) + sam.setOrigin['x'] + sam.do_SAXS() + sam.do_WAXS_measure() + + #max T=150 + if step < 10: + temperature=150 + self.setTemperature(temperature, output_channel=output_channel, verbosity=verbosity) + + # Wait until we reach the temperature + start_time = time.time() + while abs(self.temperature(temperature_probe=temperature_probe, verbosity=0) - temperature)>temperature_tolerance and time.time()-start_time<1000: + if verbosity>=3: + print(' setpoint = {:.3f}°C, Temperature = {:.3f}°C \r'.format(self.temperature_setpoint()-273.15, self.temperature(temperature_probe=temperature_probe, verbosity=0)), end='') + time.sleep(poling_period) + + start_time_150 = time.time() + self.doSamples() + + while time.time() - start_time_150< 3600*3: + sam=self.gotoSample(1) + sam.xr(0.2) + sam.setOrigin['x'] + sam.do_SAXS() + sam.do_WAXS_measure() + time.sleep(60*15) + + #cooling + if step < 15: + for index, temperature in enumerate(temperature_cool_list): + # Set new temperature + self.setTemperature(temperature, output_channel=output_channel, verbosity=verbosity) + + # Wait until we reach the temperature + start_time = time.time() + while abs(self.temperature(temperature_probe=temperature_probe, verbosity=0) - temperature)>temperature_tolerance and time.time()-start_time<1000: + if verbosity>=3: + print(' setpoint = {:.3f}°C, Temperature = {:.3f}°C \r'.format(self.temperature_setpoint()-273.15, self.temperature(temperature_probe=temperature_probe, verbosity=0)), end='') + time.sleep(poling_period) + + # Allow for additional equilibration at this temperature + sam=self.gotoSample(1) + sam.xr(0.2) + sam.setOrigin['x'] + sam.do_SAXS() + sam.do_WAXS_measure() + + + if step < 20: + temperature=45 + self.setTemperature(temperature, output_channel=output_channel, verbosity=verbosity) + + # Wait until we reach the temperature + start_time = time.time() + while abs(self.temperature(temperature_probe=temperature_probe, verbosity=0) - temperature)>temperature_tolerance and time.time()-start_time<1000: + if verbosity>=3: + print(' setpoint = {:.3f}°C, Temperature = {:.3f}°C \r'.format(self.temperature_setpoint()-273.15, self.temperature(temperature_probe=temperature_probe, verbosity=0)), end='') + time.sleep(poling_period) + + self.doSamples() + + if step < 25: + self.setTemperature(25) + + def alignSamples_Custom(self, step=0, align_step=0, verbosity=3, **md): + + cali_sample=None + + #search the middle sample for a full alignment + if step < 10: + #cali_sample = self.getSample(1) + sample_pos_list = [] + for sample in self.getSamples(): + sample_pos_list.append(sample.position) + cali_sample = sample + hol_xcenter = min(sample_pos_list)/2+max(sample_pos_list)/2 + + print(sample_pos_list) + + #locate the sample for alignning the holder + for sample in self.getSamples(): + if abs(sample.position-hol_xcenter) < abs(cali_sample.position-hol_xcenter): + cali_sample = sample + print('The current calibraion sample is {}'.format(cali_sample.name)) + + print('The calibraion sample is {}'.format(cali_sample.name)) + + if step < 15: + #full alignment on cali_sample + #cali_sample.align() + saxs_on() + get_beamline().modeAlignment() + cali_sample.gotoOrigin() + cali_sample.align(step=0) + cali_sample.gotoOrigin() + #cali_sample.yo() + #cali_sample.tho() + + #set the th and y of other samples + if step < 20: + for sample in self.getSamples(): + sample.setOrigin(['th', 'y']) + + #quickly align other samples + if step < 30: + cms.modeAlignment() + for sample in self.getSamples(): + if sample != cali_sample: + + print('Aligning sample {}...'.format(sample.name)) + sample.gotoOrigin() + sample.align_custom(step=align_step) + sample.gotoOrigin() + #wsam() + + +#cms.SAXS.setCalibration([737, 1089], 2.8, [-65, -73]) #20190320, +#cms.SAXS.setCalibration([737, 1089], 5.05, [-65, -73]) #20190320 +#cms.SAXS.setCalibration([755, 1072], 5.05, [-65, -73]) #20191020 +#cms.SAXS.setCalibration([748, 1680-590], 2.01, [-64, -73]) # 13.5 keV +#cms.SAXS.setCalibration([756, 1074], 5.05, [-65, -73]) +#cms.SAXS.setCalibration([754, 1075], 5.03, [-65, -73]) #20201021, 13.5 keV +# cms.SAXS.setCalibration([740, 1098], 3.0, [-65, -73]) + +if True: + + cali = CapillaryHolder(base=stg) + #hol = CapillaryHolderCustom(base=stg) + + cali.addSampleSlot( Sample('Lab6_cali_5m_13.5kev'), 2.0 ) + cali.addSampleSlot( Sample('FL_screen'), 5.0 ) + cali.addSampleSlot( Sample('AgBH_cali_5m_13.5kev'), 7.0 ) + cali.addSampleSlot( Sample('AgBHCeO2_cali_5m_13.5kev'), 8.0 ) + cali.addSampleSlot( Sample('Empty'), 11.0 ) + +if True: + + # Example of a multi-sample holder + + md = { + 'owner' : 'UserName' , + 'series' : 'various' , + } + + hol1 = GIBar_Custom(base=stg) + hol1.addGaragePosition(1,1) + hol1.name = 'hol1' + hol1.addSampleSlotPosition( Sample('Sample_O1', **md),1, 10,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + hol1.addSampleSlotPosition( Sample('Sample_O2', **md),2, 26,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + hol1.addSampleSlotPosition( Sample('Sample_O3', **md),3, 44,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + hol1.addSampleSlotPosition( Sample('Sample_O4', **md),4, 59,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + hol1.addSampleSlotPosition( Sample('Sample_O5', **md),5, 76,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + hol1.addSampleSlotPosition( Sample('Sample_O6', **md),6, 96,'WAXS', incident_angles=[0.05, 0.08, 0.1, 0.12, 0.15], thickness =0.7) + + hol2 = GIBar_Custom(base=stg) + hol2.addGaragePosition(1,2) + hol2.name = 'hol2' + hol2.addSampleSlotPosition( Sample('Sample_R1', **md),1, 11,'WAXS', incident_angles=[0.08, 0.1, 0.12, 0.14, 0.16, 0.18, 0.2], thickness =0.7) + + que = Queue(base=stg) + + + + que.addHolderIntoQueue(hol1, [1, 1], 1) + que.addHolderIntoQueue(hol2, [1, 2], 2) + que.addHolderIntoQueue(hol3, [1, 3], 3) + que.addHolderIntoQueue(hol4, [2, 1], 4) + que.addHolderIntoQueue(hol5, [2, 2], 5) + que.addHolderIntoQueue(hol6, [2, 3], 6) + que.addHolderIntoQueue(hol7, [3, 1], 7) + que.addHolderIntoQueue(hol8, [3, 2], 8) + que.addHolderIntoQueue(hol9, [3, 3], 9) + que.addHolderIntoQueue(hol10, [4, 1], 10) + # que.addHolderIntoQueue(hol11, [4, 2], 11) + # que.addHolderIntoQueue(hol12, [4, 3], 12) + + + + que.setSequence() + que.checkStatus(verbosity=5) + + +# sam=Sample('test') +# detselect([fs4]) +# sam.measure(1) + +''' +*mxsettings: Start_angle 0.000000 +**Preparing camera for exposure: current_11087.cbf +Setting B**_M**_CHSEL PATTERN to 0xffff (hardware pattern: 0xffff) +Starting 0.5000000 second background: 2025-09-25T02:04:57.967 +*** ERROR: Insufficient disk space + + + +Transient Scan ID: 2124096 Time: 2025-09-25 03:07:02 +Persistent Unique Scan ID: '0acfd02d-3c53-486e-aa8e-0ea18e5d1e0b' +New stream: 'primary' ++-----------+------------+----------------------------+----------------------------+ +| seq_num | time | pilatus800k-1_stats3_total | pilatus800k-1_stats4_total | ++-----------+------------+----------------------------+----------------------------+ +| 1 | 03:07:45.3 | 3259 | -40 | +Document saving failure: ReadTimeout('The read operation timed out') ++-----------+------------+----------------------------+---------------------- + + + + +Transient Scan ID: 2124103 Time: 2025-09-25 03:10:19 +Persistent Unique Scan ID: 'b1a3c49c-8630-460f-9bb4-cca3340c8efe' +New stream: 'primary' ++-----------+------------+----------------------------+----------------------------+ +| seq_num | time | pilatus800k-1_stats3_total | pilatus800k-1_stats4_total | ++-----------+------------+----------------------------+----------------------------+ +Document saving failure: ReadTimeout('The read operation timed out') +Document saving failure: ReadTimeout('The read operation timed out') +| 1 | 03:11:34.2 | 10317 | -40 | ++-----------+------------+----------------------------+----------------------------+ +generator count ['b1a3c49c'] (scan num: 2124103) + +2025_1 + +rod +In [132]: wbs() +bsx = -11.000014 +bsy = 17.0005 +bsphi = -181.50260699999998 + + + + + + +#new beamstop setup at 2023C2 + +rod bs + +In [69]: wbs() +bsx = -13.10041 +bsy = 14.500015 +bsphi = -178.00030500000003 + +round bs + +In [90]: wbs() +bsx = -16.700347 +bsy = -0.1998869999999997 +bsphi = -19.999025000000003 + + +que.runHolders() + + +#cali.origin +In [89]: wsam() +smx = -16.2 +smy = -2.0 +sth = 0.0 + + +In [425]: hol6.saveSampleStates() +Out[425]: +{1: {'origin': {'x': 7.0, 'y': 0.04625000000000057, 'th': -0.137109375}}, + 2: {'origin': {'x': 23.0, 'y': 0.050050000000000594, 'th': -0.148515625}}, + 3: {'origin': {'x': 40.0, 'y': 0.08125000000000071, 'th': 0.080390625}}, + 4: {'origin': {'x': 55.0, 'y': 0.0, 'th': 0.0}}, + 5: {'origin': {'x': 72.0, 'y': 0.0, 'th': 0.0}}, + 6: {'origin': {'x': 88.0, 'y': 0.0, 'th': 0.0 + + +cms.ventSample() +cms.pumpSample() + +#Click the tiny green button once, then in the "800k" tab, click ctrl+X twice to restart the WAXS detector + +an alternative solution is : startWAXS() + +ctrl+S to save the scripts first, then +"%run -i ..." to reload the scripts +% +y +n +hol1 +y + + +hol1.listSamples() #check sample name has been updated +hol1.gotoOrigin() #goto hol1 origin +hol1.yr(-4) #lower the bar +hol1.setOrigin('y') #reset the origin +hol2.setOrigin('y') +.. + +que.runHolders() + + +history + + + + +''' \ No newline at end of file From 24f76fe7d3288cf6e870185a85d9955e399d4ab4 Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Wed, 14 Jan 2026 10:21:11 -0500 Subject: [PATCH 18/23] uncommited data sec changes --- startup/.cms_config | 5 +++++ startup/51-linkam-stages_new.py | 2 +- startup/94-sample.py | 16 +++++++++++----- startup/cfg/LinkamTrans_step.csv | 6 +++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/startup/.cms_config b/startup/.cms_config index ddd5a43..1355e40 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1429,3 +1429,8 @@ 1427,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Mon Dec 15 10:15:44 2025 1428,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.683067,Mon Dec 15 12:11:19 2025 1429,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Mon Dec 15 12:13:28 2025 +1430,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Tue Dec 16 20:41:49 2025 +1431,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Thu Dec 18 10:36:58 2025 +1432,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Thu Dec 18 14:44:20 2025 +1433,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Thu Dec 18 23:06:06 2025 +1434,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283762999999999,Thu Dec 18 23:06:51 2025 diff --git a/startup/51-linkam-stages_new.py b/startup/51-linkam-stages_new.py index 8fcd7bb..cdbec5c 100644 --- a/startup/51-linkam-stages_new.py +++ b/startup/51-linkam-stages_new.py @@ -720,7 +720,7 @@ def mov(self, position, velocity=None, verbosity=3): return self.POS.get() def movr(self, distance, velocity=None, verbosity=3): - # move to the absolute position + # move to the relative position relative_pos = distance if relative_pos > 0: # open self.setDirection(0) diff --git a/startup/94-sample.py b/startup/94-sample.py index f60b604..b84d536 100644 --- a/startup/94-sample.py +++ b/startup/94-sample.py @@ -2014,17 +2014,21 @@ def expose(self, exposure_time=None, extra=None, handlefile=True, verbosity=3, p # Modularized exposure time setting for detector in get_beamline().detector: + # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) + md["exposure_time"] = self.set_detector_exposure_time(detector, exposure_time, self.md, verbosity=verbosity) + self.md["exposure_time"] = md["exposure_time"] + # Ensure number of images is 1 - if detector.cam.num_images.get() != 1: - detector.cam.num_images.set(1) + # if detector.cam.num_images.get() != 1: + # detector.cam.num_images.set(1) # --- Legacy comments/code preserved below --- # if detector.name is "pilatus800k-1" and exposure_time != detector.cam.acquire_time.get(): #caget('XF:11BMB-ES{Det:PIL2M}:cam1:AcquireTime'): # RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) # if detector.name is "pilatus300k-1" and exposure_time != detector.cam.acquire_time.get(): - # detector.setExposureTime(exposure_time, verbosity=verbosity) + # detector.setExposureTime(exposure_time, verbosity=verbosity) ##extra wait time when changing the exposure time. ##time.sleep(2) ############################################# @@ -3145,9 +3149,10 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", for detector in get_beamline().detector: if exposure_time != detector.cam.acquire_time.get(): RE(detector.setExposureTime(exposure_time, verbosity=verbosity)) - + self.md["exposure_time"] = detector.cam.acquire_time.get() + savename = self.get_savename(savename_extra=extra) - + md_current = self.get_md() md_current.update(self.get_measurement_md()) md_current["sample_savename"] = savename @@ -3156,6 +3161,7 @@ def measure_single(self, exposure_time=None, extra=None, measure_type="measure", # md_current['filename'] = '{:s}_{:04d}'.format(savename, RE.md['scan_id']) md_current["filename"] = "{:s}_{:06d}".format(savename, RE.md["scan_id"]) md_current.update(md) + # print(self.md) self.expose(exposure_time, extra=extra, verbosity=verbosity, **md_current) # self.expose(exposure_time, extra=extra, verbosity=verbosity, **md) diff --git a/startup/cfg/LinkamTrans_step.csv b/startup/cfg/LinkamTrans_step.csv index 1bb36b1..9ec9507 100644 --- a/startup/cfg/LinkamTrans_step.csv +++ b/startup/cfg/LinkamTrans_step.csv @@ -1,4 +1,4 @@ stepNo,temperature,rate,wait_time -0,20,5,120 -1,-90,5,1620 -2,20,5,1500 \ No newline at end of file +0,140,10,840 +1,40,5,1800 +2,140,10,840 \ No newline at end of file From 4a8cea35a6e482837b6a66a25651a1195f8cda0a Mon Sep 17 00:00:00 2001 From: "Eugene M." Date: Thu, 12 Feb 2026 12:47:54 -0500 Subject: [PATCH 19/23] ENH: Configure writing to Tiled --- startup/02-data_security.py | 12 ----- startup/02-tiled-writer.py | 105 ++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 12 deletions(-) delete mode 100644 startup/02-data_security.py create mode 100644 startup/02-tiled-writer.py diff --git a/startup/02-data_security.py b/startup/02-data_security.py deleted file mode 100644 index 0e2fd08..0000000 --- a/startup/02-data_security.py +++ /dev/null @@ -1,12 +0,0 @@ -# To be deleted when DS is done - -RE.md.pop('experiment_alias_directory', None) -RE.md.pop('experiment_user', None) -RE.md.pop('experiment_SAF_number', None) -RE.md.pop('experiment_proposal_number', None) -RE.md.pop('experiment_project', None) -RE.md.pop('experiment_group', None) -RE.md.pop('experiment_cycle', None) -RE.md.pop('userpy_alias_directory', None) - -# RE.md["experiment_alias_directory"] = "data_security_test" #NOTE TO SELF: Don't forget to delete this \ No newline at end of file diff --git a/startup/02-tiled-writer.py b/startup/02-tiled-writer.py new file mode 100644 index 0000000..6d056fd --- /dev/null +++ b/startup/02-tiled-writer.py @@ -0,0 +1,105 @@ +import numpy +import os + +from bluesky_tiled_plugins import TiledWriter +from bluesky.callbacks.buffer import BufferingWrapper +from tiled.client import from_uri + + +# Mapping from spec to mimetype for use in TiledWriter +# TODO: Only keep the necessary specs here +MIMETYPE_LOOKUP = { + "AD_TIFF": "multipart/related;type=image/tiff", + + "hdf5": "application/x-hdf5", + "A1_HDF5": "application/x-hdf5", # esm_patches:A1SoftFileHandler(HDF5DatasetSliceHandler) + "AD_CBF": "multipart/related;type=image/tiff", + "AD_EIGER_MX": "application/x-hdf5", + "AD_EIGER2": "application/x-hdf5", + "AD_JPEG": "multipart/related;type=image/jpeg", + "AD_HDF5": "application/x-hdf5", + "AD_HDF5_GERM": "application/x-hdf5", + "AD_HDF5_SWMR_STREAM": "application/x-hdf5", + "AD_HDF5_SWMR_SLICE": "application/x-hdf5", + "AD_HDF5_SWMR": "application/x-hdf5", + "AD_HDF5_TS": "application/x-hdf5", # area_detector_handlers.handlers:AreaDetectorHDF5TimestampHandler + "AD_HDF5_DET_TS": "application/x-hdf5", # csx_transforms.AreaDetectorHDF5NDArrayTimeStampHandler + "APB": "application/x-pizzabox-binary", # columns: timestamp, i0, it, ir, iff, aux1, aux2, aux3, aux4. iss_patches:APBBinFileHandler + "APB_TRIGGER": "application/x-pizzabox-binary", # columns: timestamp, transition, iss_patches:APBTriggerFileHandler + "DEX_HDF5": "application/x-hdf5", + "EIGER2_STREAM": "application/x-hdf5", + "MERLIN_FLY_STREAM_V2": "application/x-hdf5", + "MERLIN_HDF5_BULK": "application/x-hdf5", + "PANDA": "application/x-hdf5", + "PIL100k_HDF5": "application/x-hdf5", # iss_patches:ISSPilatusHDF5Handler + "PILATUS_HDF5": "application/x-hdf5", + "ROI_HDF5_FLY": "application/x-hdf5", + "ROI_HDF51_FLY": "application/x-hdf5", + "SIS_HDF51_FLY_STREAM_V1": "application/x-hdf5", + "TPX_HDF5": "application/x-hdf5", + "NPY_SEQ": "multipart/related;type=application/x-npy", + "SIS_HDF51": "application/x-hdf5", + "SPECS_HDF5_SINGLE_DATAFRAME": "application/x-hdf5", # IOS + "XIA_XMAP_HDF5": "application/x-hdf5;type=xia-xmap", + "XSP3": "application/x-hdf5", # iss_patches:ISSXspress3HDF5Handler, area_detector_handlers.handlers:Xspress3HDF5Handler + "XSP3_BULK": "application/x-hdf5", + "XSP3_FLY": "application/x-hdf5", + "XSP3_STEP": "application/x-hdf5", # databroker.assets.handlers:Xspress3HDF5Handler, area_detector_handlers.handlers:Xspress3HDF5Handler + "XSP3X": "application/x-hdf5", + } + + +# Define document-specific patches to be applied before sending them to TiledWriter +def patch_resource(doc): + + kwargs = doc.get("resource_kwargs", {}) + + # Fix the resource path + root = doc.get("root", "") + if not doc["resource_path"].startswith(root): + doc["resource_path"] = os.path.join(root, doc["resource_path"]) + doc["root"] = "" + doc["resource_path"] = doc["resource_path"].replace("/nsls2/data3/cms", "/nsls2/data/cms") + + if doc.get("spec") in ["AD_TIFF"]: + kwargs["template"] = "/" + kwargs["template"].lstrip("/") # Ensure leading slash + kwargs["join_method"] = "stack" + + return doc + + +def patch_descriptor(doc): + if 'pilatus800_image' in doc['data_keys']: + desc = doc['data_keys']['pilatus800_image'] + desc.setdefault("dtype_str", " Date: Fri, 6 Mar 2026 10:32:43 -0500 Subject: [PATCH 20/23] Update from beamline as of Mar 6 --- startup/.cms_config | 60 +++++++++- startup/00-startup.py | 14 ++- startup/20-area-detectors.py | 2 +- startup/42-diodebox.py | 53 +++++++++ startup/43-endstation-ioLogik.py | 187 ++++++++++++++++++++++++++++++- startup/95-sample-custom.py | 4 +- startup/beamstop_config.cfg | 72 ++++++++++++ xi-cam.yml | 134 ++++++++++++++++++++++ 8 files changed, 519 insertions(+), 7 deletions(-) create mode 100644 xi-cam.yml diff --git a/startup/.cms_config b/startup/.cms_config index 1355e40..5334be6 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1433,4 +1433,62 @@ 1431,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Thu Dec 18 10:36:58 2025 1432,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Thu Dec 18 14:44:20 2025 1433,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Thu Dec 18 23:06:06 2025 -1434,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283762999999999,Thu Dec 18 23:06:51 2025 +1434,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283763,Thu Dec 18 23:06:51 2025 +1435,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Tue Jan 20 10:16:33 2026 +1436,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Tue Jan 20 10:31:05 2026 +1437,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-14.983716,Tue Jan 20 10:34:51 2026 +1438,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Tue Jan 20 10:35:46 2026 +1439,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202643,Tue Jan 20 10:39:40 2026 +1440,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Wed Jan 21 13:21:36 2026 +1441,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Thu Jan 22 09:48:53 2026 +1442,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Thu Jan 22 16:13:42 2026 +1443,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Thu Jan 22 16:22:23 2026 +1444,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.283415,Fri Jan 23 09:35:32 2026 +1445,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Fri Jan 23 09:47:23 2026 +1446,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Fri Jan 23 09:47:42 2026 +1447,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Fri Jan 23 09:49:59 2026 +1448,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Fri Jan 23 15:06:08 2026 +1449,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Fri Jan 23 15:07:38 2026 +1450,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.202255,Thu Jan 29 09:54:10 2026 +1451,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Thu Jan 29 13:14:34 2026 +1452,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Thu Jan 29 17:46:51 2026 +1453,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Sat Jan 31 13:58:17 2026 +1454,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","[0.0, -100.7999, -91.3003, 88.2]","[0.0, -104.9, 0.0, 90.0]","[-96.0, -102.7, -90.8, 88.2]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Tue Feb 3 13:53:21 2026 +1455,45.1,62.8,5.0,4.0,"[-99.5, -197.5, -122.50023125, -1.35]","(0, -100.6998875, -90.99978125, 90.19999999999999)","[0.0, -104.9, 0.0, 90.0]","(-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502632,Tue Feb 3 15:12:58 2026 +1456,45.1,62.8,5.0,4.0,"(-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227)","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.50263,Tue Feb 3 15:24:32 2026 +1457,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Mon Feb 9 10:54:37 2026 +1458,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Thu Feb 12 09:27:40 2026 +1459,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Thu Feb 12 12:30:35 2026 +1460,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.500858,Thu Feb 12 12:32:31 2026 +1461,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Fri Feb 13 18:29:42 2026 +1462,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Sat Feb 14 11:50:36 2026 +1463,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.60079,Sat Feb 14 11:51:19 2026 +1464,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Mon Feb 16 10:04:21 2026 +1465,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Thu Feb 19 10:31:30 2026 +1466,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -90.99978125, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Sat Feb 21 10:11:38 2026 +1467,45.1,62.8,5.0,4.0,"[-95.6643375, -197.00001874999998, -122.99981249999999, -1.3500000000000227]","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -92, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.602268,Sat Feb 21 10:30:00 2026 +1468,45.1,62.8,5.0,4.0,"(-95.6643375, -198.50020625, -123.999625, -1.3500000000000227)","[0.0, -100.6998875, -90.99978125, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-91.00026249999999, -100.6998875, -92, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.602268,Tue Feb 24 17:55:54 2026 +1469,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","(0, -102.69974062499999, -92.59966874999999, 90.19999999999999)","[0.0, -104.9, 0.0, 90.0]","(-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.602268,Tue Feb 24 18:03:01 2026 +1470,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.602268,Wed Feb 25 12:12:14 2026 +1471,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Wed Feb 25 12:12:47 2026 +1472,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.383805,Wed Feb 25 12:14:38 2026 +1473,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Wed Feb 25 12:16:12 2026 +1474,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Wed Feb 25 13:43:59 2026 +1475,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.38345,Wed Feb 25 13:45:58 2026 +1476,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Wed Feb 25 22:38:00 2026 +1477,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Thu Feb 26 10:41:46 2026 +1478,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.383441,Thu Feb 26 10:46:53 2026 +1479,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.383439999999998,Thu Feb 26 10:46:58 2026 +1480,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Mon Mar 2 09:21:17 2026 +1481,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Mon Mar 2 09:21:38 2026 +1482,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Tue Mar 3 15:12:15 2026 +1483,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.800850999999998,Tue Mar 3 15:13:40 2026 +1484,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Tue Mar 3 20:59:44 2026 +1485,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.502254999999998,Wed Mar 4 11:03:58 2026 +1486,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-17.900453,Wed Mar 4 16:01:18 2026 +1487,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.507213999999998,Wed Mar 4 16:03:56 2026 +1488,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Wed Mar 4 16:58:14 2026 +1489,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.507213999999998,Wed Mar 4 20:55:30 2026 +1490,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Thu Mar 5 12:12:54 2026 +1491,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.483694,Thu Mar 5 12:13:53 2026 +1492,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.507213999999998,Thu Mar 5 21:16:33 2026 diff --git a/startup/00-startup.py b/startup/00-startup.py index 6a41f55..a82b3bb 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -80,4 +80,16 @@ def proposal_path(): return f"/nsls2/data/cms/proposals/{RE.md['cycle']}/{RE.md['data_session']}/" def assets_path(): - return proposal_path() + "assets/" \ No newline at end of file + return proposal_path() + "assets/" + +#swap users +from nslsii.sync_experiment import switch_redis_proposal +def proposal_swap(proposal_id, username=None): + if username == None: + username=RE.md['username'] + RE.md = switch_redis_proposal(proposal_id, beamline='cms', username=username) + # Ensure tiled_access_tags is always a list + # if tags := RE.md.get('tiled_access_tags'): + # if isinstance(tags, str): + # tags = [tags] + # RE.md['tiled_access_tags'] = tags \ No newline at end of file diff --git a/startup/20-area-detectors.py b/startup/20-area-detectors.py index 2627bd8..96d1c2c 100644 --- a/startup/20-area-detectors.py +++ b/startup/20-area-detectors.py @@ -49,7 +49,7 @@ Pilatus800_2_on = False # Pilatus800_on = False -Pilatus800_2_on = True +# Pilatus800_2_on = True # Pilatus800_2_on = False # Pilatus2M_on = False diff --git a/startup/42-diodebox.py b/startup/42-diodebox.py index 9d61f8f..aec8a8f 100644 --- a/startup/42-diodebox.py +++ b/startup/42-diodebox.py @@ -17,3 +17,56 @@ # caput('XF:11BMB-CT{DIODE-Local:3}OutCh00:Data-SP.DESC', 'Diode AO 1') # Diode_AO.get() # Diode_AO.set() + + + +class DiodeBox(Device): + """ + DiodeBox has 4 channels + The mode, unit and limits need to be configured in the EPICS. + 2026-02-18 + """ + # Setpoint, using set() or put() + A0_SP = Cpt(EpicsSignal, 'OutCh00:Data-SP') + A1_SP = Cpt(EpicsSignal, 'OutCh01:Data-SP') + A2_SP = Cpt(EpicsSignal, 'OutCh02:Data-SP') + A3_SP = Cpt(EpicsSignal, 'OutCh03:Data-SP') + + # Readback, using get() + A0_RB = Cpt(EpicsSignal, 'InCh00:Data-RB') + A1_RB = Cpt(EpicsSignal, 'InCh01:Data-RB') + A2_RB = Cpt(EpicsSignal, 'InCh02:Data-RB') + A3_RB = Cpt(EpicsSignal, 'InCh03:Data-RB') + + + + def set(self, channel=0, setpoint=0): + '''set a target value to a target channel''' + Ch_SP = getattr(self, f'A{channel}_SP') + Ch_SP.set(setpoint) + print(f'{self.name}: Channel {channel} is set to {setpoint}.') + + + def get(self, channel=0): + '''get a RB value for a target channel''' + Ch_RB = getattr(self, f'A{channel}_RB') + rb = Ch_RB.get() + print(f'{self.name}: Channel {channel} is {rb}.') + return rb + + + def set_and_waitRB(self, channel=0, setpoint=0): + '''set a target value to a target channel and wait for the readback''' + + Ch_SP = getattr(self, f'A{channel}_SP') + Ch_RB = getattr(self, f'A{channel}_RB') + + Ch_SP.set(setpoint) + start_time = time.time() + while abs(Ch_RB.get()-setpoint) > 0.001: + time.sleep(0.05) + print(f'Time elapse = {time.time()-start_time: .4f}s') + print(f'{self.name}: Channel {channel} is set to {setpoint}.') + + +diodebox3 = DiodeBox("XF:11BMB-CT{DIODE-Local:3}", name="Diodebox3") \ No newline at end of file diff --git a/startup/43-endstation-ioLogik.py b/startup/43-endstation-ioLogik.py index c5b6b21..74b78bc 100644 --- a/startup/43-endstation-ioLogik.py +++ b/startup/43-endstation-ioLogik.py @@ -733,6 +733,162 @@ def temperature_setpoint(self, verbosity=3): return self.ChillerSetpoint.get() + + +# class ioLogik7(Device, channel): +# # On/Off: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Cmd +# # Setpoint: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Sts + +# """An ophyd wrapper for TTL generator by Moxa E1213 + +# """ + +# channel_cmd = 'D0' + channel + '_Cmd' +# channel_sts = 'D0' + channel + '_Sts' + +# # cmd = Cpt(EpicsSignal, "DO1-Cmd") +# # sts = Cpt(EpicsSignal, "DO1-Sts") + +# def on(self): +# self.cmd.put(1) + +# def off(self): +# self.cmd.put(0) + +# def _on(self): +# return (yield from mv(self.cmd, 1)) + +# def _off(self): +# return (yield from mv(self.cmd, 0)) + + +class S4Dev(Device): + # On/Off: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Cmd + # Setpoint: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Sts + + """An ophyd wrapper for Sorrenson XG40 power supply + + """ + + S4Dev_in = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO2-Cmd", name="S4Dev_in") + S4Dev_out = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO1-Cmd", name="S4Dev_out") + + S4Dev_in_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO2-Sts", name="S4Dev_in_sts") + S4Dev_out_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO1-Sts", name="S4Dev_out_sts") + + def on(self): + self.S4Dev_in.put(1) + self.S4Dev_out.put(0) + + def off(self): + self.S4Dev_in.put(0) + self.S4Dev_out.put(1) + + def _on(self): + yield from mv(self.S4Dev_in, 1) + yield from mv(self.S4Dev_out, 0) + + def _off(self): + yield from mv(self.S4Dev_in, 0) + yield from mv(self.S4Dev_out, 1) + + def state(self, verbosity=3): + if S4Dev_out_sts.get()==1 and S4Dev_in_sts.get()==0: + if verbosity>=3: + print('S4 is OUT.') + return False + elif S4Dev_out_sts.get()==0 and S4Dev_in_sts.get()==1: + if verbosity>=3: + print('S4 is IN.') + return True + +class ICDev(Device): + # On/Off: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Cmd + # Setpoint: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Sts + + """An ophyd wrapper for Sorrenson XG40 power supply + + """ + # ICDev_in_cmd = Cpt(EpicsSignal, "DO4-Cmd") + # ICDev_in_sts = Cpt(EpicsSignal, "DO4-Sts") + + # ICDev_out_cmd = Cpt(EpicsSignal, "DO3-Cmd") + # ICDev_out_sts = Cpt(EpicsSignal, "DO3-Sts") + + + ICDev_in = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO4-Cmd", name="ICDev_in") + ICDev_out = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO3-Cmd", name="ICDev_out") + + ICDev_in_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO4-Sts", name="ICDev_in_sts") + ICDev_out_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO3-Sts", name="ICDev_out_sts") + + def on(self): + self.ICDev_in.put(1) + self.ICDev_out.put(0) + + def off(self): + self.ICDev_in.put(0) + self.ICDev_out.put(1) + + def _on(self): + yield from mv(self.ICDev_in, 1) + yield from mv(self.ICDev_out, 0) + + def _off(self): + yield from mv(self.ICDev_in, 0) + yield from mv(self.ICDev_out, 1) + + def state(self, verbosity=3): + if ICDev_out_sts.get()==1 and ICDev_in_sts.get()==0: + if verbosity>=3: + print('IC is OUT.') + return False + elif ICDev_out_sts.get()==0 and ICDev_in_sts.get()==1: + if verbosity>=3: + print('IC is IN.') + return True + +class BPMDev(Device): + # On/Off: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Cmd + # Setpoint: XF:11BM-ES{DM1-IOL1:E1213}:DO1-Sts + + """An ophyd wrapper for Sorrenson XG40 power supply + + """ + + BPMDev_in = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO6-Cmd", name="BPMDev_in") + BPMDev_out = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO5-Cmd", name="BPMDev_out") + + BPMDev_in_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO6-Sts", name="BPMDev_in_sts") + BPMDev_out_sts = EpicsSignal("XF:11BM-ES{DM1-IOL1:E1213}:DO5-Sts", name="BPMDev_out_sts") + + def on(self): + self.BPMDev_in.put(1) + self.BPMDev_out.put(0) + + def off(self): + self.BPMDev_in.put(0) + self.BPMDev_out.put(1) + + def _on(self): + yield from mv(self.BPMDev_in, 1) + yield from mv(self.BPMDev_out, 0) + + def _off(self): + yield from mv(self.BPMDev_in, 0) + yield from mv(self.BPMDev_out, 1) + + def state(self, verbosity=3): + if BPMDev_out_sts.get()==1 and BPMDev_in_sts.get()==0: + if verbosity>=3: + print('BPM is OUT.') + return False + elif BPMDev_out_sts.get()==0 and BPMDev_in_sts.get()==1: + if verbosity>=3: + print('BPM is IN.') + return True + + class Potentiostats(Device): """An ophyd wrapper for Biologic Potentiostats At CMS, there is only readout and trigger @@ -781,7 +937,27 @@ def read_current(self, channel=2): else: return vol - + +# PV list of Moxa ioLogik:: AO, Analog Output +# class PDUpv(object): +# def __init__(self, ii): +# self.name = "AO_Chan{}".format(ii) +# # self.sp = 'XF:11BM-ES{{Ecat:AO{}}}'.format(ii) +# #XF:11BM1-CT{PDU:2}Sw:1-Sel +# #XF:11BM1-CT{PDU:2}Sw:1-Sts + +# self.sp = "XF:11BM1-CT{{PDU:2}}Sw:{}".format(ii) +# # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) +# self.sts = self.sp +# self.PV = self.sp +# self.signal = EpicsSignal(self.PV) +# # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) + +# pdu = [None] +# for ii in range(1, 8): +# pdu.append(pdu(ii)) + + @@ -794,4 +970,11 @@ def read_current(self, channel=2): MFC = MassFlowControl() # MFC_dev = MassFlowControl_YF("XF:11BMB-ES", name='MFC') -BLP = Potentiostats(name="BLP") #BioLogic Potentiostats \ No newline at end of file +BLP = Potentiostats(name="BLP") #BioLogic Potentiostats + +if beamline_stage != 'default': + s4d = S4Dev(name="s4d") + +ic = ICDev(name="ic") +bpm = BPMDev(name="bpm") + diff --git a/startup/95-sample-custom.py b/startup/95-sample-custom.py index 9fabda1..219104a 100644 --- a/startup/95-sample-custom.py +++ b/startup/95-sample-custom.py @@ -2395,8 +2395,8 @@ def __init__(self, name="CapillaryHolder", base=None, **kwargs): # slot 15; smx = -61.94 # Set the x and y origin to be the center of slot 8 - self.ysetOrigin(-4.2) - self.xsetOrigin(-16) #as of 09/19/2025 --Siyu + self.ysetOrigin(-4.1) + self.xsetOrigin(-16.81) #as of 01/22/2026 --Siyu self.mark("right edge", x=+54.4) diff --git a/startup/beamstop_config.cfg b/startup/beamstop_config.cfg index 124f98e..ec1c0d2 100755 --- a/startup/beamstop_config.cfg +++ b/startup/beamstop_config.cfg @@ -119,6 +119,36 @@ "bsy": -12.798437999999999, "bsphi": -61.013161, "timestamp": "2025-12-15 10:15:44" + }, + { + "bsx": -16.202255, + "bsy": -12.798437999999999, + "bsphi": -61.013161, + "timestamp": "2026-01-20 10:39:40" + }, + { + "bsx": -16.502254999999998, + "bsy": -12.048168999999998, + "bsphi": -61.015694999999994, + "timestamp": "2026-01-29 13:14:34" + }, + { + "bsx": -16.502254999999998, + "bsy": -12.648169, + "bsphi": -61.015817999999996, + "timestamp": "2026-01-29 17:46:51" + }, + { + "bsx": -16.502254999999998, + "bsy": -12.648169, + "bsphi": -61.015817999999996, + "timestamp": "2026-02-21 10:30:00" + }, + { + "bsx": -16.502254999999998, + "bsy": -12.648169, + "bsphi": -61.015817999999996, + "timestamp": "2026-02-25 12:12:14" } ], "round_17": [ @@ -223,6 +253,12 @@ "bsy": -10.098904, "bsphi": 26.995889, "timestamp": "2025-11-20 18:22:36" + }, + { + "bsx": -16.507213999999998, + "bsy": -10.398603, + "bsphi": 26.994578999999998, + "timestamp": "2026-03-04 16:03:56" } ], "rod": [ @@ -333,6 +369,42 @@ "bsy": 17.003998, "bsphi": -181.016052, "timestamp": "2025-12-15 12:13:28" + }, + { + "bsx": -15.283415, + "bsy": 17.003998, + "bsphi": -181.016052, + "timestamp": "2026-01-20 10:34:51" + }, + { + "bsx": -15.083415, + "bsy": 17.004548, + "bsphi": -181.018135, + "timestamp": "2026-01-23 09:47:23" + }, + { + "bsx": -15.083414999999999, + "bsy": 17.004548, + "bsphi": -181.018135, + "timestamp": "2026-02-25 12:14:38" + }, + { + "bsx": -15.083414999999999, + "bsy": 17.004548, + "bsphi": -181.018135, + "timestamp": "2026-02-25 13:45:58" + }, + { + "bsx": -15.083414999999999, + "bsy": 17.004548, + "bsphi": -181.018135, + "timestamp": "2026-02-26 10:46:58" + }, + { + "bsx": -15.483693999999998, + "bsy": 17.004988, + "bsphi": -181.020835, + "timestamp": "2026-03-05 12:13:53" } ], "round_3m": [ diff --git a/xi-cam.yml b/xi-cam.yml new file mode 100644 index 0000000..927c921 --- /dev/null +++ b/xi-cam.yml @@ -0,0 +1,134 @@ +name: xi-cam-py3-2025 +channels: + - https://pergamon.cs.nsls2.local:8443/conda/conda-forge + - https://pergamon.cs.nsls2.local:8443/conda/nsls2-tag + - https://pergamon.cs.nsls2.local:8443/conda/anaconda + - conda-forge +dependencies: + - anaconda-client=1.6.2=py36_0 + - boltons=16.3.1=py36_0 + - cffi=1.9.1=py36_0 + - clyent=1.2.2=py36_0 + - cryptography=1.7.1=py36_0 + - cython=0.25.2=py36_0 + - doct=1.0.3=py36_0 + - filestore=0.8.0=py36_0 + - fontconfig=2.12.1=4 + - freetype=2.7=0 + - h5py=2.7.0=np112py36_0 + - hdf5=1.8.17=10 + - humanize=0.5.1=py36_0 + - icu=58.1=1 + - idna=2.2=py36_0 + - jinja2=2.9.6=py36_0 + - jpeg=9b=0 + - jsonschema=2.6.0=py36_0 + - libffi=3.2.1=1 + - libgfortran=3.0.0=1 + - libiconv=1.14=4 + - libpng=1.6.28=0 + - libtiff=4.0.6=7 + - libxml2=2.9.4=4 + - libxslt=1.1.29=3 + - markupsafe=0.23=py36_2 + - metadatastore=0.7.0=py36_0 + - mkl=2017.0.1=0 + - ncurses=5.9=10 + - numpy=1.12.1=py36_0 + - olefile=0.44=py36_0 + - openssl=1.0.2k=1 + - pillow=4.0.0=py36_2 + - pims=0.4=py36_0 + - pip=9.0.1=py36_1 + - prettytable=0.7.2=py36_1 + - pyasn1=0.2.3=py36_0 + - pycparser=2.17=py36_0 + - pymongo=3.3.0=py36_0 + - pyside=1.2.4=py36_5 + - python=3.6.1=2 + - python-dateutil=2.6.0=py36_0 + - pytz=2017.2=py36_0 + - pyyaml=3.12=py36_0 + - qt=4.8.6=3 + - readline=6.2=10 + - requests=2.13.0=py36_0 + - scipy=0.19.0=np112py36_0 + - setuptools=27.2.0=py36_0 + - six=1.10.0=py36_0 + - slicerator=0.9.8=py36_0 + - sqlite=3.13.0=0 + - tifffile=0.11.1=np112py36_0 + - tk=8.5.18=0 + - wheel=0.29.0=py36_0 + - xz=5.2.2=1 + - yaml=0.1.6=0 + - zlib=1.2.8=3 + - pip: + - appdirs==1.4.3 + - argh==0.26.2 + - asn1crypto==0.22.0 + - astropy==1.3.3 + - attrs==17.1.0 + - click==6.7 + - cloudpickle==0.2.2 + - cycler==0.10.0 + - cytoolz==0.8.2 + - dask==0.14.3 + - databroker==0.3.0+198.g78a466a + - deap==1.0.2 + - decorator==4.0.11 + - distributed==1.16.3 + - emcee==2.2.1 + - fabio==0.4.0 + - future==0.16.0 + - futures==3.1.1 + - heapdict==1.0.0 + - imageio==2.1.2 + - ipykernel==4.6.1 + - ipython==6.0.0 + - ipython-genutils==0.2.0 + - jedi==0.10.2 + - jupyter-client==5.0.1 + - jupyter-core==4.3.0 + - matplotlib==2.0.2 + - msgpack-python==0.4.8 + - networkx==1.11 + - nexusformat==0.4.6 + - numexpr==2.7.3 + - packaging==16.8 + - pandas==0.20.1 + - paramiko==2.1.2 + - pathtools==0.1.2 + - pexpect==4.2.1 + - pickleshare==0.7.4 + - prompt-toolkit==1.0.14 + - psutil==5.2.2 + - ptyprocess==0.5.1 + - pyfai==0.13.1 + - pyfits==3.4 + - pygments==2.2.0 + - pyopengl==3.1.0 + - pyparsing==2.2.0 + - pyqtgraph==0.10.0 + - pysftp==0.2.9 + - pywavelets==0.5.2 + - pyzmq==16.0.2 + - qdarkstyle==2.3.1 + - qtconsole==4.3.0 + - scikit-image==0.13.0 + - silx==0.15.0 + - simplegeneric==0.8.1 + - sortedcollections==0.5.3 + - sortedcontainers==1.5.7 + - suitcase==0+untagged.210.gf01490a + - tblib==1.3.2 + - toolz==0.8.2 + - tornado==4.5.1 + - traitlets==4.3.2 + - tzlocal==1.4 + - vispy==0.4.0 + - watchdog==0.8.3 + - wcwidth==0.1.7 + - xicam==1.2.21 + - zict==0.1.2 +prefix: /opt/conda_envs/xi-cam-py3-2025 From c8ca843cdeb3e3d7eacc1464e5885152180c8ace Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 6 Mar 2026 14:12:14 -0500 Subject: [PATCH 21/23] PDU update --- startup/10-motors.py | 4 +++ startup/43-endstation-ioLogik.py | 49 ++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/startup/10-motors.py b/startup/10-motors.py index 90896eb..ba7fc8d 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -30,6 +30,10 @@ class MotorCenterAndGap(Device): xg = Cpt(EpicsMotor, "-Ax:XG}Mtr") yg = Cpt(EpicsMotor, "-Ax:YG}Mtr") + ## TODO Adding beamstop style configuration control functions + ## Siyu Wu 2026 03 06 + + class Blades(Device): "Actual T/B/O/I and virtual center/gap using Epics Motor records" diff --git a/startup/43-endstation-ioLogik.py b/startup/43-endstation-ioLogik.py index 74b78bc..134a86b 100644 --- a/startup/43-endstation-ioLogik.py +++ b/startup/43-endstation-ioLogik.py @@ -939,26 +939,45 @@ def read_current(self, channel=2): # PV list of Moxa ioLogik:: AO, Analog Output -# class PDUpv(object): -# def __init__(self, ii): -# self.name = "AO_Chan{}".format(ii) -# # self.sp = 'XF:11BM-ES{{Ecat:AO{}}}'.format(ii) -# #XF:11BM1-CT{PDU:2}Sw:1-Sel -# #XF:11BM1-CT{PDU:2}Sw:1-Sts +class PowerDUnit(Device): + # def __init__(self, ii): + # self.name = "AO_Chan{}".format(ii) + # self.sp = 'XF:11BM-ES{{Ecat:AO{}}}'.format(ii) + #XF:11BM1-CT{PDU:2}Sw:1-Sel + #XF:11BM1-CT{PDU:2}Sw:1-Sts + + # self.sp = "XF:11BM1-CT{{PDU:2}}Sw:{}-Sel".format(ii) + # # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) + # self.sts = "XF:11BM1-CT{{PDU:2}}Sw:{}-Sts".format(ii) + # self.PV = self.sp + # self.signal = EpicsSignal(self.PV) + # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) + cmd = Cpt(EpicsSignal, "-Sel") + status = Cpt(EpicsSignal, "Sts") + + def on(self): + self.cmd.put(1) -# self.sp = "XF:11BM1-CT{{PDU:2}}Sw:{}".format(ii) -# # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) -# self.sts = self.sp -# self.PV = self.sp -# self.signal = EpicsSignal(self.PV) -# # self.sts = 'XF:11BMB-ES{}AO:{}-RB'.format('{IO}', ii) + def off(self): + self.cmd.put(0) -# pdu = [None] -# for ii in range(1, 8): -# pdu.append(pdu(ii)) + def _on(self): + return (yield from mv(self.cmd, 1)) + def _off(self): + return (yield from mv(self.cmd, 0)) + +pdu = {} +prefix_pdu = "XF:11BM1-CT{PDU:2}Sw:" +# pdu1 = PowerDUnit(prefix_pdu + '1', name="pdu1") +for ii in range(9): + # if ii ==0: + # pdu.append(0) + # else: + if ii>0: + pdu[ii] = PowerDUnit(prefix_pdu + str(ii), name="pdu{}".format(ii)) prefix = "XF:11BMB-ES{PS:1}" From f7e72f44c04acdca58f2a6f341a490de9562cedc Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 6 Mar 2026 15:01:32 -0500 Subject: [PATCH 22/23] update slits controlling commands --- startup/10-motors.py | 233 +++++++++++++++++++++++++++++++++++--- startup/cfg/s4_config.cfg | 52 +++++++++ 2 files changed, 272 insertions(+), 13 deletions(-) create mode 100644 startup/cfg/s4_config.cfg diff --git a/startup/10-motors.py b/startup/10-motors.py index ba7fc8d..12a6be3 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -1,6 +1,9 @@ print(f'Loading {__file__}') -from ophyd import EpicsMotor, Device, Component as Cpt +from ophyd import EpicsMotor, EpicsSignal, Device, Component as Cpt +import json +from pathlib import Path +from datetime import datetime # slity = EpicsMotor('XF:11BMA-OP{Slt:0-Ax:T}Mtr', name='slity') @@ -22,17 +25,211 @@ ################################################################################### +########## Universal Configuration Management for Multi-Motor Devices ########## + +class Configurable: + """ + Mixin class that adds configuration control capabilities to multi-motor devices. + Provides position saving/loading, relative/absolute movement, and show commands. + + Methods: + get(name): Load a saved position without moving + goto(name): Load and move to a saved position + load_position(name): Load a saved position configuration by name + save_position(name): Save current position with a given name + show_position(): Display all motor positions + movr(motor_name, delta): Move motor relative to current position + mov(motor_name, value): Move motor to absolute position + clear_config(config_file): Keep only latest entry for each position + + Example usage: + # After initializing s4 (MotorCenterAndGap instance): + s4.show_position() # Display current positions + s4.save_position('open') # Save as 'open' + s4.mov('xc', 5.0) # Move xc center to 5.0 + s4.movr('yg', 0.5) # Move yg gap by +0.5 + + # Load position data without moving: + pos_data = s4.get('open') # Load 'open' position + + # Load and move to saved position: + s4.goto('open') # Move s4 to 'open' position + """ + + _config_motors = [] # Override in device to specify which motors to track + _config_file = None # Override to specify config file path + + def get(self, name): + """ + Load a saved position configuration without moving motors. + + Args: + name: Position name to load + + Returns: + Dict with position data + """ + return self.load_position(name) + + def goto(self, name): + """ + Load a saved position and move all motors there. + + Args: + name: Position name to load and move to + + Usage: + s4.goto('open') # Load and move s4 to 'open' position + """ + positions_dict = self._load_config() + entries = positions_dict.get(name) + if entries and isinstance(entries, list): + target_positions = entries[-1] # Get latest entry + print(f"Moving '{self.name}' to position '{name}'...") + for motor_name in self._config_motors: + if (hasattr(self, motor_name) and + motor_name in target_positions): + motor = getattr(self, motor_name) + target_value = target_positions[motor_name] + motor.move(target_value) + self.show_position() + else: + print(f"No saved position found for '{name}'.") + + def _load_config(self): + """Load configuration from JSON file.""" + if self._config_file is None: + self._config_file = Path(f'{self.name}_config.cfg') + + config_file = Path(self._config_file) if not isinstance(self._config_file, Path) else self._config_file + if config_file.exists(): + with open(config_file, 'r') as f: + return json.load(f) + return {} + + def _save_config(self): + """Save configuration to JSON file.""" + if self._config_file is None: + self._config_file = Path(f'{self.name}_config.cfg') + + config_file = Path(self._config_file) if not isinstance(self._config_file, Path) else self._config_file + with open(config_file, 'w') as f: + json.dump(self._positions, f, indent=2) + + def _sync(self): + """Synchronize internal position cache with actual motor positions.""" + for motor_name in self._config_motors: + if hasattr(self, motor_name): + motor = getattr(self, motor_name) + setattr(self, f'_{motor_name}', motor.position) + + def _get_positions_dict(self): + """Get current positions as a dictionary.""" + positions = {} + for motor_name in self._config_motors: + if hasattr(self, motor_name): + motor = getattr(self, motor_name) + positions[motor_name] = motor.position + return positions + + def load_position(self, name): + """Load a saved position configuration by name.""" + positions = self._load_config() + entries = positions.get(name) + if entries: + latest = entries[-1] + print(f"Loaded position '{name}' from {latest.get('timestamp', 'unknown')}") + for motor_name in self._config_motors: + if motor_name in latest and hasattr(self, motor_name): + motor = getattr(self, motor_name) + setattr(self, f'_{motor_name}', latest[motor_name]) + return latest + else: + print(f"No saved position found for '{name}'.") + return None + + def save_position(self, name): + """Save current position with a given name.""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + entry = self._get_positions_dict() + entry['timestamp'] = timestamp + + positions = self._load_config() + positions.setdefault(name, []).append(entry) + self._positions = positions + self._save_config() + print(f"Saved position '{name}' at {timestamp}.") + + def show_position(self): + """Display current motor positions.""" + self._sync() + print(f"Device '{self.name}' positions:") + for motor_name in self._config_motors: + if hasattr(self, motor_name): + motor = getattr(self, motor_name) + print(f" {motor_name} = {motor.position}") + + def relative_move(self, motor_name, delta): + """Move a motor relative to current position.""" + if motor_name not in self._config_motors or not hasattr(self, motor_name): + print(f"Invalid motor name: {motor_name}") + return + + motor = getattr(self, motor_name) + new_pos = motor.position + delta + motor.move(new_pos) + print(f"{motor_name} moved relatively by {delta} -> {new_pos}") + + def movr(self, motor_name, delta): + """Move a motor relative to current position (short alias).""" + return self.relative_move(motor_name, delta) + + def absolute_move(self, motor_name, value): + """Move a motor to an absolute position.""" + if motor_name not in self._config_motors or not hasattr(self, motor_name): + print(f"Invalid motor name: {motor_name}") + return + + motor = getattr(self, motor_name) + motor.move(value) + print(f"{motor_name} moved to {value}") + + def mov(self, motor_name, value): + """Move a motor to an absolute position (short alias).""" + return self.absolute_move(motor_name, value) + + @staticmethod + def clear_config(config_file): + """Clear configuration, keeping only the latest entry for each position name.""" + path = Path(config_file) + if path.exists(): + with open(path, 'r') as f: + data = json.load(f) + for key in data: + if isinstance(data[key], list) and len(data[key]) > 0: + data[key] = [data[key][-1]] + with open(path, 'w') as f: + json.dump(data, f, indent=2) + print(f"Configuration file '{config_file}' cleaned: only latest entries retained.") + else: + print(f"Config file '{config_file}' does not exist.") + + ########## motor classes ########## -class MotorCenterAndGap(Device): +class MotorCenterAndGap(Device, Configurable): "Center and gap using Epics Motor records" xc = Cpt(EpicsMotor, "-Ax:XC}Mtr") yc = Cpt(EpicsMotor, "-Ax:YC}Mtr") xg = Cpt(EpicsMotor, "-Ax:XG}Mtr") yg = Cpt(EpicsMotor, "-Ax:YG}Mtr") - - ## TODO Adding beamstop style configuration control functions - ## Siyu Wu 2026 03 06 + _config_motors = ['xc', 'yc', 'xg', 'yg'] + + def __init__(self, *args, config_file=None, **kwargs): + super().__init__(*args, **kwargs) + if config_file is not None: + self._config_file = Path(config_file) + self._positions = self._load_config() class Blades(Device): @@ -92,11 +289,24 @@ class Filter(Device): ########## Endstation slits ######### ## jj slits -- usage: s*.xc, s*.xg, s*.yc, s*.yg -s1 = MotorCenterAndGap("XF:11BMB-OP{Slt:1", name="s1") -s2 = MotorCenterAndGap("XF:11BMB-OP{Slt:2", name="s2") -s3 = MotorCenterAndGap("XF:11BMB-OP{Slt:3", name="s3") -s4 = MotorCenterAndGap("XF:11BMB-OP{Slt:4", name="s4") -s5 = MotorCenterAndGap("XF:11BMB-OP{Slt:5", name="s5") + +# Option 1: Default behavior (config file = {device_name}_config.cfg in current directory) +# s1 = MotorCenterAndGap("XF:11BMB-OP{Slt:1", name="s1") + +# Option 2: Custom config file for each slit (recommended for separate tracking) +s1 = MotorCenterAndGap("XF:11BMB-OP{Slt:1", name="s1", config_file='cfg/s1_config.cfg') +s2 = MotorCenterAndGap("XF:11BMB-OP{Slt:2", name="s2", config_file='cfg/s2_config.cfg') +s3 = MotorCenterAndGap("XF:11BMB-OP{Slt:3", name="s3", config_file='cfg/s3_config.cfg') +s4 = MotorCenterAndGap("XF:11BMB-OP{Slt:4", name="s4", config_file='cfg/s4_config.cfg') +s5 = MotorCenterAndGap("XF:11BMB-OP{Slt:5", name="s5", config_file='cfg/s5_config.cfg') + +# Option 3: Share one config file for all slits (if you prefer centralized tracking) +# slits_config_file = 'cfg/slits_config.cfg' +# s1 = MotorCenterAndGap("XF:11BMB-OP{Slt:1", name="s1", config_file=slits_config_file) +# s2 = MotorCenterAndGap("XF:11BMB-OP{Slt:2", name="s2", config_file=slits_config_file) +# s3 = MotorCenterAndGap("XF:11BMB-OP{Slt:3", name="s3", config_file=slits_config_file) +# s4 = MotorCenterAndGap("XF:11BMB-OP{Slt:4", name="s4", config_file=slits_config_file) +# s5 = MotorCenterAndGap("XF:11BMB-OP{Slt:5", name="s5", config_file=slits_config_file) # attenuators filters = { @@ -292,10 +502,7 @@ def wGONIO(): #Add by Siyu 2025/04/14 -import json import time -from pathlib import Path -from datetime import datetime class Beamstop: def __init__(self, name, config_file='beamstop_config.cfg'): diff --git a/startup/cfg/s4_config.cfg b/startup/cfg/s4_config.cfg new file mode 100644 index 0000000..a8c7ce9 --- /dev/null +++ b/startup/cfg/s4_config.cfg @@ -0,0 +1,52 @@ +{ + "trans_inair": [ + { + "xc": -1.419993625, + "yc": -1.0499925, + "xg": 0.19999749999999894, + "yg": 0.19999949999999966, + "timestamp": "2026-03-06 14:42:05" + } + ], + "GI_inair": [ + { + "xc": -1.419993625, + "yc": -1.0499925, + "xg": 0.19999749999999894, + "yg": 0.05001249999999979, + "timestamp": "2026-03-06 14:43:28" + } + ], + "trans_vacuum": [ + { + "xc": -1.4800011249999998, + "yc": -1.37000075, + "xg": 0.19999749999999894, + "yg": 0.19999949999999966, + "timestamp": "2026-03-06 14:47:28" + } + ], + "GI_vacuum": [ + { + "xc": -1.4800011249999998, + "yc": -1.37000075, + "xg": 0.19999749999999894, + "yg": 0.05001249999999979, + "timestamp": "2026-03-06 14:48:07" + }, + { + "xc": -1.4800011249999998, + "yc": -1.3199945, + "xg": 0.19999749999999894, + "yg": 0.05001249999999979, + "timestamp": "2026-03-06 14:52:51" + }, + { + "xc": -1.4800011249999998, + "yc": -1.37000075, + "xg": 0.19999749999999894, + "yg": 0.05001249999999979, + "timestamp": "2026-03-06 14:52:59" + } + ] +} \ No newline at end of file From c02cc192da0f419a69b47ca44239daef87e6d54c Mon Sep 17 00:00:00 2001 From: CMS Operator Date: Fri, 6 Mar 2026 18:28:24 -0500 Subject: [PATCH 23/23] slits now configurable --- startup/.cms_config | 7 +++++ startup/00-startup.py | 2 +- startup/02-tiled-writer.py | 4 +-- startup/10-motors.py | 62 ++++++++++++++++++++++---------------- startup/96-automation.py | 2 +- 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/startup/.cms_config b/startup/.cms_config index 5334be6..4d7078e 100644 --- a/startup/.cms_config +++ b/startup/.cms_config @@ -1492,3 +1492,10 @@ 1490,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.083415,Thu Mar 5 12:12:54 2026 1491,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-15.483694,Thu Mar 5 12:13:53 2026 1492,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","[0.0, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[0.0, -104.9, 0.0, 90.0]","[-93.49978125, -102.69974062499999, -92.59966874999999, 90.19999999999999]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.507213999999998,Thu Mar 5 21:16:33 2026 +1493,45.1,62.8,5.0,4.0,"[-95.6643375, -198.0, -123.999625, -1.3500000000000227]","(0, -103.199803125, -95.09998125, 92.69999999999996)","[0.0, -104.9, 0.0, 90.0]","(-98.999675, -103.199803125, -95.09998125, 92.69999999999996)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503625,Fri Mar 6 16:58:27 2026 +1494,45.1,62.8,5.0,4.0,"(-91.66463125, -198.00014374999998, -131.99983125, 3.1499999999999773)","[0.0, -103.199803125, -95.09998125, 92.69999999999996]","[0.0, -104.9, 0.0, 90.0]","[-98.999675, -103.199803125, -95.09998125, 92.69999999999996]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503619999999998,Fri Mar 6 17:08:45 2026 +1495,45.1,62.8,5.0,4.0,"(-91.66463125, -197.50008125, -130.000375, 3.1499999999999773)","[0.0, -103.199803125, -95.09998125, 92.69999999999996]","[0.0, -104.9, 0.0, 90.0]","[-98.999675, -103.199803125, -95.09998125, 92.69999999999996]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503624,Fri Mar 6 17:19:07 2026 +1496,45.1,62.8,5.0,4.0,"[-91.66463125, -197.50008125, -130.000375, 3.1499999999999773]","[0.0, -103.199803125, -95.09998125, 92.69999999999996]","[0.0, -104.9, 0.0, 90.0]","[-98.999675, -103.199803125, -95.09998125, 92.69999999999996]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503621,Fri Mar 6 17:35:01 2026 +1497,45.1,62.8,5.0,4.0,"[-91.66463125, -197.50008125, -130.000375, 3.1499999999999773]","(0, -99.19969999999999, -95.09998125, 93.19999999999996)","[0.0, -104.9, 0.0, 90.0]","(-86.9997625, -99.19969999999999, -95.09998125, 93.19999999999996)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503621,Fri Mar 6 17:43:33 2026 +1498,44.45,63.5,7.0,4.0,"(-89.0000125, -194.000040625, -130.000375, 1.9999999999999432)","[0, -103, -94.5, 91]","[0, -104.9, 0.0, 90]","[-98, -103, -94.5, 91]","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503618,Fri Mar 6 18:07:59 2026 +1499,44.45,63.5,7.0,4.0,"[-89.0000125, -194.000040625, -130.000375, 1.9999999999999432]","(0, -99.9998, -94.49990625, 92.49999999999997)","[0.0, -104.9, 0.0, 90.0]","(-84.00018125, -99.9998, -94.49990625, 92.49999999999997)","[51.5, -1.87]","[-30.0, -2.37]",,-1.7,-16.503615,Fri Mar 6 18:13:42 2026 diff --git a/startup/00-startup.py b/startup/00-startup.py index a82b3bb..85b8557 100644 --- a/startup/00-startup.py +++ b/startup/00-startup.py @@ -88,7 +88,7 @@ def proposal_swap(proposal_id, username=None): if username == None: username=RE.md['username'] RE.md = switch_redis_proposal(proposal_id, beamline='cms', username=username) - # Ensure tiled_access_tags is always a list + # # Ensure tiled_access_tags is always a list # if tags := RE.md.get('tiled_access_tags'): # if isinstance(tags, str): # tags = [tags] diff --git a/startup/02-tiled-writer.py b/startup/02-tiled-writer.py index 6d056fd..7c0a828 100644 --- a/startup/02-tiled-writer.py +++ b/startup/02-tiled-writer.py @@ -101,5 +101,5 @@ def patch_descriptor(doc): tw = BufferingWrapper(tw) # Subscribe the TiledWriter -RE.md["tiled_access_tags"] = (RE.md["data_session"],) -RE.subscribe(tw) +# RE.md["tiled_access_tags"] = (RE.md["data_session"],) +# RE.subscribe(tw) diff --git a/startup/10-motors.py b/startup/10-motors.py index 12a6be3..6d3da23 100644 --- a/startup/10-motors.py +++ b/startup/10-motors.py @@ -28,32 +28,26 @@ ########## Universal Configuration Management for Multi-Motor Devices ########## class Configurable: - """ - Mixin class that adds configuration control capabilities to multi-motor devices. - Provides position saving/loading, relative/absolute movement, and show commands. - - Methods: - get(name): Load a saved position without moving - goto(name): Load and move to a saved position - load_position(name): Load a saved position configuration by name - save_position(name): Save current position with a given name - show_position(): Display all motor positions - movr(motor_name, delta): Move motor relative to current position - mov(motor_name, value): Move motor to absolute position - clear_config(config_file): Keep only latest entry for each position - - Example usage: - # After initializing s4 (MotorCenterAndGap instance): - s4.show_position() # Display current positions - s4.save_position('open') # Save as 'open' - s4.mov('xc', 5.0) # Move xc center to 5.0 - s4.movr('yg', 0.5) # Move yg gap by +0.5 - - # Load position data without moving: - pos_data = s4.get('open') # Load 'open' position - - # Load and move to saved position: - s4.goto('open') # Move s4 to 'open' position + """Mixin for multi-motor devices: save/load named positions and move motors. + + Features: + - `get(name)` loads a saved position (no motion). + - `goto(name)` loads and moves all tracked motors to the saved values. + - `save_position(name)` stores the current motor positions under `name`. + - `mov(motor, value)` and `movr(motor, delta)` are short aliases for + absolute and relative motor movement. + + Example: + # The s4 config contains: 'trans_inair', 'GI_inair', 'trans_vacuum', + # and 'GI_vacuum'. + # Example usage for s4 (a MotorCenterAndGap instance): + + s4.show_position() # print current positions + s4.goto('trans_inair') # move s4 to the 'trans_inair' set + s4.save_position('test') # save current position as 'test' + s4.mov('xc', -1.48) # absolute move for a single axis + s4.movr('yg', 0.05) # relative move for a single axis + """ _config_motors = [] # Override in device to specify which motors to track @@ -300,6 +294,22 @@ class Filter(Device): s4 = MotorCenterAndGap("XF:11BMB-OP{Slt:4", name="s4", config_file='cfg/s4_config.cfg') s5 = MotorCenterAndGap("XF:11BMB-OP{Slt:5", name="s5", config_file='cfg/s5_config.cfg') +# Practical s4 examples (useful copy-paste snippets): +# The file cfg/s4_config.cfg contains named sets: 'trans_inair', 'GI_inair', +# 'trans_vacuum', and 'GI_vacuum'. Use these names with get/goto. +# Show current positions: +# s4.show_position() +# Load saved values without moving: +# data = s4.get('trans_inair') +# print('trans_inair:', data) +# Move to a saved configuration (performs motion): +# s4.goto('trans_inair') +# Move to the latest GI vacuum config (there are multiple saved entries): +# s4.goto('GI_vacuum') +# Single-axis moves (no RE required): +# s4.mov('xc', -1.48) # absolute +# s4.movr('yg', 0.05) # relative + # Option 3: Share one config file for all slits (if you prefer centralized tracking) # slits_config_file = 'cfg/slits_config.cfg' # s1 = MotorCenterAndGap("XF:11BMB-OP{Slt:1", name="s1", config_file=slits_config_file) diff --git a/startup/96-automation.py b/startup/96-automation.py index 58fb266..8c5ee79 100644 --- a/startup/96-automation.py +++ b/startup/96-automation.py @@ -35,7 +35,7 @@ def __init__(self, name="SampleExchangeRobot", base=None, use_gs=True, **kwargs) # self.yabs(-82.0) # Good height for 'slotted approach' # self.yabs(-77.0) # Good height for 'grip' (grip-screws sitting at bottom of wells) # self.yabs(-67.0) # Good height for 'hover' (sample held above stage) - self._delta_y_hover = 5.0 + self._delta_y_hover = 7.0 self._delta_y_slot = 4.0 #'SAFE' position of gripper