diff --git a/.gitignore b/.gitignore index 205a56bc..317311bb 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,5 @@ target/ *#* *py.* runEdna_old_21Jan2020.py + +.pixi/ diff --git a/Gen_Commands.py b/Gen_Commands.py deleted file mode 100644 index 318273dd..00000000 --- a/Gen_Commands.py +++ /dev/null @@ -1,60 +0,0 @@ -import numpy as np -import epics -import logging -logger = logging.getLogger(__name__) - -#12/19 - skinner did not write this. -def gen_commands(traj,exptime): - - # The traj is a 3 x N array which contains the (x,y,z) positions of the trajectory - # The purpose of this function is generating three lists (for x, y, z, repectively). - # Each list contains a series of "WAV ... " commands to write - - size = traj.shape - - if size[0] != 3: - size = traj.transpose().shape - - commands = [ - 'WGC 0 1', - 'STP', - 'WTR 0 %d 1'% (exptime*20000,) - ] - - for j in range(3): - commands.append('WOS %d 0' % (j+1,)) - for i in range(int(np.floor(size[1]/2))): - SegLength = 2 - StartPoint = 0 - Amp = traj[j][i*2+1] - traj[j][i*2] - Offset = traj[j][i*2] - WaveLength = SegLength - SpeedUpDown = 0 - Replace = 'X' if i == 0 else '&' - commands.append("WAV %d %s LIN %d %.3f %.3f %d %d %d" % (j+1, Replace, SegLength, Amp, Offset, WaveLength, StartPoint, SpeedUpDown)) - commands.append('WSL %d %d' % (j+1,j+1)) - - oeos = epics.PV('XF:17IDC-CT:FMX{MC:21}Asyn.OEOS') - aout = epics.PV('XF:17IDC-CT:FMX{MC:21}Asyn.AOUT') - tmod = epics.PV('XF:17IDC-CT:FMX{MC:21}Asyn.TMOD') - - tmod.put('Write') - oeos.put('\n') - - for cmd in commands: -# logger.info(len(cmd),cmd) - aout.put(cmd, wait=True) - - tmod.put('Write/Read') - -def go_all(): - aout = epics.PV('XF:17IDC-CT:FMX{MC:21}Asyn.AOUT') - tmod = epics.PV('XF:17IDC-CT:FMX{MC:21}Asyn.TMOD') - - tmod.put('Write') - for n in range(1, 4): - logger.info("Arming waveform", n) - aout.put('WGO %d 0x102' % (n,), wait=True) - - tmod.put('Write/Read') - diff --git a/Gen_Traj_Square.py b/Gen_Traj_Square.py deleted file mode 100644 index 5d12526b..00000000 --- a/Gen_Traj_Square.py +++ /dev/null @@ -1,59 +0,0 @@ -import logging -logger = logging.getLogger(__name__) - -def gen_traj_square(x_start, x_end, y_start, y_end, z_start, z_end, col, row): -#12/19 skinner did not write this - # x/y/z_start/end are the definitions (in micron) of the raster area - # x_end - x_start should be positive - # step defines the step size of the raster and should be positive - import numpy as np - - # ========================================================================== - # may need a few lines of code to center the (0,0,0) of the piezo stage to - # the center of the raster area - # ========================================================================== - - x = x_end-x_start - # If the X size of the raster area is larger than 180 micron, return an error message - if np.abs(x) > 180: - logger.error('x range error') - return - - y = y_end-y_start - z = z_end-z_start - # If the Y size of the raster area is larger than 150 micron, return an error message - if np.abs(y) > 150: - logger.error('y range error') - return - - # If the Z size of the raster area is larger than 150 micron, return an error message - if np.abs(z) > 150: - logger.error('z range error') - return - - step = x / col - d = np.sqrt(y * y + z * z) - delta_y = y/(row-1) - delta_z = z/(row-1) - - x_traj_odd = np.arange(x_start, x_end, step) # x positions of the odd line - x_traj_even = np.arange(x_end, x_start, -step) # x positions of the even line - y_traj_unit = np.zeros(x_traj_odd.size) + y_start # y positions of the first line - z_traj_unit = np.zeros(x_traj_odd.size) + z_start # z positions of the first line - - x_traj = x_traj_odd # first line of the x - y_traj = y_traj_unit # first line of the y - z_traj = z_traj_unit # first line of the z - for i in range(np.int(row-1)): - if np.remainder(i, 2) == 0: # add (x,y,z) positions for the even lines - x_traj = np.append(x_traj, x_traj_even) - y_traj = np.append(y_traj, y_traj_unit + delta_y * (i+1)) - z_traj = np.append(z_traj, z_traj_unit + delta_z * (i+1)) - if np.remainder(i, 2) == 1: # add (x,y,z) positions for the odd lines - x_traj = np.append(x_traj, x_traj_odd) - y_traj = np.append(y_traj, y_traj_unit + delta_y * (i+1)) - z_traj = np.append(z_traj, z_traj_unit + delta_z * (i+1)) - - Traj = np.append([x_traj], [y_traj], 0) - Traj = np.append(Traj, [z_traj], 0) - return Traj diff --git a/beamline_support.py b/beamline_support.py index 0d8a926d..6693ddae 100644 --- a/beamline_support.py +++ b/beamline_support.py @@ -319,14 +319,8 @@ def read_db(): def init_motors(): global motor_channel_dict - md2_motors = ["omega","sampleX","sampleY","sampleZ", "finex", "finey", "finez"] - for key in list(motor_dict.keys()): - if beamline_designation == "XF:19ID" and key in md2_motors: - from mxbluesky.devices.md2 import MD2Positioner - motor_channel_dict[motor_dict[key]] = MD2Positioner(motor_dict[key],name = key) - else: - motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) + motor_channel_dict[motor_dict[key]] = EpicsMotor(motor_dict[key],name = key) def initControlPVs(): diff --git a/bin/amx_env.sh b/bin/amx_env.sh new file mode 100755 index 00000000..e573e798 --- /dev/null +++ b/bin/amx_env.sh @@ -0,0 +1,8 @@ +#!/bin/bash +export PROJDIR=/nsls2/software/mx/daq/ +export CONFIGDIR=${PROJDIR}bnlpx_config/ +export LSDCHOME=${PROJDIR}lsdc_amx + +export PATH=/usr/local/bin:/usr/bin:/bin +export PYTHONPATH=".:${CONFIGDIR}:/opt/dectris/albula/4.0/python:${LSDCHOME}" +source ${CONFIGDIR}daq_env_amx.txt diff --git a/bin/fmx_env.sh b/bin/fmx_env.sh new file mode 100755 index 00000000..b5ccd90e --- /dev/null +++ b/bin/fmx_env.sh @@ -0,0 +1,8 @@ +#!/bin/bash +export PROJDIR=/nsls2/software/mx/daq/ +export CONFIGDIR=${PROJDIR}bnlpx_config/ +export LSDCHOME=${PROJDIR}lsdc_fmx + +export PATH=/usr/local/bin:/usr/bin:/bin +export PYTHONPATH=".:${CONFIGDIR}:/opt/dectris/albula/4.0/python:${LSDCHOME}" +source ${CONFIGDIR}daq_env.txt diff --git a/bin/lsdcGui_amx b/bin/lsdcGui_amx index b39909a2..9cdaa851 100755 --- a/bin/lsdcGui_amx +++ b/bin/lsdcGui_amx @@ -5,4 +5,4 @@ export LSDCHOME=${PROJDIR}lsdc_amx export PATH=/usr/local/bin:/usr/bin:/bin export PYTHONPATH=".:${CONFIGDIR}:/opt/dectris/albula/4.0/python:${LSDCHOME}" source ${CONFIGDIR}daq_env_amx.txt -/opt/conda_envs/lsdc-gui-2023-2-latest/bin/python $LSDCHOME/lsdcGui.py& +pixi run --manifest-path $LSDCHOME/pixi.toml amx-gui diff --git a/bin/lsdcGui_fmx b/bin/lsdcGui_fmx index e028aee7..22aa1ccd 100755 --- a/bin/lsdcGui_fmx +++ b/bin/lsdcGui_fmx @@ -5,4 +5,4 @@ export LSDCHOME=${PROJDIR}lsdc_fmx export PATH=/usr/local/bin:/usr/bin:/bin export PYTHONPATH=".:${CONFIGDIR}:/opt/dectris/albula/4.0/python:${LSDCHOME}" source ${CONFIGDIR}daq_env.txt -/opt/conda_envs/lsdc-gui-2023-2-latest/bin/python $LSDCHOME/lsdcGui.py& +pixi run --manifest-path $LSDCHOME/pixi.toml fmx-gui diff --git a/bin/lsdcGui_nyx b/bin/lsdcGui_nyx deleted file mode 100755 index 55d2122d..00000000 --- a/bin/lsdcGui_nyx +++ /dev/null @@ -1,10 +0,0 @@ -export PROJDIR=/nsls2/software/mx/daq/ -export CONFIGDIR=${PROJDIR}bnlpx_config/ -export LSDCHOME=${PROJDIR}lsdc_nyx -export EPICS_CA_AUTO_ADDR_LIST=NO -export EPICS_CA_ADDR_LIST=10.67.147.255 - -export PATH=/usr/local/bin:/usr/bin:/bin -export PYTHONPATH=".:${CONFIGDIR}:/opt/dectris/albula/4.0/python:/usr/local/crys/edna-mx/mxv1/src:/usr/local/crys/edna-mx/kernel/src:${LSDCHOME}" -source ${CONFIGDIR}daq_env_nyx.txt -/opt/conda_envs/lsdc-gui-2023-2-latest/bin/python $LSDCHOME/lsdcGui.py& diff --git a/bin/lsdcRemote_amx.cmd b/bin/lsdcRemote_amx.cmd index 13f770b7..25426b66 100755 --- a/bin/lsdcRemote_amx.cmd +++ b/bin/lsdcRemote_amx.cmd @@ -9,6 +9,6 @@ export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib export LD_LIBRARY_PATH=$matlab_distrib/bin/glnx86:$matlab_distrib/toolbox export PINALIGNDIR=${PROJDIR}pinAlign/pin_align-master/ export MXPROCESSINGSCRIPTSDIR=${PROJDIR}mx-processing/ -# below not ideal as environment name also needed by daq_mainAux -conda activate lsdc-server-2023-2-latest -$LSDCHOME/daq_mainAux.py +export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt +export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt +pixi run --manifest-path $LSDCHOME/pixi.toml amx-remote diff --git a/bin/lsdcRemote_fmx.cmd b/bin/lsdcRemote_fmx.cmd index 052d8241..737141a1 100755 --- a/bin/lsdcRemote_fmx.cmd +++ b/bin/lsdcRemote_fmx.cmd @@ -9,6 +9,6 @@ export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib export LD_LIBRARY_PATH=$matlab_distrib/bin/glnx86:$matlab_distrib/toolbox export PINALIGNDIR=${PROJDIR}pinAlign/pin_align-master/ export MXPROCESSINGSCRIPTSDIR=${PROJDIR}mx-processing/ -# below not ideal as environment name also needed by daq_mainAux -conda activate lsdc-server-2023-2-latest -$LSDCHOME/daq_mainAux.py +export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt +export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt +pixi run --manifest-path $LSDCHOME/pixi.toml fmx-remote diff --git a/bin/lsdcServer_amx.cmd b/bin/lsdcServer_amx.cmd index 64e6d4ed..5524906e 100755 --- a/bin/lsdcServer_amx.cmd +++ b/bin/lsdcServer_amx.cmd @@ -9,6 +9,6 @@ export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib export LD_LIBRARY_PATH=$matlab_distrib/bin/glnx86:$matlab_distrib/toolbox export PINALIGNDIR=${PROJDIR}pinAlign/pin_align-master/ export MXPROCESSINGSCRIPTSDIR=${PROJDIR}lsdc-processing/ -# below not idea as environment name also needed by daq_main2 -conda activate lsdc-server-2023-2-latest -$LSDCHOME/lsdcServer +export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt +export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt +pixi run --manifest-path $LSDCHOME/pixi.toml amx-server diff --git a/bin/lsdcServer_fmx.cmd b/bin/lsdcServer_fmx.cmd index 59e63cfb..205b5673 100755 --- a/bin/lsdcServer_fmx.cmd +++ b/bin/lsdcServer_fmx.cmd @@ -9,6 +9,6 @@ export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib export LD_LIBRARY_PATH=$matlab_distrib/bin/glnx86:$matlab_distrib/toolbox export PINALIGNDIR=${PROJDIR}pinAlign/pin_align-master/ export MXPROCESSINGSCRIPTSDIR=${PROJDIR}lsdc-processing/ -# below not idea as environment name also needed by daq_main2 -conda activate lsdc-server-2023-2-latest -$LSDCHOME/lsdcServer +export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt +export SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt +pixi run --manifest-path $LSDCHOME/pixi.toml fmx-server diff --git a/bin/lsdcServer_nyx.cmd b/bin/lsdcServer_nyx.cmd deleted file mode 100755 index 47fe7b11..00000000 --- a/bin/lsdcServer_nyx.cmd +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -l -export PROJDIR=/nsls2/software/mx/daq/ -export CONFIGDIR=${PROJDIR}bnlpx_config/ -export LSDCHOME=${PROJDIR}lsdc_nyx -#export EPICS_CA_AUTO_ADDR_LIST=NO -export PYTHONPATH=".:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib" -export PATH=/usr/local/bin:/usr/bin:/bin:${PROJDIR}/software/bin:/opt/ccp4/bin -source ${CONFIGDIR}daq_env_nyx.txt -export matlab_distrib=${PROJDIR}/software/c3d/matlab_distrib -export LD_LIBRARY_PATH=$matlab_distrib/bin/glnx86:$matlab_distrib/toolbox -export PINALIGNDIR=${PROJDIR}pinAlign/pin_align-master/ -export MXPROCESSINGSCRIPTSDIR=${PROJDIR}lsdc-processing/ -conda activate lsdc-server-2023-2-latest -$LSDCHOME/lsdcServer diff --git a/config_params.py b/config_params.py index 97425456..7e772f6d 100644 --- a/config_params.py +++ b/config_params.py @@ -1,9 +1,13 @@ import grp import os from enum import Enum +import requests # BlConfig parameter variable names +# Eiger detectors +EIGER_DETECTORS = ["EIGER2-9M-XE", "EIGER-16"] + # rastering parameters RASTER_TUNE_LOW_RES = "rasterTuneLowRes" RASTER_TUNE_HIGH_RES = "rasterTuneHighRes" @@ -65,6 +69,7 @@ class OnMountAvailOptions(Enum): HUTCH_TIMER_DELAY = 500 SAMPLE_TIMER_DELAY = 40 +SAMPLE_CAM_SNAPSHOT_DELAY = 100 # 10 fps for single-image snapshot polling SERVER_CHECK_DELAY = 2000 FAST_DP_MIN_NODES = 4 @@ -85,50 +90,60 @@ class OnMountAvailOptions(Enum): DETECTOR_OBJECT_TYPE_OPHYD = "ophyd" # instantiated in start_bs, using Bluesky scans DETECTOR_OBJECT_TYPE = "detectorObjectType" -DETECTOR_SAFE_DISTANCE = {"fmx": 200.0, "amx": 180.0, "nyx": 200.0} +DETECTOR_SAFE_DISTANCE = {"fmx": 200.0, "amx": 180.0} GOVERNOR_TIMEOUT = 120 # seconds for a governor move -DEWAR_SECTORS = {'amx':8, 'fmx':8, 'nyx':8} -PUCKS_PER_DEWAR_SECTOR = {'amx':3, 'fmx':3, 'nyx':3} +DEWAR_SECTORS = {'amx':8, 'fmx':8} +PUCKS_PER_DEWAR_SECTOR = {'amx':3, 'fmx':3} -cryostreamTempPV = {"amx": "XF:17ID1:CS700:TEMP", "fmx": "XF:17ID2:CS700:TEMP", "nyx":"XF:19ID2:CS700:TEMP"} +cryostreamTempPV = {"amx": "XF:17IDB-ES:AMX{CS:1}SAMPLE_TEMP_RBV", "fmx": "XF:17IDC-ES:FMX{CS:1}SAMPLE_TEMP_RBV"} VALID_EXP_TIMES = { "amx": {"min": 0.005, "max": 1, "digits": 3}, - "fmx": {"min": 0.01, "max": 10, "digits": 3}, - "nyx": {"min": 0.002, "max": 10, "digits": 4}, + "fmx": {"min": 0.002, "max": 10, "digits": 3}, } VALID_DET_DIST = { "amx": {"min": 100, "max": 500, "digits": 3}, - "fmx": {"min": 137, "max": 2000, "digits": 2}, - "nyx": {"min": 100, "max": 500, "digits": 3}, + "fmx": {"min": 100, "max": 2000, "digits": 2}, } VALID_TOTAL_EXP_TIMES = { "amx": {"min": 0.005, "max": 300, "digits": 3}, - "fmx": {"min": 0.01, "max": 300, "digits": 3}, - "nyx": {"min": 0.01, "max": 1000, "digits": 3}, + "fmx": {"min": 0.002, "max": 300, "digits": 3}, } VALID_PREFIX_LENGTH = 25 # TODO centralize with spreadsheet validation? VALID_PREFIX_NAME = "[0-9a-zA-Z-_]{0,%s}" % VALID_PREFIX_LENGTH VALID_TRANSMISSION = { "amx": {"min": 0.001, "max": 1.0, "digits": 3}, "fmx": {"min": 0.001, "max": 1.0, "digits": 3}, - "nyx": {"min": 0.001, "max": 0.999, "digits": 3}, } -MINIMUM_RASTER_SIZE = {"amx": 6, "fmx": 6, "nyx": 2} +MINIMUM_RASTER_SIZE = {"amx": 6, "fmx": 6} -LSDC_SERVICE_USERS = ("lsdc-amx", "lsdc-fmx", "lsdc-nyx") +LSDC_SERVICE_USERS = ("lsdc-amx", "lsdc-fmx") IS_STAFF = ( True if os.environ.get("STAFF_GROUP") is not None and os.environ["STAFF_GROUP"] in [grp.getgrgid(g).gr_name for g in os.getgroups()] else False ) -EMBL_SERVER_PV_BASE = { - "amx": "XF:17IDB-ES:AMX{EMBL}", - "fmx": "XF:17IDC-ES:FMX{EMBL}" -} +def get_current_cycle(): + try: + cycle_r = requests.get( + f"{os.environ['NSLS2_API_URL']}/v1/facility/nsls2/cycles/current" + ) + cycle_r.raise_for_status() + cycle = cycle_r.json().get("cycle") + except requests.exceptions.HTTPError: + return None + except KeyError: + return None + if cycle is None: + return None + return cycle + +CURRENT_CYCLE = get_current_cycle() + +EMBL_SERVER_PV_BASE = {"amx": "XF:17IDB-ES:AMX{EMBL}", "fmx": "XF:17IDC-ES:FMX{EMBL}"} BEAMSIZE_OPTIONS = { "S": ["V0", "H0"], @@ -154,8 +169,6 @@ def get_text(cls, enum_value): } return text_values.get(enum_value, "\n(Unknown State)") -OPHYD_COLLECTIONS = {"amx": False, "fmx": False, "nyx": True} - class CollectionProtocols(str, Enum): STANDARD = "standard" RASTER = "raster" @@ -176,7 +189,6 @@ def get_beamline_options(cls, beamline): cls.STEP_RASTER, cls.STEP_VECTOR, cls.MULTI_COL, cls.CHARACTERIZE, cls.EDNA_COL) beamline_options = { - "nyx": (cls.STANDARD, cls.RASTER, cls.VECTOR), "amx": all_protocols, "fmx": all_protocols } diff --git a/daq_lib.py b/daq_lib.py index 0977262b..c469b86f 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -21,12 +21,13 @@ import bluesky.plan_stubs as bps import logging from utils import validation -logger = logging.getLogger(__name__) +from utils.command_execution import safe_to_run_in_parallel +import requests +import threading +import json -try: - import ispybLib -except Exception as e: - logger.error("daq_lib: ISPYB import error, %s" % e) +from utils.bluesky import get_bluesky_metadata +logger = logging.getLogger(__name__) if daq_utils.beamline in ["amx", "fmx"]: from start_bs import gov_mon_signal @@ -42,7 +43,7 @@ global gui_popup_message_string_pv global data_directory_name global currentIspybDCID - +currentIspybDCID = 1 global fastDPNodeCount fastDPNodeCount = 4 global fastDPNodeCounter @@ -59,6 +60,9 @@ global abort_flag abort_flag = 0 +unpause_evt = threading.Event() +unpause_evt.set() + def init_var_channels(): global var_channel_list @@ -160,11 +164,22 @@ def continue_data_collection(): set_field("pause_button_state","Pause") -def abort_data_collection(flag): +def abort_data_collection(flag, action=None): global datafile_name,abort_flag,image_started if (flag==2): #stop queue after current collection abort_flag = 2 + if action in ("pause", "stop"): + if unpause_evt.is_set(): + unpause_evt.clear() + set_field("pause_button_state", "Continue") + else: + unpause_evt.set() + from epics import PV + set_field("pause_button_state", "Pause") + comm_pv = PV(daq_utils.beamlineComm + "immediate_command_s") + message = json.dumps({"function": "runDCQueue", "args": [2], "kwargs": {}}) + comm_pv.put(message) return gui_message("Aborting. This may take a minute or more.") while not (getPvDesc("VectorActive")): #only stop if actually collecting @@ -236,14 +251,14 @@ def flocoUnlock(): unlockGUI() govMonOn() -def flocoStopOperations(): +def floco_stop_operations(): try: daq_macros.run_recovery_procedure(stop=True) lockGUI() except Exception as e: - logger.exception("Error encountered while running flocoStopOperations. Stopping...") + logger.exception("Error encountered while running floco_stop_operations. Stopping...") -def flocoContinueOperations(): +def floco_continue_operations(): try: daq_macros.run_recovery_procedure(stop=False) flocoUnlock() @@ -400,7 +415,7 @@ def unmountCold(): set_mounted_pin_data(currentMountedSampleID, mount_state=MountState.CURRENTLY_UNMOUNTING.value) if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID): db_lib.deleteCompletedRequestsforSample(currentMountedSampleID) - if getBlConfig("robot_online"): + if getBlConfig("robot_online") and not getBlConfig("special_mount_enabled"): robot_lib.parkGripper() clearMountedSample() setPvDesc("robotGovActive",1) @@ -444,7 +459,20 @@ def runDCQueue(): #maybe don't run rasters from here??? autoMounted = 0 #this means the mount was performed from a runQueue, as opposed to a manual mount button push logger.info("running queue in daq server") while (1): + logger.info(f"Unpause event is_set: {unpause_evt.is_set()}") + # unpause_evt.wait() + if not unpause_evt.is_set(): + return currentRequest = db_lib.popNextRequest(daq_utils.beamline) + logger.info(f"Next request in queue: {currentRequest}") + if (currentRequest == {}): + break + elif currentRequest is None: + gui_message("Queue contains collection requests from different proposals" + "and not using commissioning directory." + "Please remove invalid requests or switch to" + "commissioning directory to continue") + break if (getBlConfig("queueCollect") == 1): if (getBlConfig(BEAM_CHECK) == 1): waitBeam() @@ -458,17 +486,6 @@ def runDCQueue(): #maybe don't run rasters from here??? refreshGuiTree() continue - if (abort_flag): - abort_flag = 0 #careful about when to reset this - return - if (currentRequest == {}): - break - elif currentRequest is None: - gui_message("Queue contains collection requests from different proposals" - "and not using commissioning directory." - "Please remove invalid requests or switch to" - "commissioning directory to continue") - break logger.info("processing request " + str(time.time())) reqObj = currentRequest["request_obj"] gov_lib.set_detz_in(gov_robot, reqObj["detDist"]) @@ -517,9 +534,10 @@ def runDCQueue(): #maybe don't run rasters from here??? -def stopDCQueue(flag): +@safe_to_run_in_parallel +def stopDCQueue(flag, action=None): logger.info("stopping queue in daq server " + str(flag)) - abort_data_collection(int(flag)) + abort_data_collection(int(flag), action=action) @@ -553,15 +571,6 @@ def logMxRequestParams(currentRequest,wait=True): logfile.write("detector distance: " + str(reqObj["detDist"]) +"\n") logfile.write("wavelength: " + str(reqObj["wavelength"]) +"\n") logfile.close() - visitName = daq_utils.getVisitName() - try: #I'm worried about unforseen ispyb db errors - #rasters results are entered in ispyb by the GUI, no need to wait - if wait: - time.sleep(getBlConfig(ISPYB_RESULT_ENTRY_DELAY)) - currentIspybDCID = ispybLib.insertResult(newResult,"mxExpParams",currentRequest,visitName) - except Exception as e: - currentIspybDCID = 999999 - logger.error("logMxRequestParams - ispyb error: %s" % e) @@ -613,7 +622,8 @@ def collectData(currentRequest): db_lib.updateRequest(currentRequest) if prot == CollectionProtocols.RASTER: logger.info('entering raster') - RE(daq_macros.snakeRaster(currentRequest["uid"])) + request_id = currentRequest["uid"] + RE(daq_macros.snakeRaster(request_id), **get_bluesky_metadata(request_id=request_id)) status = 0 logger.info('exiting raster') elif prot == CollectionProtocols.STEP_RASTER: @@ -621,7 +631,8 @@ def collectData(currentRequest): elif prot in (CollectionProtocols.VECTOR, CollectionProtocols.STEP_VECTOR): imagesAttempted = collect_detector_seq_hw(sweep_start,range_degrees,img_width,exposure_period,file_prefix,data_directory_name,file_number_start,currentRequest) elif prot == CollectionProtocols.MULTI_COL: - RE(daq_macros.snakeRaster(currentRequest["uid"])) + request_id = currentRequest["uid"] + RE(daq_macros.snakeRaster(request_id), **get_bluesky_metadata(request_id=request_id)) elif prot == CollectionProtocols.RASTER_SCREEN: daq_macros.rasterScreen(currentRequest) elif prot == CollectionProtocols.MULTI_COL_Q: @@ -641,7 +652,7 @@ def collectData(currentRequest): # Store original user-requested start angle before auto-centering original_sweep_start = reqObj["sweep_start"] - daq_macros.run_loop_center_plan() + daq_macros.run_loop_center_plan(str(reqObj["sample"])) if not (daq_macros.autoRasterLoop(currentRequest)): logger.info("could not center sample") db_lib.updatePriority(currentRequest["uid"],-1) @@ -658,60 +669,9 @@ def collectData(currentRequest): sweep_start = reqObj["sweep_start"] daq_macros.setTrans(attenuation) - if (reqObj["protocol"] in (CollectionProtocols.CHARACTERIZE, CollectionProtocols.EDNA_COL)): - characterizationParams = reqObj["characterizationParams"] - index_success = daq_macros.dna_execute_collection3(0.0,img_width,2,exposure_period,data_directory_name+"/",file_prefix,1,-89.0,1,currentRequest) - if (index_success): - resultsList = db_lib.getResultsforRequest(currentRequest["uid"]) # because for testing I keep running the same request. Probably not in usual use. - results = None - for i in range(0,len(resultsList)): - if (resultsList[i]['result_type'] == 'characterizationStrategy'): - results = resultsList[i] - break - if (results != None): - - strategyResults = results["result_obj"]["strategy"] - stratStart = strategyResults["start"] - stratEnd = strategyResults["end"] - stratWidth = strategyResults["width"] - stratExptime = strategyResults["exptime"] - stratTrans = strategyResults["transmission"] - stratDetDist = strategyResults["detDist"] - sampleID = currentRequest["sample"] - tempnewStratRequest = daq_utils.createDefaultRequest(sampleID) - newReqObj = tempnewStratRequest["request_obj"] - newReqObj["sweep_start"] = stratStart - newReqObj["sweep_end"] = stratEnd - newReqObj["img_width"] = stratWidth - newReqObj["exposure_time"] = stratExptime - newReqObj["attenuation"] = stratTrans - newReqObj["detDist"] = stratDetDist - newReqObj["directory"] = data_directory_name - newReqObj["pos_x"] = beamline_lib.motorPosFromDescriptor("sampleX") - newReqObj["pos_y"] = beamline_lib.motorPosFromDescriptor("sampleY") - newReqObj["pos_z"] = beamline_lib.motorPosFromDescriptor("sampleZ") - newReqObj["fastDP"] = True # this is where you might want a "new from old" request to carry over stuff like this. - newReqObj["fastEP"] = reqObj["fastEP"] - newReqObj["dimple"] = reqObj["dimple"] - newReqObj["xia2"] = reqObj["xia2"] - runNum = db_lib.incrementSampleRequestCount(sampleID) - newReqObj["runNum"] = runNum - newStratRequest = db_lib.addRequesttoSample(sampleID,newReqObj["protocol"],daq_utils.owner,newReqObj,priority=0,proposalID=daq_utils.getProposalID()) - if (reqObj["protocol"] == CollectionProtocols.EDNA_COL): - logger.info("new strat req = ") - logger.info(newStratRequest) - db_lib.updatePriority(currentRequest["uid"],-1) - refreshGuiTree() - collectData(db_lib.getRequestByID(newStratRequest)) - return 1 - else: #standard - logger.info("moving omega to start " + str(time.time())) - if daq_utils.beamline == "nyx": - direction = (sweep_end - sweep_start) / abs(sweep_end - sweep_start) - beamline_lib.mvaDescriptor("omega",sweep_start - direction*0.05) - else: - beamline_lib.mvaDescriptor("omega",sweep_start) - collect_detector_seq_hw(sweep_start,range_degrees,img_width,exposure_period,file_prefix,data_directory_name,file_number_start,currentRequest) + logger.info("moving omega to start " + str(time.time())) + beamline_lib.mvaDescriptor("omega",sweep_start) + collect_detector_seq_hw(sweep_start,range_degrees,img_width,exposure_period,file_prefix,data_directory_name,file_number_start,currentRequest) try: if (logMe) and prot == CollectionProtocols.RASTER: logMxRequestParams(currentRequest,wait=False) @@ -726,50 +686,57 @@ def collectData(currentRequest): # collection finished, start processing if prot in (CollectionProtocols.VECTOR, CollectionProtocols.STANDARD, CollectionProtocols.STEP_VECTOR): - if daq_utils.beamline != "nyx": - seqNum = flyer.detector.cam.sequence_id.get() - comm_s = os.environ["LSDCHOME"] + "/runSpotFinder4syncW.py " + data_directory_name + " " + file_prefix + " " + str(currentRequest["uid"]) + " " + str(seqNum) + " " + str(currentIspybDCID)+ "&" - logger.info(f"NOT running spotfinding for per-image analysis in Synchweb: {comm_s}") - #os.system(comm_s) - filename = f"{data_directory_name}/{file_prefix}_{seqNum}_master.h5" - logger.info(f"Checking integrity of {filename}") - timeout_index = 0 - while not validation.validate_master_HDF5_file(filename): - timeout_index += 1 - time.sleep(3) - if timeout_index > 15: - logger.error(f"Unable to verify master file after {timeout_index} tries, not proceeding with processing") - return - if img_width > 0: #no dataset processing in stills mode - if (reqObj["fastDP"]): - if (reqObj["fastEP"]): - fastEPFlag = 1 - else: - fastEPFlag = 0 - if (reqObj["dimple"]): - dimpleFlag = 1 - else: - dimpleFlag = 0 - nodeName = "fastDPNode" + str((fastDPNodeCounter%fastDPNodeCount)+1) - fastDPNodeCounter+=1 - node = getBlConfig(nodeName) - dimpleNode = getBlConfig("dimpleNode") - if (daq_utils.detector_id == "EIGER-16"): - seqNum = flyer.detector.cam.sequence_id.get() - comm_s = os.environ["LSDCHOME"] + "/runFastDPH5.py " + data_directory_name + " " + str(seqNum) + " " + str(currentRequest["uid"]) + " " + str(fastEPFlag) + " " + node + " " + str(dimpleFlag) + " " + dimpleNode + " " + str(currentIspybDCID)+ "&" - else: - comm_s = os.environ["LSDCHOME"] + "/runFastDP.py " + data_directory_name + " " + file_prefix + " " + str(file_number_start) + " " + str(int(round(range_degrees/img_width))) + " " + str(currentRequest["uid"]) + " " + str(fastEPFlag) + " " + node + " " + str(dimpleFlag) + " " + dimpleNode + "&" - logger.info(f'Running fastdp command: {comm_s}') - if daq_utils.beamline in ("amx", "fmx"): - visitName = daq_utils.getVisitName() - if (not os.path.exists(visitName + "/fast_dp_dir")) or subprocess.run(['pgrep', '-f', 'loop-fdp-dple-populate'], stdout=subprocess.PIPE).returncode == 1: # for pgrep, return of 1 means string not found - os.system("killall -KILL loop-fdp-dple-populate") - logger.info('starting fast dp result gathering script') - os.system("cd " + visitName + ";${LSDCHOME}/bin/loop-fdp-dple-populate.sh&") - os.system(comm_s) - if (reqObj["xia2"]): - comm_s = f"ssh -q xf17id2-srv1 \"{os.environ['MXPROCESSINGSCRIPTSDIR']}xia2.sh {currentRequest['uid']} \"&" - os.system(comm_s) + seqNum = flyer.detector.cam.sequence_id.get() + comm_s = os.environ["LSDCHOME"] + "/runSpotFinder4syncW.py " + data_directory_name + " " + file_prefix + " " + str(currentRequest["uid"]) + " " + str(seqNum) + " " + str(currentIspybDCID)+ "&" + logger.info(f"NOT running spotfinding for per-image analysis in Synchweb: {comm_s}") + #os.system(comm_s) + filename = f"{data_directory_name}/{file_prefix}_{seqNum}_master.h5" + logger.info(f"Checking integrity of {filename}") + timeout_index = 0 + while not validation.validate_master_HDF5_file(filename): + timeout_index += 1 + time.sleep(3) + if timeout_index > 15: + logger.error(f"Unable to verify master file after {timeout_index} tries, not proceeding with processing") + return + if img_width > 0: #no dataset processing in stills mode + if (reqObj["fastDP"]): + if (reqObj["fastEP"]): + fastEPFlag = 1 + else: + fastEPFlag = 0 + if (reqObj["dimple"]): + dimpleFlag = 1 + else: + dimpleFlag = 0 + nodeName = "fastDPNode" + str((fastDPNodeCounter%fastDPNodeCount)+1) + fastDPNodeCounter+=1 + node = getBlConfig(nodeName) + dimpleNode = getBlConfig("dimpleNode") + if (daq_utils.detector_id in EIGER_DETECTORS): + with open(f"{os.environ['CONFIGDIR']}/autoproc_blacklist.json") as autoproc_blacklist_file: + autoproc_proposal_data = json.load(autoproc_blacklist_file) + run_autoproc = 0 + try: + r = requests.get(f"{os.environ['NSLS2_API_URL']}/v1/proposal/{currentRequest['proposalID']}") + r.raise_for_status() + response = r.json()['proposal'] + if ("proprietary" in response["type"].lower() + or int(currentRequest["proposalID"]) in autoproc_proposal_data["blacklist"]): + run_autoproc = 0 + else: + run_autoproc = 1 + except Exception as e: + run_autoproc = 0 + seqNum = flyer.detector.cam.sequence_id.get() + comm_s = os.environ["LSDCHOME"] + "/runFastDPH5.py " + data_directory_name + " " + str(seqNum) + " " + str(currentRequest["uid"]) + " " + str(fastEPFlag) + " " + node + " " + str(dimpleFlag) + " " + dimpleNode + " " + str(currentIspybDCID)+ " " + str(run_autoproc) + " &" + else: + comm_s = os.environ["LSDCHOME"] + "/runFastDP.py " + data_directory_name + " " + file_prefix + " " + str(file_number_start) + " " + str(int(round(range_degrees/img_width))) + " " + str(currentRequest["uid"]) + " " + str(fastEPFlag) + " " + node + " " + str(dimpleFlag) + " " + dimpleNode + "&" + logger.info(f'Running fastdp command: {comm_s}') + os.system(comm_s) + if (reqObj["xia2"]): + comm_s = f"ssh -q xf17id2-srv1 \"{os.environ['MXPROCESSINGSCRIPTSDIR']}xia2.sh {currentRequest['uid']} \"&" + os.system(comm_s) logger.info('processing should be triggered') db_lib.updatePriority(currentRequest["uid"],-1) @@ -794,7 +761,7 @@ def collect_detector_seq_hw(sweep_start,range_degrees,image_width,exposure_perio else: number_of_images = round(range_degrees/image_width) range_seconds = number_of_images*exposure_period - if (daq_utils.detector_id == "EIGER-16"): + if (daq_utils.detector_id in EIGER_DETECTORS): exposure_time = exposure_period - .00001 else: exposure_time = exposure_period - .0024 @@ -807,28 +774,18 @@ def collect_detector_seq_hw(sweep_start,range_degrees,image_width,exposure_perio logger.info("collect %f degrees for %f seconds %d images exposure_period = %f exposure_time = %f" % (range_degrees,range_seconds,number_of_images,exposure_period,exposure_time)) - if OPHYD_COLLECTIONS[daq_utils.beamline]: - logger.info("ophyd collections enabled") - if (protocol == CollectionProtocols.STANDARD): - RE(daq_macros.standard_plan_wrapped(currentRequest)) - elif (protocol == CollectionProtocols.VECTOR): - RE(daq_macros.vector_plan_wrapped(currentRequest)) - else: - if (protocol in (CollectionProtocols.STANDARD, CollectionProtocols.CHARACTERIZE, - CollectionProtocols.EDNA_COL, CollectionProtocols.BURN)): - logger.info("vectorSync " + str(time.time())) - daq_macros.vectorSync() - logger.info("zebraDaq " + str(time.time())) - - vector_params = daq_macros.gatherStandardVectorParams() - logger.debug(f"vector_params: {vector_params}") - RE(daq_macros.standard_zebra_plan(flyer,angleStart,number_of_images,range_degrees,image_width,exposure_period,file_prefix_minus_directory,data_directory_name,file_number, vector_params, file_prefix_minus_directory)) - elif (protocol == CollectionProtocols.VECTOR): - RE(daq_macros.vectorZebraScan(currentRequest)) - elif (protocol == CollectionProtocols.STEP_VECTOR): - daq_macros.vectorZebraStepScan(currentRequest) - else: - pass + if (protocol in (CollectionProtocols.STANDARD, CollectionProtocols.BURN)): + logger.info("vectorSync " + str(time.time())) + daq_macros.vectorSync() + logger.info("zebraDaq " + str(time.time())) + + vector_params = daq_macros.gatherStandardVectorParams() + logger.debug(f"vector_params: {vector_params}") + RE(daq_macros.standard_zebra_plan(flyer,angleStart,number_of_images,range_degrees,image_width,exposure_period,file_prefix_minus_directory,data_directory_name,file_number, vector_params, file_prefix_minus_directory), **get_bluesky_metadata(request_dict=currentRequest)) + elif (protocol == CollectionProtocols.VECTOR): + RE(daq_macros.vectorZebraScan(currentRequest), **get_bluesky_metadata(request_dict=currentRequest)) + elif (protocol == CollectionProtocols.STEP_VECTOR): + daq_macros.vectorZebraStepScan(currentRequest) return @@ -881,21 +838,6 @@ def center_on_click(x,y,fovx,fovy,source="screen",maglevel=0,jog=0,viewangle=daq #source=screen = from screen click, otherwise from macro with full pixel dimensions #viewangle=daq_utils.CAMERA_ANGLE_BEAM, default camera angle is in-line with the beam - if daq_utils.beamline == "nyx": - logger.info("center_on_click: %s" % str((x,y))) - lsdc_x = daq_utils.screenPixX - lsdc_y = daq_utils.screenPixY - md2_x = getPvDesc("md2CenterPixelX") * 2 - md2_y = getPvDesc("md2CenterPixelY") * 2 - scale_x = md2_x / lsdc_x - scale_y = md2_y / lsdc_y - x = x * scale_x - y = y * scale_y - str_coords = f'{x} {y}' - logger.info(f'center_on_click: {str_coords}') - setPvDesc("MD2C2C", str_coords) - return - if (getBlConfig('robot_online')): #so that we don't move things when robot moving? robotGovState = (getPvDesc("robotSaActive") or getPvDesc("humanSaActive")) if (not robotGovState): @@ -952,4 +894,3 @@ def setProposalID(proposalID): def getProposalID(): return daq_utils.getProposalID() - diff --git a/daq_macros.py b/daq_macros.py index 9d091d22..2c2d8581 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -1,5 +1,3 @@ -import Gen_Commands -import Gen_Traj_Square import beamline_support from beamline_support import getPvValFromDescriptor as getPvDesc, setPvValFromDescriptor as setPvDesc import beamline_lib #did this really screw me if I imported b/c of daq_utils import?? @@ -7,14 +5,15 @@ import daq_utils import db_lib from daq_utils import getBlConfig, setBlConfig -from utils.raster import get_raster_max_col, get_flattened_indices_of_max_col +from utils.bluesky import get_bluesky_metadata, get_sample_metadata +from utils.command_execution import safe_to_run_in_parallel +from utils.raster import get_raster_max_col, get_flattened_indices_of_max_col, determine_raster_shape import det_lib import math import time import glob import xmltodict from start_bs import * -import super_state_machine import _thread import parseSheet import attenCalc @@ -45,19 +44,16 @@ from bluesky.log import config_bluesky_logging config_bluesky_logging(level='INFO') from fmx_annealer import govStatusGet, govStateSet, fmxAnnealer, amxAnnealer # for using annealer specific to FMX and AMX -from config_params import ON_MOUNT_OPTION, OnMountAvailOptions, BEAMSIZE_OPTIONS -from mxbluesky.plans import detect_loop, topview_optimized +from config_params import ON_MOUNT_OPTION, OnMountAvailOptions, BEAMSIZE_OPTIONS, EIGER_DETECTORS +from mxbluesky.plans.loop_detection import detect_loop +import json if daq_utils.beamline == 'fmx': from setenergy_lsdc import setELsdc - - -try: - import ispybLib -except Exception as e: - logger.error("daq_macros: ISPYB import error, %s" % e) +if daq_utils.beamline == 'amx': + from mxbluesky.plans.top_view import topview_optimized -from XSDataMXv1 import XSDataResultCharacterisation + global rasterRowResultsList, processedRasterRowCount global ednaActiveFlag @@ -83,24 +79,6 @@ #12/19 - general comments. This file takes the brunt of the near daily changes and additions the scientists request. Some duplication and sloppiness reflects that. # I'm going to leave a lot of the commented lines in, since they might shed light on things or be useful later. -def hi_macro(): - logger.info("hello from macros\n") - daq_lib.broadcast_output("broadcast hi") - - -def BS(): - movr(omega,40) - -def BS2(): - ascan(omega,0,100,10) - -def abortBS(): - if (RE.state != "idle"): - try: - RE.abort() - except super_state_machine.errors.TransitionError: - logger.error("caught BS") - def set_energy(energy): try: daq_lib.set_field("program_state","Setting Energy") @@ -280,6 +258,8 @@ def run_robot_recovery_procedure(): def recoverCS8(): logger.info("Starting CS8 recovery") + logger.info("Toggling magnet") + smart_magnet.toggle() logger.info("Recovering robot") robot_lib.recoverRobot() logger.info("Drying gripper") @@ -298,12 +278,24 @@ def recoverCS8(): def run_recovery_procedure(stop=True): """ - Manual recovery procedure used in daq_lib.flocoStopOperations and daq_lib.flocoContinueOperations + Manual recovery procedure used in daq_lib.floco_stop_operations and daq_lib.floco_continue_operations """ + RESET = "\033[0m" + BOLD = "\033[1m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" logger.info(f"Running recovery procedure: {'stop operations' if stop else 'continue operations'}") + def check_robot(): - if not getBlConfig("robot_online") or not getBlConfig("mountEnabled"): - raise ValueError("Robot is offline or mount is disabled, sample found in gripper. Stopping recovery...") + if not getBlConfig("robot_online"): + logger.info(f"{YELLOW}Robot was off, running robotOn(){RESET}") + robotOn() + + if not getBlConfig("mountEnabled"): + logger.info(f"{YELLOW}Mounting was disabled. Running enableMount(){RESET}") + enableMount() + # raise ValueError(f"{RED}{BOLD}Robot is offline or mount is disabled. Stopping recovery...{RESET}") def run_home_pin(): from start_bs import home_pins @@ -314,34 +306,64 @@ def gov_state_to_se(): def check_robot_speed(): if not robot_arm.is_full_speed(): - logger.error("Robot arm speed is NOT 100%") + logger.error(f"{RED}{BOLD}Robot arm speed is NOT 100%{RESET}") def check_beam(): if not (getPvDesc("beamAvailable") or getBlConfig(BEAM_CHECK) == 0): - logger.error("Beam not available, please open shutter") + logger.error(f"{RED}{BOLD}Beam not available, please open shutter{RESET}") + + def check_gonio(): + print(f"BoostStatus: {getPvDesc('boostStatus') == 0}" ) + print(f'Mount status {daq_lib.get_field("mounted_pin") != ""}') + print(f'Sample detected: {getPvDesc("sampleDetected") == 0}') + if (getPvDesc("boostStatus") != 0 + or daq_lib.get_field("mounted_pin") != '' + or getPvDesc("sampleDetected") == 0): + logger.error("Pin is mounted on gonio") + raise Exception(f"{RED}{BOLD}Gonio has a sample, stopping recovery procedure{RESET}") + + def set_fts(): + # Value is set to 9 because 0.1 seconds is the 10th option in the css dropdown! + force_torque_sensor.put(9) - steps_to_run = {"Recover robot": robot_lib.recoverRobot, - "Check robot status": check_robot, - "Dry gripper": robot_lib.dryGripper, - "Home pin": run_home_pin, } if stop: - steps_to_run.update({"Disable mount": disableMount, "Robot off": robotOff}) + steps_to_run = { + "Logging": logMe, + "Recover robot": robot_lib.recoverRobot, + "Setting FTS to 0.1 sec polling": set_fts, + "Disable mount": disableMount, + "Robot off": robotOff} else: - steps_to_run.update({"Enable mount": enableMount, "Robot on": robotOn}) - - steps_to_run.update({"Move Governor to SE": gov_state_to_se, }) - if not stop: - steps_to_run.update({"Checking robot arm speed": check_robot_speed, - "Checking beam": check_beam}) - - for i, (step_message, func) in enumerate(steps_to_run.items()): - logger.info(f"Step {i+1} of {len(steps_to_run)}: {step_message}") - func() - logger.info(f"Completed step: {step_message}") - + steps_to_run = { + "Logging": logMe, + "Checking sample in gonio": check_gonio, + "Recover robot": robot_lib.recoverRobotFloco, + "Setting FTS to 0.1 sec polling": set_fts, + "Check robot status": check_robot, + "Dry gripper": robot_lib.dryGripperFloco, + "Home pin": run_home_pin, + "Enable mount": enableMount, + "Robot on": robotOn, + "Move Governor to SE": gov_state_to_se, + "Checking robot arm speed": check_robot_speed, + "Checking beam": check_beam} + + try: + daq_lib.set_field("program_state","Running floco recovery") + for i, (step_message, func) in enumerate(steps_to_run.items()): + logger.info(f"{YELLOW}Executing step {i+1} of {len(steps_to_run)}:{RESET} {GREEN}{step_message}{RESET}") + func() + # logger.info(f"{GREEN}Completed step: {step_message}{RESET}") + except Exception as e: + logger.info(f"{RED}Exception while running floco recovery{RESET} : {e}") + if "unrecognized location" in str(e).lower(): + message = "Unrecognized location, Manual recovery required" + logger.info(f"{RED}Recovery aborted: {message}{RESET}") + finally: + daq_lib.set_field("program_state","Program Ready") -def run_top_view_optimized(): - RE(topview_optimized()) +def run_top_view_optimized(sample_id): + RE(topview_optimized(), sample_metadata=get_sample_metadata(sample_id)) def run_on_mount_option(sample_id): option = OnMountAvailOptions(daq_utils.getBlConfig(ON_MOUNT_OPTION)) @@ -354,7 +376,7 @@ def run_on_mount_option(sample_id): if (option == OnMountAvailOptions.CENTER_SAMPLE or option == OnMountAvailOptions.AUTO_RASTER): # Center using ML model - run_loop_center_plan() + run_loop_center_plan(sample_id) if option == OnMountAvailOptions.AUTO_RASTER: # Set up a fake standard collection for autoRasterLoop @@ -364,12 +386,13 @@ def run_on_mount_option(sample_id): "xbeam": getPvDesc('beamCenterX'), "ybeam": getPvDesc('beamCenterY'), "wavelength": daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6), + "detDist": beamline_devices.detector.dist.user_readback.get(), "basePath": getBlConfig("visitDirectory") } } autoRasterLoop(request) -def run_loop_center_plan(): +def run_loop_center_plan(sample_id): if daq_utils.beamline == "fmx": # Run xrec for FMX, they don't have a top cam retries = 3 @@ -379,7 +402,8 @@ def run_loop_center_plan(): retries -= 1 else: retries = 0 - RE(loop_center_plan()) + sample_metadata = get_sample_metadata(sample_id) + RE(loop_center_plan(), sample_metadata=sample_metadata) def loop_center_plan(): global sample_detection @@ -430,12 +454,27 @@ def autoRasterLoop(currentRequest): # Oscillation range is zero which means its a raster screen request return 0 - RE(bps.mv(gonio.gx, sample_detection["center_x"], - gonio.py, sample_detection["center_y"], - gonio.pz, sample_detection["center_z"])) + + if getBlConfig("use_mini_raster"): + width = step_size * 3 + num_rows = sample_detection["small_box_height"]//step_size + if num_rows < MINIMUM_RASTER_SIZE[daq_utils.beamline]: + width = step_size * MINIMUM_RASTER_SIZE[daq_utils.beamline] + # When collecting a mini ortho raster we want to position + # the beam at the center of the ortho raster + # In which case y and z will be the same, x will correspond to + # the max position + RE(bps.mv(gonio.gx, face_on_max_coords[0], + gonio.py, sample_detection["center_y"], + gonio.pz, sample_detection["center_z"])) + else: + width = sample_detection["large_box_width"] + RE(bps.mv(gonio.gx, sample_detection["center_x"], + gonio.py, sample_detection["center_y"], + gonio.pz, sample_detection["center_z"])) runRasterScan(currentRequest, rasterType="Custom", - width=sample_detection["large_box_width"], + width=width, height=sample_detection["small_box_height"], step_size=step_size, omega_rel=90) @@ -482,141 +521,7 @@ def autoRasterLoop(currentRequest): else: return 0 -def autoRasterLoopOld(currentRequest): - global autoRasterFlag - - gov_status = gov_lib.setGovRobot(gov_robot, 'SA') - if not gov_status.success: - return 0 - if (getBlConfig("queueCollect") == 1): - delayTime = getBlConfig("autoRasterDelay") - time.sleep(delayTime) - - reqObj = currentRequest["request_obj"] - if ("centeringOption" in reqObj): - if (reqObj["centeringOption"] == "AutoLoop"): - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - time.sleep(2.0) - status = loop_center_xrec() - return status - setTrans(getBlConfig("rasterDefaultTrans")) - daq_lib.set_field("xrecRasterFlag","100") - sampleID = currentRequest["sample"] - logger.info("auto raster " + str(sampleID)) - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - time.sleep(2.0) - status = loop_center_xrec() - if (status == -99): #abort, never hit this - db_lib.updatePriority(currentRequest["uid"],5000) - return 0 - if not (status): - return 0 - time.sleep(2.0) #looks like I really need this sleep, they really improve the appearance - runRasterScan(currentRequest,"Coarse") - time.sleep(1.5) - loop_center_mask() - time.sleep(1) - autoRasterFlag = 1 - runRasterScan(currentRequest,"Fine") - time.sleep(1) - runRasterScan(currentRequest,"Line") - gov_lib.setGovRobot(gov_robot, 'DI') - time.sleep(1) - autoRasterFlag = 0 - - return 1 - - -def autoVector(currentRequest): #12/19 - not tested! - global autoVectorFlag - - gov_status = gov_lib.setGovRobot(gov_robot, 'SA') - if not gov_status.success: - return 0 - reqObj = currentRequest["request_obj"] - daq_lib.set_field("xrecRasterFlag","100") - sampleID = currentRequest["sample"] - logger.info("auto raster " + str(sampleID)) - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - if (status== 0): - beamline_lib.mvrDescriptor("sampleX",1000) - status = loop_center_xrec() - time.sleep(2.0) - status = loop_center_xrec() - if (status == -99): #abort, never hit this - db_lib.updatePriority(currentRequest["uid"],5000) - return 0 - if not (status): - return 0 - time.sleep(2.0) #looks like I really need this sleep, they really improve the appearance - autoVectorFlag = True - runRasterScan(currentRequest,"autoVector") - logger.info("autovec coarse coords 1") - logger.info(autoVectorCoarseCoords) - x1Start = autoVectorCoarseCoords["start"]["x"] - y1Start = autoVectorCoarseCoords["start"]["y"] - z1Start = autoVectorCoarseCoords["start"]["z"] - x1End = autoVectorCoarseCoords["end"]["x"] - y1End = autoVectorCoarseCoords["end"]["y"] - z1End = autoVectorCoarseCoords["end"]["z"] - loop_center_mask() - time.sleep(1) - runRasterScan(currentRequest,"autoVector") - autoVectorFlag = False - logger.info("autovec coarse coords 2") - logger.info(autoVectorCoarseCoords) - x2Start = autoVectorCoarseCoords["start"]["x"] - y2Start = autoVectorCoarseCoords["start"]["y"] - z2Start = autoVectorCoarseCoords["start"]["z"] - x2End = autoVectorCoarseCoords["end"]["x"] - y2End = autoVectorCoarseCoords["end"]["y"] - z2End = autoVectorCoarseCoords["end"]["z"] - - x_vec_start = min(x1Start,x2Start) - y_vec_start = (y1Start+y2Start)/2.0 - z_vec_start = (z1Start+z2Start)/2.0 - - x_vec_end = max(x1End,x2End) - y_vec_end = (y1End+y2End)/2.0 - z_vec_end = (z1End+z2End)/2.0 - - vectorStart = {"x":x_vec_start,"y":y_vec_start,"z":z_vec_start} - vectorEnd = {"x":x_vec_end,"y":y_vec_end,"z":z_vec_end} - - x_vec = x_vec_end - x_vec_start - y_vec = y_vec_end - y_vec_start - z_vec = z_vec_end - z_vec_start - trans_total = math.sqrt(x_vec**2 + y_vec**2 + z_vec**2) - framesPerPoint = 1 - vectorParams={"vecStart":vectorStart,"vecEnd":vectorEnd,"x_vec":x_vec,"y_vec":y_vec,"z_vec":z_vec,"trans_total":trans_total,"fpp":framesPerPoint} - reqObj["vectorParams"] = vectorParams - reqObj["centeringOption"] = "Interactive" #kind of kludgy so that collectData doesn't go rastering for vector params again - currentRequest["request_obj"] = reqObj - db_lib.updateRequest(currentRequest) - daq_lib.collectData(currentRequest) - gov_lib.setGovRobot(gov_robot, 'SA') - return 1 - def rasterScreen(currentRequest): - if (daq_utils.beamline == "fmx" and getBlConfig("scannerType") == "PI"): - gridRaster(currentRequest) - return - daq_lib.set_field("xrecRasterFlag","100") sampleID = currentRequest["sample"] reqObj = currentRequest["request_obj"] @@ -642,139 +547,10 @@ def rasterScreen(currentRequest): else: rasterW = 630 rasterH = 510 - rasterReqID = defineRectRaster(currentRequest,rasterW,rasterH,gridStep) + rasterReqID, collection_metadata = defineRectRaster(currentRequest,rasterW,rasterH,gridStep) db_lib.updatePriority(rasterReqID, -1) - RE(snakeRaster(rasterReqID)) - - -def multiCol(currentRequest): - daq_lib.set_field("xrecRasterFlag","100") - sampleID = currentRequest["sample"] - logger.info("multiCol " + str(sampleID)) - status = loop_center_xrec() - if not (status): - return 0 - time.sleep(1) #looks like I really need this sleep, they really improve the appearance - runRasterScan(currentRequest,"Coarse") - -def loop_center_xrec_slow(): - global face_on - - daq_lib.abort_flag = 0 - - for i in range(0,360,40): - if (daq_lib.abort_flag == 1): - logger.info("caught abort in loop center") - return 0 - beamline_lib.mvaDescriptor("omega",i) - pic_prefix = "findloop_" + str(i) - time.sleep(1.5) #for video lag. This sucks - daq_utils.take_crystal_picture(filename=pic_prefix) - comm_s = "xrec " + os.environ["CONFIGDIR"] + "/xrec_360_40.txt xrec_result.txt" - logger.info(comm_s) - os.system(comm_s) - xrec_out_file = open("xrec_result.txt","r") - target_angle = 0.0 - radius = 0 - x_centre = 0 - y_centre = 0 - reliability = 0 - for result_line in xrec_out_file.readlines(): - logger.info(result_line) - tokens = result_line.split() - tag = tokens[0] - val = tokens[1] - if (tag == "TARGET_ANGLE"): - target_angle = float(val ) - elif (tag == "RADIUS"): - radius = float(val ) - elif (tag == "Y_CENTRE"): - y_centre_xrec = float(val ) - elif (tag == "X_CENTRE"): - x_centre_xrec = float(val ) - elif (tag == "RELIABILITY"): - reliability = int(val ) - elif (tag == "FACE"): - face_on = float(tokens[3]) - xrec_out_file.close() - xrec_check_file = open("Xrec_check.txt","r") - check_result = int(xrec_check_file.read(1)) - logger.info("result = " + str(check_result)) - xrec_check_file.close() - if (reliability < 70 or check_result == 0): #bail if xrec couldn't align loop - return 0 - beamline_lib.mvaDescriptor("omega",target_angle) - x_center = getPvDesc("lowMagCursorX") - y_center = getPvDesc("lowMagCursorY") - logger.info("center on click " + str(x_center) + " " + str(y_center-radius)) - logger.info("center on click " + str((x_center*2) - y_centre_xrec) + " " + str(x_centre_xrec)) - fovx = daq_utils.lowMagFOVx - fovy = daq_utils.lowMagFOVy + RE(snakeRaster(rasterReqID), **get_bluesky_metadata(request_id=rasterReqID)) - daq_lib.center_on_click(x_center,y_center-radius,fovx,fovy,source="macro") - daq_lib.center_on_click((x_center*2) - y_centre_xrec,x_centre_xrec,fovx,fovy,source="macro") - beamline_lib.mvaDescriptor("omega",face_on) - #now try to get the loopshape starting from here - return 1 - - -def generateRasterCoords4Traj(rasterRequest): - - reqObj = rasterRequest["request_obj"] - exptimePerCell = reqObj["exposure_time"] - rasterDef = reqObj["rasterDef"] - stepsize = float(rasterDef["stepsize"]) - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - rasterCellMap = {} - numsteps = float(rasterDef["rowDefs"][0]["numsteps"]) - columns = numsteps - rows = len(rasterDef["rowDefs"]) - firstRow = rasterDef["rowDefs"][0] - sx1 = firstRow["start"]["x"] #startX - sy1 = firstRow["start"]["y"] - logger.info("start x,y") - logger.info(sx1) - logger.info(sy1) - -#9/18 - I think these are crap, but will leave them - xRelativeMove = sx1 - yzRelativeMove = sy1*math.sin(omegaRad) - yyRelativeMove = sy1*cos(omegaRad) - xMotAbsoluteMove1 = xRelativeMove - yMotAbsoluteMove1 = yyRelativeMove - zMotAbsoluteMove1 = yzRelativeMove - - - lastRow= rasterDef["rowDefs"][-1] - ex1 = lastRow["end"]["x"] #endX - ey1 = lastRow["end"]["y"] - logger.info("end x,y") - logger.info(ex1) - logger.info(ey1) - deltax = ex1-sx1 - deltay = ey1-sy1 - xMotAbsoluteMove1 = -(deltax/2.0) - xMotAbsoluteMove2 = (deltax/2.0) - yMotAbsoluteMove1 = -(deltay/2.0)*math.cos(omegaRad) - yMotAbsoluteMove2 = (deltay/2.0)*math.cos(omegaRad) - zMotAbsoluteMove1 = -(deltay/2.0)*math.sin(omegaRad) - zMotAbsoluteMove2 = (deltay/2.0)*math.sin(omegaRad) - - logger.info(xMotAbsoluteMove1) - logger.info(yMotAbsoluteMove1) - logger.info(zMotAbsoluteMove1) - logger.info(xMotAbsoluteMove2) - logger.info(yMotAbsoluteMove2) - logger.info(zMotAbsoluteMove2) - logger.info(stepsize) - genTraj = Gen_Traj_Square.gen_traj_square(xMotAbsoluteMove1, xMotAbsoluteMove2, yMotAbsoluteMove2, yMotAbsoluteMove1, zMotAbsoluteMove2, zMotAbsoluteMove1, columns,rows) - Gen_Commands.gen_commands(genTraj,exptimePerCell) - - def generateGridMap(rasterRequest,rasterEncoderMap=None): #12/19 - there's some dials vs dozor stuff in here global rasterRowResultsList @@ -869,10 +645,6 @@ def generateGridMap(rasterRequest,rasterEncoderMap=None): #12/19 - there's some return rasterResult -def rasterWait(): - time.sleep(0.2) - while (getPvDesc("RasterActive")): - time.sleep(0.2) def vectorWait(): time.sleep(0.15) @@ -886,14 +658,6 @@ def vectorActiveWait(): raise TimeoutError() time.sleep(0.05) -def vectorHoldWait(): - time.sleep(0.15) - while (getPvDesc("VectorState")!=2): - time.sleep(0.05) - -def vectorProceed(): - setPvDesc("vectorProceed",1) - def vectorSync(): setPvDesc("vectorSync",1) @@ -979,10 +743,7 @@ def makeDozorInputFile(directory,prefix,rowIndex,rowCellCount,seqNum,rasterReqOb src = Template(inputTemplate.read()) dozorRowDir = makeDozorRowDir(directory,rowIndex) dozorSpotLevel = getBlConfig(RASTER_DOZOR_SPOT_LEVEL) - if daq_utils.beamline == "nyx": - dozorPlugin = "/nsls2/software/mx/nyx/bin/dectris-neggia.so" - else: - dozorPlugin = "/usr/lib64/dectris-neggia.so" + dozorPlugin = "/usr/lib64/dectris-neggia.so" templateDict = {"detector": detector, "nx": nx, "ny": ny, @@ -1084,9 +845,6 @@ def runDozorThread(directory, global rasterRowResultsList,processedRasterRowCount file_writing_delay = 0.5 node = getNodeName("spot", rowIndex, 8) - if daq_utils.beamline == 'nyx': - file_writing_delay = 10 - node = "titania-cpu00"+str((rowIndex%4)+1) time.sleep(file_writing_delay) #allow for file writing if (seqNum>-1): #eiger @@ -1162,751 +920,31 @@ def runDialsThread(requestID, directory,prefix,rowIndex,rowCellCount,seqNum): processedRasterRowCount+=1 logger.info("leaving thread") -def generateGridMapFine(rasterRequest,rasterEncoderMap=None,rowsOfSubrasters=0,columnsOfSubrasters=0,rowsPerSubraster=0,cellsPerSubrasterRow=0): - global rasterRowResultsList - - reqObj = rasterRequest["request_obj"] - rasterDef = reqObj["rasterDef"] - stepsize = float(rasterDef["stepsize"]) - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - filePrefix = reqObj["directory"]+"/"+reqObj["file_prefix"] - rasterCellMap = {} - os.system("mkdir -p " + reqObj["directory"]) - for i in range(len(rasterDef["rowDefs"])): - numsteps = float(rasterDef["rowDefs"][i]["numsteps"]) -#next 6 lines to differentiate horizontal vs vertical raster - startX = rasterDef["rowDefs"][i]["start"]["x"] - endX = rasterDef["rowDefs"][i]["end"]["x"] - startY = rasterDef["rowDefs"][i]["start"]["y"] - endY = rasterDef["rowDefs"][i]["end"]["y"] - deltaX = abs(endX-startX) - deltaY = abs(endY-startY) - - if (deltaX>deltaY): #horizontal raster - if (i%2 == 0): #left to right if even, else right to left - a snake attempt - startX = rasterDef["rowDefs"][i]["start"]["x"]+(stepsize/2.0) #this is relative to center, so signs are reversed from motor movements. - else: - startX = (numsteps*stepsize) + rasterDef["rowDefs"][i]["start"]["x"]-(stepsize/2.0) - startY = rasterDef["rowDefs"][i]["start"]["y"]+(stepsize/2.0) - xRelativeMove = startX - yzRelativeMove = startY*math.sin(omegaRad) - yyRelativeMove = startY*math.cos(omegaRad) - xMotAbsoluteMove = rasterStartX+xRelativeMove - yMotAbsoluteMove = rasterStartY-yyRelativeMove - zMotAbsoluteMove = rasterStartZ-yzRelativeMove - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - for j in range(numsteps): - imIndexStr = str((i*numsteps)+j+1) - if (i%2 == 0): #left to right if even, else right to left - a snake attempt - xMotCellAbsoluteMove = xMotAbsoluteMove+(j*stepsize) - else: - xMotCellAbsoluteMove = xMotAbsoluteMove-(j*stepsize) - if (daq_utils.detector_id == "EIGER-16"): - dataFileName = "%s_%06d.cbf" % (reqObj["directory"]+"/cbf/"+reqObj["file_prefix"]+"_Raster_"+str(i),(i*numsteps)+j+1) - else: - dataFileName = daq_utils.create_filename(filePrefix+"_Raster_"+str(i),(i*numsteps)+j+1) - rasterCellCoords = {"x":xMotCellAbsoluteMove,"y":yMotAbsoluteMove,"z":zMotAbsoluteMove} - rasterCellMap[dataFileName[:-4]] = rasterCellCoords - else: #vertical raster - if (i%2 == 0): #top to bottom if even, else bottom to top - a snake attempt - startY = rasterDef["rowDefs"][i]["start"]["y"]+(stepsize/2.0) #this is relative to center, so signs are reversed from motor movements. - else: - startY = (numsteps*stepsize) + rasterDef["rowDefs"][i]["start"]["y"]-(stepsize/2.0) - startX = rasterDef["rowDefs"][i]["start"]["x"]+(stepsize/2.0) - xRelativeMove = startX - yzRelativeMove = startY*math.sin(omegaRad) - yyRelativeMove = startY*math.cos(omegaRad) - xMotAbsoluteMove = rasterStartX+xRelativeMove - yMotAbsoluteMove = rasterStartY-yyRelativeMove - zMotAbsoluteMove = rasterStartZ-yzRelativeMove - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - for j in range(numsteps): - imIndexStr = str((i*numsteps)+j+1) - if (i%2 == 0): #top to bottom if even, else bottom to top - a snake attempt - yMotCellAbsoluteMove = yMotAbsoluteMove-(math.cos(omegaRad)*(j*stepsize)) - zMotCellAbsoluteMove = zMotAbsoluteMove-(math.sin(omegaRad)*(j*stepsize)) - else: - yMotCellAbsoluteMove = yMotAbsoluteMove+(math.cos(omegaRad)*(j*stepsize)) - zMotCellAbsoluteMove = zMotAbsoluteMove+(math.sin(omegaRad)*(j*stepsize)) - if (daq_utils.detector_id == "EIGER-16"): - dataFileName = "%s_%06d.cbf" % (reqObj["directory"]+"/cbf/"+reqObj["file_prefix"]+"_Raster_"+str(i),(i*numsteps)+j+1) - else: - dataFileName = daq_utils.create_filename(filePrefix+"_Raster_"+str(i),j+1) - rasterCellCoords = {"x":xMotAbsoluteMove,"y":yMotCellAbsoluteMove,"z":zMotCellAbsoluteMove} - rasterCellMap[dataFileName[:-4]] = rasterCellCoords - -#commented out all of the processing, as this should have been done by the thread - if (rasterEncoderMap!= None): - rasterCellMap = rasterEncoderMap - if ("parentReqID" in rasterRequest["request_obj"]): - parentReqID = rasterRequest["request_obj"]["parentReqID"] - else: - parentReqID = -1 - logger.info("RASTER CELL RESULTS") - if (rowsOfSubrasters != 0): - cellsPerSubraster = rowsPerSubraster*cellsPerSubrasterRow - subrastersPerCompositeRaster = rowsOfSubrasters*columnsOfSubrasters - rowsPerCompositeRaster = rowsPerSubraster*rowsOfSubrasters - cellsPerCompositeRasterRow = columnsOfSubrasters*cellsPerSubrasterRow - cellsPerCompositeRaster = cellsPerSubraster*subrastersPerCompositeRaster - subRasterListFlipped = [] - for ii in range (0,len(rasterRowResultsList)): - subrasterFlipped = [] - for i in range (0,rowsPerSubraster): - for j in range (0,cellsPerSubrasterRow): - origSubrasterIndex = (i*cellsPerSubrasterRow)+j - if (i%2 == 1): #odd,flip - subrasterIndex = (i*cellsPerSubrasterRow)+(cellsPerSubrasterRow-j-1) - else: - subrasterIndex = origSubrasterIndex - subrasterFlipped.append(rasterRowResultsList[ii][subrasterIndex]) - subRasterListFlipped.append(subrasterFlipped) - dialsResultLocalList = [] - for ii in range (0,rowsOfSubrasters): - for jj in range (0,rowsPerSubraster): - for i in range (0,columnsOfSubrasters): - for j in range (0,cellsPerSubrasterRow): - dialsResultLocalList.append(subRasterListFlipped[(ii*columnsOfSubrasters)+i][(jj*cellsPerSubrasterRow)+j]) #first dimension is easy, - else: - dialsResultLocalList = [] - for i in range (0,len(rasterRowResultsList)): - for j in range (0,len(rasterRowResultsList[i])): - try: - dialsResultLocalList.append(rasterRowResultsList[i][j]) - except KeyError: #this is to deal with single cell row. Instead of getting back a list of one row, I get back just the row from Dials. - dialsResultLocalList.append(rasterRowResultsList[i]) - break - - rasterResultObj = {"sample_id": rasterRequest["sample"],"parentReqID":parentReqID,"rasterCellMap":rasterCellMap,"rasterCellResults":{"type":"dozorRasterResult","resultObj":dialsResultLocalList}} - rasterResultID = db_lib.addResultforRequest("rasterResult",rasterRequest["uid"], owner=daq_utils.owner,result_obj=rasterResultObj,proposalID=daq_utils.getProposalID(),beamline=daq_utils.beamline) - rasterResult = db_lib.getResult(rasterResultID) - return rasterResult - def getNodeName(node_type, row_index, num_nodes=8): #calculate node name based on row index node_number = row_index % num_nodes + 1 node_config_name = f'{node_type}Node{node_number}' return getBlConfig(node_config_name) def snakeRaster(rasterReqID,grain=""): - scannerType = getBlConfig("scannerType") - if (scannerType == "PI"): - snakeRasterFine(rasterReqID,grain) - else: - if daq_utils.beamline == "nyx": - yield from raster_plan_wrapped(rasterReqID) - else: - finalize_plan = finalize_wrapper(snakeRasterBluesky(rasterReqID,grain), bps.mv(raster_flyer.detector.cam.acquire, 0)) - yield from finalize_plan - #RE(snakeRasterBluesky(rasterReqID,grain)) + finalize_plan = finalize_wrapper(snakeRasterBluesky(rasterReqID,grain), bps.mv(raster_flyer.detector.cam.acquire, 0)) + yield from finalize_plan -def snakeRasterNoTile(rasterReqID,grain=""): - global dialsResultDict,rasterRowResultsList,processedRasterRowCount - - gov_status = gov_lib.setGovRobot(gov_robot, 'DA') - if not gov_status.success: - return - - rasterRequest = db_lib.getRequestByID(rasterReqID) - reqObj = rasterRequest["request_obj"] - parentReqID = reqObj["parentReqID"] - parentReqProtocol = "" - - if (parentReqID != -1): - parentRequest = db_lib.getRequestByID(parentReqID) - parentReqObj = parentRequest["request_obj"] - parentReqProtocol = parentReqObj["protocol"] - detDist = parentReqObj["detDist"] - data_directory_name = str(reqObj["directory"]) - os.system("mkdir -p " + data_directory_name) - filePrefix = str(reqObj["file_prefix"]) - file_number_start = reqObj["file_number_start"] - dataFilePrefix = reqObj["directory"]+"/"+reqObj["file_prefix"] - exptimePerCell = reqObj["exposure_time"] - img_width_per_cell = reqObj["img_width"] - wave = reqObj["wavelength"] - xbeam = getPvDesc("beamCenterX") * 0.075 - ybeam = getPvDesc("beamCenterY") * 0.075 - rasterDef = reqObj["rasterDef"] - stepsize = float(rasterDef["stepsize"]) - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) #these are real sample motor positions - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - rowCount = len(rasterDef["rowDefs"]) - rasterRowResultsList = [{} for i in range(0,rowCount)] - processedRasterRowCount = 0 - rasterEncoderMap = {} - totalImages = 0 -#get the center of the raster, in screen view, mm, relative to center - rows = rasterDef["rowDefs"] - numrows = len(rows) - rasterCenterScreenX = (rows[0]["start"]["x"]+rows[0]["end"]["x"])/2.0 - rasterCenterScreenY = ((rows[-1]["start"]["y"]+rows[0]["start"]["y"])/2.0)+(stepsize/2.0) - xRelativeMove = rasterCenterScreenX - yzRelativeMove = -(rasterCenterScreenY*math.sin(omegaRad)) - yyRelativeMove = -(rasterCenterScreenY*math.cos(omegaRad)) - - xMotAbsoluteMove = rasterStartX+xRelativeMove #note we convert relative to absolute moves, using the raster center that was saved in x,y,z - yMotAbsoluteMove = rasterStartY+yyRelativeMove - zMotAbsoluteMove = rasterStartZ+yzRelativeMove - - - beamline_lib.mvaDescriptor("sampleX",xMotAbsoluteMove,"sampleY",yMotAbsoluteMove,"sampleZ",zMotAbsoluteMove) - - #raster centered, now zero motors - beamline_lib.mvaDescriptor("fineX",0,"fineY",0,"fineZ",0) - for i in range(len(rasterDef["rowDefs"])): - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - totalImages = totalImages+numsteps - rasterFilePrefix = dataFilePrefix + "_Raster" - - det_lib.detector_set_num_triggers(totalImages) - det_lib.detector_set_trigger_mode(3) - det_lib.detector_setImagesPerFile(numsteps) - daq_lib.detectorArm(omega,img_width_per_cell,totalImages,exptimePerCell,rasterFilePrefix,data_directory_name,file_number_start) #this waits - - zebraVecDaqSetup(omega,img_width_per_cell,exptimePerCell,totalImages,rasterFilePrefix,data_directory_name,file_number_start) - procFlag = int(getBlConfig("rasterProcessFlag")) - generateRasterCoords4Traj(rasterRequest) - total_exposure_time = exptimePerCell*totalImages - - setPvDesc("zebraPulseMax",totalImages) - vectorSync() - setPvDesc("vectorStartOmega",omega) - setPvDesc("vectorEndOmega",(img_width_per_cell*totalImages)+omega) - setPvDesc("vectorframeExptime",exptimePerCell*1000.0) - setPvDesc("vectorNumFrames",totalImages) - rasterFilePrefix = dataFilePrefix + "_Raster_" + str(i) - Gen_Commands.go_all() - setPvDesc("vectorGo",1) - vectorActiveWait() - vectorWait() - zebraWait() - #delete these - time.sleep(2.0) - det_lib.detector_stop_acquire() - det_lib.detector_wait() - beamline_lib.mvaDescriptor("fineX",0,"fineY",0,"fineZ",0) - if (daq_utils.beamline == "amxz"): - setPvDesc("zebraReset",1) - - if (procFlag): - if (daq_utils.beamline == "amx"): - rasterRowEncoderVals = {"x":getPvDesc("zebraEncX"),"y":getPvDesc("zebraEncY"),"z":getPvDesc("zebraEncZ"),"omega":getPvDesc("zebraEncOmega")} - for j in range (0,numsteps): - dataFileName = "%s_%06d.cbf" % (reqObj["directory"]+"/cbf/"+reqObj["file_prefix"]+"_Raster_"+str(i),(i*numsteps)+j+1) - imIndexStr = str((i*numsteps)+j+1) - rasterEncoderMap[dataFileName[:-4]] = {"x":rasterRowEncoderVals["x"][j],"y":rasterRowEncoderVals["y"][j],"z":rasterRowEncoderVals["z"][j],"omega":rasterRowEncoderVals["omega"][j]} - seqNum = int(det_lib.detector_get_seqnum()) - for i in range(len(rasterDef["rowDefs"])): - _thread.start_new_thread(runDialsThread,(rasterRequest["uid"], data_directory_name,filePrefix+"_Raster",i,numsteps,seqNum)) - else: - rasterRequestID = rasterRequest["uid"] - db_lib.updateRequest(rasterRequest) - db_lib.updatePriority(rasterRequestID,-1) - if (lastOnSample()): - gov_lib.setGovRobot(gov_robot, 'SA') - return 1 - - rasterTimeout = 600 - timerCount = 0 - while (1): - timerCount +=1 - if (timerCount>rasterTimeout): - break - time.sleep(1) - logger.info('rastering row processed: %s' % processedRasterRowCount) - if (processedRasterRowCount == rowCount): - break - if (daq_utils.beamline == "amx"): - rasterResult = generateGridMap(rasterRequest,rasterEncoderMap) #I think rasterRequest is entire request, of raster type - else: - rasterResult = generateGridMap(rasterRequest) - rasterRequest["request_obj"]["rasterDef"]["status"] = 2 - protocol = reqObj["protocol"] - logger.info("protocol = " + protocol) - if (protocol == CollectionProtocols.MULTI_COL or - parentReqProtocol == CollectionProtocols.MULTI_COL_Q): - if (parentReqProtocol == CollectionProtocols.MULTI_COL_Q): - multiColThreshold = parentReqObj["diffCutoff"] - else: - multiColThreshold = reqObj["diffCutoff"] - gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) - rasterRequestID = rasterRequest["uid"] - db_lib.updateRequest(rasterRequest) - - db_lib.updatePriority(rasterRequestID,-1) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - if (lastOnSample()): - gov_lib.setGovRobot(gov_robot, 'SA') - return 1 - - - -def snakeRasterFine(rasterReqID,grain=""): #12/19 - This is for the PI scanner. It was challenging to write. It was working the last time the scanner was on. - global dialsResultDict,rasterRowResultsList,processedRasterRowCount - - gov_status = gov_lib.setGovRobot(gov_robot, 'DA') - if not gov_status.success: - return - - rasterRequest = db_lib.getRequestByID(rasterReqID) - reqObj = rasterRequest["request_obj"] - rasterDef = reqObj["rasterDef"] - stepsize = float(rasterDef["stepsize"]) - data_directory_name = str(reqObj["directory"]) - filePrefix = str(reqObj["file_prefix"]) - file_number_start = reqObj["file_number_start"] - dataFilePrefix = reqObj["directory"]+"/"+reqObj["file_prefix"] - wave = reqObj["wavelength"] - xbeam = getPvDesc("beamCenterX") - ybeam = getPvDesc("beamCenterY") - processedRasterRowCount = 0 - totalImages = 0 -#get the center of the raster, in screen view, mm, relative to center - rows = rasterDef["rowDefs"] - numrows = len(rows) - origRasterCenterScreenX = (rows[0]["start"]["x"]+rows[0]["end"]["x"])/2.0 - origRasterCenterScreenY = ((rows[-1]["start"]["y"]+rows[0]["start"]["y"])/2.0)+(stepsize/2.0) - beamline_lib.mvaDescriptor("fineX",0,"fineY",0,"fineZ",0) - exptimePerCell = reqObj["exposure_time"] - img_width_per_cell = reqObj["img_width"] - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - firstRow = rasterDef["rowDefs"][0] - lastRow= rasterDef["rowDefs"][-1] - sx1 = firstRow["start"]["x"] #startX - sy1 = firstRow["start"]["y"] - ex1 = lastRow["end"]["x"] #endX - ey1 = lastRow["end"]["y"] - rasterLenX = ex1-sx1 - rasterLenY = ey1-sy1 - omegaLimit = 40.0 - xLimit = 180.0 - yLimit = 120.0 - if (rasterLenXrasterTimeout): - break - time.sleep(1) - logger.info(processedRasterRowCount) - if (processedRasterRowCount == numberOfSubrasters): - break - rasterResult = generateGridMapFine(rasterRequest,rowsOfSubrasters=rowsOfSubrasters,columnsOfSubrasters=columnsOfSubrasters,rowsPerSubraster=rowsPerSubraster,cellsPerSubrasterRow=subrasterColumns) - - rasterRequest["request_obj"]["rasterDef"]["status"] = 2 - rasterRequestID = rasterRequest["uid"] - db_lib.updateRequest(rasterRequest) #so that it will fill heatmap? - db_lib.updatePriority(rasterRequestID,-1) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - if (lastOnSample()): - gov_lib.setGovRobot(gov_robot, 'SA') - return 1 - - - -def snakeRasterNormal(rasterReqID,grain=""): - global rasterRowResultsList,processedRasterRowCount - - if (daq_utils.beamline == "fmx"): - setPvDesc("sampleProtect",0) - setPvDesc("vectorGo", 0) #set to 0 to allow easier camonitoring vectorGo - gov_lib.setGovRobot(gov_robot, "DA") - rasterRequest = db_lib.getRequestByID(rasterReqID) - reqObj = rasterRequest["request_obj"] - parentReqID = reqObj["parentReqID"] - parentReqProtocol = "" - - data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, omega, rasterStartX, rasterStartY, rasterStartZ, omegaRad, rowCount, numsteps, totalImages = params_from_raster_req_id(rasterReqID) - - rasterRowResultsList = [{} for i in range(0,rowCount)] - processedRasterRowCount = 0 - rasterEncoderMap = {} - - # a couple of items we can get independent of rasterReqID - xbeam = getPvDesc("beamCenterX") - ybeam = getPvDesc("beamCenterY") - - if (parentReqID != -1): - parentRequest = db_lib.getRequestByID(parentReqID) - parentReqObj = parentRequest["request_obj"] - parentReqProtocol = parentReqObj["protocol"] - detDist = parentReqObj["detDist"] - - # now we do stuff with that info - rasterFilePrefix = dataFilePrefix + "_Raster" - total_exposure_time = exptimePerCell*totalImages - # TODO decide - put everything below here into a function? how should the processing be handled? - det_lib.detector_set_num_triggers(totalImages) - det_lib.detector_set_trigger_mode(3) - det_lib.detector_setImagesPerFile(numsteps) - daq_lib.detectorArm(omega,img_width_per_cell,totalImages,exptimePerCell,rasterFilePrefix,data_directory_name,file_number_start) #this waits - try: - gov_status = gov_lib.setGovRobot(gov_robot, 'DA') - if not gov_status.success: - if (daq_utils.beamline == "fmx"): - setPvDesc("sampleProtect",1) - raise Exception('not in DA state') - zebraVecDaqSetup(omega,img_width_per_cell,exptimePerCell,numsteps,rasterFilePrefix,data_directory_name,file_number_start) #TODO set up a bunch of stuff? - procFlag = int(getBlConfig("rasterProcessFlag")) - - spotFindThreadList = [] - for i in range(len(rasterDef["rowDefs"])): - if (daq_lib.abort_flag == 1): # TODO better to do this in a catch/finally block? - gov_lib.setGovRobot(gov_robot, 'SA') - if (daq_utils.beamline == "fmx"): - setPvDesc("sampleProtect",1) - raise Exception('raster aborted') - # TODO surprisingly, no direct move to start position - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - startX = rasterDef["rowDefs"][i]["start"]["x"] - endX = rasterDef["rowDefs"][i]["end"]["x"] - startY = rasterDef["rowDefs"][i]["start"]["y"] - endY = rasterDef["rowDefs"][i]["end"]["y"] - deltaX = abs(endX-startX) - deltaY = abs(endY-startY) - if ((deltaX != 0) and (deltaX>deltaY or not getBlConfig("vertRasterOn"))): #horizontal raster - startY = startY + (stepsize/2.0) - endY = startY - else: #vertical raster - startX = startX + (stepsize/2.0) - endX = startX - - # TODO move all of this calculation stuff out - xRelativeMove = startX - - yzRelativeMove = startY*math.sin(omegaRad) - yyRelativeMove = startY*math.cos(omegaRad) - logger.info("x rel move = " + str(xRelativeMove)) - xMotAbsoluteMove = rasterStartX+xRelativeMove #note we convert relative to absolute moves, using the raster center that was saved in x,y,z - yMotAbsoluteMove = rasterStartY-yyRelativeMove - zMotAbsoluteMove = rasterStartZ-yzRelativeMove - xRelativeMove = endX-startX - yRelativeMove = endY-startY - - yyRelativeMove = yRelativeMove*math.cos(omegaRad) - yzRelativeMove = yRelativeMove*math.sin(omegaRad) - - xEnd = xMotAbsoluteMove + xRelativeMove - yEnd = yMotAbsoluteMove - yyRelativeMove - zEnd = zMotAbsoluteMove - yzRelativeMove - - if (i%2 != 0): #this is to scan opposite direction for snaking - xEndSave = xEnd - yEndSave = yEnd - zEndSave = zEnd - xEnd = xMotAbsoluteMove - yEnd = yMotAbsoluteMove - zEnd = zMotAbsoluteMove - xMotAbsoluteMove = xEndSave - yMotAbsoluteMove = yEndSave - zMotAbsoluteMove = zEndSave - zebraVecBluesky() # just do the vector scan part - xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(rasterdef[i], stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, i) - setPvDesc("zebraPulseMax",numsteps) #moved this - setPvDesc("vectorStartOmega",omega) - if (img_width_per_cell != 0): - setPvDesc("vectorEndOmega",(img_width_per_cell*numsteps)+omega) - else: - setPvDesc("vectorEndOmega",omega) - setPvDesc("vectorStartX",xMotAbsoluteMove) - setPvDesc("vectorStartY",yMotAbsoluteMove) - setPvDesc("vectorStartZ",zMotAbsoluteMove) - setPvDesc("vectorEndX",xEnd) - setPvDesc("vectorEndY",yEnd) - setPvDesc("vectorEndZ",zEnd) - setPvDesc("vectorframeExptime",exptimePerCell*1000.0) - setPvDesc("vectorNumFrames",numsteps) - rasterFilePrefix = dataFilePrefix + "_Raster_" + str(i) - scanWidth = float(numsteps)*img_width_per_cell - logger.info('raster done setting up') - vectorWaitForGo(source="snakeRasterNormal") - vectorWait() - zebraWait() - zebraWaitDownload(numsteps) - logger.info('done raster') - - # processing - if (procFlag): - if (daq_utils.detector_id == "EIGER-16"): - seqNum = int(det_lib.detector_get_seqnum()) - else: - seqNum = -1 - logger.info('beginning raster processing with dozor spot_level at %s' - % getBlConfig(RASTER_DOZOR_SPOT_LEVEL)) - spotFindThread = Thread(target=runDozorThread,args=(data_directory_name, #TODO this can't move outside of the thread checking block - ''.join([filePrefix,"_Raster"]), - i, - numsteps, - seqNum, - reqObj, - rasterReqID)) - spotFindThread.start() - spotFindThreadList.append(spotFindThread) - - """governor transitions: - initiate transitions here allows for GUI sample/heat map image to update - after moving to known position""" - logger.debug(f'lastOnSample(): {lastOnSample()} autoRasterFlag: {autoRasterFlag}') - if (lastOnSample() and not autoRasterFlag): - govStatus = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) # TODO standardize naming of govStatus and gov_status objects - targetGovState = 'SA' - else: - govStatus = gov_lib.setGovRobot(gov_robot, 'DI') - targetGovState = 'DI' - - # priorities: - # 1. make heat map visible to users correctly aligned with sample - # 2. take snapshot for ISPyB with heat map and sample visible (governor moved to - # a position with backlight in) and aligned - - #data acquisition is finished, now processing and sample positioning - if not procFlag: - #must go to known position to account for windup dist. - logger.info("moving to raster start") - gonio.put(rasterStartX, rasterStartY, rasterStartZ, omega) - logger.info("done moving to raster start") - - if (procFlag): - if daq_lib.abort_flag != 1: - [thread.join(timeout=120) for thread in spotFindThreadList] - else: - logger.info("raster aborted, do not wait for spotfind threads") - logger.info(str(processedRasterRowCount) + "/" + str(rowCount)) - rasterResult = generateGridMap(rasterRequest) - - logger.info(f'protocol = {reqObj["protocol"]}') - if (reqObj["protocol"] == CollectionProtocols.MULTI_COL or - parentReqProtocol == CollectionProtocols.MULTI_COL_Q): - if (parentReqProtocol == CollectionProtocols.MULTI_COL_Q): - multiColThreshold = parentReqObj["diffCutoff"] - else: - multiColThreshold = reqObj["diffCutoff"] - gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold) - else: - try: - # go to start omega for faster heat map display - gotoMaxRaster(rasterResult,omega=omega) - except ValueError: - #must go to known position to account for windup dist. - logger.info("moving to raster start") - beamline_lib.mvaDescriptor("sampleX",rasterStartX, - "sampleY",rasterStartY, - "sampleZ",rasterStartZ, - "omega",omega) - logger.info("done moving to raster start") - - """change request status so that GUI only fills heat map when - xrecRasterFlag PV is set""" - rasterRequest["request_obj"]["rasterDef"]["status"] = ( - RasterStatus.READY_FOR_FILL.value - ) - db_lib.updateRequest(rasterRequest) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - logger.info(f'setting xrecRasterFlag to: {rasterRequest["uid"]}') - except Exception as e: - logger.error(f'Exception while rastering: {e}') - return - finally: - #use this required pause to allow GUI time to fill map and for db update - logger.info('stopping detector') - det_lib.detector_stop_acquire() - det_lib.detector_wait() - logger.info('detector finished waiting') - - """change request status so that GUI only takes a snapshot of - sample plus heat map for ispyb when xrecRasterFlag PV is set""" - rasterRequestID = rasterRequest["uid"] - rasterRequest["request_obj"]["rasterDef"]["status"] = ( - RasterStatus.READY_FOR_SNAPSHOT.value - ) - db_lib.updateRequest(rasterRequest) - db_lib.updatePriority(rasterRequestID,-1) - - #ensure gov transitions have completed successfully - timeout = 20 - gov_lib.waitGov(govStatus, timeout) - if not(govStatus.success) or not(govs.gov.robot.state == targetGovState): - logger.error(f"gov status check failed, did not achieve {targetGovState}") - - if (procFlag): - """if sleep too short then black ispyb image, timing affected by speed - of governor transition. Sleep constraint can be relaxed with gov - transitions and concomitant GUI moved to an earlier stage.""" - if (rasterRequest["request_obj"]["rasterDef"]["numCells"] - > getBlConfig(RASTER_NUM_CELLS_DELAY_THRESHOLD)): - #larger rasters can delay GUI scene update - time.sleep(getBlConfig(RASTER_LONG_SNAPSHOT_DELAY)) - else: - time.sleep(getBlConfig(RASTER_SHORT_SNAPSHOT_DELAY)) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - time.sleep(getBlConfig(RASTER_POST_SNAPSHOT_DELAY)) - if (daq_utils.beamline == "fmx"): - setPvDesc("sampleProtect",1) - return 1 - -def raster_positions(currentRow, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, index): - numsteps = int(currentRow["numsteps"]) - startX = currentRow["start"]["x"] - endX = currentRow["end"]["x"] - startY = currentRow["start"]["y"] - endY = currentRow["end"]["y"] - deltaX = abs(endX-startX) - deltaY = abs(endY-startY) - if ((deltaX != 0) and (deltaX>deltaY or not getBlConfig("vertRasterOn"))): #horizontal raster - startY = startY + (stepsize/2.0) - endY = startY - else: #vertical raster - startX = startX + (stepsize/2.0) - endX = startX - - xRelativeMove = startX +def raster_positions(currentRow, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, index): + numsteps = int(currentRow["numsteps"]) + startX = currentRow["start"]["x"] + endX = currentRow["end"]["x"] + startY = currentRow["start"]["y"] + endY = currentRow["end"]["y"] + deltaX = abs(endX-startX) + deltaY = abs(endY-startY) + if ((deltaX != 0) and (deltaX>deltaY or not getBlConfig("vertRasterOn"))): #horizontal raster + startY = startY + (stepsize/2.0) + endY = startY + else: #vertical raster + startX = startX + (stepsize/2.0) + endX = startX + + xRelativeMove = startX yzRelativeMove = startY*math.sin(omegaRad) yyRelativeMove = startY*math.cos(omegaRad) @@ -1953,147 +991,21 @@ def params_from_raster_req_id(rasterReqID): detDist = reqObj["detDist"] rasterDef = reqObj["rasterDef"] stepsize = float(rasterDef["stepsize"]) - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) #these are real sample motor positions - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - rowCount = len(rasterDef["rowDefs"]) - - totalImages = 0 - for i in range(len(rasterDef["rowDefs"])): # TODO assume rectangular for current hardware? - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - totalImages = totalImages+numsteps - return data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, omega, rasterStartX, rasterStartY, rasterStartZ, omegaRad, rowCount, numsteps, totalImages, rasterDef["rowDefs"] - - -def reprocessRaster(rasterReqID): - global rasterRowResultsList,processedRasterRowCount - - rasterRequest = db_lib.getRequestByID(rasterReqID) - reqObj = rasterRequest["request_obj"] - data_directory_name = str(reqObj["directory"]) - filePrefix = str(reqObj["file_prefix"]) - file_number_start = reqObj["file_number_start"] - dataFilePrefix = reqObj["directory"]+"/"+reqObj["file_prefix"] - exptimePerCell = reqObj["exposure_time"] - img_width_per_cell = reqObj["img_width"] - wave = reqObj["wavelength"] - xbeam = getPvDesc("beamCenterX") - ybeam = getPvDesc("beamCenterY") - rasterDef = reqObj["rasterDef"] - stepsize = float(rasterDef["stepsize"]) - omega = float(rasterDef["omega"]) - rasterStartX = float(rasterDef["x"]) #these are real sample motor positions - rasterStartY = float(rasterDef["y"]) - rasterStartZ = float(rasterDef["z"]) - omegaRad = math.radians(omega) - rowCount = len(rasterDef["rowDefs"]) - rasterRowResultsList = [{} for i in range(0,rowCount)] - processedRasterRowCount = 0 - rasterEncoderMap = {} - totalImages = 0 - for i in range(len(rasterDef["rowDefs"])): - numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) - totalImages = totalImages+numsteps - rasterFilePrefix = dataFilePrefix + "_Raster" - total_exposure_time = exptimePerCell*totalImages - procFlag = 1 - - for i in range(len(rasterDef["rowDefs"])): - currentRow = rasterDef["rowDefs"][i] - time.sleep(0.5) - numsteps = int(currentRow["numsteps"]) - startX = currentRow["start"]["x"] - endX = currentRow["end"]["x"] - startY = currentRow["start"]["y"] - endY = currentRow["end"]["y"] - deltaX = abs(endX-startX) - deltaY = abs(endY-startY) - if ((deltaX != 0) and (deltaX>deltaY or not getBlConfig("vertRasterOn"))): #horizontal raster - startY = startY + (stepsize/2.0) - endY = startY - else: #vertical raster - startX = startX + (stepsize/2.0) - endX = startX - - xRelativeMove = startX - - yzRelativeMove = startY*math.sin(omegaRad) - yyRelativeMove = startY*math.cos(omegaRad) - xMotAbsoluteMove = rasterStartX+xRelativeMove #note we convert relative to absolute moves, using the raster center that was saved in x,y,z - yMotAbsoluteMove = rasterStartY-yyRelativeMove - zMotAbsoluteMove = rasterStartZ-yzRelativeMove - xRelativeMove = endX-startX - yRelativeMove = endY-startY - - yyRelativeMove = yRelativeMove*math.cos(omegaRad) - yzRelativeMove = yRelativeMove*math.sin(omegaRad) - - xEnd = xMotAbsoluteMove + xRelativeMove - yEnd = yMotAbsoluteMove - yyRelativeMove - zEnd = zMotAbsoluteMove - yzRelativeMove - - if (i%2 != 0): #this is to scan opposite direction for snaking - xEndSave = xEnd - yEndSave = yEnd - zEndSave = zEnd - xEnd = xMotAbsoluteMove - yEnd = yMotAbsoluteMove - zEnd = zMotAbsoluteMove - xMotAbsoluteMove = xEndSave - yMotAbsoluteMove = yEndSave - zMotAbsoluteMove = zEndSave - rasterFilePrefix = dataFilePrefix + "_Raster_" + str(i) - scanWidth = float(numsteps)*img_width_per_cell - if (procFlag): - #if (daq_utils.detector_id == "EIGER-16"): - # seqNum = int(det_lib.detector_get_seqnum()) - #else: - # seqNum = -1 - _thread.start_new_thread(runDialsThread,(rasterReqID, data_directory_name, - filePrefix+"_Raster", - i, - numsteps, - int(det_lib.detector_get_seqnum()))) #note the -1 last param. That eliminates rerun of eiger2cbf. -#I guess this starts the gather loop - if (procFlag): - rasterTimeout = 300 - timerCount = 0 - while (1): - timerCount +=1 - if (daq_lib.abort_flag == 1): - logger.error("caught abort waiting for raster!") - break - if (timerCount>rasterTimeout): - logger.error("Raster timeout!") - break - time.sleep(1) - logger.info(str(processedRasterRowCount) + "/" + str(rowCount)) - if (processedRasterRowCount == rowCount): - break - rasterResult = generateGridMap(rasterRequest) - rasterRequest["request_obj"]["rasterDef"]["status"] = ( - RasterStatus.READY_FOR_REPROCESS.value - ) - protocol = reqObj["protocol"] - logger.info("protocol = " + protocol) - try: - gotoMaxRaster(rasterResult) - except ValueError: - logger.info("reprocessRaster: no max raster found, did not move gonio") - - rasterRequestID = rasterRequest["uid"] - db_lib.updateRequest(rasterRequest) - db_lib.updatePriority(rasterRequestID,-1) - - if (procFlag): - """sleep allows for map update after gonio move, slightly longer - than 2 sec sleep for normal raster because no gov transition here""" - time.sleep(2.5) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - return 1 + omega = float(rasterDef["omega"]) + rasterStartX = float(rasterDef["x"]) #these are real sample motor positions + rasterStartY = float(rasterDef["y"]) + rasterStartZ = float(rasterDef["z"]) + omegaRad = math.radians(omega) + rowCount = len(rasterDef["rowDefs"]) + + totalImages = 0 + numsteps = 0 # init before loop; stays 0 if rowDefs is empty + for i in range(len(rasterDef["rowDefs"])): # TODO assume rectangular for current hardware? + numsteps = int(rasterDef["rowDefs"][i]["numsteps"]) + totalImages = totalImages+numsteps + return data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, omega, rasterStartX, rasterStartY, rasterStartZ, omegaRad, rowCount, numsteps, totalImages, rasterDef["rowDefs"] + def snakeRasterBluesky(rasterReqID, grain=""): global rasterRowResultsList,processedRasterRowCount @@ -2144,6 +1056,18 @@ def snakeRasterBluesky(rasterReqID, grain=""): raster_flyer.detector.stage() procFlag = int(getBlConfig("rasterProcessFlag")) spotFindThreadList = [] + + run_md = { + "plan_name": "snakeRaster", + "protocol": str(CollectionProtocols.RASTER), + "raster_req_id": rasterReqID, + "sample_id": rasterRequest["sample"], + "num_rows": rowCount, + "num_steps_per_row": numsteps, + } + raster_flyer._num_images = numsteps + yield from bps.open_run(md=run_md) + yield from bps.declare_stream(raster_flyer, name="primary", collect=True) for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(row, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, row_index) vector = {'x': (xMotAbsoluteMove, xEnd), 'y': (yMotAbsoluteMove, yEnd), 'z': (zMotAbsoluteMove, zEnd)} @@ -2153,7 +1077,7 @@ def snakeRasterBluesky(rasterReqID, grain=""): time.sleep(0.2) # necessary for reliable row processing - see comment in commit 6793f4 # processing if (procFlag): - if (daq_utils.detector_id == "EIGER-16"): + if (daq_utils.detector_id in EIGER_DETECTORS): seqNum = int(raster_flyer.detector.file.sequence_id.get()) else: seqNum = -1 @@ -2169,6 +1093,8 @@ def snakeRasterBluesky(rasterReqID, grain=""): spotFindThread.start() spotFindThreadList.append(spotFindThread) logger.info('row complete') + yield from bps.close_run() + """governor transitions: initiate transitions here allows for GUI sample/heat map image to update after moving to known position""" @@ -2403,87 +1329,23 @@ def snakeStepRaster(rasterReqID,grain=""): #12/19 - only tested recently, but ap return 1 -def setGridRasterParams(xsep,ysep,xstep,ystep,sizex,sizey,stepsize): - """setGridRasterParams(xsep,ysep,xstep,ystep,sizex,sizey,stepsize)""" - db_lib.setBeamlineConfigParam("fmx","gridRasterXSep",float(xsep)) - db_lib.setBeamlineConfigParam("fmx","gridRasterYSep",float(ysep)) - db_lib.setBeamlineConfigParam("fmx","gridRasterXStep",int(xstep)) - db_lib.setBeamlineConfigParam("fmx","gridRasterYStep",int(ystep)) - db_lib.setBeamlineConfigParam("fmx","gridRasterSizeX",float(sizex)) - db_lib.setBeamlineConfigParam("fmx","gridRasterSizeY",float(sizey)) - db_lib.setBeamlineConfigParam("fmx","gridRasterStepsize",float(stepsize)) - -def printGridRasterParams(): - """printGridRasterParams()""" - - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterXSep")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterYSep")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterXStep")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterYStep")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterSizeX")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterSizeY")) - logger.info(db_lib.getBeamlineConfigParam("fmx","gridRasterStepsize")) - - -def gridRaster(currentRequest): - gov_status = gov_lib.setGovRobot(gov_robot, 'DA') - if not gov_status.success: - return - - sampleID = currentRequest["sample"] - reqObj = currentRequest["request_obj"] - omega = beamline_lib.motorPosFromDescriptor("omega") - omegaRad = math.radians(omega) - xwells = int(db_lib.getBeamlineConfigParam("fmx","gridRasterXStep")) - ywells = int(db_lib.getBeamlineConfigParam("fmx","gridRasterYStep")) - xsep = float(db_lib.getBeamlineConfigParam("fmx","gridRasterXSep")) - ysep = float(db_lib.getBeamlineConfigParam("fmx","gridRasterYSep")) - sizex = float(db_lib.getBeamlineConfigParam("fmx","gridRasterSizeX")) - sizey = float(db_lib.getBeamlineConfigParam("fmx","gridRasterSizeY")) - stepsize = float(db_lib.getBeamlineConfigParam("fmx","gridRasterStepsize")) - rasterStartX = beamline_lib.motorPosFromDescriptor("sampleX") #these are real sample motor positions - rasterStartY = beamline_lib.motorPosFromDescriptor("sampleY") - rasterStartZ = beamline_lib.motorPosFromDescriptor("sampleZ") - yzRelativeMove = ysep*math.sin(omegaRad) - yyRelativeMove = ysep*math.cos(omegaRad) - for i in range (0,ywells): - for j in range (0,xwells): - beamline_lib.mvaDescriptor("sampleX",rasterStartX+(j*xsep),"sampleY",rasterStartY+(i*yyRelativeMove),"sampleZ",rasterStartZ+(i*yzRelativeMove)) - beamline_lib.mvaDescriptor("omega",omega) - rasterReqID = defineRectRaster(currentRequest,sizex,sizey,stepsize) - RE(snakeRaster(rasterReqID)) - - def runRasterScan(currentRequest,rasterType="", width=0, height=0, step_size=10, omega_rel=0): #this actually defines and runs sampleID = currentRequest["sample"] - if (rasterType=="Fine"): - daq_lib.set_field("xrecRasterFlag","100") - rasterReqID = defineRectRaster(currentRequest,90,90,10) - RE(snakeRaster(rasterReqID)) - elif (rasterType=="Coarse"): - daq_lib.set_field("xrecRasterFlag","100") - rasterReqID = defineRectRaster(currentRequest,630,390,30) - RE(snakeRaster(rasterReqID)) - elif (rasterType=="autoVector"): - daq_lib.set_field("xrecRasterFlag","100") - rasterReqID = defineRectRaster(currentRequest,615,375,15) - RE(snakeRaster(rasterReqID)) - elif (rasterType=="Line"): - daq_lib.set_field("xrecRasterFlag","100") - beamline_lib.mvrDescriptor("omega",90) - rasterReqID = defineRectRaster(currentRequest,10,290,10) - RE(snakeRaster(rasterReqID)) - daq_lib.set_field("xrecRasterFlag","100") - elif (rasterType=="Custom"): - daq_lib.set_field("xrecRasterFlag","100") - beamline_lib.mvrDescriptor("omega",omega_rel) - rasterReqID = defineRectRaster(currentRequest, width+step_size, height+step_size, step_size) - RE(snakeRaster(rasterReqID)) - else: - rasterReqID = getXrecLoopShape(currentRequest) - logger.info("snake raster " + str(rasterReqID)) - time.sleep(1) #I think I really need this, not sure why - RE(snakeRaster(rasterReqID)) + params = { + "Fine": {"omega": 0, "width": 90, "height": 90, "step_size": 10}, + "Coarse": {"omega": 0, "width": 630, "height": 390, "step_size": 30}, + "autoVector": {"omega": 0, "width": 615, "height": 375, "step_size": 15}, + "Line": {"omega": 90, "width": 10, "height": 290, "step_size": 10}, + } + selected_params = params.get(rasterType, {"omega": omega_rel, "width": width, "height": height, "step_size": step_size}) + + daq_lib.set_field("xrecRasterFlag", "100") + beamline_lib.mvrDescriptor("omega",omega_rel) + rasterReqID, collection_metadata = defineRectRaster(currentRequest, + selected_params["width"], + selected_params["height"], + selected_params["step_size"]) + RE(snakeRaster(rasterReqID), **get_bluesky_metadata(request_id=rasterReqID)) def get_score_vals(cellResults, scoreOption): """ @@ -2498,25 +1360,36 @@ def get_score_vals(cellResults, scoreOption): return score_vals def get_score_index(score_vals: "np.ndarray", scoreOption, indices=None): - """ - Returns the maximum or minimum score value in the 1d array of scores and the corresponding index - If specific indices are provided the max/min score will be selected from those indices + """ + Returns the max/min score and corresponding index. + If indices are provided, restricts to valid ones. + """ - """ - if indices: - score_vals = score_vals[indices] - if scoreOption == "d_min": - # If value is -1 replace with inf so that it is not considered for np.min - score_vals = np.where(score_vals == -1, np.inf, score_vals) - scoreVal = np.min(score_vals) - max_index = np.argmin(score_vals) - else: - scoreVal = np.max(score_vals) - max_index = np.argmax(score_vals) - - max_index = max_index if not indices else indices[max_index] + valid_indices = None + + if indices is not None: + indices = np.array(indices) + valid_indices = indices[(indices >= 0) & (indices < len(score_vals))] + + # Handle empty case early + if valid_indices.size == 0: + return None, None + + score_vals = score_vals[valid_indices] + + if scoreOption == "d_min": + score_vals = np.where(score_vals == -1, np.inf, score_vals) + scoreVal = np.min(score_vals) + max_index = np.argmin(score_vals) + else: + scoreVal = np.max(score_vals) + max_index = np.argmax(score_vals) - return scoreVal, max_index + # Map back to original indices if needed + if valid_indices is not None: + max_index = valid_indices[max_index] + + return scoreVal, max_index def get_gonio_pos_from_raster_result(cell_results, raster_map, index): """ @@ -2581,7 +1454,7 @@ def gotoMaxRaster(rasterResult,multiColThreshold=None,**kwargs): raise ValueError("No max position found for gonio move") def run_auto_raster(max_index, score_vals, scoreOption, cellResults, rasterMap, **kwargs): - global max_col, face_on_max_coords, ortho_max_coords + global max_col, face_on_max_coords, ortho_max_coords hotFile, (x, y, z) = get_gonio_pos_from_raster_result(cellResults, rasterMap, max_index) if "rasterRequest" in kwargs and autoRasterFlag: # Update the raster request with the location of the max raster image and co-ordinates. @@ -2602,6 +1475,10 @@ def run_auto_raster(max_index, score_vals, scoreOption, cellResults, rasterMap, else: # max_col should be available for orthogonal rasters # Find maximum in col defined in max_col and then reset max_col + if getBlConfig("use_mini_raster"): + raster_dir, num_rows, num_cols = determine_raster_shape(rasterDef) + max_col = num_cols//2 + indices = get_flattened_indices_of_max_col(rasterDef, max_col) score_val, max_index = get_score_index(score_vals, scoreOption, indices) hotFile, (x, y, z) = get_gonio_pos_from_raster_result(cellResults, rasterMap, max_index) @@ -2756,32 +1633,7 @@ def screenYPixels2microns(pixels): fovY = fov["y"] return float(pixels)*(fovY/daq_utils.lowMagPixY) -def defineTiledRaster(rasterDef,numsteps_h,numsteps_v,origRasterCenterScreenX,origRasterCenterScreenY): #I need this to redefine composite for tiling. Much of the reqObf can stay same. - - rasterDef["rowDefs"] = [] - stepsize = rasterDef["stepsize"] - point_offset_x = origRasterCenterScreenX-(numsteps_h*stepsize)/2.0 - point_offset_y = origRasterCenterScreenY-(numsteps_v*stepsize)/2.0 - if (numsteps_v > numsteps_h): #vertical raster - for i in range(numsteps_h): - vectorStartX = point_offset_x+(i*stepsize) - vectorEndX = vectorStartX - vectorStartY = point_offset_y - vectorEndY = vectorStartY + (numsteps_v*stepsize) - newRowDef = {"start":{"x": vectorStartX,"y":vectorStartY},"end":{"x":vectorEndX,"y":vectorEndY},"numsteps":numsteps_v} - rasterDef["rowDefs"].append(newRowDef) - else: #horizontal raster - for i in range(numsteps_v): - vectorStartX = point_offset_x - vectorEndX = vectorStartX + (numsteps_h*stepsize) - vectorStartY = point_offset_y+(i*stepsize) - vectorEndY = vectorStartY - newRowDef = {"start":{"x": vectorStartX,"y":vectorStartY},"end":{"x":vectorEndX,"y":vectorEndY},"numsteps":numsteps_h} - rasterDef["rowDefs"].append(newRowDef) - rasterDef["status"] = RasterStatus.DRAWN.value # this will tell clients that the raster should be displayed. - return rasterDef - -def defineRectRaster(currentRequest,raster_w_s,raster_h_s,stepsizeMicrons_s,xoff=0.0,yoff=0.0,zoff=0.0): #maybe point_x and point_y are image center? #everything can come as microns, make this a horz vector scan, note this never deals with pixels. +def defineRectRaster(currentRequest,raster_w_s,raster_h_s,stepsizeMicrons_s,xoff=0.0,yoff=0.0,zoff=0.0): sampleID = currentRequest["sample"] sample = db_lib.getSampleByID(sampleID) @@ -2795,8 +1647,8 @@ def defineRectRaster(currentRequest,raster_w_s,raster_h_s,stepsizeMicrons_s,xoff beamWidth = stepsize beamHeight = stepsize rasterDef = {"beamWidth":beamWidth,"beamHeight":beamHeight,"status":RasterStatus.NEW.value,"x":beamline_lib.motorPosFromDescriptor("sampleX")+xoff,"y":beamline_lib.motorPosFromDescriptor("sampleY")+yoff,"z":beamline_lib.motorPosFromDescriptor("sampleZ")+zoff,"omega":beamline_lib.motorPosFromDescriptor("omega"),"stepsize":stepsize,"rowDefs":[]} - numsteps_h = int(raster_w/stepsize) - numsteps_v = int(raster_h/stepsize) #the numsteps is decided in code, so is already odd + numsteps_h = max(1, int(raster_w/stepsize)) + numsteps_v = max(1, int(raster_h/stepsize)) #the numsteps is decided in code, so is already odd rasterDef["numCells"] = numsteps_h * numsteps_v point_offset_x = -(numsteps_h*stepsize)/2.0 point_offset_y = -(numsteps_v*stepsize)/2.0 @@ -2838,6 +1690,9 @@ def defineRectRaster(currentRequest,raster_w_s,raster_h_s,stepsizeMicrons_s,xoff reqObj["xbeam"] = currentRequest['request_obj']["xbeam"] reqObj["ybeam"] = currentRequest['request_obj']["ybeam"] reqObj["wavelength"] = currentRequest['request_obj']["wavelength"] + reqObj["detDist"] = currentRequest["request_obj"]["detDist"] + reqObj["energy"] = beamline_devices.optics.energy.user_readback.get() + reqObj["resolution"] = daq_utils.calc_reso(daq_utils.det_radius, reqObj["detDist"], reqObj["wavelength"], 0) # request params to save the file and location of max_raster cell # This data is saved as part of the raster request and not result is because analysisstore does not allow updating reqObj["max_raster"] = { @@ -2849,37 +1704,9 @@ def defineRectRaster(currentRequest,raster_w_s,raster_h_s,stepsizeMicrons_s,xoff newRasterRequestUID = db_lib.addRequesttoSample(sampleID,reqObj["protocol"],daq_utils.owner,reqObj,priority=5000,proposalID=propNum) daq_lib.set_field("xrecRasterFlag",newRasterRequestUID) time.sleep(1) - return newRasterRequestUID - - - -def collectSpec(filename,gotoSA=True): - """collectSpec(filenamePrefix) : collect a spectrum, save to file""" - gov_lib.setGovRobot(gov_robot, 'XF') - daq_lib.open_shutter() - setPvDesc("mercuryEraseStart",1) - while (1): - if (getPvDesc("mercuryReadStat") == 0): - break - time.sleep(0.05) - specArray = getPvDesc("mercurySpectrum") - plt.plot(specArray) - plt.show(block=False) - nowtime_s = str(int(time.time())) - specFileName = filename + "_" + nowtime_s + ".txt" - specFile = open(specFileName,"w+") - channelCount = len(specArray) - for i in range (0,channelCount): - if (i == 0): - specFile.write(str(specArray[i])) - else: - specFile.write("," + str(specArray[i])) - specFile.close() - daq_lib.close_shutter() - if (gotoSA): - gov_lib.setGovRobot(gov_robot, 'SA') + return newRasterRequestUID, reqObj - + def eScan(energyScanRequest): plt.clf() sampleID = energyScanRequest["sample"] @@ -2995,113 +1822,10 @@ def eScan(energyScanRequest): daq_lib.set_field("choochResultFlag",choochResultID) def vectorZebraScan(vecRequest): - scannerType = getBlConfig("scannerType") - if (scannerType == "PI"): - vectorZebraScanFine(vecRequest) - else: - finalize_plan = finalize_wrapper(vectorZebraScanNormal(vecRequest), bps.mv(flyer.detector.cam.acquire, 0)) - yield from finalize_plan - - -def vectorZebraScanFine(vecRequest): - gov_status = gov_lib.setGovRobot(gov_robot, 'DA') - if not gov_status.success: - return - - reqObj = vecRequest["request_obj"] - file_prefix = str(reqObj["file_prefix"]) - data_directory_name = str(reqObj["directory"]) - file_number_start = reqObj["file_number_start"] - - sweep_start_angle = reqObj["sweep_start"] - sweep_end_angle = reqObj["sweep_end"] - imgWidth = reqObj["img_width"] - expTime = reqObj["exposure_time"] - numImages = int((sweep_end_angle - sweep_start_angle) / imgWidth) - x_vec_start=reqObj["vectorParams"]["vecStart"]["x"] - y_vec_start=reqObj["vectorParams"]["vecStart"]["y"] - z_vec_start=reqObj["vectorParams"]["vecStart"]["z"] - x_vec_end=reqObj["vectorParams"]["vecEnd"]["x"] - y_vec_end=reqObj["vectorParams"]["vecEnd"]["y"] - z_vec_end=reqObj["vectorParams"]["vecEnd"]["z"] + finalize_plan = finalize_wrapper(vectorZebraScanNormal(vecRequest), bps.mv(flyer.detector.cam.acquire, 0)) + yield from finalize_plan - xCenterCoarse = (x_vec_end+x_vec_start)/2.0 - yCenterCoarse = (y_vec_end+y_vec_start)/2.0 - zCenterCoarse = (z_vec_end+z_vec_start)/2.0 - beamline_lib.mvaDescriptor("sampleX",xCenterCoarse,"sampleY",yCenterCoarse,"sampleZ",zCenterCoarse) - xRelLen = x_vec_end-x_vec_start - xRelStart = -xRelLen/2.0 - xRelEnd = -xRelStart - yRelLen = y_vec_end-y_vec_start - yRelStart = -yRelLen/2.0 - yRelEnd = -yRelStart - zRelLen = z_vec_end-z_vec_start - zRelStart = -zRelLen/2.0 - zRelEnd = -zRelStart - - det_lib.detector_set_num_triggers(numImages) - det_lib.detector_set_trigger_mode(3) - det_lib.detector_setImagesPerFile(1000) - daq_lib.detectorArm(sweep_start_angle,imgWidth,numImages,expTime,file_prefix,data_directory_name,file_number_start) #this waits - - zebraVecDaqSetup(sweep_start_angle,imgWidth,expTime,numImages,file_prefix,data_directory_name,file_number_start) - - - total_exposure_time=expTime*numImages - trajPoints = int(total_exposure_time/.005) - totalScanWidthX = xRelLen - totalScanWidthY = yRelLen - totalScanWidthZ = zRelLen - xRelativeMoveFine = xRelStart - yRelativeMoveFine = yRelStart - zRelativeMoveFine = zRelStart - beamline_lib.mvaDescriptor("fineX",xRelativeMoveFine,"fineY",yRelativeMoveFine,"fineZ",zRelativeMoveFine) - setPvDesc("fineXPoints",trajPoints) - setPvDesc("fineYPoints",trajPoints) - setPvDesc("fineZPoints",trajPoints) - setPvDesc("fineXAmp",totalScanWidthX) - setPvDesc("fineYAmp",totalScanWidthY) - setPvDesc("fineZAmp",totalScanWidthZ) - setPvDesc("fineXOffset",xRelativeMoveFine) - setPvDesc("fineYOffset",yRelativeMoveFine) - setPvDesc("fineZOffset",zRelativeMoveFine) - setPvDesc("fineXSendWave",1) - time.sleep(0.1) - setPvDesc("fineYSendWave",1) - time.sleep(0.1) - setPvDesc("fineZSendWave",1) - time.sleep(0.1) - #move xyz fine mots to relative from centered raster - setPvDesc("fineXVecGo",1) - time.sleep(0.1) - setPvDesc("fineYVecGo",1) - time.sleep(0.1) - setPvDesc("fineZVecGo",1) - time.sleep(0.1) - setPvDesc("zebraPulseMax",numImages) #moved this - vectorSync() - setPvDesc("vectorStartOmega",sweep_start_angle) - setPvDesc("vectorEndOmega",sweep_end_angle) - setPvDesc("vectorframeExptime",expTime*1000.0) - setPvDesc("vectorNumFrames",numImages) - setPvDesc("vectorGo",1) - vectorActiveWait() - vectorWait() - zebraWait() - zebraWaitDownload(numImages) - time.sleep(2.0) - det_lib.detector_stop_acquire() - det_lib.detector_wait() - if (daq_utils.beamline == "amxz"): - setPvDesc("zebraReset",1) - - #raster centered, now zero motors - beamline_lib.mvaDescriptor("fineX",0,"fineY",0,"fineZ",0) - - if (lastOnSample()): - gov_lib.setGovRobot(gov_robot, 'SA') - def vectorZebraScanNormal(vecRequest): reqObj = vecRequest["request_obj"] file_prefix = str(reqObj["file_prefix"]) @@ -3122,7 +1846,7 @@ def vectorZebraScanNormal(vecRequest): det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") # TODO replace this area with gatherStandardVectorParams det_distance_m /= 1000 # on all beamlines, detectorDist is in mm so convert distance to m - if daq_utils.beamline in ("nyx", "fmx"): + if daq_utils.beamline in ("fmx",): transmission = getPvDesc("RI_Atten_SP") else: transmission = getPvDesc("transmissionRBV") @@ -3219,10 +1943,8 @@ def dna_execute_collection3(dna_startIgnore,dna_range,dna_number_of_images,dna_e det_radius = 105.0 elif (daq_utils.detector_id == "PILATUS-6"): det_radius = 212.0 - elif daq_utils.detector_id == "EIGER-16" and daq_utils.beamline in ("amx", "nyx"): + elif daq_utils.detector_id in EIGER_DETECTORS: det_radius = 233.1 # Eiger2-9M - elif daq_utils.detector_id == "EIGER-16": # FMX - det_radius = 311.1 # Eiger2-16M else: #default Pilatus det_radius = 212.0 theta_radians = 0.0 @@ -3409,7 +2131,7 @@ def dna_execute_collection3(dna_startIgnore,dna_range,dna_number_of_images,dna_e return 1 def setTrans(transmission): #where transmission = 0.0-1.0 - if (daq_utils.beamline in ["fmx", "nyx"]): + if (daq_utils.beamline in ["fmx",]): if (getBlConfig("attenType") == "RI"): setPvDesc("RIattenEnergySP",beamline_lib.motorPosFromDescriptor("energy")) setPvDesc("RI_Atten_SP",transmission) @@ -3423,9 +2145,8 @@ def setTrans(transmission): #where transmission = 0.0-1.0 setPvDesc("transmissionSet",transmission) setPvDesc("transmissionGo",1) time.sleep(0.5) - if daq_utils.beamline != "nyx": # transmissionDone not available on NYX - while (not getPvDesc("transmissionDone")): - time.sleep(0.1) + while (not getPvDesc("transmissionDone")): + time.sleep(0.1) @@ -3456,20 +2177,6 @@ def zebraDaqPrep(): setPvDesc("zebraM3SetPosProc",1) setPvDesc("zebraArmTrigSource",1) - -def zebraArm(): - setPvDesc("zebraArm",1) - while(1): - time.sleep(.1) - if (getPvDesc("zebraArmOut") == 1): - break - -def zebraWaitOld(): - while(1): - time.sleep(.1) - if (getPvDesc("zebraDownloading") == 0): - break - def zebraWait(timeoutCheck=True): timeoutLimit = 20.0 downloadStart = time.time() @@ -3484,46 +2191,7 @@ def zebraWait(timeoutCheck=True): if (getPvDesc("zebraDownloading") == 0): break -def zebraWaitDownload(numsteps): - - timeoutLimit = 5.0 - downloadStart = time.time() - while(1): - now = time.time() - if (now > (downloadStart+timeoutLimit)): - setPvDesc("zebraReset",1) - logger.error("timeout in zebra wait download!") - break - time.sleep(.1) - if (getPvDesc("zebraDownloadCount") == numsteps): - break -def loop_center_mask(): - os.system("cp $CONFIGDIR/bkgrnd.jpg .") - beamline_lib.mvrDescriptor("omega",90.0) - daq_utils.take_crystal_picture(filename="findslice_0") - comm_s = f'{C3D_SEARCH_BASE}find_loopslice.txt' - logger.debug(f'loop_center_mask: {comm_s}') - os.system(comm_s) - os.system("dos2unix res0.txt") - os.system("echo \"\n\">>res0.txt") - c3d_out_file = open("res0.txt","r") - line = c3d_out_file.readline() - loop_line = c3d_out_file.readline() - c3d_out_file.close() - loop_tokens = loop_line.split() - logger.info(loop_tokens) - loop_found = int(loop_tokens[2]) -#crash here if loop not found - if (loop_found > 0): - x = float(loop_tokens[9]) - y = float(loop_tokens[10]) - logger.info("x = " + str(x) + " y = " + str(y)) - fovx = daq_utils.lowMagFOVx - fovy = daq_utils.lowMagFOVy - daq_lib.center_on_click(320.0,y,fovx,fovy,source="macro") - beamline_lib.mvrDescriptor("omega",-90.0) - def getLoopSize(): os.system("cp $CONFIGDIR/bkgrnd.jpg .") daq_utils.take_crystal_picture(filename="findsize_0") @@ -3569,12 +2237,7 @@ def loop_center_xrec(): pic_prefix = "findloop" output_file = 'xrec_result.txt' clean_up_files(pic_prefix, output_file) - if daq_utils.beamline=='nyx': - print('post clean') - xrec_no_zebra(0) - print('post no zebra') - else: - zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) + zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0) comm_s = f'xrec {os.environ["CONFIGDIR"]}/xrec_360_40Fast.txt {output_file}' logger.info(comm_s) try: @@ -3714,7 +2377,7 @@ def gatherStandardVectorParams(): y_beam = getPvDesc("beamCenterY") wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 - if daq_utils.beamline in ("nyx", "fmx"): + if daq_utils.beamline in ("fmx",): transmission = getPvDesc("RI_Atten_SP") else: transmission = getPvDesc("transmissionRBV") @@ -3729,341 +2392,6 @@ def standard_zebra_plan(flyer,angle_start,num_images,scanWidth,imgWidth,exposure final_plan = finalize_wrapper(zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path), bps.mv(flyer.detector.cam.acquire, 0)) yield from final_plan -def standard_plan_wrapped(currentRequest): - yield from finalize_wrapper(standardDaq(currentRequest), clean_up_collection()) - -def vector_plan_wrapped(currentRequest): - yield from finalize_wrapper(vectorDaq(currentRequest), clean_up_collection()) - -def raster_plan_wrapped(rasterReqID): - yield from finalize_wrapper(rasterDaq(rasterReqID), clean_up_collection()) - #time.sleep(15) - #rasterRequest = db_lib.getRequestByID(rasterReqID) - #rasterResult = generateGridMap(rasterRequest) - #rasterRequest["request_obj"]["rasterDef"]["status"] = ( - # RasterStatus.READY_FOR_SNAPSHOT.value - #) - #db_lib.updateRequest(rasterRequest) - #db_lib.updatePriority(rasterReqID,-1) - #daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - - - -def standardDaq(currentRequest): - # collect all parameters - # perform preparatory movements - # arm the detector - # perform governor and phase transitions - # update flyer parameters - # fly - x_beam = getPvDesc("beamCenterX") - y_beam = getPvDesc("beamCenterY") - wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 - reqObj = currentRequest["request_obj"] - file_prefix = str(reqObj["file_prefix"]) - data_directory_name = str(reqObj["directory"]) - file_number_start = reqObj["file_number_start"] - sweep_start_angle = reqObj["sweep_start"] - sweep_end_angle = reqObj["sweep_end"] - file_prefix = str(reqObj["file_prefix"]) - data_directory_name = str(reqObj["directory"]) - file_number_start = reqObj["file_number_start"] - img_width = reqObj["img_width"] - exposure_per_image = reqObj["exposure_time"] - total_num_images = int(round(((sweep_end_angle - sweep_start_angle) / img_width), 4)) - total_exposure_time = exposure_per_image * total_num_images - scan_range = float(total_num_images)*img_width - angle_start = sweep_start_angle - wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - - yield from bps.mv(beamstop.distance_preset, 20.0) - md2.save_center() - if det_move_done.get() != 1: - def det_move_done_callback(value, old_value, **kwargs): - return (old_value!=1 and value ==1) - det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) - det_move_status.wait() - if flyer.detector.cam.armed.get() == 1: - daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' - 'should check the most recent collection to determine if it was successful. Cancelling' - 'this collection, retry when ready.') - logger.warning("Detector was in the armed state prior to this attempted collection.") - return 0 - start_time = time.time() - logger.info(f"Configuring detector for standard collection with file_prefix {file_prefix} and data_directory_name {data_directory_name}") - flyer.configure_detector(file_prefix, data_directory_name) - logger.info(f"Arming detector for standard collection with angle_start {angle_start}, img_width {img_width}, total_num_images {total_num_images}, exposure_per_image {exposure_per_image}, file_prefix {file_prefix}, data_directory_name {data_directory_name}, file_number_start {file_number_start}, x_beam {x_beam}, y_beam {y_beam}, wavelength {wavelength}, det_distance_m {det_distance_m}") - flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, - file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m) - def armed_callback(value, old_value, **kwargs): - return (old_value == 0 and value == 1) - arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False) - flyer.detector.cam.acquire.put(1) - govStatus = gov_lib.setGovRobot(gov_robot, "DA") - try: - arm_status.wait(timeout=20) - govStatus.wait(timeout=20) - except WaitTimeoutError: - logger.error("Timeout during arming or governor move, aborting collection") - return - logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") - if govStatus.exception(): - logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") - return - flyer.detector.stage() - start_time = time.time() - yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - try: - md2.ready_status().wait(timeout=10) - except WaitTimeoutError: - logger.error("timeout: md2 failed to enter ready state, aborting collection") - return - logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - flyer.update_parameters(total_num_images, angle_start, scan_range, total_exposure_time) - logger.info(f"flyer handoff") - yield from bp.fly([flyer]) - logger.info(f"fly complete") - -def vectorDaq(currentRequest): - # collect all parameters - # perform preparatory movements - # arm the detector - # perform governor and phase transitions - # update flyer parameters - # fly - x_beam = getPvDesc("beamCenterX") - y_beam = getPvDesc("beamCenterY") - wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - det_distance_m = beamline_lib.motorPosFromDescriptor("detectorDist") / 1000 - reqObj = currentRequest["request_obj"] - file_prefix = str(reqObj["file_prefix"]) - data_directory_name = str(reqObj["directory"]) - file_number_start = reqObj["file_number_start"] - sweep_start_angle = reqObj["sweep_start"] - sweep_end_angle = reqObj["sweep_end"] - file_prefix = str(reqObj["file_prefix"]) - data_directory_name = str(reqObj["directory"]) - file_number_start = reqObj["file_number_start"] - img_width = reqObj["img_width"] - exposure_per_image = reqObj["exposure_time"] - total_num_images = int(round(((sweep_end_angle - sweep_start_angle) / img_width), 4)) - total_exposure_time = reqObj["exposure_time"] * total_num_images - scan_range = float(total_num_images)*img_width - angle_start = sweep_start_angle - wavelength = daq_utils.energy2wave(beamline_lib.motorPosFromDescriptor("energy"), digits=6) - - vector_params = reqObj["vectorParams"] - start_y=vector_params["vecStart"]["y"] - start_z=vector_params["vecStart"]["z"] - start_cx=vector_params["vecStart"]["finex"] - start_cy=vector_params["vecStart"]["finey"] - stop_cx=vector_params["vecEnd"]["finex"] - stop_cy=vector_params["vecEnd"]["finey"] - stop_y=vector_params["vecEnd"]["y"] - stop_z=vector_params["vecEnd"]["z"] - if det_move_done.get() != 1: - def det_move_done_callback(value, old_value, **kwargs): - return (old_value!=1 and value ==1) - det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) - det_move_status.wait() - - md2.save_center() - yield from bps.mv(beamstop.distance_preset, 20.0) - - if vector_flyer.detector.cam.armed.get() == 1: - daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' - 'should check the most recent collection to determine if it was successful. Cancelling' - 'this collection, retry when ready.') - logger.warning("Detector was in the armed state prior to this attempted collection.") - return 0 - start_time = time.time() - vector_flyer.configure_detector(file_prefix, data_directory_name) - vector_flyer.detector_arm(angle_start, img_width, total_num_images, exposure_per_image, - file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m) - def armed_callback(value, old_value, **kwargs): - return (old_value == 0 and value == 1) - arm_status = SubscriptionStatus(vector_flyer.detector.cam.armed, armed_callback, run=False) - vector_flyer.detector.cam.acquire.put(1) - govStatus = gov_lib.setGovRobot(gov_robot, "DA") - try: - arm_status.wait(timeout=10) - govStatus.wait(timeout=20) - except WaitTimeoutError: - logger.error("Timeout reached during arming or governor move, aborting") - return - logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") - if govStatus.exception(): - logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") - return - flyer.detector.stage() - start_time = time.time() - yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - try: - md2.ready_status().wait(timeout=10) - except WaitTimeoutError: - logger.error("timeout: md2 failed to reach ready state, aborting") - return - logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - vector_flyer.update_parameters(angle_start, scan_range, total_exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy) - yield from bp.fly([vector_flyer]) - -def rasterDaq(rasterReqID): - global rasterRowResultsList,processedRasterRowCount - data_directory_name, file_prefix, file_number_start, dataFilePrefix, exposure_per_image, img_width_per_cell, wavelength, detDist, rasterDef, stepsize, start_omega, start_x, start_y, start_z, omegaRad, number_of_lines, numsteps, total_num_images, rows = params_from_raster_req_id(rasterReqID) - rasterRowResultsList = [{} for i in range(0,number_of_lines)] - processedRasterRowCount = 0 - rasterRequest = db_lib.getRequestByID(rasterReqID) - reqObj = rasterRequest["request_obj"] - parentReqID = reqObj["parentReqID"] - - xbeam = getPvDesc("beamCenterX") - ybeam = getPvDesc("beamCenterY") - if (parentReqID != -1): - parentRequest = db_lib.getRequestByID(parentReqID) - parentReqObj = parentRequest["request_obj"] - detDist = parentReqObj["detDist"] - - rasterFilePrefix = dataFilePrefix # + "_Raster" - - logger.info(f"prepping raster with: {rasterFilePrefix}, {data_directory_name}, {file_number_start}, {dataFilePrefix}, {exposure_per_image}, {img_width_per_cell}, {wavelength}, {detDist}, {rasterDef}, {stepsize}, {start_omega}, {start_x}, {start_y}, {start_z}, {omegaRad}, {number_of_lines}, {numsteps}, {total_num_images}, {rows}") - #logger.info(f"req_obj: {reqObj}") - i = 0 - xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(rows[i], stepsize, (start_omega*0), start_x, start_y, start_z, i) - stepsize /= 1000 # MD2 wants mm - logger.info(f"move calculations: {xMotAbsoluteMove}, {xEnd}, {yMotAbsoluteMove}, {yEnd}, {zMotAbsoluteMove}, {zEnd}") - line_range = stepsize * numsteps - total_uturn_range = stepsize * number_of_lines - start_y = start_y - (xEnd / 1000) - start_z = start_z - (yMotAbsoluteMove / 1000) - #start_z = start_z - (xEnd / 1000) - start_cx = md2.cx.val()# + (xEnd/1000) - start_cy = md2.cy.val() - frames_per_line = numsteps - total_exposure_time = exposure_per_image * frames_per_line - invert_direction = False - use_centring_table = True - use_fast_mesh_scans = True - omega_range = 0 - logger.info(f"TASK INFO: {md2.task_info.get()}") - logger.info(f"TASK INFO[6]: {md2.task_info.get()[6]=='1'}") - logger.info(f"TASK OUTPUT: {md2.task_output}") - logger.info(f"omega_range = {omegaRad}") - logger.info(f"line_range = {line_range}") - logger.info(f"total_uturn_range = {total_uturn_range}") - logger.info(f"start_omega = {start_omega}") - logger.info(f"start_y = {start_y}") - logger.info(f"current yzcxcy: {md2.y.get()}, {md2.z.get()}, {md2.cx.get()}, {md2.cy.get()}") - logger.info(f"start_z = {start_z}") - logger.info(f"start_cx = {start_cx}") - logger.info(f"start_cy = {start_cy}") - logger.info(f"number_of_lines = {number_of_lines}") - logger.info(f"frames_per_line = {frames_per_line}") - logger.info(f"total_exposure_time = {total_exposure_time}") - logger.info(f"invert_direction = {invert_direction}") - logger.info(f"use_centring_table = {use_centring_table}") - logger.info(f"use_fast_mesh_scans = {use_fast_mesh_scans}") - if det_move_done.get() != 1: - def det_move_done_callback(value, old_value, **kwargs): - return (old_value!=1 and value ==1) - det_move_status = SubscriptionStatus(det_move_done, det_move_done_callback, run=False) - det_move_status.wait() - - md2.save_center() - yield from bps.mv(beamstop.distance_preset, 20.0) - - if raster_flyer.detector.cam.armed.get() == 1: - daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user ' - 'should check the most recent collection to determine if it was successful. Cancelling' - 'this collection, retry when ready.') - logger.warning("Detector was in the armed state prior to this attempted collection.") - return 0 - start_time = time.time() - raster_flyer.configure_detector(rasterFilePrefix, data_directory_name) - raster_flyer.detector_arm(start_omega, img_width_per_cell, total_num_images, exposure_per_image, - file_prefix, data_directory_name, file_number_start, xbeam, ybeam, - wavelength, detDist) - def armed_callback(value, old_value, **kwargs): - return (old_value == 0 and value == 1) - arm_status = SubscriptionStatus(raster_flyer.detector.cam.armed, armed_callback, run=False) - raster_flyer.detector.cam.acquire.put(1) - govStatus = gov_lib.setGovRobot(gov_robot, "DA") - try: - arm_status.wait(timeout=10) - govStatus.wait(timeout=20) - except WaitTimeoutError: - logger.error("arming or governor status failure") - return - logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.") - if govStatus.exception(): - logger.error(f"Problem during start-of-collection governor move, aborting! exception: {govStatus.exception()}") - return - flyer.detector.stage() - start_time = time.time() - yield from bps.mv(md2.phase, 2) # TODO: Enum for MD2 phases and states - try: - md2.ready_status().wait(timeout=10) - except: - logger.error("md2 failed to reach ready state, aborting collection") - return - logger.info(f"MD2 phase transition to 2-DataCollection took {time.time()-start_time} seconds.") - raster_flyer.update_parameters(omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, total_exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans) - yield from bp.fly([raster_flyer]) - spotFindThreadList = [] - row_index = 1 - logger.info(f"raster prefix {rasterFilePrefix}") - rasterFilePrefix = rasterFilePrefix.split("/")[-1] - logger.info(f"raster prefix {rasterFilePrefix}") - for i in range(0, number_of_lines): - time.sleep(1.0) - row_index = i - logger.info(f'spot finding for row {i}') - seqNum = raster_flyer.detector.cam.sequence_id.get() - spotFindThread = Thread(target=runDozorThread,args=(data_directory_name, #TODO this can't move outside of the thread checking block - rasterFilePrefix, - row_index, - numsteps, - seqNum, - reqObj, - rasterReqID)) - spotFindThread.start() - spotFindThreadList.append(spotFindThread) - [thread.join(timeout=120) for thread in spotFindThreadList] - logger.info(str(processedRasterRowCount) + "/" + str(number_of_lines)) - rasterResult = generateGridMap(rasterRequest) - rasterRequestID = rasterRequest["uid"] - rasterRequest["request_obj"]["rasterDef"]["status"] = ( - RasterStatus.READY_FOR_SNAPSHOT.value - ) - db_lib.updateRequest(rasterRequest) - db_lib.updatePriority(rasterRequestID,-1) - if (rasterRequest["request_obj"]["rasterDef"]["numCells"] - > getBlConfig(RASTER_NUM_CELLS_DELAY_THRESHOLD)): - #larger rasters can delay GUI scene update - time.sleep(getBlConfig(RASTER_LONG_SNAPSHOT_DELAY)) - else: - time.sleep(getBlConfig(RASTER_SHORT_SNAPSHOT_DELAY)) - daq_lib.set_field("xrecRasterFlag",rasterRequest["uid"]) - - - - - -def clean_up_collection(): - # this is a plan that should will always be run after a collection is complete - start_time = time.time() - yield from bps.mv(flyer.detector.cam.acquire, 0) - flyer.detector.unstage() - if (lastOnSample()): - gov_status = gov_lib.setGovRobot(gov_robot, 'SA', wait=False) - gov_status.wait(timeout=30) - yield from bps.mv(md2.phase, 0) - md2.ready_status().wait(timeout= 20) - # trigger processing here - logger.info(f"clean_up took {time.time()-start_time} seconds.") - def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposurePeriodPerImage, filePrefix, data_directory_name, file_number_start, vector_params, data_path, scanEncoder=3, changeState=True): logger.info("in Zebra Daq Bluesky #1") @@ -4075,13 +2403,6 @@ def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposur x_vec_end=vector_params["vecEnd"]["x"] y_vec_end=vector_params["vecEnd"]["y"] z_vec_end=vector_params["vecEnd"]["z"] - if beamline == "nyx": - x_vec_start *= 1000 - y_vec_start *= 1000 - z_vec_start *= 1000 - x_vec_end *= 1000 - y_vec_end *= 1000 - z_vec_end *= 1000 try: detectorDeadTime=flyer.detector.cam.dead_time.get() @@ -4140,13 +2461,6 @@ def zebraDaqRasterBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, e x_vec_end=vector["x"][1] y_vec_end=vector["y"][1] z_vec_end=vector["z"][1] - if beamline == "nyx": - x_vec_start *= 1000 - y_vec_start *= 1000 - z_vec_start *= 1000 - x_vec_end *= 1000 - y_vec_end *= 1000 - z_vec_end *= 1000 try: detectorDeadTime=flyer.detector.cam.dead_time.get() @@ -4161,7 +2475,11 @@ def zebraDaqRasterBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, e file_prefix=filePrefix, data_directory_name=data_directory_name,\ detector_dead_time=detectorDeadTime, scan_encoder=scanEncoder, change_state=changeState,\ row_index=row_index, transmission=1, protocol=CollectionProtocols.RASTER) - yield from bp.fly([raster_flyer]) + # yield from bp.fly([raster_flyer]) + yield from bps.kickoff(raster_flyer, wait=True) + yield from bps.complete(raster_flyer, wait=True) + yield from bps.collect(raster_flyer, name="primary") + logger.info("vector Done") logger.info("zebraDaqRasterBluesky Done") @@ -4345,11 +2663,13 @@ def setAttenRI(): def robotOn(): """robotOn() : use the robot to mount samples""" setBlConfig("robot_online",1) + daq_lib.gui_message(json.dumps({"robot_online": True})) def robotOff(): """robotOff() : fake mounting samples""" setBlConfig("robot_online",0) + daq_lib.gui_message(json.dumps({"robot_online": False})) def zebraVecDaqSetup(angle_start,imgWidth,exposurePeriodPerImage,numImages,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega @@ -4463,6 +2783,14 @@ def queueCollectOff(): """queueCollectOff() : do not allow creating requests for samples that are not mounted""" setBlConfig("queueCollect",0) +def unmountColdOn(): + setBlConfig(UNMOUNT_COLD_CHECK, 1) + daq_lib.gui_message(json.dumps({"unmount_cold": True})) + +def unmountColdOff(): + setBlConfig(UNMOUNT_COLD_CHECK, 0) + daq_lib.gui_message(json.dumps({"unmount_cold": False})) + def guiLocal(): #monitor omega RBV """guiLocal() : show the readback of the Omega motor as it's moving. Can lead to lags when operating remotely with reduced bandwidth.""" setBlConfig("omegaMonitorPV","RBV") @@ -4490,10 +2818,6 @@ def fastDPNodes(*args): def setVisitName(vname): setBlConfig("visitName",str(vname)) -def setScannerType(s_type): #either "PI" or "Normal" - """setScannerType(s_type): #either PI or Normal""" - setBlConfig("scannerType",str(s_type)) - def getVisitName(beamline): return db_lib.getBeamlineConfigParam(beamline,"visitName") @@ -4523,10 +2847,12 @@ def backoffDetector(): def disableMount(): """disableMount() : turn off robot mounting. Usually done in an error situation where we want staff intervention before resuming.""" setBlConfig("mountEnabled",0) + daq_lib.gui_message(json.dumps({"enable_mount": False})) def enableMount(): """enableMount() : allow robot mounting""" setBlConfig("mountEnabled",1) + daq_lib.gui_message(json.dumps({"enable_mount": True})) def set_beamsize(sizeV, sizeH): if (sizeV == 'V0'): @@ -4596,6 +2922,12 @@ def emptyQueue(): def topViewCheckOn(): setBlConfig(TOP_VIEW_CHECK,1) +def set_cryostream_temp(temp): + cs1000.move(float(temp)) + +def set_cryostream_ramp_rate(ramp_rate): + cs1000.ramp_rate.put(float(ramp_rate)) + def anneal(annealTime=1.0): if daq_utils.beamline == 'fmx': annealer = fmxAnnealer @@ -4635,10 +2967,12 @@ def lockGUI(): """lockGUI() : lock lsdcGui""" daq_lib.lockGUI() - + +@safe_to_run_in_parallel def beamCheckOn(): setBlConfig(BEAM_CHECK,1) +@safe_to_run_in_parallel def beamCheckOff(): setBlConfig(BEAM_CHECK,0) @@ -4650,8 +2984,6 @@ def HePathOn(): def lsdcHelp(): - print(setGridRasterParams.__doc__) - print(printGridRasterParams.__doc__) print(robotOn.__doc__) print(robotOff.__doc__) print(procOn.__doc__) @@ -4671,8 +3003,6 @@ def lsdcHelp(): print(setAttenBCU.__doc__) print(setAttenRI.__doc__) print(unlockGUI.__doc__) - print(collectSpec.__doc__) - print(setScannerType.__doc__) print("recoverRobot()") print("setFastDPNode(nodeName)") print("setDimpleNode(nodeName)") diff --git a/daq_main2.py b/daq_main2.py index 2041931b..d4ffac6b 100755 --- a/daq_main2.py +++ b/daq_main2.py @@ -1,4 +1,4 @@ -#!/opt/conda_envs/lsdc-server-2023-2-latest/bin/ipython -i +##!/opt/conda_envs/lsdc-server-2023-2-latest/bin/ipython -i """ The main server for the LSDC system """ diff --git a/daq_mainAux.py b/daq_mainAux.py index 4d46c43a..a55e36f9 100755 --- a/daq_mainAux.py +++ b/daq_mainAux.py @@ -1,4 +1,4 @@ -#!/opt/conda_envs/lsdc-server-2023-2-latest/bin/ipython -i +#### !/opt/conda_envs/lsdc-server-2023-2-latest/bin/ipython -i """ The server run when lsdcRemote is used """ @@ -33,6 +33,12 @@ logger.addHandler(handler1) logger.addHandler(handler2) +# Add stream handlers to output in the terminal +console_handler = logging.StreamHandler() # or just StreamHandler() +console_handler.setFormatter(myformat) + +logger.addHandler(console_handler) + sitefilename = "" global command_list,immediate_command_list,z command_list = [] diff --git a/daq_main_common.py b/daq_main_common.py index dd459370..a22a8a44 100755 --- a/daq_main_common.py +++ b/daq_main_common.py @@ -1,5 +1,3 @@ -import _thread -import atexit import json import logging import os @@ -26,10 +24,6 @@ logger = logging.getLogger(__name__) sitefilename = "" -global command_list,immediate_command_list,z -command_list = [] -immediate_command_list = [] -z = 25 def setGovState(state): setGovRobot(gov_robot, state) @@ -56,7 +50,6 @@ def setGovState(state): continue_data_collection, mountSample, unmountSample, - reprocessRaster, fastDPNodes, spotNodes, unmountCold, @@ -83,13 +76,21 @@ def setGovState(state): unlatchGov, backoffDetector, enableMount, + disableMount, robotOn, - set_energy + set_cryostream_ramp_rate, + set_cryostream_temp, + robotOff, + set_energy, + anneal, + beamCheckOn, + beamCheckOff, + queueCollectOn, + queueCollectOff, + unmountColdOn, + unmountColdOff ] -if daq_utils.beamline != "nyx": - functions = functions + [anneal] - whitelisted_functions: "Dict[str, Callable]" = { func.__name__: func for func in functions } @@ -118,105 +119,8 @@ def pybass_init(): daq_lib.message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "message_string") daq_lib.gui_popup_message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "gui_popup_message_string") beamline_lib.read_db() - logger.info("init mots") - beamline_lib.init_mots() #for now - logger.info("init done mots") + beamline_lib.init_mots() daq_lib.init_diffractometer() - try: - sitefilename = os.environ["LSDC_SITE_FILE"] - except KeyError: - gui_message("\$LSDC_SITE_FILE not defined. Questionable defaults in effect.") - if (sitefilename != ""): - if (os.path.exists(sitefilename) == 0): - error_msg = "\$LSDC_SITE_FILE: %s does not exist. Questionable defaults in effect." % sitefilename - gui_message(error_msg) - else: - process_command_file(sitefilename) - - -def process_command_file(command_file_name): - echo_s = "reading %s\n" % command_file_name - logger.info(echo_s) - command_file = open(command_file_name,"r") - while 1: - command = command_file.readline() - if not command: - break - else: - input_tokens = string.split(command) - if (len(input_tokens)>0): - command_string = "%s(" % input_tokens[0] - for i in range(1,len(input_tokens)): - command_string = command_string + "\"" + input_tokens[i] + "\"" - if (i != (len(input_tokens)-1)): - command_string = command_string + "," - command_string = command_string + ")" - logger.info(command_string) - try: - exec(command_string); - except NameError as e: - error_string = "Unknown command in file: %s Error: %s" % (command_string, e) - logger.error(error_string) - except SyntaxError: - logger.error("Syntax error") - except KeyError as e: - logger.error("Key error. Error: %s" % e) - command_file.close() - - - -def process_immediate_commands(frequency): - while (1): - if (len(immediate_command_list) > 0): - command = immediate_command_list.pop(0) - logger.info('immediate command: %s' % command) - process_input(command) - time.sleep(frequency) - -def process_commands(frequency): - while (1): - if (len(command_list) > 0): - command = command_list.pop(0) - logger.info('command: %s' % command) - process_input(command) - - -def print_status_thread(frequency): - global count_list,ring_intensity - - previous_image_started = 0 - percent_done = 0 - shutter_dead_time = .6 - while 1: - time.sleep(frequency) - current_percent_done = daq_lib.get_field("state_percent") - if (daq_lib.image_started > 0): - if (start_time == 0 or daq_lib.image_started != previous_image_started): - previous_image_started = daq_lib.image_started - start_time = time.time() - now = time.time() - total_time = float(daq_lib.image_started) - if (total_time>0.0): - percent_done = int(.5+((now-start_time)/total_time*100)) - else: - percent_done = 0 - else: - start_time = 0 - percent_done = 0 - if (percent_done != current_percent_done): - daq_lib.set_field("state_percent",percent_done) - - -def comm_cb(value=None, char_value=None, **kw): - command = char_value - command_list.append(command) - -def comm2_cb(value=None, char_value=None, **kw): - command = char_value - if not (command == "\n"): - immediate_command_list.append(command) - - def process_input(command_string): if (command_string == ""): @@ -244,16 +148,5 @@ def process_input(command_string): except KeyboardInterrupt: abort_data_collection() logger.info("Interrupt caught by daq server\n") - if (command_string != "pause_data_collection()" and command_string != "continue_data_collection()" and command_string != "abort_data_collection()" and command_string != "unmount_after_abort()" and command_string != "no_unmount_after_abort()"): - daq_lib.set_field("program_state","Program Ready") - + daq_lib.set_field("program_state","Program Ready") -def run_server(): - _thread.start_new_thread(process_immediate_commands,(.25,)) - comm_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "command_s") - beamline_support.pvPut(comm_pv,"\n") - immediate_comm_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "immediate_command_s") - beamline_support.pvPut(immediate_comm_pv,"\n") - comm_pv.add_callback(comm_cb) - immediate_comm_pv.add_callback(comm2_cb) - process_commands(0.5) diff --git a/daq_utils.py b/daq_utils.py index 526b46b3..d5938c31 100644 --- a/daq_utils.py +++ b/daq_utils.py @@ -1,5 +1,5 @@ import os -from config_params import BEAM_CHECK, UNMOUNT_COLD_CHECK +from config_params import BEAM_CHECK, EIGER_DETECTORS, UNMOUNT_COLD_CHECK from math import * import math import requests @@ -7,11 +7,6 @@ import logging logger = logging.getLogger(__name__) -try: - import ispybLib -except Exception as e: - logger.error("daq_utils: ISPYB import error, %s" %e) - import db_lib from config_params import CollectionProtocols @@ -31,7 +26,6 @@ global screenYCenterPixelsLowMagOffset screenYCenterPixelsLowMagOffset = 58 # Constants for use with C2C -global CAMERA_ANGLE_BEAM,CAMERA_ANGLE_ABOVE, CAMERA_ANGLE_BELOW CAMERA_ANGLE_BEAM = 0 # viewing angle is in line with beam, upstream from the sample, facing downstream, top toward ceiling CAMERA_ANGLE_ABOVE = 1 # viewing angle is directly above sample facing downward, top of view is downstream CAMERA_ANGLE_BELOW = 2 # viewing angle is directly below sample facing upward, top of view is downstream @@ -43,8 +37,6 @@ EV_ANGSTROM_CONSTANT = 12398.42 # https://www.kmlabs.com/en/wavelength-to-photon-energy-calculator -EV_ANGSTROM_CONSTANT = 12398.42 # https://www.kmlabs.com/en/wavelength-to-photon-energy-calculator - def getBlConfig(param, beamline=beamline): return db_lib.getBeamlineConfigParam(beamline, param) @@ -52,7 +44,7 @@ def setBlConfig(param, value, beamline=beamline): db_lib.setBeamlineConfigParam(beamline, param, value) def init_environment(): - global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,has_dna,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle,exporter_enabled + global beamline,detector_id,mono_mot_code,has_beamline,has_xtalview,xtal_url,xtal_url_small,unitScaling,sampleCameraCount,xtalview_user,xtalview_pass,det_type,beamstop_x_pvname,beamstop_y_pvname,camera_offset,det_radius,lowMagFOVx,lowMagFOVy,highMagFOVx,highMagFOVy,lowMagPixX,lowMagPixY,highMagPixX,highMagPixY,screenPixX,screenPixY,screenPixCenterX,screenPixCenterY,screenProtocol,screenPhist,screenPhiend,screenWidth,screenDist,screenExptime,screenWave,screenReso,gonioPvPrefix,searchParams,screenEnergy,detectorOffline,imgsrv_host,imgsrv_port,beamlineComm,primaryDewarName,lowMagCamURL,highMagZoomCamURL,lowMagZoomCamURL,highMagCamURL,owner,dewarPlateMap,mag1ViewAngle,mag2ViewAngle,mag3ViewAngle,mag4ViewAngle owner = getpass.getuser() primaryDewarName = getBlConfig("primaryDewarName") @@ -72,42 +64,9 @@ def init_environment(): highMagPixY = float(getBlConfig("highMagPixY")) screenPixX = float(getBlConfig("screenPixX")) screenPixY = float(getBlConfig("screenPixY")) - if beamline == 'nyx': - exporter_enabled = bool(getBlConfig("exporterEnabled")) - else: - exporter_enabled = False - - try: - unitScaling = float(getBlConfig("unitScaling")) - sampleCameraCount = float(getBlConfig("sampleCameraCount")) - except KeyError as e: - unitScaling = 1 - sampleCameraCount = 4 - logging.info(f"Missing unitScaling or sampleCameraCount configs, switching to default values: unitScaling: {unitScaling}, sampleCameraCount: {sampleCameraCount}") - - try: - mag1ViewAngle = int(getBlConfig("mag1ViewAngle")) - except KeyError as e: - mag1ViewAngle = CAMERA_ANGLE_BEAM - logging.info(f"Missing or invalid mag1ViewAngle config, using default value {mag1ViewAngle}") - - try: - mag2ViewAngle = int(getBlConfig("mag2ViewAngle")) - except KeyError as e: - mag2ViewAngle = CAMERA_ANGLE_BEAM - logging.info(f"Missing or invalid mag2ViewAngle config, using default value {mag2ViewAngle}") - - try: - mag3ViewAngle = int(getBlConfig("mag3ViewAngle")) - except KeyError as e: - mag3ViewAngle = CAMERA_ANGLE_BEAM - logging.info(f"Missing or invalid mag3ViewAngle config, using default value {mag3ViewAngle}") - try: - mag4ViewAngle = int(getBlConfig("mag4ViewAngle")) - except KeyError as e: - mag4ViewAngle = CAMERA_ANGLE_BEAM - logging.info(f"Missing or invalid mag4ViewAngle config, using default value {mag4ViewAngle}") + unitScaling = 1 + sampleCameraCount = 4 beamlineComm = getBlConfig("beamlineComm") screenPixCenterX = screenPixX/2.0 @@ -283,7 +242,7 @@ def take_crystal_picture(filename=None,czoom=0,reqID=None,omega=-999): def create_filename(prefix,number): - if (detector_id == "EIGER-16"): + if (detector_id in EIGER_DETECTORS): tmp_filename = findOneH5Master(prefix) else: tmp_filename = "%s_%05d.cbf" % (prefix,int(number)) diff --git a/db_lib.py b/db_lib.py index 3b473dcb..71086935 100755 --- a/db_lib.py +++ b/db_lib.py @@ -3,6 +3,7 @@ import time import uuid from collections import defaultdict +import requests.exceptions import amostra.client.commands as acc from config_params import CollectionProtocols @@ -13,6 +14,26 @@ from pathlib import Path logger = logging.getLogger(__name__) + +class RetryProxy: + """Wraps a DB client object; retries any method call infinitely on ConnectionError.""" + def __init__(self, obj): + object.__setattr__(self, '_obj', obj) + + def __getattr__(self, name): + attr = getattr(object.__getattribute__(self, '_obj'), name) + if callable(attr): + def wrapper(*args, **kwargs): + while True: + try: + return attr(*args, **kwargs) + except requests.exceptions.ConnectionError as e: + logger.warning(f"Connection error calling {name}, retrying in 2s: {e}") + time.sleep(2) + return wrapper + return attr + + #12/19 - Skinner inherited this from Hugo, who inherited it from Matt. Arman wrote the underlying DB and left BNL in 2018. # TODO: get the beamline_id from parameter @@ -41,12 +62,12 @@ def db_connect(params=services_config): """ recommended idiom: """ - sample_ref = acc.SampleReference(**params['amostra']) - container_ref = acc.ContainerReference(**params['amostra']) - request_ref = acc.RequestReference(**params['amostra']) + sample_ref = RetryProxy(acc.SampleReference(**params['amostra'])) + container_ref = RetryProxy(acc.ContainerReference(**params['amostra'])) + request_ref = RetryProxy(acc.RequestReference(**params['amostra'])) - configuration_ref = ccc.ConfigurationReference(**services_config['conftrak']) - analysis_ref = AnalysisClient(services_config['analysisstore']) + configuration_ref = RetryProxy(ccc.ConfigurationReference(**services_config['conftrak'])) + analysis_ref = RetryProxy(AnalysisClient(services_config['analysisstore'])) logger.info(analysis_ref) # should be in config :( @@ -238,11 +259,14 @@ def getResult(result_id): return header[0] -def getResultsforRequest(request_id): +def getResultsforRequest(request_id, result_type=None): """ Takes an integer request_id and returns a list of matching results or []. """ - resultGen = analysis_ref.find_analysis_header(request=request_id) + params = {"request": request_id} + if result_type is not None: + params["result_type"] = result_type + resultGen = analysis_ref.find_analysis_header(**params) if (resultGen != None): headers = list(resultGen) return headers @@ -756,6 +780,64 @@ def updatePriority(request_id, priority): updateRequest(r) +def queueRequest(request_id, fallback_priority=5000): + """ + Queue a request without clobbering a previously saved priority. + + If a request was dequeued and has a saved priority in + ``priority_before_dequeue``, restore that. Otherwise use + ``fallback_priority``. + """ + request = getRequestByID(request_id) + if not request: + return None + + current_priority = request.get("priority", 0) + + # Preserve existing running/completed sentinel semantics. + if current_priority == 99999 or current_priority < 0: + return request + + # Already queued; nothing to do. + if current_priority > 0: + return request + + restore_priority = request.get("priority_before_dequeue") + if ( + restore_priority is not None + and restore_priority > 0 + and restore_priority != 99999 + ): + request["priority"] = restore_priority + else: + request["priority"] = fallback_priority + + updateRequest(request) + return request + + +def dequeueRequest(request_id): + """ + Dequeue a request while preserving its queued priority for later restore. + """ + request = getRequestByID(request_id) + if not request: + return None + + current_priority = request.get("priority", 0) + + # Preserve existing running/completed sentinel semantics. + if current_priority == 99999 or current_priority < 0: + return request + + if current_priority > 0: + request["priority_before_dequeue"] = current_priority + + request["priority"] = 0 + updateRequest(request) + return request + + def getPriorityMap(beamlineName): """ returns a dictionary with priorities as keys and lists of requests @@ -890,4 +972,3 @@ def deleteCompletedRequestsforSample(sid): if (requestList[i]["priority"] == -1): #good to clean up completed requests after unmount if requestList[i]["protocol"] in (CollectionProtocols.RASTER, CollectionProtocols.VECTOR): deleteRequest(requestList[i]['uid']) - diff --git a/embl_robot.py b/embl_robot.py index a8225d1e..47f2adcd 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -66,7 +66,7 @@ def warmupGripperRecoverThread(self, savedThreshold,junk): setPvDesc("warmupThreshold",savedThreshold) - def recoverRobot(self): + def _recoverRobot(self, raise_on_error=False): try: self.rebootEMBL() time.sleep(8.0) @@ -74,15 +74,27 @@ def recoverRobot(self): if bLoaded: daq_macros.robotOff() daq_macros.disableMount() - daq_lib.gui_message("Found a sample in the gripper - CALL STAFF! disableMount() executed.") + message = "FATAL ROBOT ERROR Found a sample in the gripper - CALL STAFF! disableMount() and robotOff() executed." + daq_lib.gui_message(message) + if raise_on_error: + raise Exception(message) else: RobotControlLib.runCmd("goHome") except Exception as e: e_s = str(e) daq_lib.gui_message("ROBOT Recover failed! " + e_s) + if raise_on_error: + raise + def recoverRobot(self): + self._recoverRobot() + RobotControlLib.initialize() - def dryGripper(self): + def recoverRobotFloco(self): + self._recoverRobot(raise_on_error=True) + RobotControlLib.initialize() + + def _dryGripper(self, raise_on_error=False): try: saveThreshold = getPvDesc("warmupThresholdRBV") setPvDesc("warmupThreshold",50) @@ -92,6 +104,14 @@ def dryGripper(self): e_s = str(e) daq_lib.gui_message("Dry gripper failed! " + e_s) setPvDesc("warmupThreshold",saveThreshold) + if raise_on_error: + raise + + def dryGripper(self): + self._dryGripper() + + def dryGripperFloco(self): + self._dryGripper(raise_on_error=True) def DewarAutoFillOn(self): RobotControlLib.runCmd("turnOnAutoFill") @@ -366,7 +386,7 @@ def postMount(self, gov_robot, puck, pinPos, sampID): try: #make sure workposThread is finished before proceeding to robotGovActive check timeout = 20 start_time = time.time() - while self.workposThread.isAlive(): + while self.workposThread.is_alive(): time.sleep(0.5) if time.time() - start_time > timeout: raise Exception(f'setWorkposThread failed to finish before {timeout}s timeout') @@ -385,7 +405,7 @@ def postMount(self, gov_robot, puck, pinPos, sampID): logger.info("Cannot align pin - Mount next sample.") if daq_utils.beamline == "amx": try: - daq_macros.run_top_view_optimized() + daq_macros.run_top_view_optimized(sampID) except: logger.exception("Error running top_view_optimized") if gov_robot.state.get() != "SA": @@ -407,11 +427,7 @@ def preUnmount(self, gov_robot, puckPos,pinPos,sampID): #will somehow know where if daq_utils.beamline == "fmx": beamline_lib.mvaDescriptor("omega", 0) logger.info("Setting SE state") - if daq_utils.beamline == "amx": - wait = False - else: - wait = True - gov_lib.setGovRobot(gov_robot, "SE") + gov_lib.setGovRobot(gov_robot, "SE", wait=True) logger.info("Done setting SE") logger.info("unmounting " + str(puckPos) + " " + str(pinPos) + " " + str(sampID)) logger.info("absPos = " + str(absPos)) diff --git a/epics_det.py b/epics_det.py index da502390..95cccd9f 100644 --- a/epics_det.py +++ b/epics_det.py @@ -4,6 +4,8 @@ from string import * from beamline_support import * import logging + +from config_params import EIGER_DETECTORS logger = logging.getLogger(__name__) global det_pv_list @@ -91,7 +93,7 @@ def det_channels_init(): set_det_pv('det_trigger_mode',0) #internal for devel det_set_autoinc_filenum(1) det_set_file_template("cbf") - elif (det_id == "EIGER-16"): + elif (det_id in EIGER_DETECTORS): pass #leave alone for now else: set_det_pv('det_trigger_mode',1) @@ -132,7 +134,7 @@ def det_setheader(phist,phiinc,dist,wave,theta,exptime,xbeam,ybeam,rot_ax,omega, def det_set_fileprefix(prefix): if (det_id=="PILATUS-6"): set_det_pv("data_filename_val",prefix) - elif (det_id=="EIGER-16"): + elif (det_id in EIGER_DETECTORS): set_det_pv("data_filename_val",prefix+"_$id") def det_set_filepath(filepath): diff --git a/gon_lib.py b/gon_lib.py index a42d4540..a4994b2b 100755 --- a/gon_lib.py +++ b/gon_lib.py @@ -4,17 +4,9 @@ from beamline_support import getPvValFromDescriptor as getPvDesc, setPvValFromDescriptor as setPvDesc import logging import daq_utils -if daq_utils.beamline == 'nyx': - from start_bs import back_light, back_light_range, md2 logger = logging.getLogger(__name__) BACK_LIGHT_STEP = 0.05 # percent of intensity range -def omegaMoveAbs(angle): - md2.omega.set(angle) - -def omegaMoveRel(angle): - md2.omega.set(md2.omega.get() + angle) - def backlightBrighter(): intensity = back_light.get() intensity += BACK_LIGHT_STEP * (back_light_range[1]-back_light_range[0]) diff --git a/gov_lib.py b/gov_lib.py index 09a7dba6..fd62f135 100644 --- a/gov_lib.py +++ b/gov_lib.py @@ -7,9 +7,25 @@ from daq_utils import getBlConfig import logging logger = logging.getLogger(__name__) +import time + +def set_with_retry(sig, value, *, timeout=None, retries=20, delay=0.5): + for attempt in range(retries): + try: + st = sig.set(value, timeout=timeout) + # optional: wait here if you want to block until done + # st.wait(timeout=timeout) + return st + except RuntimeError as exc: + msg = "Another set() call is still in progress" + if msg not in str(exc): + raise + time.sleep(delay) + + raise TimeoutError(f"{sig.name}: still busy after {retries} retries") def set_detz_in(gov_robot, distance): - gov_robot.dev.dz.target_In.set(distance) + set_with_retry(gov_robot.dev.dz.target_In, distance) def set_detz_out(gov_robot, distance): gov_robot.dev.dz.target_Out.set(distance) @@ -49,11 +65,9 @@ def waitGovNoSleep(timeout=GOVERNOR_TIMEOUT): pass def toggleLowMagCameraSettings(stateCode): - - if daq_utils.beamline != "nyx": - if (stateCode == "DA"): - setPvDesc("lowMagGain", getBlConfig(LOW_MAG_GAIN_DA)) - setPvDesc("lowMagAcquireTime",getBlConfig(LOW_MAG_EXP_TIME_DA)) - else: - setPvDesc("lowMagGain", getBlConfig(LOW_MAG_GAIN)) - setPvDesc("lowMagAcquireTime",getBlConfig(LOW_MAG_EXP_TIME)) + if (stateCode == "DA"): + setPvDesc("lowMagGain", getBlConfig(LOW_MAG_GAIN_DA)) + setPvDesc("lowMagAcquireTime",getBlConfig(LOW_MAG_EXP_TIME_DA)) + else: + setPvDesc("lowMagGain", getBlConfig(LOW_MAG_GAIN)) + setPvDesc("lowMagAcquireTime",getBlConfig(LOW_MAG_EXP_TIME)) diff --git a/gui/albula/interface.py b/gui/albula/interface.py index 4d10ae9f..2c68e155 100644 --- a/gui/albula/interface.py +++ b/gui/albula/interface.py @@ -62,7 +62,9 @@ def open(self, args, kwargs): ], # -u for unbuffered I/O, -i to keep stdin open stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, universal_newlines=True, + env=os.environ.copy() ) if "ip" in kwargs and "gov_message_pv_name" in kwargs: diff --git a/gui/control_main.py b/gui/control_main.py index cc623aae..7c6bbdca 100644 --- a/gui/control_main.py +++ b/gui/control_main.py @@ -8,14 +8,10 @@ import time from typing import Dict, List, Optional from pathlib import Path -import threading +from collections import defaultdict -from queue import Queue -import cv2 import numpy as np import requests -from epics import PV -from PyMca5.PyMcaGui.physics.xrf.McaAdvancedFit import McaAdvancedFit from PyMca5.PyMcaGui.pymca.McaWindow import McaWindow, ScanWindow from PyMca5.PyMcaPhysics.xrf import Elements from qt_epics.QtEpicsPVEntry import QtEpicsPVEntry @@ -26,18 +22,16 @@ from qtpy.QtWidgets import QCheckBox, QFrame, QGraphicsPixmapItem, QApplication, QHBoxLayout import daq_utils -if daq_utils.beamline == 'nyx': - from mxbluesky.devices.md2 import GonioDevice, CameraDevice, MD2Device, LightDevice, MD2ApertureDevice -else: - from mxbluesky.devices.generic import GoniometerStack as GonioDevice +from mxbluesky.devices.beamline_devices import BeamlineDevices import db_lib import lsdcOlog from config_params import ( BEAMSIZE_OPTIONS, CRYOSTREAM_ONLINE, + CURRENT_CYCLE, HUTCH_TIMER_DELAY, MINIMUM_RASTER_SIZE, - RASTER_GUI_XREC_FILL_DELAY, + SAMPLE_CAM_SNAPSHOT_DELAY, SAMPLE_TIMER_DELAY, SERVER_CHECK_DELAY, SET_ENERGY_CHECK, @@ -46,7 +40,6 @@ VALID_TOTAL_EXP_TIMES, VALID_TRANSMISSION, RasterStatus, - cryostreamTempPV, CollectionProtocols ) from daq_utils import getBlConfig, setBlConfig @@ -66,16 +59,81 @@ CalculatorWindow, MultiColDialog, ) -from gui.widgets.log_widget import get_summary_widget +from gui.widgets.log_widget import get_summary_widget, LogViewerWidget from gui.raster import RasterCell, RasterGroup from gui.vector import VectorMarker, VectorWidget from QPeriodicTable import QPeriodicTable -from threads import RaddoseThread, ServerCheckThread, VideoThread +from threads import DataFetchRunnable, RaddoseThread, ServerCheckThread, VideoThread from utils import validation, custom_pv logger = logging.getLogger() +def safe_get(signal, default=None, timeout: float = 1.0): + """Return signal.get(timeout=timeout) or default on any failure. Logs WARNING.""" + try: + return signal.get(timeout=timeout) + except Exception as exc: + logger.warning( + "PV read failed for %s (%s: %s) — using default %r", + getattr(signal, "pvname", repr(signal)), + type(exc).__name__, + exc, + default, + ) + return default + + +def _fetch_raster_data(xrecRasterFlag, raster_eval_option): + """Background-thread worker: DB I/O only, no Qt access.""" + try: + rasterReq = db_lib.getRequestByID(xrecRasterFlag) + status = rasterReq["request_obj"]["rasterDef"]["status"] + rasterResults = None + if status in (RasterStatus.READY_FOR_FILL.value, + RasterStatus.READY_FOR_REPROCESS.value): + rasterResults = db_lib.getResultsforRequest(rasterReq["uid"], result_type="rasterResult") + return { + "rasterReq": rasterReq, + "rasterResults": rasterResults, + "raster_eval_option": raster_eval_option, + } + except Exception as e: + logger.error("Error fetching raster data for flag %s: %s" % (xrecRasterFlag, e)) + return e + +def _save_snapshot_result(reqID, resultObj): + if ( + reqID != None + ): # assuming raster here, but will probably need to check the type + db_lib.addResultforRequest( + "rasterJpeg", + reqID, + owner=daq_utils.owner, + result_obj=resultObj, + proposalID=daq_utils.getProposalID(), + beamline=daq_utils.beamline, + ) + else: # the user pushed the snapshot button on the gui + mountedSampleID = self.get_mounted_sample_id() + if mountedSampleID != "": + db_lib.addResulttoSample( + "snapshotResult", + mountedSampleID, + owner=daq_utils.owner, + result_obj=resultObj, + proposalID=daq_utils.getProposalID(), + beamline=daq_utils.beamline, + ) + else: # beamline result, no sample mounted + db_lib.addResulttoBL( + "snapshotResult", + daq_utils.beamline, + owner=daq_utils.owner, + result_obj=resultObj, + proposalID=daq_utils.getProposalID(), + ) + def get_request_object_escan( reqObj, symbol, @@ -123,47 +181,20 @@ def get_request_object_escan( return reqObj +def _mjpg_to_jpg(url: str) -> str: + """Convert IOC MJPG stream URL to single-image JPEG endpoint.""" + if url.endswith('.mjpg'): + return url[:-5] + '.jpg' + return url + + class ControlMain(QtWidgets.QMainWindow): - # 1/13/15 - are these necessary? - Signal = QtCore.Signal() - refreshTreeSignal = QtCore.Signal() - serverMessageSignal = QtCore.Signal(str) - serverPopupMessageSignal = QtCore.Signal(str) - programStateSignal = QtCore.Signal(str) - pauseButtonStateSignal = QtCore.Signal(str) - - xrecRasterSignal = QtCore.Signal(str) - choochResultSignal = QtCore.Signal(str) - energyChangeSignal = QtCore.Signal(float) - mountedPinSignal = QtCore.Signal(int) - beamSizeSignal = QtCore.Signal(float) - controlMasterSignal = QtCore.Signal(int) - zebraArmStateSignal = QtCore.Signal(int) - govRobotSeReachSignal = QtCore.Signal(int) - govRobotSaReachSignal = QtCore.Signal(int) - govRobotDaReachSignal = QtCore.Signal(int) - govRobotBlReachSignal = QtCore.Signal(int) - detMessageSignal = QtCore.Signal(str) - sampleFluxSignal = QtCore.Signal(float) - zebraPulseStateSignal = QtCore.Signal(int) - stillModeStateSignal = QtCore.Signal(int) - zebraDownloadStateSignal = QtCore.Signal(int) - zebraSentTriggerStateSignal = QtCore.Signal(int) - zebraReturnedTriggerStateSignal = QtCore.Signal(int) - fastShutterSignal = QtCore.Signal(float) - gripTempSignal = QtCore.Signal(float) - ringCurrentSignal = QtCore.Signal(float) - threeClickSignal = QtCore.Signal(str) - sampleExposedSignal = QtCore.Signal(float) - sampMoveSignal = QtCore.Signal(int, str) - roiChangeSignal = QtCore.Signal(int, str) - highMagCursorChangeSignal = QtCore.Signal(int, str) - lowMagCursorChangeSignal = QtCore.Signal(int, str) - cryostreamTempSignal = QtCore.Signal(object) + # Signal used for zoom-level switching → camera thread (not a PV bridge). sampleZoomChangeSignal = QtCore.Signal(object) def __init__(self): super(ControlMain, self).__init__() + self._raster_fill_in_progress = False self.proposal_directories = {} self.SelectedItemData = "" # attempt to know what row is selected self.popUpMessageInit = 1 # I hate these next two, but I don't want to catch old messages. Fix later, maybe. @@ -173,7 +204,6 @@ def __init__(self): self.popupMessage.setStyleSheet("background-color: red") self.popupMessage.setModal(False) self.groupName = "skinner" - self.scannerType = getBlConfig("scannerType") self.centerMarkerCharSize = 20 self.centerMarkerCharOffsetX = 12 self.centerMarkerCharOffsetY = 18 @@ -181,39 +211,71 @@ def __init__(self): self.redPen = QtGui.QPen(QtCore.Qt.red) self.bluePen = QtGui.QPen(QtCore.Qt.blue) self.yellowPen = QtGui.QPen(QtCore.Qt.yellow) - if daq_utils.beamline != "nyx": - self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], - gov_message_pv_name=daq_utils.pvLookupDict["governorMessage"],) + self.bl_devices = BeamlineDevices.from_beamline( + daq_utils.beamline, + comm_prefix=daq_utils.beamlineComm, + ) + self.albulaInterface = AlbulaInterface(ip=os.environ["EIGER_DCU_IP"], + gov_message_pv_name=self.bl_devices.governor.message.pvname, + python_path=os.environ["PYTHONPATH_ALBULA"]) + + self.dewar_plate_pos = self.bl_devices.sample_env.dewar_plate_pos + self.governor_message = self.bl_devices.governor.message + self.low_mag_full_frame_rate = self.bl_devices.low_mag.full_frame_rate + self.low_mag_zoom_frame_rate = self.bl_devices.low_mag.zoom_frame_rate + self.high_mag_full_frame_rate = self.bl_devices.high_mag.full_frame_rate + self.high_mag_zoom_frame_rate = self.bl_devices.high_mag.zoom_frame_rate + self.sample_flux = self.bl_devices.optics.sample_flux + self.still_mode = self.bl_devices.misc.still_mode + self.standard_mode = self.bl_devices.misc.standard_mode + self.low_mag_cursor_x = self.bl_devices.misc.low_mag_cursor_x + self.low_mag_cursor_y = self.bl_devices.misc.low_mag_cursor_y + self.high_mag_cursor_x = self.bl_devices.misc.high_mag_cursor_x + self.high_mag_cursor_y = self.bl_devices.misc.high_mag_cursor_y + self.fast_shutter_open_pos = self.bl_devices.goniometer.fast_shutter_open_pos + self.gripper_temp = self.bl_devices.sample_env.grip_temp + self.ring_current = self.bl_devices.diagnostics.ring_current + self.beam_available = self.bl_devices.misc.beam_available + self.sample_exposed = self.bl_devices.misc.exposing + self.beam_size_mode = self.bl_devices.comm.size_mode + self.energy_readback = self.bl_devices.optics.energy.user_readback + self.still_mode_status = self.bl_devices.misc.still_mode_status + self.command_signal = self.bl_devices.comm.command_s + self.immediate_command_signal = self.bl_devices.comm.immediate_command_s + self.queue_change_signal = self.bl_devices.comm.live_q_change_flag + self.mounted_pin_signal = self.bl_devices.comm.mounted_pin + self.control_master_signal = self.bl_devices.comm.zinger_flag + self.chooch_result_flag = self.bl_devices.comm.chooch_result_flag + self.xrec_raster_flag = self.bl_devices.comm.xrec_raster_flag + self.server_message_signal = self.bl_devices.comm.message_string + self.popup_message_signal = self.bl_devices.comm.gui_popup_message_string + self.program_state_signal = self.bl_devices.comm.program_state + self.pause_button_state_signal = self.bl_devices.comm.pause_button_state + self.restart_server_signal = self.bl_devices.comm.restart_server_signal + self.photon_shutter_open = self.bl_devices.misc.photon_shutter_open + self.photon_shutter_close = self.bl_devices.misc.photon_shutter_close + self.fast_shutter_rbv = self.bl_devices.sample_env.fast_shutter.user_readback + self.detector_message = self.bl_devices.detector.status_message + self.gov_robot_se_reach = self.bl_devices.governor.robot_se_reach + self.gov_robot_sa_reach = self.bl_devices.governor.robot_sa_reach + self.gov_robot_da_reach = self.bl_devices.governor.robot_da_reach + self.gov_robot_bl_reach = self.bl_devices.governor.robot_bl_reach + self.zebra_armed = self.bl_devices.zebra.pos_capt.arm.input_status + self.zebra_pulse = self.bl_devices.zebra.pos_capt.pulse.input_status + self.zebra_download = self.bl_devices.zebra.download_status + self.zebra_sent_trigger = self.bl_devices.zebra.sent_trigger_status + self.zebra_returned_trigger = self.bl_devices.zebra.trigger_return_status + self.stop_detector = self.bl_devices.detector.acquire + self.reboot_detector_ioc = self.bl_devices.ioc_control.eiger_ioc_reboot + self.reset_zebra = self.bl_devices.zebra.reset + self.reboot_zebra_ioc = self.bl_devices.ioc_control.zebra_ioc_reboot + self.beam_center_x = self.bl_devices.misc.beam_center_x + self.beam_center_y = self.bl_devices.misc.beam_center_y + self.slit1_x_gap_setpoint = self.bl_devices.optics.slit1_x_gap_setpoint + self.slit1_y_gap_setpoint = self.bl_devices.optics.slit1_y_gap_setpoint self.initUI() self.initOphyd() - self.govStateMessagePV = PV(daq_utils.pvLookupDict["governorMessage"]) - self.zoom1FrameRatePV = PV(daq_utils.pvLookupDict["zoom1FrameRate"]) - self.zoom2FrameRatePV = PV(daq_utils.pvLookupDict["zoom2FrameRate"]) - self.zoom3FrameRatePV = PV(daq_utils.pvLookupDict["zoom3FrameRate"]) - self.zoom4FrameRatePV = PV(daq_utils.pvLookupDict["zoom4FrameRate"]) - self.sampleFluxPV = PV(daq_utils.pvLookupDict["sampleFlux"]) - self.beamFlux_pv = PV(daq_utils.pvLookupDict["flux"]) - self.stillMode_pv = PV(daq_utils.pvLookupDict["stillMode"]) - self.standardMode_pv = PV(daq_utils.pvLookupDict["standardMode"]) - self.lowMagCursorX_pv = PV(daq_utils.pvLookupDict["lowMagCursorX"]) - self.lowMagCursorY_pv = PV(daq_utils.pvLookupDict["lowMagCursorY"]) - self.highMagCursorX_pv = PV(daq_utils.pvLookupDict["highMagCursorX"]) - self.highMagCursorY_pv = PV(daq_utils.pvLookupDict["highMagCursorY"]) - self.fastShutterOpenPos_pv = PV(daq_utils.pvLookupDict["fastShutterOpenPos"]) - self.gripTemp_pv = PV(daq_utils.pvLookupDict["gripTemp"]) - if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTemp_pv = PV(cryostreamTempPV[daq_utils.beamline]) - if daq_utils.beamline == "fmx": - self.slit1XGapSP_pv = PV(daq_utils.motor_dict["slit1XGap"] + ".VAL") - self.slit1YGapSP_pv = PV(daq_utils.motor_dict["slit1YGap"] + ".VAL") - ringCurrentPvName = "SR:C03-BI{DCCT:1}I:Real-I" - self.ringCurrent_pv = PV(ringCurrentPvName) - - self.beamAvailable_pv = PV(daq_utils.pvLookupDict["beamAvailable"]) - self.sampleExposed_pv = PV(daq_utils.pvLookupDict["exposing"]) - - self.beamSize_pv = PV(daq_utils.beamlineComm + "size_mode") - self.energy_pv = PV(daq_utils.motor_dict["energy"] + ".RBV") + self.cryostream_temp = self.bl_devices.cryostream.readback self.rasterStepDefs = {"Coarse": 20.0, "Fine": 10.0, "VFine": 5.0} # Timer that waits for a second before calling raddose 3d @@ -227,32 +289,19 @@ def __init__(self): self.createSampleTab() self.userScreenDialog = UserScreenDialog(self) self.initCallbacks() - if self.scannerType != "PI": - self.motPos = { - "x": self.gon.x.val(), - "y": self.gon.y.val(), - "z": self.gon.z.val(), - "omega": self.gon.omega.val(), - } - else: - self.motPos = { - "x": self.gon.x.val(), - "y": self.gon.y.val(), - "z": self.gon.z.val(), - "omega": self.gon.omega.val(), - "fineX": self.sampFineX_pv.get(), - "fineY": self.sampFineY_pv.get(), - "fineZ": self.sampFineZ_pv.get(), - } + self.motPos = { + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), + "omega": self.gon.omega.val(), + } self.staffScreenDialog = StaffScreenDialog(self, show=False) - if daq_utils.beamline == "nyx": # requires staffScreenDialog to be present - self.staffScreenDialog.fastDPCheckBox.setDisabled(True) - if self.mountedPin_pv.get() == "": + if self.get_mounted_sample_id() == "": mountedPin = db_lib.beamlineInfo(daq_utils.beamline, "mountedSample")[ "sampleID" ] - self.mountedPin_pv.put(mountedPin) + self.bl_devices.comm.mounted_pin.put(mountedPin) self.rasterExploreDialog = RasterExploreDialog() self.detDistMotorEntry.getEntry().setText( @@ -260,13 +309,30 @@ def __init__(self): ) # this is to fix the current val being overwritten by reso self.proposalID = -999999 self.sampleCameraMutex = QMutex() + self.camera_mutexs = defaultdict(QMutex) if len(sys.argv) > 1: if sys.argv[1] == "master": self.changeControlMasterCB(1) self.controlMasterCheckBox.setChecked(True) self.XRFInfoDict = self.parseXRFTable() # I don't like this + self.protoComboActivatedCB("") # self.dewarTree.refreshTreeDewarView() + def get_mounted_pin_raw(self) -> str: + value = self.mounted_pin_signal.get() + if value is None: + return "" + return str(value) + + def get_mounted_sample_id(self) -> str: + return self.get_mounted_pin_raw().split(",")[0] + + def get_mounted_pin_state(self) -> Optional[str]: + parts = self.get_mounted_pin_raw().split(",", 1) + if len(parts) == 2 and parts[1]: + return parts[1] + return None + def eventFilter(self, obj, event): # Event filter to hide vector nodes when shift is held. This is to allow the user to see # the raster hidden by large vector nodes @@ -310,7 +376,15 @@ def parseXRFTable(self): def closeEvent(self, evnt): + self.hutchCornerCamThread.stop() + self.hutchTopCamThread.stop() + self.hutchCornerCamThread.wait() + self.hutchTopCamThread.wait() self.sampleCameraThread.stop() + self.sampleCameraThread.wait() + self.serverCheckThread.stop() + self.serverCheckThread.wait() + self.albulaInterface.close() evnt.accept() sys.exit() # doing this to close any windows left open @@ -358,10 +432,29 @@ def createSampleTab(self): self.dewarViewRadio.toggled.connect( functools.partial(self.dewarViewToggledCB, "dewarView") ) + self.puck_type_radio_group = QtWidgets.QButtonGroup() + self.special_puck_radio = QtWidgets.QRadioButton("Special Puck") + self.dewar_puck_radio = QtWidgets.QRadioButton("Dewar Puck") + self.puck_type_radio_group.addButton(self.special_puck_radio) + self.puck_type_radio_group.addButton(self.dewar_puck_radio) + puck_type_radio_layout = QtWidgets.QHBoxLayout() + puck_type_radio_layout.addWidget(self.dewar_puck_radio) + puck_type_radio_layout.addWidget(self.special_puck_radio) + if getBlConfig("special_mount_enabled"): + self.special_puck_radio.setChecked(True) + else: + self.dewar_puck_radio.setChecked(True) + self.dewar_puck_radio.toggled.connect( + lambda checked: self.toggle_special_puck(False) if checked else None + ) + self.special_puck_radio.toggled.connect( + lambda checked: self.toggle_special_puck(True) if checked else None + ) + hBoxRadioLayout1.addWidget(self.dewarViewRadio) - hBoxRadioLayout1.addWidget(self.priorityViewRadio) self.viewRadioGroup.addButton(self.dewarViewRadio) vBoxDFlayout.addLayout(hBoxRadioLayout1) + vBoxDFlayout.addLayout(puck_type_radio_layout) vBoxDFlayout.addWidget(self.dewarTree) vBoxDFlayout.addWidget(self.follow_current_request_checkbox) queueSelectedButton = QtWidgets.QPushButton("Queue All Selected") @@ -442,9 +535,10 @@ def createSampleTab(self): colParamsGB = QtWidgets.QGroupBox() colParamsGB.setTitle("Acquisition") vBoxColParams1 = QtWidgets.QVBoxLayout() - colStartLabel = QtWidgets.QLabel("Oscillation Start:") - colStartLabel.setFixedWidth(140) - colStartLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colStartLabel = QtWidgets.QLabel("Oscillation Start:") + self.colStartLabel.setFixedWidth(140) + self.colStartLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colStartLabel.setStyleSheet("color: #228B22;") self.osc_start_ledit = QtWidgets.QLineEdit() self.setGuiValues({"osc_start": "0.0"}) self.osc_start_ledit.setFixedWidth(60) @@ -452,6 +546,7 @@ def createSampleTab(self): self.colEndLabel = QtWidgets.QLabel("Oscillation Range:") self.colEndLabel.setAlignment(QtCore.Qt.AlignCenter) self.colEndLabel.setFixedWidth(140) + self.colEndLabel.setStyleSheet("color: #228B22;") self.osc_end_ledit = QtWidgets.QLineEdit() self.setGuiValues({"osc_end": "180.0"}) self.osc_end_ledit.setFixedWidth(60) @@ -461,28 +556,29 @@ def createSampleTab(self): ) if daq_utils.beamline == "fmx": self.osc_end_ledit.textChanged.connect(self.calcLifetimeCB) - colRangeLabel = QtWidgets.QLabel("Oscillation Width:") - colRangeLabel.setFixedWidth(140) - colRangeLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colRangeLabel = QtWidgets.QLabel("Oscillation Width:") + self.colRangeLabel.setFixedWidth(140) + self.colRangeLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colRangeLabel.setStyleSheet("color: #228B22;") self.osc_range_ledit = QtWidgets.QLineEdit() self.osc_range_ledit.setFixedWidth(60) self.osc_range_ledit.setValidator(QtGui.QDoubleValidator(0.001, 3600, 3)) self.stillModeCheckBox = QCheckBox("Stills") self.stillModeCheckBox.setEnabled(False) - if self.stillModeStatePV.get(): + if self.still_mode_status.get(): self.stillModeCheckBox.setChecked(True) self.setGuiValues({"osc_range": "0.0"}) self.osc_range_ledit.setEnabled(False) else: self.stillModeCheckBox.setChecked(False) self.osc_range_ledit.setEnabled(True) - colExptimeLabel = QtWidgets.QLabel("ExposureTime:") + self.colExptimeLabel = QtWidgets.QLabel("ExposureTime:") self.stillModeCheckBox.clicked.connect(self.stillModeUserPushCB) self.osc_range_ledit.textChanged[str].connect( functools.partial(self.totalExpChanged, "oscRange") ) - colExptimeLabel.setFixedWidth(140) - colExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colExptimeLabel.setFixedWidth(140) + self.colExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) self.exp_time_ledit = QtWidgets.QLineEdit() self.exp_time_ledit.setFixedWidth(60) self.exp_time_ledit.textChanged[str].connect(self.totalExpChanged) @@ -494,9 +590,9 @@ def createSampleTab(self): ) ) self.exp_time_ledit.textChanged.connect(self.checkEntryState) - totalExptimeLabel = QtWidgets.QLabel("Total Exposure Time (s):") - totalExptimeLabel.setFixedWidth(155) - totalExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) + self.totalExptimeLabel = QtWidgets.QLabel("Total Exposure Time (s):") + self.totalExptimeLabel.setFixedWidth(155) + self.totalExptimeLabel.setAlignment(QtCore.Qt.AlignCenter) self.totalExptime_ledit = QtWidgets.QLineEdit() self.totalExptime_ledit.setReadOnly(True) self.totalExptime_ledit.setFrame(False) @@ -510,48 +606,60 @@ def createSampleTab(self): ) self.totalExptime_ledit.textChanged.connect(self.checkEntryState) - sampleLifetimeLabel = QtWidgets.QLabel("Estimated Sample Lifetime (s): ") + self.sampleLifetimeLabel = QtWidgets.QLabel("Estimated Sample Lifetime (s): ") if daq_utils.beamline == "amx": self.sampleLifetimeReadback = QtEpicsPVLabel( - daq_utils.pvLookupDict["sampleLifetime"], self, 70, 2 + self.bl_devices.sample_lifetime.pvname, self, 70, 2 ) self.sampleLifetimeReadback_ledit = self.sampleLifetimeReadback.getEntry() elif daq_utils.beamline == "fmx": calcLifetimeButton = QtWidgets.QPushButton("Calc. Lifetime") calcLifetimeButton.clicked.connect(self.calcLifetimeCB) - self.sampleLifetimeReadback_ledit = QtWidgets.QLabel() + self.sampleLifetimeReadback_ledit = QtWidgets.QLineEdit() + self.sampleLifetimeReadback_ledit.setReadOnly(True) self.calcLifetimeCB() - if daq_utils.beamline in ("fmx", "nyx"): + # Make the lifetime row visually similar in height to "normal" input rows + # so that the Detector Dist row lines up better with Column 3's rows. + ref_height = self.exp_time_ledit.sizeHint().height() + self.sampleLifetimeReadback_ledit.setFixedWidth(60) + self.sampleLifetimeReadback_ledit.setMinimumHeight(ref_height) + self.sampleLifetimeReadback_ledit.setMaximumHeight(ref_height) + self.sampleLifetimeReadback_ledit.setSizePolicy( + QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed + ) + + if daq_utils.beamline in ("fmx"): if getBlConfig("attenType") == "RI": self.transmissionReadback = QtEpicsPVLabel( - daq_utils.pvLookupDict["RI_Atten_SP"], self, 60, 3 + self.bl_devices.attenuation.ri_trans_sp.pvname, self, 60, 3 ) self.transmissionSetPoint = QtEpicsPVEntry( - daq_utils.pvLookupDict["RI_Atten_SP"], self, 60, 3 + self.bl_devices.attenuation.ri_trans_sp.pvname, self, 60, 3 ) - colTransmissionLabel = QtWidgets.QLabel("Transmission (RI) (0.0-1.0):") + self.colTransmissionLabel = QtWidgets.QLabel("Trans.(0-1):") else: self.transmissionReadback = QtEpicsPVLabel( - daq_utils.pvLookupDict["transmissionRBV"], self, 60, 3 + self.bl_devices.attenuation.transmission_rbv.pvname, self, 60, 3 ) self.transmissionSetPoint = QtEpicsPVEntry( - daq_utils.pvLookupDict["transmissionSet"], self, 60, 3 + self.bl_devices.attenuation.transmission_set.pvname, self, 60, 3 ) - colTransmissionLabel = QtWidgets.QLabel("Transmission (BCU) (0.0-1.0):") + self.colTransmissionLabel = QtWidgets.QLabel("Trans.(0-1):") else: self.transmissionReadback = QtEpicsPVLabel( - daq_utils.pvLookupDict["transmissionRBV"], self, 60, 3 + self.bl_devices.attenuation.transmission_rbv.pvname, self, 60, 3 ) self.transmissionSetPoint = QtEpicsPVEntry( - daq_utils.pvLookupDict["transmissionSet"], self, 60, 3 + self.bl_devices.attenuation.transmission_set.pvname, self, 60, 3 ) - colTransmissionLabel = QtWidgets.QLabel("Transmission (0.0-1.0):") + self.colTransmissionLabel = QtWidgets.QLabel("Trans.(0-1):") self.transmissionReadback_ledit = self.transmissionReadback.getEntry() + self.colTransmissionLabel.setStyleSheet("color: #228B22;") - colTransmissionLabel.setAlignment(QtCore.Qt.AlignCenter) - colTransmissionLabel.setFixedWidth(190) + self.colTransmissionLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colTransmissionLabel.setFixedWidth(190) - transmisionSPLabel = QtWidgets.QLabel("SetPoint:") + self.transmisionSPLabel = QtWidgets.QLabel("Set:") self.transmission_ledit = self.transmissionSetPoint.getEntry() self.transmission_ledit.setValidator( @@ -567,27 +675,23 @@ def createSampleTab(self): self.transmission_ledit.textChanged.connect(self.calcLifetimeCB) setTransButton = QtWidgets.QPushButton("Set Trans") setTransButton.clicked.connect(self.setTransCB) - beamsizeLabel = QtWidgets.QLabel("BeamSize:") - if daq_utils.beamline == "nyx": - # beamSizeOptionList = self.aperture.get_diameter_list() PV not working, needs investigation - beamSizeOptionList = ["10", "20", "30", "50", "100"] - current_index = self.aperture.current_index.get() - else: - beamSizeOptionList = BEAMSIZE_OPTIONS.keys() - current_index = int(self.beamSize_pv.get()) + self.beamsizeLabel = QtWidgets.QLabel("BeamSize:") + self.beamsizeLabel.setStyleSheet("color: #228B22;") + beamSizeOptionList = BEAMSIZE_OPTIONS.keys() + current_index = int(self.beam_size_mode.get()) self.beamsizeComboBox = QtWidgets.QComboBox(self) self.beamsizeComboBox.addItems(beamSizeOptionList) self.beamsizeComboBox.setCurrentIndex(current_index) - self.beamsizeComboBox.activated[str].connect(self.beamsizeComboActivatedCB) - if daq_utils.beamline == "amx" or self.energy_pv.get() < 9000: + self.beamsizeComboBox.currentTextChanged.connect(self.beamsizeComboActivatedCB) + if daq_utils.beamline == "amx" or self.energy_readback.get() < 9000: self.beamsizeComboBox.setEnabled(False) - colEnergyLabel = QtWidgets.QLabel("Energy (eV):") - colEnergyLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colEnergyLabel = QtWidgets.QLabel("Energy (eV):") + self.colEnergyLabel.setAlignment(QtCore.Qt.AlignCenter) self.energyMotorEntry = QtEpicsPVLabel( daq_utils.motor_dict["energy"] + ".RBV", self, 70, 2 ) self.energyReadback = self.energyMotorEntry.getEntry() - energySPLabel = QtWidgets.QLabel("SetPoint:") + self.energySPLabel = QtWidgets.QLabel("Set:") self.energyMoveLedit = QtEpicsPVEntry( daq_utils.motor_dict["energy"] + ".VAL", self, 75, 2 ) @@ -597,39 +701,36 @@ def createSampleTab(self): moveEnergyButton = QtWidgets.QPushButton("Move Energy") moveEnergyButton.clicked.connect(self.moveEnergyCB) hBoxColParams4 = QtWidgets.QHBoxLayout() - colBeamWLabel = QtWidgets.QLabel("Beam Width:") - colBeamWLabel.setFixedWidth(140) - colBeamWLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colBeamWLabel = QtWidgets.QLabel("Beam Width:") + self.colBeamWLabel.setFixedWidth(140) + self.colBeamWLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamWidth_ledit = QtWidgets.QLineEdit() self.beamWidth_ledit.setFixedWidth(60) - colBeamHLabel = QtWidgets.QLabel("Beam Height:") - colBeamHLabel.setFixedWidth(140) - colBeamHLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colBeamHLabel = QtWidgets.QLabel("Beam Height:") + self.colBeamHLabel.setFixedWidth(140) + self.colBeamHLabel.setAlignment(QtCore.Qt.AlignCenter) self.beamHeight_ledit = QtWidgets.QLineEdit() self.beamHeight_ledit.setFixedWidth(60) - if daq_utils.beamline == 'nyx': - self.beamWidth_ledit.setText(getBlConfig("screen_default_beamWidth")) - self.beamHeight_ledit.setText(getBlConfig("screen_default_beamHeight")) - hBoxColParams4.addWidget(colBeamWLabel) + hBoxColParams4.addWidget(self.colBeamWLabel) hBoxColParams4.addWidget(self.beamWidth_ledit) - hBoxColParams4.addWidget(colBeamHLabel) + hBoxColParams4.addWidget(self.colBeamHLabel) hBoxColParams4.addWidget(self.beamHeight_ledit) - colResoLabel = QtWidgets.QLabel("Edge Resolution:") - colResoLabel.setAlignment(QtCore.Qt.AlignCenter) + self.colResoLabel = QtWidgets.QLabel("Edge Resolution:") + self.colResoLabel.setStyleSheet("color: #228B22;") + self.colResoLabel.setAlignment(QtCore.Qt.AlignCenter) self.resolution_ledit = QtWidgets.QLineEdit() self.resolution_ledit.setFixedWidth(60) self.resolution_ledit.setValidator(QtGui.QDoubleValidator()) self.resolution_ledit.textEdited[str].connect(self.resoTextChanged) - if daq_utils.beamline == "nyx": - self.resolution_ledit.setEnabled(False) - detDistLabel = QtWidgets.QLabel("Detector Dist.") - #detDistLabel.setAlignment(QtCore.Qt.AlignCenter) - detDistRBLabel = QtWidgets.QLabel("Readback:") + self.detDistLabel = QtWidgets.QLabel("Detector Dist.") + self.detDistLabel.setStyleSheet("color: #228B22;") + #self.detDistLabel.setAlignment(QtCore.Qt.AlignCenter) + self.detDistRBLabel = QtWidgets.QLabel("Readback:") self.detDistRBVLabel = QtEpicsPVLabel( daq_utils.motor_dict["detectorDist"] + ".RBV", self, 70 ) self.detDistTextChanged(self.detDistRBVLabel.getEntry().text()) - detDistSPLabel = QtWidgets.QLabel("SetPoint:") + self.detDistSPLabel = QtWidgets.QLabel("Set:") self.detDistMotorEntry = QtEpicsPVEntry( daq_utils.motor_dict["detectorDist"] + ".VAL", self, 70, 2 ) @@ -651,8 +752,8 @@ def createSampleTab(self): hBoxColParams6.setAlignment(QtCore.Qt.AlignLeft) hBoxColParams7 = QtWidgets.QHBoxLayout() hBoxColParams7.setAlignment(QtCore.Qt.AlignLeft) - centeringLabel = QtWidgets.QLabel("Sample Centering:") - centeringLabel.setFixedWidth(140) + self.centeringLabel = QtWidgets.QLabel("Sample Centering:") + self.centeringLabel.setFixedWidth(140) centeringOptionList = ["Interactive", "AutoLoop", "AutoRaster", "Testing"] self.centeringComboBox = QtWidgets.QComboBox(self) self.centeringComboBox.addItems(centeringOptionList) @@ -695,14 +796,28 @@ def createSampleTab(self): protoOptionList = CollectionProtocols.get_beamline_options(daq_utils.beamline) self.protoComboBox = QtWidgets.QComboBox(self) self.protoComboBox.addItems(protoOptionList) - self.protoComboBox.activated[str].connect(self.protoComboActivatedCB) + self.protoComboBox.currentTextChanged.connect(self.protoComboActivatedCB) hBoxColParams6.addWidget(protoLabel) hBoxColParams6.addWidget(self.protoStandardRadio) hBoxColParams6.addWidget(self.protoRasterRadio) hBoxColParams6.addWidget(self.protoVectorRadio) hBoxColParams6.addWidget(self.protoComboBox) - hBoxColParams7.addWidget(centeringLabel) + hBoxColParams7.addWidget(self.centeringLabel) hBoxColParams7.addWidget(self.centeringComboBox) + + # hBoxColParams7.addSpacing(-190) + hBoxColParams7.addWidget(self.colEnergyLabel) + hBoxColParams7.addWidget(self.energyReadback) + hBoxColParams7.addWidget(self.energySPLabel) + + if daq_utils.beamline == "fmx" and getBlConfig(SET_ENERGY_CHECK): + # For FMX with SET_ENERGY_CHECK, show a button instead of free text SP + hBoxColParams7.addWidget(self.moveEnergyButton) + else: + # Other cases: show the text entry for the setpoint + hBoxColParams7.addWidget(self.energy_ledit) + + hBoxColParams7.addStretch(1) self.processingOptionsFrame = QFrame() self.hBoxProcessingLayout1 = QtWidgets.QHBoxLayout() self.hBoxProcessingLayout1.setAlignment(QtCore.Qt.AlignLeft) @@ -766,7 +881,7 @@ def createSampleTab(self): self.rasterEvalComboBox.setCurrentIndex( db_lib.beamlineInfo(daq_utils.beamline, "rasterScoreFlag")["index"] ) - self.rasterEvalComboBox.activated[str].connect(self.rasterEvalComboActivatedCB) + self.rasterEvalComboBox.currentTextChanged.connect(self.rasterEvalComboActivatedCB) self.hBoxRasterLayout1.addWidget(rasterStepLabel) self.hBoxRasterLayout1.addWidget(self.rasterStepEdit) self.hBoxRasterLayout1.addWidget(self.rasterGrainCoarseRadio) @@ -837,98 +952,60 @@ def createSampleTab(self): setVectorStartButton.clicked.connect( lambda: self.setVectorPointCB("vector_start") ) + setVectorStartButton.hide() setVectorEndButton = QtWidgets.QPushButton("Vector\nEnd") setVectorEndButton.setStyleSheet("background-color: red") setVectorEndButton.clicked.connect(lambda: self.setVectorPointCB("vector_end")) + setVectorEndButton.hide() self.vecLine = None - vectorFPPLabel = QtWidgets.QLabel("Number of Wedges") + vectorFPPLabel = QtWidgets.QLabel("wedges") self.vectorFPP_ledit = QtWidgets.QLineEdit("1") self.vectorFPP_ledit.setValidator(QIntValidator(self)) - vecLenLabel = QtWidgets.QLabel(" Length(microns):") + + # length / speed readbacks + vecLenLabel = QtWidgets.QLabel("length (um):") self.vecLenLabelOutput = QtWidgets.QLabel("---") - vecSpeedLabel = QtWidgets.QLabel(" Speed(microns/s):") + vecSpeedLabel = QtWidgets.QLabel("speed (um/s):") self.vecSpeedLabelOutput = QtWidgets.QLabel("---") - hBoxVectorLayout1.addWidget(setVectorStartButton) - hBoxVectorLayout1.addWidget(setVectorEndButton) - hBoxVectorLayout1.addWidget(vectorFPPLabel) - hBoxVectorLayout1.addWidget(self.vectorFPP_ledit) - hBoxVectorLayout1.addWidget(vecLenLabel) - hBoxVectorLayout1.addWidget(self.vecLenLabelOutput) - hBoxVectorLayout1.addWidget(vecSpeedLabel) - hBoxVectorLayout1.addWidget(self.vecSpeedLabelOutput) - vector_widgets_layout = QtWidgets.QVBoxLayout() - vector_widgets_layout.addLayout(hBoxVectorLayout1) - hBoxVectorLayout2 = QtWidgets.QHBoxLayout() + + # Quick vector controls setVectorButton = QtWidgets.QPushButton("Set Quick\nVector") - setVectorButton.clicked.connect(lambda: self.setVectorPointCB("full_vector")) - vector_length_label = QtWidgets.QLabel("Quick vector length (microns)") + setVectorButton.clicked.connect( + lambda: self.setVectorPointCB("full_vector") + ) + vector_length_label = QtWidgets.QLabel("Quick vector length (um)") self.vector_length_ledit = QtWidgets.QLineEdit("40") self.vector_length_ledit.setValidator(QIntValidator(self)) - - hBoxVectorLayout2.addWidget(setVectorButton) - hBoxVectorLayout2.addWidget(vector_length_label) - hBoxVectorLayout2.addWidget(self.vector_length_ledit) - - vector_widgets_layout.addLayout(hBoxVectorLayout2) - self.vectorParamsFrame.setLayout(vector_widgets_layout) + self.vector_length_ledit.setFixedWidth(70) # make this a bit shorter + self.vector_length_ledit.textChanged.connect(self.calcLifetimeCB) + + # Single-row layout: + # Set Quick Vector | Quick vector length (um) | wedges | length | speed + hBoxVectorLayout = QtWidgets.QHBoxLayout() + hBoxVectorLayout.addWidget(setVectorButton) + hBoxVectorLayout.addSpacing(8) + hBoxVectorLayout.addWidget(vector_length_label) + hBoxVectorLayout.addWidget(self.vector_length_ledit) + hBoxVectorLayout.addSpacing(12) + hBoxVectorLayout.addWidget(vectorFPPLabel) + hBoxVectorLayout.addWidget(self.vectorFPP_ledit) + hBoxVectorLayout.addSpacing(12) + hBoxVectorLayout.addWidget(vecLenLabel) + hBoxVectorLayout.addWidget(self.vecLenLabelOutput) + hBoxVectorLayout.addSpacing(12) + hBoxVectorLayout.addWidget(vecSpeedLabel) + hBoxVectorLayout.addWidget(self.vecSpeedLabelOutput) + hBoxVectorLayout.addStretch(1) + + self.vectorParamsFrame.setLayout(hBoxVectorLayout) paramsGridGB = QtWidgets.QGroupBox() paramsGridGB.setTitle("Acquisition") - paramSubspace = QtWidgets.QGridLayout() - - - # Parameter Collection Column 1, Labels - colStartLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colStartLabel,1,0, alignment=QtCore.Qt.AlignLeft) - self.colEndLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.colEndLabel,2,0, alignment=QtCore.Qt.AlignLeft) - colRangeLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colRangeLabel,0,0, alignment=QtCore.Qt.AlignLeft) - colExptimeLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colExptimeLabel,3,0, alignment=QtCore.Qt.AlignLeft) - totalExptimeLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(totalExptimeLabel,4,0, alignment=QtCore.Qt.AlignLeft) - if daq_utils.beamline in ['amx', 'fmx']: - paramSubspace.addWidget(sampleLifetimeLabel, 5, 0, alignment=QtCore.Qt.AlignLeft) - # Parameter Collection Column 2, Input Boxes - paramSubspace.addWidget(self.osc_start_ledit,1,1, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.osc_end_ledit,2,1, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.osc_range_ledit,0,1, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.exp_time_ledit,3,1, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.totalExptime_ledit,4,1, alignment=QtCore.Qt.AlignLeft) - # Parameter Collection Column 3, Labels - paramSubspace.addWidget(detDistLabel,0,2, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colResoLabel,1,2, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colEnergyLabel,2,2, alignment=QtCore.Qt.AlignLeft) - colTransmissionLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(colTransmissionLabel,3,2, alignment=QtCore.Qt.AlignLeft) - transmisionSPLabel.setAlignment(QtCore.Qt.AlignLeft) - paramSubspace.addWidget(beamsizeLabel,4,2, alignment=QtCore.Qt.AlignLeft) - if daq_utils.beamline in ['amx', 'fmx']: - paramSubspace.addWidget(self.sampleLifetimeReadback_ledit, 5, 1, alignment=QtCore.Qt.AlignLeft) - - # Parameter Collection Column 4, Input Boxes - paramSubspace.addWidget(self.detDistMotorEntry.getEntry(),0,3, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.resolution_ledit,1,3, alignment=QtCore.Qt.AlignLeft) - if daq_utils.beamline == "fmx": - if getBlConfig(SET_ENERGY_CHECK): - paramSubspace.addWidget(moveEnergyButton,2,3, alignment=QtCore.Qt.AlignLeft) - else: - paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) - else: - paramSubspace.addWidget(self.energy_ledit,2,3, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.transmission_ledit,3,3, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.beamsizeComboBox,4,3, alignment=QtCore.Qt.AlignLeft) - - # Param Collection Column 5, RBV - paramSubspace.addWidget(self.energyReadback,2,4, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.detDistRBVLabel.getEntry(),0,4, alignment=QtCore.Qt.AlignLeft) - paramSubspace.addWidget(self.transmissionReadback_ledit,3,4, alignment=QtCore.Qt.AlignLeft) + paramSubspace = self.amx_fmx_parameter_layout() improvedParamSpacing = QtWidgets.QVBoxLayout() - improvedParamSpacing.addWidget(self.stillModeCheckBox) improvedParamSpacing.addLayout(paramSubspace) improvedParamSpacing.addLayout(hBoxColParams7) improvedParamSpacing.addLayout(hBoxColParams6) @@ -950,6 +1027,9 @@ def createSampleTab(self): hBoxDisplayOptionLayout.addWidget(self.albulaDispCheckBox) vBoxMainColLayout.addWidget(paramsGridGB) + hBoxQueueButtons = QHBoxLayout() + vBoxMainColLayout.addLayout(hBoxQueueButtons) + vBoxMainColLayout.addWidget(self.dataPathGB) visit_path = Path(getBlConfig("visitDirectory")) visit_name = daq_utils.getVisitName() @@ -1001,32 +1081,30 @@ def createSampleTab(self): editScreenParamsButton.clicked.connect(self.editScreenParamsCB) vBoxMainSetup.addWidget(self.mainToolBox) #vBoxMainSetup.addLayout(hBoxPriorityLayout1) - hBoxQueueButtons = QHBoxLayout() hBoxQueueButtons.addWidget(queueSampleButton) hBoxQueueButtons.addWidget(editSampleButton) hBoxQueueButtons.addWidget(cloneRequestButton) hBoxQueueButtons.addWidget(editScreenParamsButton) - vBoxMainSetup.addLayout(hBoxQueueButtons) self.mainSetupFrame.setLayout(vBoxMainSetup) self.VidFrame = QFrame() self.VidFrame.setFixedWidth(680) vBoxVidLayout = QtWidgets.QVBoxLayout() - self.captureLowMag = None - self.captureHighMag = None - self.captureHighMagZoom = None - self.captureLowMagZoom = None + self.captureLowMag = daq_utils.lowMagCamURL + self.captureHighMag = daq_utils.highMagCamURL + self.captureHighMagZoom = daq_utils.highMagZoomCamURL + self.captureLowMagZoom = daq_utils.lowMagZoomCamURL if daq_utils.has_xtalview: - if self.zoom3FrameRatePV.get() != 0: + if self.high_mag_full_frame_rate.get() != 0: _thread.start_new_thread(self.initVideo2, (0.25,)) # highMag - if self.zoom4FrameRatePV.get() != 0: + if self.high_mag_zoom_frame_rate.get() != 0: _thread.start_new_thread( self.initVideo4, (0.25,) ) # this sets up highMagDigiZoom - if self.zoom2FrameRatePV.get() != 0: + if self.low_mag_zoom_frame_rate.get() != 0: _thread.start_new_thread( self.initVideo3, (0.25,) ) # this sets up lowMagDigiZoom - if self.zoom1FrameRatePV.get() != 0: + if self.low_mag_full_frame_rate.get() != 0: #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) self.captureLowMag = daq_utils.lowMagCamURL logger.debug('lowMagCamURL: "' + daq_utils.lowMagCamURL + '"') @@ -1257,6 +1335,7 @@ def createSampleTab(self): magLevelLabel = QtWidgets.QLabel("Vid:") snapshotButton = QtWidgets.QPushButton("SnapShot") snapshotButton.clicked.connect(self.saveVidSnapshotButtonCB) + snapshotButton.hide() self.hideRastersCheckBox = QCheckBox("Hide\nRasters") self.hideRastersCheckBox.setChecked(False) self.hideRastersCheckBox.stateChanged.connect(self.hideRastersCB) @@ -1283,6 +1362,7 @@ def createSampleTab(self): hBoxSampleAlignLayout = QtWidgets.QHBoxLayout() centerLoopButton = QtWidgets.QPushButton("Center\nLoop") centerLoopButton.clicked.connect(self.autoCenterLoopCB) + centerLoopButton.hide() measureButton = QtWidgets.QPushButton("Measure") measureButton.clicked.connect(self.measurePolyCB) loopShapeButton = QtWidgets.QPushButton("Add Raster\nto Queue") @@ -1440,7 +1520,7 @@ def createSampleTab(self): fileHBoxLayout = QtWidgets.QHBoxLayout() fileHBoxLayout2 = QtWidgets.QHBoxLayout() self.controlMasterCheckBox = QCheckBox("Control Master") - self.controlMasterCheckBox.stateChanged.connect(self.changeControlMasterCB) + self.controlMasterCheckBox.toggled.connect(self.changeControlMasterCB) self.controlMasterCheckBox.setChecked(False) fileHBoxLayout.addWidget(self.controlMasterCheckBox) self.statusLabel = QtEpicsPVLabel( @@ -1453,14 +1533,14 @@ def createSampleTab(self): self.shutterStateLabel = QtWidgets.QLabel("Shutter State:") governorMessageLabel = QtWidgets.QLabel("Governor Message:") self.governorMessage = QtEpicsPVLabel( - daq_utils.pvLookupDict["governorMessage"], + self.bl_devices.governor.message.pvname, self, 140, highlight_on_change=False, ) ringCurrentMessageLabel = QtWidgets.QLabel("Ring (mA):") - self.ringCurrentMessage = QtWidgets.QLabel(str(self.ringCurrent_pv.get())) - beamAvailable = self.beamAvailable_pv.get() + self.ringCurrentMessage = QtWidgets.QLabel(str(safe_get(self.ring_current, default=0))) + beamAvailable = safe_get(self.beam_available, default=False) ''' changing beam available label @@ -1476,7 +1556,7 @@ def createSampleTab(self): else: self.beamAvailLabel = QtWidgets.QLabel("No Beam") self.beamAvailLabel.setStyleSheet("background-color: red;") - sampleExposed = self.sampleExposed_pv.get() + sampleExposed = safe_get(self.sample_exposed, default=False) if sampleExposed: self.sampleExposedLabel = QtWidgets.QLabel("Sample Exposed") self.sampleExposedLabel.setStyleSheet("background-color: red;") @@ -1484,14 +1564,11 @@ def createSampleTab(self): self.sampleExposedLabel = QtWidgets.QLabel("Sample Not Exposed") self.sampleExposedLabel.setStyleSheet("background-color: #99FF66;") gripperLabel = QtWidgets.QLabel("Gripper Temp (K):") - if daq_utils.beamline == "nyx": - self.gripperTempLabel = QtWidgets.QLabel("N/A") - else: - self.gripperTempLabel = QtWidgets.QLabel("%.1f" % self.gripTemp_pv.get()) + self.gripperTempLabel = QtWidgets.QLabel("%.1f" % safe_get(self.gripper_temp, default=0.0)) cryostreamLabel = QtWidgets.QLabel("Cryostream Temp (K):") if getBlConfig(CRYOSTREAM_ONLINE): self.cryostreamTempLabel = QtWidgets.QLabel( - str(self.cryostreamTemp_pv.get()) + str(safe_get(self.cryostream_temp, default=0.0)) ) else: self.cryostreamTempLabel = QtWidgets.QLabel("N/A") @@ -1522,78 +1599,323 @@ def createSampleTab(self): # 12/19 - uncomment this to expose the PyMCA XRF interface. It's not connected to anything. self.zoomLevelToggledCB("Zoom1") - if daq_utils.beamline == "nyx": # hiding unused GUI elements - self.protoRasterRadio.setVisible(False) - self.protoStandardRadio.setVisible(False) - self.protoVectorRadio.setVisible(False) - self.protoOtherRadio.setVisible(False) - self.autoProcessingCheckBox.setVisible(False) - self.fastEPCheckBox.setVisible(False) - self.dimpleCheckBox.setVisible(False) - self.centeringComboBox.setVisible(False) - annealButton.setVisible(False) - centerLoopButton.setVisible(False) - clearGraphicsButton.setVisible(False) - saveCenteringButton.setVisible(False) - selectAllCenteringButton.setVisible(False) - snapshotButton.setVisible(False) - annealTimeLabel.setVisible(False) - self.annealTime_ledit.setVisible(False) - self.vidActionDefineCenterRadio.setVisible(False) - self.hideRastersCheckBox.setEnabled(True) - self.vidActionC2CRadio.setEnabled(True) - self.vidActionRasterExploreRadio.setEnabled(True) - self.vidActionRasterDefRadio.setEnabled(True) - - - - #self.captureLowMag = cv2.VideoCapture(daq_utils.lowMagCamURL) - #self.captureLowMag.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.captureLowMag = daq_utils.lowMagCamURL self.capture = self.captureLowMag - if daq_utils.beamline == "nyx": - self.sampleCameraThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=daq_utils.highMagCamURL - ) - else: - self.sampleCameraThread = VideoThread( - parent=self, delay=SAMPLE_TIMER_DELAY, mjpg_url=self.capture + self.sampleCameraThread = VideoThread( + parent=self, delay=SAMPLE_CAM_SNAPSHOT_DELAY, url=_mjpg_to_jpg(self.capture) + ) + self.sampleZoomChangeSignal.connect( + lambda url: self.sampleCameraThread.updateSnapshotUrl( + _mjpg_to_jpg(url), delay=SAMPLE_CAM_SNAPSHOT_DELAY ) - self.sampleZoomChangeSignal.connect(self.sampleCameraThread.updateCam) + ) self.sampleCameraThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item, frame) ) self.sampleCameraThread.start() - + self.hutch_corner_cam_url = getBlConfig("hutchCornerCamURL") self.hutchCornerCamThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchCornerCamURL") + parent=self, delay=HUTCH_TIMER_DELAY, url=self.hutch_corner_cam_url + "/jpg/image.jpg", width=320, height=180 ) + # self.hutchCornerCamThread = VideoThread( + # parent=self, delay=HUTCH_TIMER_DELAY, mjpg_url=self.hutch_corner_cam_url + "/axis-cgi/mjpg/video.cgi?resolution=320x180" + # ) self.hutchCornerCamThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item_HutchCorner, frame) ) self.hutchCornerCamThread.start() + self.hutch_top_cam_url = getBlConfig("hutchTopCamURL") self.hutchTopCamThread = VideoThread( - parent=self, delay=HUTCH_TIMER_DELAY, url=getBlConfig("hutchTopCamURL") + parent=self, delay=HUTCH_TIMER_DELAY, url=self.hutch_top_cam_url + "/jpg/image.jpg", width=320, height=180 ) + # self.hutchTopCamThread = VideoThread( + # parent=self, delay=HUTCH_TIMER_DELAY, mjpg_url=self.hutch_top_cam_url + "/axis-cgi/mjpg/video.cgi?resolution=320x180" + # ) self.hutchTopCamThread.frame_ready.connect( lambda frame: self.updateCam(self.pixmap_item_HutchTop, frame) ) self.hutchTopCamThread.start() - serverCheckThread = ServerCheckThread(parent=self, delay=SERVER_CHECK_DELAY) - serverCheckThread.visit_dir_changed.connect(QApplication.instance().quit) - serverCheckThread.start() + self.serverCheckThread = ServerCheckThread(parent=self, delay=SERVER_CHECK_DELAY) + self.serverCheckThread.visit_dir_changed.connect(QApplication.instance().quit) + self.serverCheckThread.start() + self.threadPool = QtCore.QThreadPool.globalInstance() + + def toggle_special_puck(self, activate_special: bool): + setBlConfig("special_mount_enabled", activate_special) + self.dewarTree.refreshTreeThreaded(hard_refresh=True) def updateCam(self, pixmapItem: "QGraphicsPixmapItem", frame): - if pixmapItem == self.pixmap_item: - with QMutexLocker(self.sampleCameraMutex): - pixmapItem.setPixmap(frame) - else: - pixmapItem.setPixmap(frame) + if frame is None: + return + with QMutexLocker(self.camera_mutexs[pixmapItem]): + pixmap = QtGui.QPixmap.fromImage(frame) + pixmapItem.setPixmap(pixmap) + + def amx_fmx_parameter_layout(self): + # Keep FMX lifetime callback behavior sane (avoid multiple connects) + if daq_utils.beamline == "fmx": + try: + self.osc_end_ledit.textChanged.disconnect(self.calcLifetimeCB) + except TypeError: + # Not connected yet; that's fine + pass + self.osc_end_ledit.textChanged.connect(self.calcLifetimeCB) + + main = QtWidgets.QHBoxLayout() + main.setSpacing(8) + main.setContentsMargins(4, 2, 4, 2) + + # ---- helper: "normal" row with shared column label width ---- + def make_row(label_widget, value_widgets, label_width=None): + """ + Row where the label uses a shared fixed width (per column), + so labels in that column line up and values mostly align. + """ + row = QtWidgets.QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(4) + + if label_width is not None: + label_widget.setMinimumWidth(label_width) + label_widget.setMaximumWidth(label_width) + + # left-justify label text within that width + label_widget.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + row.addWidget(label_widget) + + # small gap between label and value(s) + row.addSpacing(4) + + if isinstance(value_widgets, (list, tuple)): + for w in value_widgets: + row.addWidget(w) + else: + row.addWidget(value_widgets) + + row.addStretch(1) + return row + + # ---- helper: tight row, label only as wide as its text ---- + def make_tight_row(label_widget, value_widgets): + """ + Row where the label is only as wide as its text, and the + value widgets start immediately after. Used for Energy and + Detector Distance so there's minimal space after the label. + """ + row = QtWidgets.QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(2) + + natural_width = label_widget.sizeHint().width() + label_widget.setMinimumWidth(natural_width) + label_widget.setMaximumWidth(natural_width) + label_widget.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + row.addWidget(label_widget) + + # very small gap before values + row.addSpacing(2) + + if isinstance(value_widgets, (list, tuple)): + for w in value_widgets: + row.addWidget(w) + else: + row.addWidget(value_widgets) + + row.addStretch(1) + return row + + # ---- compute label widths per column ---- + col1_labels = [ + self.colStartLabel, # Oscillation Start + self.colRangeLabel, # Oscillation Width + self.colEndLabel, # Oscillation Range + ] + + col2_labels = [ + self.colExptimeLabel, # Exposure Time + self.totalExptimeLabel, # Total Exposure Time + self.sampleLifetimeLabel, # Estimated Sample Lifetime + ] + + col3_labels = [ + self.colTransmissionLabel, + self.beamsizeLabel, + self.colResoLabel, + # self.doseMultiplierLabel, + ] + + def max_width(widgets): + return max(w.sizeHint().width() for w in widgets) + + col1_width = max_width(col1_labels) + col2_width = max_width(col2_labels) + col3_width = max_width(col3_labels) + + # Narrow column 3 so labels + values are closer together + col3_width = min(col3_width, 95) + + # ---- modest width hints for busy rows (Energy / Det / Transmission) ---- + # Energy row widgets + self.energyReadback.setFixedWidth(80) + self.energySPLabel.setFixedWidth(24) + if hasattr(self, "energy_ledit"): + self.energy_ledit.setFixedWidth(80) + if hasattr(self, "moveEnergyButton"): + self.moveEnergyButton.setFixedWidth(90) + + # Detector distance widgets + det_rbv = self.detDistRBVLabel.getEntry() + det_sp = self.detDistSPLabel + det_set = self.detDistMotorEntry.getEntry() + + det_rbv.setFixedWidth(80) + det_sp.setFixedWidth(24) + det_set.setFixedWidth(60) + + # Transmission widgets + self.transmissionReadback_ledit.setFixedWidth(40) + self.transmisionSPLabel.setFixedWidth(24) + self.transmission_ledit.setFixedWidth(40) + + # ----------------------- + # Column 1: Osc / Energy + # ----------------------- + col1 = QtWidgets.QVBoxLayout() + col1.setContentsMargins(0, 0, 0, 0) + col1.setSpacing(4) + + # A narrower label width for osc rows + osc_label_width = min(col1_width, 110) # tune 100–120 to taste + + # Oscillation Start + col1.addLayout( + make_row(self.colStartLabel, + self.osc_start_ledit, + label_width=osc_label_width) + ) + + # Oscillation Width (colRangeLabel) + col1.addLayout( + make_row(self.colRangeLabel, + self.osc_range_ledit, + label_width=osc_label_width) + ) + + # Oscillation Range (colEndLabel) + col1.addLayout( + make_row(self.colEndLabel, + self.osc_end_ledit, + label_width=osc_label_width) + ) + + # # Energy (eV) + set -> tight row so values come right after label + # energy_value_widgets = [self.energyReadback, self.energySPLabel] + # if daq_utils.beamline == "fmx" and getBlConfig(SET_ENERGY_CHECK): + # energy_value_widgets.append(self.moveEnergyButton) + # else: + # energy_value_widgets.append(self.energy_ledit) + # + # col1.addLayout( + # make_tight_row(self.colEnergyLabel, energy_value_widgets) + # ) + + col1.addStretch(1) + + # ----------------------------- + # Column 2: Time / Lifetime / Dist + # ----------------------------- + lifetime_group = QtWidgets.QGroupBox() + lifetime_group_layout = QtWidgets.QVBoxLayout() + det_dist_group = QtWidgets.QGroupBox() + det_dist_group_layout = QtWidgets.QVBoxLayout() + col2 = QtWidgets.QVBoxLayout() + col2.setContentsMargins(0, 0, 0, 0) + col2.setSpacing(4) + + + # Total Exposure Time + lifetime_group_layout.addLayout( + make_row(self.totalExptimeLabel, + self.totalExptime_ledit, + label_width=col2_width) + ) + + # Estimated Sample Lifetime + lifetime_group_layout.addLayout( + make_row(self.sampleLifetimeLabel, + self.sampleLifetimeReadback_ledit, + label_width=col2_width) + ) + lifetime_group.setLayout(lifetime_group_layout) + + col2.addWidget(lifetime_group) + + # Detector distance (RBV + SP) -> tight row + det_widgets = [det_rbv, det_sp, det_set] + det_dist_group_layout.addLayout( + make_tight_row(self.detDistLabel, det_widgets) + ) + # Edge resolution + det_dist_group_layout.addLayout( + make_row(self.colResoLabel, + self.resolution_ledit, + label_width=col2_width) + ) + det_dist_group.setLayout(det_dist_group_layout) + col2.addWidget(det_dist_group) + + col2.addStretch(1) + + # -------------------------------- + # Column 3: Transmission / Beam / Edge / Dose (aligned, narrow) + # -------------------------------- + col3 = QtWidgets.QVBoxLayout() + col3.setContentsMargins(0, 0, 0, 0) + col3.setSpacing(4) + + # Exposure Time + col3.addLayout( + make_row(self.colExptimeLabel, + self.exp_time_ledit, + label_width=col3_width) + ) + + # Transmission (RBV + Set + SP) + trans_widgets = [ + self.transmissionReadback_ledit, + self.transmisionSPLabel, + self.transmission_ledit, + ] + col3.addLayout( + make_row(self.colTransmissionLabel, + trans_widgets, + label_width=col3_width) + ) + + # Beamsize + col3.addLayout( + make_row(self.beamsizeLabel, + self.beamsizeComboBox, + label_width=col3_width) + ) + + + col3.addStretch(1) + + # Add the three columns to the main horizontal layout + main.addLayout(col1) + main.addSpacing(8) + main.addLayout(col2) + main.addSpacing(8) + main.addLayout(col3) + main.addStretch(1) # extra space, if any, sits on the far right + + return main def annealButtonCB(self): try: @@ -1607,6 +1929,37 @@ def annealButtonCB(self): except: pass + def manage_gov_state_change(self, state: str): + # This function reacts to changes in governor state + # Currently changes what camera angle is shown in the center + if state in ("state SE", "transition SA to SE"): + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateSnapshotUrl( + self.hutch_corner_cam_url + "/axis-cgi/jpg/image.cgi?resolution=640x360", + delay=HUTCH_TIMER_DELAY, + ) + elif state in ("transition SE to TA"): + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateSnapshotUrl( + self.hutch_top_cam_url + "/axis-cgi/jpg/image.cgi?resolution=640x360", + delay=HUTCH_TIMER_DELAY, + ) + elif state in ("state TA"): + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateSnapshotUrl( + _mjpg_to_jpg(self.capture), delay=SAMPLE_CAM_SNAPSHOT_DELAY + ) + elif state in ("state SA"): + logger.info("Govstate: %s", state) + self.sampleCameraThread.updateSnapshotUrl( + _mjpg_to_jpg(self.capture), delay=SAMPLE_CAM_SNAPSHOT_DELAY + ) + + + def update_dewar_plate_position(self, state: int): + position = int(state) if state else "Rotating" + self.dewar_plate_position_status_widget.setText(f"Plate Position: {position}") + def hideRastersCB(self, state): if state == QtCore.Qt.Checked: self.eraseRastersCB() @@ -1617,13 +1970,13 @@ def stillModeUserPushCB(self, state): logger.info("still checkbox state " + str(state)) if self.controlEnabled(): if state: - self.stillMode_pv.put(1) + self.still_mode.put(1) self.setGuiValues({"osc_range": "0.0"}) else: - self.standardMode_pv.put(1) + self.standard_mode.put(1) else: self.popupServerMessage("You don't have control") - if self.stillModeStatePV.get(): + if self.still_mode_status.get(): self.stillModeCheckBox.setChecked(True) else: self.stillModeCheckBox.setChecked(False) @@ -1669,7 +2022,6 @@ def vidActionToggledCB(self): if self.protoComboBox.currentText() in (CollectionProtocols.RASTER, CollectionProtocols.STEP_RASTER): self.protoComboBox.setCurrentText(CollectionProtocols.STANDARD) - self.protoComboActivatedCB(CollectionProtocols.STANDARD) self.showProtParams() def adjustGraphics4ZoomChange(self, fov): @@ -1703,13 +2055,13 @@ def adjustGraphics4ZoomChange(self, fov): self.fillPolyRaster( db_lib.getRequestByID(saveRasterList[i]["uid"]) ) - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") + self.processSampMove(self.gon.x.readback.get(), "x") + self.processSampMove(self.gon.y.readback.get(), "y") + self.processSampMove(self.gon.z.readback.get(), "z") if self.vector_widget.vector_start != None: - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") + self.processSampMove(self.gon.x.readback.get(), "x") + self.processSampMove(self.gon.y.readback.get(), "y") + self.processSampMove(self.gon.z.readback.get(), "z") if self.centeringMarksList != []: self.processSampMove(self.gon.x.val(), "x") self.processSampMove(self.gon.y.val(), "y") @@ -1741,8 +2093,8 @@ def zoomLevelToggledCB(self, identifier): self.capture = self.captureLowMagZoom fov["x"] = daq_utils.lowMagFOVx / 2.0 fov["y"] = daq_utils.lowMagFOVy / 2.0 - unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX - unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY + unzoomedCursorX = self.low_mag_cursor_x.get() - self.centerMarkerCharOffsetX + unzoomedCursorY = self.low_mag_cursor_y.get() - self.centerMarkerCharOffsetY if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 if unzoomedCursorY * 2.0 < self.getBeamCenterY(): @@ -1775,8 +2127,8 @@ def zoomLevelToggledCB(self, identifier): fov["x"] = daq_utils.lowMagFOVx fov["y"] = daq_utils.lowMagFOVy self.centerMarker.setPos( - self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX, - self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY, + self.low_mag_cursor_x.get() - self.centerMarkerCharOffsetX, + self.low_mag_cursor_y.get() - self.centerMarkerCharOffsetY, ) self.beamSizeXPixels = self.screenXmicrons2pixels(self.tempBeamSizeXMicrons) self.beamSizeYPixels = self.screenYmicrons2pixels(self.tempBeamSizeYMicrons) @@ -1795,10 +2147,10 @@ def zoomLevelToggledCB(self, identifier): fov["x"] = daq_utils.highMagFOVx / 2.0 fov["y"] = daq_utils.highMagFOVy / 2.0 unzoomedCursorX = ( - self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX + self.high_mag_cursor_x.get() - self.centerMarkerCharOffsetX ) unzoomedCursorY = ( - self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY + self.high_mag_cursor_y.get() - self.centerMarkerCharOffsetY ) if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 @@ -1832,8 +2184,8 @@ def zoomLevelToggledCB(self, identifier): fov["x"] = daq_utils.highMagFOVx fov["y"] = daq_utils.highMagFOVy self.centerMarker.setPos( - self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX, - self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY, + self.high_mag_cursor_x.get() - self.centerMarkerCharOffsetX, + self.high_mag_cursor_y.get() - self.centerMarkerCharOffsetY, ) self.beamSizeXPixels = self.screenXmicrons2pixels(self.tempBeamSizeXMicrons) self.beamSizeYPixels = self.screenYmicrons2pixels(self.tempBeamSizeYMicrons) @@ -1859,6 +2211,12 @@ def saveVidSnapshotButtonCB(self): def saveVidSnapshotCB( self, comment="", useOlog=False, reqID=None, rasterHeatJpeg=None ): + snapshot_start = time.monotonic() + logger.info( + "SNAPSHOT_START req_id=%s raster_heatmap=%s", + reqID, + rasterHeatJpeg is not None, + ) if not os.path.exists("snapshots"): os.system("mkdir snapshots") width = 640 @@ -1869,78 +2227,63 @@ def saveVidSnapshotCB( painter = QtGui.QPainter(pix) self.scene.render(painter, targetrect, sourcerect) painter.end() + render_done = time.monotonic() now = time.time() if rasterHeatJpeg == None: if reqID != None: filePrefix = db_lib.getRequestByID(reqID)["request_obj"]["file_prefix"] imagePath = f"{getBlConfig('visitDirectory')}/snapshots/{filePrefix}{int(now)}.jpg" else: - if self.dataPathGB.prefix_ledit.text() != "": - imagePath = f"{getBlConfig('visitDirectory')}/snapshots/{self.dataPathGB.prefix_ledit.text()}{int(now)}.jpg" + if self.dataPathGB.getFilePrefix() != "": + imagePath = f"{getBlConfig('visitDirectory')}/snapshots/{self.dataPathGB.getFilePrefix()}{int(now)}.jpg" else: imagePath = f"{getBlConfig('visitDirectory')}/snapshots/capture{int(now)}.jpg" else: imagePath = rasterHeatJpeg logger.info("saving " + imagePath) pix.save(imagePath, "JPG") + save_done = time.monotonic() if useOlog: lsdcOlog.toOlogPicture(imagePath, str(comment)) resultObj = {} imgRef = imagePath # for now, just the path, might want to use filestore later, if they really do facilitate moving files resultObj["data"] = imgRef resultObj["comment"] = str(comment) - if ( - reqID != None - ): # assuming raster here, but will probably need to check the type - db_lib.addResultforRequest( - "rasterJpeg", - reqID, - owner=daq_utils.owner, - result_obj=resultObj, - proposalID=daq_utils.getProposalID(), - beamline=daq_utils.beamline, - ) - else: # the user pushed the snapshot button on the gui - mountedSampleID = self.mountedPin_pv.get() - if mountedSampleID != "": - db_lib.addResulttoSample( - "snapshotResult", - mountedSampleID, - owner=daq_utils.owner, - result_obj=resultObj, - proposalID=daq_utils.getProposalID(), - beamline=daq_utils.beamline, - ) - else: # beamline result, no sample mounted - db_lib.addResulttoBL( - "snapshotResult", - daq_utils.beamline, - owner=daq_utils.owner, - result_obj=resultObj, - proposalID=daq_utils.getProposalID(), - ) + snapshot_save_runnable = DataFetchRunnable(_save_snapshot_result, reqID, resultObj) + self.threadPool.start(snapshot_save_runnable) + + + end_time = time.monotonic() + logger.info( + "SNAPSHOT_DONE req_id=%s render_s=%.3f save_s=%.3f post_save_s=%.3f total_s=%.3f", + reqID, + render_done - snapshot_start, + save_done - render_done, + end_time - save_done, + end_time - snapshot_start, + ) def changeControlMasterCB( self, state, processID=os.getpid() ): # when someone touches checkbox, either through interaction or code logger.info("change control master") logger.info(processID) - currentMaster = self.controlMaster_pv.get() + currentMaster = self.control_master_signal.get() if currentMaster < 0: - self.controlMaster_pv.put( + self.control_master_signal.put( currentMaster ) # this makes sure if things are locked, and someone tries to get control, their checkbox will uncheck itself self.popupServerMessage("Control is locked by staff. Please stand by.") return - if state == QtCore.Qt.Checked: - self.controlMaster_pv.put(processID) + if state: + self.control_master_signal.put(processID) if ( len(self.osc_range_ledit.text()) == 0 or abs(float(self.osc_range_ledit.text())) > 0 ): - self.standardMode_pv.put(1) + self.standard_mode.put(1) elif float(self.osc_range_ledit.text()) == 0: - self.stillMode_pv.put(1) + self.still_mode.put(1) else: self.userScreenDialog.hide() if self.staffScreenDialog != None: @@ -1950,14 +2293,8 @@ def calculateNewYCoordPos(self, startYX, startYY): startY_pixels = 0 zMotRBV = self.motPos["y"] yMotRBV = self.motPos["z"] - if self.scannerType == "PI": - fineYRBV = self.motPos["fineY"] - fineZRBV = self.motPos["fineZ"] - deltaYX = startYX - zMotRBV - fineZRBV - deltaYY = startYY - yMotRBV - fineYRBV - else: - deltaYX = startYX - zMotRBV - deltaYY = startYY - yMotRBV + deltaYX = startYX - zMotRBV + deltaYY = startYY - yMotRBV omegaRad = math.radians(self.motPos["omega"]) newYY = ( float(startY_pixels - (self.screenYmicrons2pixels(deltaYY))) @@ -1968,15 +2305,12 @@ def calculateNewYCoordPos(self, startYX, startYY): newY = newYX + newYY return newY - def processROIChange(self, posRBV, ID): - pass - def processLowMagCursorChange(self, posRBV, ID): zoomedCursorX = self.getBeamCenterX() - self.centerMarkerCharOffsetX zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom2Radio.isChecked(): # lowmagzoom - unzoomedCursorX = self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX - unzoomedCursorY = self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY + unzoomedCursorX = self.low_mag_cursor_x.get() - self.centerMarkerCharOffsetX + unzoomedCursorY = self.low_mag_cursor_y.get() - self.centerMarkerCharOffsetY if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 if unzoomedCursorY * 2.0 < self.getBeamCenterY(): @@ -2006,8 +2340,8 @@ def processLowMagCursorChange(self, posRBV, ID): ) else: self.centerMarker.setPos( - self.lowMagCursorX_pv.get() - self.centerMarkerCharOffsetX, - self.lowMagCursorY_pv.get() - self.centerMarkerCharOffsetY, + self.low_mag_cursor_x.get() - self.centerMarkerCharOffsetX, + self.low_mag_cursor_y.get() - self.centerMarkerCharOffsetY, ) self.beamSizeXPixels = self.screenXmicrons2pixels(self.tempBeamSizeXMicrons) self.beamSizeYPixels = self.screenYmicrons2pixels(self.tempBeamSizeYMicrons) @@ -2027,10 +2361,10 @@ def processHighMagCursorChange(self, posRBV, ID): zoomedCursorY = self.getBeamCenterY() - self.centerMarkerCharOffsetY if self.zoom4Radio.isChecked(): # highmagzoom unzoomedCursorX = ( - self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX + self.high_mag_cursor_x.get() - self.centerMarkerCharOffsetX ) unzoomedCursorY = ( - self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY + self.high_mag_cursor_y.get() - self.centerMarkerCharOffsetY ) if unzoomedCursorX * 2.0 < self.getBeamCenterX(): zoomedCursorX = unzoomedCursorX * 2.0 @@ -2061,8 +2395,8 @@ def processHighMagCursorChange(self, posRBV, ID): ) else: self.centerMarker.setPos( - self.highMagCursorX_pv.get() - self.centerMarkerCharOffsetX, - self.highMagCursorY_pv.get() - self.centerMarkerCharOffsetY, + self.high_mag_cursor_x.get() - self.centerMarkerCharOffsetX, + self.high_mag_cursor_y.get() - self.centerMarkerCharOffsetY, ) self.beamSizeXPixels = self.screenXmicrons2pixels(self.tempBeamSizeXMicrons) self.beamSizeYPixels = self.screenYmicrons2pixels(self.tempBeamSizeYMicrons) @@ -2078,7 +2412,6 @@ def processHighMagCursorChange(self, posRBV, ID): ) def processSampMove(self, posRBV, motID): - # print "new " + motID + " pos=" + str(posRBV) self.motPos[motID] = posRBV if self.centeringMarksList: for mark in self.centeringMarksList: @@ -2138,53 +2471,87 @@ def processSampMove(self, posRBV, motID): def queueEnScanCB(self): self.protoComboBox.setCurrentText(CollectionProtocols.E_SCAN) self.addRequestsToAllSelectedCB() - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def clearEnScanPlotCB(self): self.EScanGraph.removeCurves() # get list of all curves to provide to method? self.choochGraph.removeCurves() def displayXrecRaster(self, xrecRasterFlag): - self.xrecRasterFlag_pv.put("0") + callback_start = time.monotonic() + self.xrec_raster_flag.put("0") if xrecRasterFlag == "100": for i in range(len(self.rasterList)): - if self.rasterList[i] != None: + if self.rasterList[i] is not None: self.scene.removeItem(self.rasterList[i]["graphicsItem"]) + logger.info("XREC_RASTER_CLEAR duration_s=%.3f", time.monotonic() - callback_start) + return + + # Capture GUI state on the main thread before dispatching to background + raster_eval_option = str(self.rasterEvalComboBox.currentText()) + logger.info("xrecrasterflag = %s" % xrecRasterFlag) + + runnable = DataFetchRunnable(_fetch_raster_data, xrecRasterFlag, raster_eval_option) + runnable.signal.finished.connect(self._on_raster_data_fetched) + self.threadPool.start(runnable) + logger.info( + "XREC_RASTER_DISPATCHED flag=%s duration_s=%.3f", + xrecRasterFlag, + time.monotonic() - callback_start, + ) + + def _on_raster_data_fetched(self, result): + """Main-thread slot: receives pre-fetched data, drives all Qt updates.""" + handler_start = time.monotonic() + if isinstance(result, Exception): + logger.error("Failed to fetch raster data: %s" % result) + return + + rasterReq = result["rasterReq"] + rasterResults = result["rasterResults"] + rasterDef = rasterReq["request_obj"]["rasterDef"] + + if rasterDef["status"] == RasterStatus.DRAWN.value: + self.drawPolyRaster(rasterReq) + elif rasterDef["status"] == RasterStatus.READY_FOR_FILL.value: + self.fillPolyRaster(rasterReq, rasterResults=rasterResults) + logger.info("polyraster filled by displayXrecRaster") + elif rasterDef["status"] == RasterStatus.READY_FOR_SNAPSHOT.value: + if self.controlEnabled(): + self.takeRasterSnapshot(rasterReq) + logger.info("raster snapshot taken") + self.vidActionRasterExploreRadio.setChecked(True) + self.selectedSampleID = rasterReq["sample"] + self.queue_change_signal.put(1) # not sure about this + elif rasterDef["status"] == RasterStatus.READY_FOR_REPROCESS.value: + self.fillPolyRaster(rasterReq, rasterResults=rasterResults) + logger.info("reprocessed polyraster filled by displayXrecraster") + if self.controlEnabled(): + self.takeRasterSnapshot(rasterReq) + logger.info("reprocessed raster snapshot taken") + self.vidActionRasterExploreRadio.setChecked(True) + self.selectedSampleID = rasterReq["sample"] + self.queue_change_signal.put(1) # not sure about this + + handler_duration = time.monotonic() - handler_start + if handler_duration > 1.0: + logger.warning( + "XREC_RASTER_HANDLER_SLOW status=%s uid=%s duration_s=%.3f", + rasterDef.get("status"), + rasterReq.get("uid"), + handler_duration, + ) else: - logger.info("xrecrasterflag = %s" % xrecRasterFlag) - try: - rasterReq = db_lib.getRequestByID(xrecRasterFlag) - except IndexError: - logger.error("bad xrecRasterFlag: %s" % xrecRasterFlag) - return - rasterDef = rasterReq["request_obj"]["rasterDef"] - if rasterDef["status"] == RasterStatus.DRAWN.value: - self.drawPolyRaster(rasterReq) - elif rasterDef["status"] == RasterStatus.READY_FOR_FILL.value: - self.fillPolyRaster(rasterReq) - logger.info("polyraster filled by displayXrecRaster") - elif rasterDef["status"] == RasterStatus.READY_FOR_SNAPSHOT.value: - if self.controlEnabled(): - self.takeRasterSnapshot(rasterReq) - logger.info("raster snapshot taken") - self.vidActionRasterExploreRadio.setChecked(True) - self.selectedSampleID = rasterReq["sample"] - self.treeChanged_pv.put(1) # not sure about this - elif rasterDef["status"] == RasterStatus.READY_FOR_REPROCESS.value: - self.fillPolyRaster(rasterReq) - logger.info("reprocessed polyraster filled by displayXrecraster") - if self.controlEnabled(): - self.takeRasterSnapshot(rasterReq) - logger.info("reprocessed raster snapshot taken") - self.vidActionRasterExploreRadio.setChecked(True) - self.selectedSampleID = rasterReq["sample"] - self.treeChanged_pv.put(1) # not sure about this - else: - pass + logger.info( + "XREC_RASTER_HANDLER_DONE status=%s uid=%s duration_s=%.3f", + rasterDef.get("status"), + rasterReq.get("uid"), + handler_duration, + ) def processMountedPin(self, mountedPinPos): self.eraseCB() - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) ''' @@ -2193,7 +2560,7 @@ def processMountedPin(self, mountedPinPos): ''' def processFastShutter(self, shutterVal): - if round(shutterVal) == round(self.fastShutterOpenPos_pv.get()): + if round(shutterVal) == round(self.fast_shutter_open_pos.get()): self.shutterStateLabel.setText("Shutter State:Open") self.shutterStateLabel.setStyleSheet("background-color: red;") else: @@ -2230,20 +2597,12 @@ def processRingCurrent(self, ringCurrentVal): function is processThreClickCentering ''' def processThreeClickCentering(self, beamAvailVal): - if daq_utils.beamline == 'nyx': - if beamAvailVal == '0': - self.beamAvailLabel.setText("Beam Available") - self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") - else: - self.beamAvailLabel.setText(beamAvailVal) - self.beamAvailLabel.setStyleSheet("background-color: yellow") - else: - if beamAvailVal == "1": - self.beamAvailLabel.setText("Beam Available") - self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") - elif beamAvailVal == "0": - self.beamAvailLabel.setText("No Beam") - self.beamAvailLabel.setStyleSheet("background-color: red") + if beamAvailVal == "1": + self.beamAvailLabel.setText("Beam Available") + self.beamAvailLabel.setStyleSheet("background-color: #99FF66;") + elif beamAvailVal == "0": + self.beamAvailLabel.setText("No Beam") + self.beamAvailLabel.setStyleSheet("background-color: red") def processSampleExposed(self, sampleExposedVal): if int(sampleExposedVal) == 1: @@ -2254,7 +2613,7 @@ def processSampleExposed(self, sampleExposedVal): self.sampleExposedLabel.setStyleSheet("background-color: #99FF66;") def processBeamSize(self, beamSizeFlag): - self.beamsizeComboBox.setCurrentIndex(beamSizeFlag) + self.beamsizeComboBox.setCurrentIndex(int(beamSizeFlag)) def processEnergyChange(self, energyVal): if daq_utils.beamline != "amx": @@ -2336,11 +2695,6 @@ def processZebraReturnedTriggerState(self, state): else: self.userScreenDialog.zebraReturnedTriggerCheckBox.setChecked(False) - def processControlMasterNew(self, controlPID): - logger.info("in callback controlPID = " + str(controlPID)) - if abs(int(controlPID)) != self.processID: - self.controlMasterCheckBox.setChecked(False) - def processChoochResult(self, choochResultFlag): if choochResultFlag == "0": return @@ -2376,9 +2730,8 @@ def processChoochResult(self, choochResultFlag): self.choochFPrimePeak.setText(str(choochResultObj["fprime_peak"])) self.choochF2PrimeInfl.setText(str(choochResultObj["f2prime_infl"])) self.choochF2PrimePeak.setText(str(choochResultObj["f2prime_peak"])) - self.choochResultFlag_pv.put("0") + self.chooch_result_flag.put("0") self.protoComboBox.setCurrentText(CollectionProtocols.STANDARD) - self.protoComboActivatedCB(CollectionProtocols.STANDARD) except TypeError as e: logger.error( "Chooch plotting failed - check whether scan had a strong signal or not: %s" @@ -2419,7 +2772,6 @@ def showProtParams(self): elif protocol == CollectionProtocols.STEP_RASTER: self.rasterParamsFrame.show() - self.processingOptionsFrame.show() elif protocol in (CollectionProtocols.MULTI_COL, CollectionProtocols.MULTI_COL_Q): self.rasterParamsFrame.show() self.osc_start_ledit.setEnabled(False) @@ -2427,14 +2779,8 @@ def showProtParams(self): self.multiColParamsFrame.show() elif protocol in (CollectionProtocols.VECTOR, CollectionProtocols.STEP_VECTOR): self.vectorParamsFrame.show() - self.processingOptionsFrame.show() elif protocol in (CollectionProtocols.CHARACTERIZE, CollectionProtocols.EDNA_COL): self.characterizeParamsFrame.show() - self.processingOptionsFrame.show() - elif protocol in (CollectionProtocols.STANDARD, CollectionProtocols.BURN): - self.processingOptionsFrame.show() - else: - pass def rasterStepChanged(self, text): self.beamWidth_ledit.setText(text) @@ -2466,7 +2812,7 @@ def totalExpChanged(self, text): if float(str(self.osc_range_ledit.text())) == 0: if text == "oscRange": if self.controlEnabled(): - self.stillMode_pv.put(1) + self.still_mode.put(1) self.colEndLabel.setText("Number of Images: ") if self.protoComboBox.currentText() not in (CollectionProtocols.STANDARD, CollectionProtocols.VECTOR): @@ -2487,13 +2833,13 @@ def totalExpChanged(self, text): else: if text == "oscRange": if self.controlEnabled(): - self.standardMode_pv.put(1) + self.standard_mode.put(1) self.colEndLabel.setText("Oscillation Range:") except ValueError: return if self.protoComboBox.currentText() not in (CollectionProtocols.STANDARD, - CollectionProtocols.VECTOR) + CollectionProtocols.VECTOR): self.totalExptime_ledit.setText("----") else: try: @@ -2626,22 +2972,22 @@ def validateFields(self, field_values_dict): def protoRadioToggledCB(self, text): if self.protoStandardRadio.isChecked(): self.protoComboBox.setCurrentText(CollectionProtocols.STANDARD) - self.protoComboActivatedCB(text) elif self.protoRasterRadio.isChecked(): self.protoComboBox.setCurrentText(CollectionProtocols.RASTER) - self.protoComboActivatedCB(text) elif self.protoVectorRadio.isChecked(): self.protoComboBox.setCurrentText(CollectionProtocols.VECTOR) - self.protoComboActivatedCB(text) else: pass def beamsizeComboActivatedCB(self, text): - if daq_utils.beamline == "nyx": - index = self.beamsizeComboBox.findText(str(text)) - self.aperture.current_index.put(index) - else: + if self.controlEnabled(): self.send_to_server("set_beamsize", BEAMSIZE_OPTIONS[text]) + self.beamsizeComboBox.setEnabled(False) + self.calcLifetimeCB() + else: + self.popupServerMessage("You don't have control") + current_index = int(self.beam_size_mode.get()) + self.beamsizeComboBox.setCurrentIndex(current_index) def protoComboActivatedCB(self, text): self.showProtParams() @@ -2783,7 +3129,7 @@ def upPriorityCB( if currentPriority == priority: priority = priority + 20 db_lib.updatePriority(self.selectedSampleRequest["uid"], priority) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def downPriorityCB(self): currentPriority = self.selectedSampleRequest["priority"] @@ -2802,7 +3148,7 @@ def downPriorityCB(self): if currentPriority == priority: priority = priority - 20 db_lib.updatePriority(self.selectedSampleRequest["uid"], priority) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def topPriorityCB(self): currentPriority = self.selectedSampleRequest["priority"] @@ -2811,7 +3157,7 @@ def topPriorityCB(self): priority = int(self.getMaxPriority()) priority = priority + 100 db_lib.updatePriority(self.selectedSampleRequest["uid"], priority) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def bottomPriorityCB(self): currentPriority = self.selectedSampleRequest["priority"] @@ -2820,21 +3166,21 @@ def bottomPriorityCB(self): priority = int(self.getMinPriority()) priority = priority - 100 db_lib.updatePriority(self.selectedSampleRequest["uid"], priority) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def dewarViewToggledCB(self, identifier): self.selectedSampleRequest = {} # should probably clear textfields here too if identifier == "dewarView": if self.dewarViewRadio.isChecked(): - self.dewarTree.refreshTreeDewarView() + self.dewarTree.refreshTreeThreaded() else: if self.priorityViewRadio.isChecked(): self.dewarTree.refreshTreePriorityView() def dewarViewToggleCheckCB(self): if self.dewarViewRadio.isChecked(): - self.dewarTree.refreshTreeDewarView() + self.dewarTree.refreshTreeThreaded() else: self.dewarTree.refreshTreePriorityView() @@ -2848,7 +3194,7 @@ def moveOmegaCB(self): def moveEnergyMaxDeltaCB(self, max_delta=10.0): energyRequest = float(str(self.energy_ledit.text())) if self.controlEnabled(): - if abs(energyRequest - self.energy_pv.get()) > max_delta: + if abs(energyRequest - self.energy_readback.get()) > max_delta: self.popupServerMessage(f"Energy change must be less than or equal to {max_delta:.2f} ev") return else: @@ -2860,7 +3206,7 @@ def moveEnergyMaxDeltaCB(self, max_delta=10.0): def add_multicol(self): if self.selectedSampleRequest and self.selectedSampleRequest.get("request_type") == "raster": - raster_results = db_lib.getResultsforRequest(self.selectedSampleRequest["uid"]) + raster_results = db_lib.getResultsforRequest(self.selectedSampleRequest["uid"], result_type="rasterResult") for result in raster_results: if result["result_type"] == 'rasterResult': @@ -2891,8 +3237,8 @@ def spawnRaddoseThread(self): if not os.path.exists("2vb1.pdb"): os.system("cp -a $CONFIGDIR/2vb1.pdb .") os.system("mkdir rd3d") - energyReadback = self.energy_pv.get() / 1000.0 - sampleFlux = self.sampleFluxPV.get() + energyReadback = self.energy_readback.get() / 1000.0 + sampleFlux = self.sample_flux.get() if hasattr(self, "transmission_ledit") and hasattr( self, "transmissionReadback_ledit" ): @@ -2912,6 +3258,16 @@ def spawnRaddoseThread(self): pass try: + # Get current beam size setting + # current_beamsize = list(BEAMSIZE_OPTIONS.keys())[self.beamsizeComboBox.currentIndex()] + current_beamsize = self.beamsizeComboBox.currentText() + + # Get user dose multiplier + try: + dm_user = float(self.userScreenDialog.doseMultiplier_ledit.text()) + except: + dm_user = 1.0 + wedge = float(self.osc_end_ledit.text()) raddose_thread = RaddoseThread( parent=self, @@ -2922,6 +3278,8 @@ def spawnRaddoseThread(self): wedge=wedge, flux=sampleFlux, verbose=True, + dm_user=dm_user, + beamsize_type=current_beamsize, ) raddose_thread.lifetime.connect( lambda lifetime: self.setLifetimeCB(lifetime) @@ -3104,19 +3462,16 @@ def measurePolyCB(self): def center3LoopCB(self): logger.info("3-click center loop") self.threeClickCount = 1 - self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) #time.sleep(0.3) self.click3Button.setStyleSheet("background-color: yellow") - if(daq_utils.exporter_enabled): - self.md2.exporter.cmd("startManualSampleCentring", "") - else: - self.send_to_server("mvaDescriptor", ["omega", 0]) + self.send_to_server("mvaDescriptor", ["omega", 0]) def fillPolyRaster( - self, rasterReq + self, rasterReq, rasterResults=None ): # at this point I should have a drawn polyRaster logger.info("filling poly for " + str(rasterReq["uid"])) - rasterResults = db_lib.getResultsforRequest(rasterReq["uid"]) + if rasterResults is None: + rasterResults = db_lib.getResultsforRequest(rasterReq["uid"], result_type="rasterResult") if not rasterResults: return @@ -3178,20 +3533,10 @@ def fillPolyRaster( ) return # means a raster failure, and not enough data to cover raster, caused a gui crash - - if ( - lenX > 180 and self.scannerType == "PI" - ): # this is trying to figure out row direction + if i % 2 == 0: # this is trying to figure out row direction cellIndex = spotLineCounter else: - if daq_utils.beamline == "nyx": - is_raster_inverted = 1 - else: - is_raster_inverted = 0 - if i % 2 == is_raster_inverted: # this is trying to figure out row direction - cellIndex = spotLineCounter - else: - cellIndex = rowStartIndex + ((numsteps - 1) - j) + cellIndex = rowStartIndex + ((numsteps - 1) - j) try: my_array[cellIndex] = cellResult[cell_result_key] @@ -3228,10 +3573,14 @@ def fillPolyRaster( numsteps = rasterDef["rowDefs"][i]["numsteps"] rowStartIndex = cellCounter for j in range(numsteps): + if index % 50 == 0: + QApplication.processEvents() if i % 2 == 0: # this is trying to figure out row direction index = cellCounter else: index = rowStartIndex + ((numsteps - 1) - j) + if index >= len(self.currentRasterCellList): + continue if color_id is None: #param = my_array[cellCounter] if rasterEvalOption == "Resolution": @@ -3253,6 +3602,7 @@ def fillPolyRaster( cellCounter += 1 def takeRasterSnapshot(self, rasterReq): + start_time = time.monotonic() request_obj = rasterReq["request_obj"] directory = request_obj["directory"] filePrefix = request_obj["file_prefix"] @@ -3276,13 +3626,6 @@ def takeRasterSnapshot(self, rasterReq): reqID=rasterReq["uid"], rasterHeatJpeg=jpegImageFilename, ) - self.saveVidSnapshotCB( - "Raster Result from sample " + str(rasterReq["request_obj"]["file_prefix"]), - useOlog=False, - reqID=rasterReq["uid"], - rasterHeatJpeg=jpegImageFilename, - ) - self.send_to_server("insertRasterResult", [str(rasterReq["uid"]), str(visitName)]) def reFillPolyRaster(self): rasterEvalOption = str(self.rasterEvalComboBox.currentText()) @@ -3452,70 +3795,30 @@ def getCurrentFOV(self): return fov def screenXPixels2microns(self, pixels): - if daq_utils.beamline == 'nyx': - img_scale_factor = self.getMD2ImageXRatio() - pixels_per_mm = 1 / self.camera.scale_x.get() - pixels_per_micron = pixels_per_mm / 1000.0 - return float(pixels * img_scale_factor) / pixels_per_micron - else: - fov = self.getCurrentFOV() - fovX = fov["x"] - return float(pixels) * (fovX / daq_utils.screenPixX) + fov = self.getCurrentFOV() + fovX = fov["x"] + return float(pixels) * (fovX / daq_utils.screenPixX) def screenYPixels2microns(self, pixels): - if daq_utils.beamline == 'nyx': - pixels_per_mm = 1 / self.camera.scale_y.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageYRatio() - return float(pixels * img_scale_factor) / pixels_per_micron - else: - fov = self.getCurrentFOV() - fovY = fov["y"] - return float(pixels) * (fovY / daq_utils.screenPixY) + fov = self.getCurrentFOV() + fovY = fov["y"] + return float(pixels) * (fovY / daq_utils.screenPixY) def screenXmicrons2pixels(self, microns): - if daq_utils.beamline == 'nyx': - pixels_per_mm = 1 / self.camera.scale_x.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageXRatio() - return float(microns * pixels_per_micron) / img_scale_factor - else: - fov = self.getCurrentFOV() - fovX = fov["x"] - return int(round(microns * (daq_utils.screenPixX / fovX))) + fov = self.getCurrentFOV() + fovX = fov["x"] + return int(round(microns * (daq_utils.screenPixX / fovX))) def screenYmicrons2pixels(self, microns): - if daq_utils.beamline == 'nyx': - pixels_per_mm = 1 / self.camera.scale_y.get() - pixels_per_micron = pixels_per_mm / 1000.0 - img_scale_factor = self.getMD2ImageYRatio() - return float(microns * pixels_per_micron) / img_scale_factor - else: - fov = self.getCurrentFOV() - fovY = fov["y"] - return int(round(microns * (daq_utils.screenPixY / fovY))) - - def getMD2ImageXRatio(self): - md2_img_width = daq_utils.highMagPixX - lsdc_img_width = daq_utils.screenPixX - return float(md2_img_width) / float(lsdc_img_width) - - def getMD2ImageYRatio(self): - md2_img_height = daq_utils.highMagPixY - lsdc_img_height = daq_utils.screenPixY - return float(md2_img_height) / float(lsdc_img_height) - + fov = self.getCurrentFOV() + fovY = fov["y"] + return int(round(microns * (daq_utils.screenPixY / fovY))) + def getBeamCenterX(self): - if daq_utils.beamline == 'nyx': - return self.md2.center_pixel_x.get() / self.getMD2ImageXRatio() - else: - return daq_utils.screenPixCenterX + return daq_utils.screenPixCenterX def getBeamCenterY(self): - if daq_utils.beamline == 'nyx': - return self.md2.center_pixel_y.get() / self.getMD2ImageYRatio() - else: - return daq_utils.screenPixCenterY + return daq_utils.screenPixCenterY def definePolyRaster( self, raster_w, raster_h, stepsizeXPix, stepsizeYPix, point_x, point_y, stepsize @@ -3536,32 +3839,18 @@ def definePolyRaster( logger.error("bad value for beam width or beam height") self.popupServerMessage("bad value for beam width or beam height") return - if self.scannerType == "PI": - rasterDef = { - "rasterType": "normal", - "beamWidth": beamWidth, - "beamHeight": beamHeight, - "status": RasterStatus.NEW.value, - "x": self.gon.x.val() + self.sampFineX_pv.get(), - "y": self.gon.y.val() + self.sampFineY_pv.get(), - "z": self.gon.z.val() + self.sampFineZ_pv.get(), - "omega": self.gon.omega.val(), - "stepsize": stepsize, - "rowDefs": [], - } # just storing step as microns, not using her - else: - rasterDef = { - "rasterType": "normal", - "beamWidth": beamWidth, - "beamHeight": beamHeight, - "status": RasterStatus.NEW.value, - "x": self.gon.x.val(), - "y": self.gon.y.val(), - "z": self.gon.z.val(), - "omega": self.gon.omega.val(), - "stepsize": stepsize, - "rowDefs": [], - } # just storing step as microns, not using here + rasterDef = { + "rasterType": "normal", + "beamWidth": beamWidth, + "beamHeight": beamHeight, + "status": RasterStatus.NEW.value, + "x": self.gon.x.val(), + "y": self.gon.y.val(), + "z": self.gon.z.val(), + "omega": self.gon.omega.val(), + "stepsize": stepsize, + "rowDefs": [], + } # just storing step as microns, not using here numsteps_h = int( raster_w / stepsizeXPix ) # raster_w = width,goes to numsteps horizonatl @@ -3640,7 +3929,7 @@ def definePolyRaster( setBlConfig("rasterDefaultTrans", float(self.transmission_ledit.text())) - self.selectedSampleID = self.mountedPin_pv.get() + self.selectedSampleID = self.get_mounted_sample_id() sample_data = db_lib.getSampleByID(self.selectedSampleID) prop_dir = self.get_proposal_directory(sample_data["proposalID"]) @@ -3807,7 +4096,7 @@ def sceneKey(self, event): pass self.scene.removeItem(self.rasterList[i]["graphicsItem"]) self.rasterList[i] = None - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) for i in range(len(self.centeringMarksList)): if self.centeringMarksList[i] != None: if self.centeringMarksList[i]["graphicsItem"].isSelected(): @@ -3828,15 +4117,7 @@ def pixelSelect(self, event): penRed = QtGui.QPen(QtCore.Qt.red) ''' For three click centering, this if statement checks the omega state of the motor. - This ideally gives feedback on wether the MD2 is in the rotation portion of the three click centering - ''' - if daq_utils.beamline == 'nyx': - state = self.md2.exporter.read('OmegaState') - if state != 'Ready': - logger.info('waiting for motor rotation') - logger.info('Click not registered') - return if self.vidActionDefineCenterRadio.isChecked(): self.vidActionC2CRadio.setChecked( True @@ -3868,10 +4149,10 @@ def pixelSelect(self, event): return fov = self.getCurrentFOV() correctedC2C_x = self.getBeamCenterX() + ( - x_click - (self.centerMarker.x() - self.centerMarkerCharOffsetX) - 20 + x_click - (self.centerMarker.x() + self.centerMarkerCharOffsetX) ) correctedC2C_y = self.getBeamCenterY() + ( - y_click - (self.centerMarker.y() - self.centerMarkerCharOffsetY) - 40 + y_click - (self.centerMarker.y() + self.centerMarkerCharOffsetY) ) current_viewangle = daq_utils.mag1ViewAngle @@ -3887,42 +4168,12 @@ def pixelSelect(self, event): ''' if self.threeClickCount > 0: # 3-click centering self.threeClickCount = self.threeClickCount + 1 - self.threeClickSignal.emit('{} more clicks'.format(str(4-self.threeClickCount))) #adding drawing for three click centering logger.info('Drawing 3 click line {} at x_value: {} and y_value {}'.format(self.threeClickCount, x_click, y_click)) self.threeClickLines.append( self.scene.addLine(x_click, 0, x_click, 512, penGreen) ) - - - if daq_utils.exporter_enabled: - correctedC2C_x = x_click + 5 + ((daq_utils.screenPixX/2) - (self.centerMarker.x() + self.centerMarkerCharOffsetX)) - correctedC2C_y = y_click - 35 + ((daq_utils.screenPixY/2) - (self.centerMarker.y() + self.centerMarkerCharOffsetY)) - lsdc_x = daq_utils.screenPixX - lsdc_y = daq_utils.screenPixY - md2_x = self.md2.center_pixel_x.get() * 2 - md2_y = self.md2.center_pixel_y.get() * 2 - scale_x = md2_x / lsdc_x - scale_y = md2_y / lsdc_y - correctedC2C_x = correctedC2C_x * scale_x - correctedC2C_y = correctedC2C_y * scale_y - self.md2.centring_click.put(f"{correctedC2C_x} {correctedC2C_y}") - #logger.info('waiting for motor rotation') - #time.sleep(0.2) - #self.omegaMoveCheck(0.02,'OmegaState') - - if self.threeClickCount == 4: - self.threeClickCount = 0 - self.threeClickSignal.emit('0') - self.click3Button.setStyleSheet("background-color: None") - #removing drawing for three click centering - logger.info('Removing 3 click lines') - for i in range(len(self.threeClickLines)): - self.scene.removeItem(self.threeClickLines[i]) - self.threeClickLines = [] - return - else: - comm_s = ( + comm_s = ( "center_on_click", [ correctedC2C_x, @@ -3944,10 +4195,10 @@ def pixelSelect(self, event): ], ) if not self.vidActionRasterExploreRadio.isChecked(): - self.aux_send_to_server(*comm_s) + self.send_to_server(*comm_s) if self.threeClickCount == 4: self.threeClickCount = 0 - self.threeClickSignal.emit('0') + self.processThreeClickCentering('0') self.click3Button.setStyleSheet("background-color: None") #removing drawing for three cick centering logger.info('Removing 3 click lines') @@ -3958,24 +4209,6 @@ def pixelSelect(self, event): return - ''' - Function to check if MD motors are rotating or not - ''' - def omegaMoveCheck(self, sleeptime,call='OmegaState'): - state = self.md2.exporter.read(call) - while(state == 'Moving'): - time.sleep(sleeptime) - state = self.md2.exporter.read(call) - #logger.info('\nIn Moving\n{}\n'.format(state)) - if state == 'Ready': - logger.info('ready for next click') - return state - else: - logger.info('\ndone moving, current state is: {}'.format(state)) - return state - - - def editScreenParamsCB(self): self.screenDefaultsDialog = ScreenDefaultsDialog(self) self.screenDefaultsDialog.show() @@ -3993,7 +4226,7 @@ def editSelectedRequestsCB(self): self.selectedSampleRequest = db_lib.getRequestByID(itemData) self.editSampleRequestCB(singleRequest) singleRequest = 0 - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def editSampleRequestCB(self, singleRequest): colRequest = self.selectedSampleRequest @@ -4012,11 +4245,11 @@ def editSampleRequestCB(self, singleRequest): if ( singleRequest == 1 ): # a touch kludgy, but I want to be able to edit parameters for multiple requests w/o screwing the data loc info - reqObj["file_prefix"] = str(self.dataPathGB.prefix_ledit.text()) + reqObj["file_prefix"] = str(self.dataPathGB.getFilePrefix()) reqObj["basePath"] = getBlConfig("visitDirectory") reqObj["directory"] = str(self.dataPathGB.dataPath_ledit.text()) reqObj["file_number_start"] = int( - self.dataPathGB.file_numstart_ledit.text() + self.dataPathGB.getFileNumstart() ) reqObj["attenuation"] = float(self.transmission_ledit.text()) reqObj["slit_width"] = float(self.beamWidth_ledit.text()) @@ -4042,7 +4275,7 @@ def editSampleRequestCB(self, singleRequest): reqObj["vectorParams"]["fpp"] = int(self.vectorFPP_ledit.text()) colRequest["request_obj"] = reqObj db_lib.updateRequest(colRequest) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def get_proposal_directory(self, proposal_num): if proposal_num not in self.proposal_directories: @@ -4056,7 +4289,9 @@ def get_proposal_directory(self, proposal_num): # Proposal does not exist return None for directory in response: - if directory["beamline"].lower() == daq_utils.beamline: + if (directory["beamline"].lower() == daq_utils.beamline + and directory["cycle"] in (CURRENT_CYCLE, 'commissioning') + ): self.proposal_directories[proposal_num] = directory["path"] return self.proposal_directories[proposal_num] @@ -4090,7 +4325,8 @@ def addRequestsToAllSelectedCB(self): elif itemDataType == "request": selectedSampleRequest = db_lib.getRequestByID(item.data(32)) self.selectedSampleID = selectedSampleRequest["sample"] - + else: # If its not a request or sample, move on + continue # If a request is already added to the sample, move on if self.selectedSampleID in samplesConsidered: continue @@ -4131,7 +4367,7 @@ def addRequestsToAllSelectedCB(self): self.addSampleRequestCB(selectedSampleID=self.selectedSampleID) samplesConsidered.add(self.selectedSampleID) else: # If queue collect is off does not matter how many requests you select only one will be added to current pin - self.selectedSampleID = self.mountedPin_pv.get() + self.selectedSampleID = self.get_mounted_sample_id() sample_data = db_lib.getSampleByID(self.selectedSampleID) prop_dir = self.get_proposal_directory(sample_data["proposalID"]) @@ -4154,7 +4390,7 @@ def addRequestsToAllSelectedCB(self): self.addSampleRequestCB(selectedSampleID=self.selectedSampleID) self.progressDialog.close() - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) if invalid_samples: self.popupServerMessage(f"Requests not added to {', '.join(invalid_samples)}") @@ -4185,8 +4421,8 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): daq_utils.setProposalID(propNum, createVisit=True) if getBlConfig("queueCollect") == 0: - if self.mountedPin_pv.get() != self.selectedSampleID: - self.selectedSampleID = self.mountedPin_pv.get() + if self.get_mounted_sample_id() != self.selectedSampleID: + self.selectedSampleID = self.get_mounted_sample_id() if not self.validateAllFields(): return @@ -4239,12 +4475,12 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): colRequest["request_obj"], self.periodicTable.eltCurrent.symbol, runNum, - self.EScanDataPathGB.prefix_ledit.text(), + self.EScanDataPathGB.getFilePrefix(), self.EScanDataPathGB.base_path_ledit.text(), sampleName, containerID, samplePositionInContainer, - self.EScanDataPathGB.file_numstart_ledit.text(), + self.EScanDataPathGB.getFileNumstart(), self.exp_time_ledit.text(), targetEnergy, self.escan_steps_ledit.text(), @@ -4270,13 +4506,13 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): if ( selectedSampleID == None ): # this is a temp kludge to see if this is called from addAll - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) else: logger.info("choose an element and try again") return # I don't like the code duplication, but one case is the mounted sample and selected centerings - so it's in a loop for multiple reqs, the other requires autocenter. - if (self.mountedPin_pv.get() == self.selectedSampleID) and ( + if (self.get_mounted_sample_id() == self.selectedSampleID) and ( len(self.centeringMarksList) != 0 ): selectedCenteringFound = 0 @@ -4315,7 +4551,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): reqObj["exposure_time"] = float(self.exp_time_ledit.text()) reqObj["resolution"] = float(self.resolution_ledit.text()) reqObj["file_prefix"] = str( - self.dataPathGB.prefix_ledit.text() + "_C" + str(i + 1) + self.dataPathGB.getFilePrefix() + "_C" + str(i + 1) ) reqObj["basePath"] = getBlConfig("visitDirectory") reqObj["directory"] = ( @@ -4333,7 +4569,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): + "/" ) reqObj["file_number_start"] = int( - self.dataPathGB.file_numstart_ledit.text() + self.dataPathGB.getFileNumstart() ) reqObj["attenuation"] = float(self.transmission_ledit.text()) reqObj["slit_width"] = float(self.beamWidth_ledit.text()) @@ -4415,7 +4651,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): reqObj["centeringOption"] = centeringOption if ( centeringOption == "Interactive" - and self.mountedPin_pv.get() == self.selectedSampleID + and self.get_mounted_sample_id() == self.selectedSampleID ) or centeringOption == "Testing": # user centered manually reqObj["pos_x"] = float(self.gon.x.val()) reqObj["pos_y"] = float(self.gon.y.val()) @@ -4446,7 +4682,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): + "/" + str(daq_utils.getVisitName()) + "/" - + str(self.dataPathGB.prefix_ledit.text()) + + str(self.dataPathGB.getFilePrefix()) + "/" + str(runNum) + "/" @@ -4456,9 +4692,9 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): + "/" ) reqObj["basePath"] = getBlConfig("visitDirectory") - reqObj["file_prefix"] = str(self.dataPathGB.prefix_ledit.text()) + reqObj["file_prefix"] = str(self.dataPathGB.getFilePrefix()) reqObj["file_number_start"] = int( - self.dataPathGB.file_numstart_ledit.text() + self.dataPathGB.getFileNumstart() ) if abs(reqObj["sweep_end"] - reqObj["sweep_start"]) < 5.0: reqObj["fastDP"] = False @@ -4561,7 +4797,7 @@ def addSampleRequestCB(self, rasterDef=None, selectedSampleID=None): if ( selectedSampleID == None ): # this is a temp kludge to see if this is called from addAll - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) def cloneRequestCB(self): self.eraseCB() @@ -4575,7 +4811,7 @@ def collectQueueCB(self): if currentRequest == {}: self.addRequestsToAllSelectedCB() logger.info("running queue") - self.send_to_server("runDCQueue") + self.aux_send_to_server("runDCQueue") def warmupGripperCB(self): @@ -4608,15 +4844,14 @@ def restartServerCB(self): #self.timerSample.start(SAMPLE_TIMER_DELAY) if reply == QtWidgets.QMessageBox.Yes: if daq_utils.beamline == "fmx" or daq_utils.beamline == "amx": - restart_pv = PV(daq_utils.beamlineComm + "RestartServerSignal") - restart_pv.put(not (restart_pv.get())) + self.restart_server_signal.put(not self.restart_server_signal.get()) else: logger.error("Not restarting server - unknown beamline") else: self.popupServerMessage("You don't have control") def openPhotonShutterCB(self): - self.photonShutterOpen_pv.put(1) + self.photon_shutter_open.put(1) def popUserScreenCB(self): if self.controlEnabled(): @@ -4625,13 +4860,10 @@ def popUserScreenCB(self): self.popupServerMessage("You don't have control") def parkRobotCB(self): - if daq_utils.beamline == "nyx": - self.send_to_server("parkRobot()") - else: - self.send_to_server("parkGripper") + self.send_to_server("parkGripper") def closePhotonShutterCB(self): - self.photonShutterClose_pv.put(1) + self.photon_shutter_close.put(1) def removePuckCB(self): #self.timerSample.stop() @@ -4649,10 +4881,10 @@ def setVectorPointCB(self, pointName): pointName: Name of the point to be placed (Either "vectorStart" or "vectorEnd") """ gonio_coords = { - "x": self.sampx_pv.get(), - "y": self.sampy_pv.get(), - "z": self.sampz_pv.get(), - "omega": self.omegaRBV_pv.get(), + "x": self.gon.x.readback.get(), + "y": self.gon.y.readback.get(), + "z": self.gon.z.readback.get(), + "omega": self.gon.omega.readback.get(), } center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY @@ -4663,6 +4895,7 @@ def setVectorPointCB(self, pointName): center=(center_x, center_y), length=int(self.vector_length_ledit.text()) ) + self.protoVectorRadio.setChecked(True) else: self.vector_widget.set_vector_point( point_name=pointName, @@ -4670,12 +4903,11 @@ def setVectorPointCB(self, pointName): gonio_coords=gonio_coords, center=(center_x, center_y), ) - self.processSampMove(self.sampx_pv.get(), "x") - self.processSampMove(self.sampy_pv.get(), "y") - self.processSampMove(self.sampz_pv.get(), "z") + self.processSampMove(self.gon.x.readback.get(), "x") + self.processSampMove(self.gon.y.readback.get(), "y") + self.processSampMove(self.gon.z.readback.get(), "z") def drawVector(self): - self.protoVectorRadio.setChecked(True) center_x = self.centerMarker.x() + self.centerMarkerCharOffsetX center_y = self.centerMarker.y() + self.centerMarkerCharOffsetY center = (center_x, center_y) @@ -4704,26 +4936,20 @@ def puckToDewarCB(self): ipos, db_lib.getContainerIDbyName(puckName, daq_utils.owner), ) - self.treeChanged_pv.put(1) + self.queue_change_signal.put(1) else: break def stopRunCB(self): logger.info("stopping collection") - self.aux_send_to_server("stopDCQueue", [1]) + self.send_to_server("stopDCQueue", [1]) def stopQueueCB(self): - logger.info("stopping queue") - if self.pauseQueueButton.text() == "Continue": - self.aux_send_to_server("continue_data_collection") - else: - self.aux_send_to_server("stopDCQueue", [2]) - + action = "pause" if self.pauseQueueButton.text().find("Pause") != -1 else "resume" + logger.info(f"queue control requested: {action}") + self.send_to_server("stopDCQueue", [2], {"action": action}) def mountSampleCB(self): - - - if getBlConfig("mountEnabled") == 0: self.popupServerMessage("Mounting disabled!! Call staff!") return @@ -4742,7 +4968,6 @@ def mountSampleCB(self): self.zoom2Radio.setChecked(True) self.zoomLevelToggledCB("Zoom2") self.protoComboBox.setCurrentText(CollectionProtocols.STANDARD) - self.protoComboActivatedCB(CollectionProtocols.STANDARD) def unmountSampleCB(self): logger.info("unmount sample") @@ -4816,8 +5041,7 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): firstFilename = daq_utils.create_filename(prefix_long, fnumstart) if validate_hdf5: if validation.validate_master_HDF5_file(firstFilename): - if daq_utils.beamline != "nyx": - self.albulaInterface.open_file(firstFilename) + self.albulaInterface.open_file(firstFilename) else: QtWidgets.QMessageBox.information( self, @@ -4846,10 +5070,10 @@ def refreshCollectionParams(self, selectedSampleRequest, validate_hdf5=True): self.fillPolyRaster(selectedSampleRequest) if ( - str(self.govStateMessagePV.get(as_string=True)) == "state SA" + str(self.governor_message.get()) == "state SA" and self.controlEnabled() # Move only in SA (Any other way for GUI to detect governor state?) and self.selectedSampleRequest["sample"] # with control enabled - == self.mountedPin_pv.get() + == self.get_mounted_sample_id() ): # And the sample of the selected request is mounted logger.info("attempting to move to raster start") self.processSampMove(self.gon.x.val(), "x") @@ -4936,7 +5160,7 @@ def row_clicked( sample_name = db_lib.getSampleNamebyID(self.selectedSampleID) logger.info("sample in pos " + str(itemData)) if ( - sample["uid"] != self.mountedPin_pv.get() + sample["uid"] != self.get_mounted_sample_id() and getBlConfig("queueCollect") == 0 ): # Don't fill data paths if an unmounted sample is clicked and queue collect is off return @@ -4945,7 +5169,7 @@ def row_clicked( itemData, createVisit=False ) self.refreshCollectionParams(self.selectedSampleRequest) - if self.stillModeStatePV.get(): + if self.still_mode_status.get(): self.setGuiValues({"osc_range": "0.0"}) reqObj = self.selectedSampleRequest["request_obj"] self.dataPathGB.setFilePrefix_ledit(str(reqObj["file_prefix"])) @@ -4981,7 +5205,7 @@ def row_clicked( self.selectedSampleID = self.selectedSampleRequest["sample"] sample = db_lib.getSampleByID(self.selectedSampleID) if ( - sample["uid"] != self.mountedPin_pv.get() + sample["uid"] != self.get_mounted_sample_id() and getBlConfig("queueCollect") == 0 ): # Don't fill request data if unmounted sample and queuecollect is off return @@ -4989,7 +5213,7 @@ def row_clicked( if reqObj["protocol"] == CollectionProtocols.E_SCAN: try: if reqObj["runChooch"]: - resultList = db_lib.getResultsforRequest(reqID) + resultList = db_lib.getResultsforRequest(reqID, result_type="choochResult") if len(resultList) > 0: lastResult = resultList[-1] if ( @@ -5005,163 +5229,11 @@ def row_clicked( ) self.refreshCollectionParams(self.selectedSampleRequest) - def processXrecRasterCB(self, value=None, char_value=None, **kw): - xrecFlag = value - if xrecFlag != "0": - self.xrecRasterSignal.emit(xrecFlag) - - def processChoochResultsCB(self, value=None, char_value=None, **kw): - choochFlag = value - if choochFlag != "0": - self.choochResultSignal.emit(choochFlag) - - def processEnergyChangeCB(self, value=None, char_value=None, **kw): - energyVal = value - self.energyChangeSignal.emit(energyVal) - - def mountedPinChangedCB(self, value=None, char_value=None, **kw): - mountedPinPos = value - self.mountedPinSignal.emit(mountedPinPos) - - def beamSizeChangedCB(self, value=None, char_value=None, **kw): - beamSizeFlag = value - self.beamSizeSignal.emit(beamSizeFlag) - - def controlMasterChangedCB(self, value=None, char_value=None, **kw): - controlMasterPID = value - self.controlMasterSignal.emit(controlMasterPID) - - def zebraArmStateChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.zebraArmStateSignal.emit(armState) - - def govRobotSeReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotSeReachSignal.emit(armState) - - def govRobotSaReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotSaReachSignal.emit(armState) - - def govRobotDaReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotDaReachSignal.emit(armState) - - def govRobotBlReachChangedCB(self, value=None, char_value=None, **kw): - armState = value - self.govRobotBlReachSignal.emit(armState) - - def detMessageChangedCB(self, value=None, char_value=None, **kw): - state = char_value - self.detMessageSignal.emit(state) - - def sampleFluxChangedCB(self, value=None, char_value=None, **kw): - state = value - self.sampleFluxSignal.emit(state) - - def zebraPulseStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraPulseStateSignal.emit(state) - - def stillModeStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.stillModeStateSignal.emit(state) - - def zebraDownloadStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraDownloadStateSignal.emit(state) - - def zebraSentTriggerStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraSentTriggerStateSignal.emit(state) - - def zebraReturnedTriggerStateChangedCB(self, value=None, char_value=None, **kw): - state = value - self.zebraReturnedTriggerStateSignal.emit(state) - - def shutterChangedCB(self, value=None, char_value=None, **kw): - shutterVal = value - self.fastShutterSignal.emit(shutterVal) - - def gripTempChangedCB(self, value=None, char_value=None, **kw): - gripVal = value - self.gripTempSignal.emit(gripVal) - - def cryostreamTempChangedCB(self, value=None, char_value=None, **kw): - cryostreamTemp = value - self.cryostreamTempSignal.emit(cryostreamTemp) - - def ringCurrentChangedCB(self, value=None, char_value=None, **kw): - ringCurrentVal = value - self.ringCurrentSignal.emit(ringCurrentVal) - - def beamAvailableChangedCB(self, value=None, char_value=None, **kw): - threeClickVal = value - self.threeClickSignal.emit(threeClickVal) - - def sampleExposedChangedCB(self, value=None, char_value=None, **kw): - sampleExposedVal = value - self.sampleExposedSignal.emit(sampleExposedVal) - - def processSampMoveCB(self, value=None, char_value=None, **kw): - posRBV = value - motID = kw["motID"] - self.sampMoveSignal.emit(posRBV, motID) - - def processROIChangeCB(self, value=None, char_value=None, **kw): - posRBV = value - ID = kw["ID"] - self.roiChangeSignal.emit(posRBV, ID) - - def processHighMagCursorChangeCB(self, value=None, char_value=None, **kw): - posRBV = value - ID = kw["ID"] - self.highMagCursorChangeSignal.emit(posRBV, ID) - - def processLowMagCursorChangeCB(self, value=None, char_value=None, **kw): - posRBV = value - ID = kw["ID"] - self.lowMagCursorChangeSignal.emit(posRBV, ID) - - def treeChangedCB(self, value=None, char_value=None, **kw): - if self.processID != self.treeChanged_pv.get(): - self.refreshTreeSignal.emit() - - def serverMessageCB(self, value=None, char_value=None, **kw): - serverMessageVar = char_value - self.serverMessageSignal.emit(serverMessageVar) - - def serverPopupMessageCB(self, value=None, char_value=None, **kw): - serverMessageVar = char_value - self.serverPopupMessageSignal.emit(serverMessageVar) - - def programStateCB(self, value=None, char_value=None, **kw): - programStateVar = value - self.programStateSignal.emit(programStateVar) - - def pauseButtonStateCB(self, value=None, char_value=None, **kw): - pauseButtonStateVar = value - self.pauseButtonStateSignal.emit(pauseButtonStateVar) - def initOphyd(self): - if daq_utils.beamline == "nyx": - # initialize devices - self.gon = GonioDevice("XF:19IDC-ES{MD2}:", name="gonio") - self.camera = CameraDevice("XF:19IDC-ES{MD2}:", name="camera") - self.md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") - self.front_light = LightDevice("XF:19IDC-ES{MD2}:Front", name="front_light") - self.back_light = LightDevice("XF:19IDC-ES{MD2}:Back", name="back_light") - self.aperture = MD2ApertureDevice("XF:19IDC-ES{MD2}:", name="aperture") - elif daq_utils.beamline == "amx": - self.gon = GonioDevice("XF:17IDB-ES:AMX{Gon:1", name="gonio") - elif daq_utils.beamline == "fmx": - self.gon = GonioDevice("XF:17IDC-ES:FMX{Gon:1", name="gonio") + self.gon = self.bl_devices.goniometer def initUI(self): self.tabs = QtWidgets.QTabWidget() - self.comm_pv = PV(daq_utils.beamlineComm + "command_s") - self.immediate_comm_pv = PV(daq_utils.beamlineComm + "immediate_command_s") - self.stillModeStatePV = PV(daq_utils.pvLookupDict["stillModeStatus"]) self.progressDialog = QtWidgets.QProgressDialog() self.progressDialog.setCancelButtonText("Cancel") self.progressDialog.setModal(False) @@ -5212,8 +5284,18 @@ def initUI(self): exitAction.setStatusTip("Exit application") exitAction.triggered.connect(self.closeAll) self.statusBar() - self.queue_collect_status_widget = QtWidgets.QLabel("Queue Collect: ON") + self.statusBar().setStyleSheet("QStatusBar { font-size: 14pt; }") + queue_collect_status = "ON" if getBlConfig("queueCollect") else "OFF" + self.queue_collect_status_widget = QtWidgets.QLabel(f"Queue Collect: {queue_collect_status}") + self.dewar_plate_position_status_widget = QtWidgets.QLabel(f"Plate Position: {int(safe_get(self.dewar_plate_pos, default=0))}") + sep = QtWidgets.QFrame() + sep.setFrameShape(QtWidgets.QFrame.VLine) + sep.setFrameShadow(QtWidgets.QFrame.Sunken) + sep.setLineWidth(1) self.statusBar().addPermanentWidget(self.queue_collect_status_widget) + self.statusBar().addPermanentWidget(sep) + self.statusBar().addPermanentWidget(self.dewar_plate_position_status_widget) + menubar = self.menuBar() fileMenu = menubar.addMenu("&File") @@ -5297,167 +5379,99 @@ def popStaffDialogCB(self): self.popupServerMessage("You don't have control") def closeAll(self): - self.hutchCornerCamThread.stop() - self.hutchTopCamThread.stop() - self.hutchCornerCamThread.wait() - self.hutchTopCamThread.wait() QtWidgets.QApplication.instance().quit() def initCallbacks(self): - self.beamSizeSignal.connect(self.processBeamSize) - self.beamSize_pv.add_callback(self.beamSizeChangedCB) - - self.treeChanged_pv = PV(daq_utils.beamlineComm + "live_q_change_flag") - self.refreshTreeSignal.connect(self.dewarTree.refreshTreeThreaded) - self.treeChanged_pv.add_callback(self.treeChangedCB) - self.mountedPin_pv = custom_pv.MountedPinPV(daq_utils.beamlineComm + "mounted_pin") - self.mountedPinSignal.connect(self.processMountedPin) - self.mountedPin_pv.add_callback(self.mountedPinChangedCB) - det_stop_pv = daq_utils.pvLookupDict["stopEiger"] - logger.info("setting stop Eiger detector PV: %s" % det_stop_pv) - self.stopDet_pv = PV(det_stop_pv) - det_reboot_pv = daq_utils.pvLookupDict["eigerIOC_reboot"] - logger.info("setting detector ioc reboot PV: %s" % det_reboot_pv) - self.rebootDetIOC_pv = PV(det_reboot_pv) - rz_pv = daq_utils.pvLookupDict["zebraReset"] - logger.info("setting zebra reset PV: %s" % rz_pv) - self.resetZebra_pv = PV(rz_pv) - rz_reboot_pv = daq_utils.pvLookupDict["zebraRebootIOC"] - logger.info("setting zebra reboot ioc PV: %s" % rz_reboot_pv) - self.rebootZebraIOC_pv = PV(rz_reboot_pv) - self.zebraArmedPV = PV(daq_utils.pvLookupDict["zebraArmStatus"]) - self.zebraArmStateSignal.connect(self.processZebraArmState) - self.zebraArmedPV.add_callback(self.zebraArmStateChangedCB) - - self.govRobotSeReachPV = PV(daq_utils.pvLookupDict["govRobotSeReach"]) - self.govRobotSeReachSignal.connect(self.processGovRobotSeReach) - self.govRobotSeReachPV.add_callback(self.govRobotSeReachChangedCB) - - self.govRobotSaReachPV = PV(daq_utils.pvLookupDict["govRobotSaReach"]) - self.govRobotSaReachSignal.connect(self.processGovRobotSaReach) - self.govRobotSaReachPV.add_callback(self.govRobotSaReachChangedCB) - - self.govRobotDaReachPV = PV(daq_utils.pvLookupDict["govRobotDaReach"]) - self.govRobotDaReachSignal.connect(self.processGovRobotDaReach) - self.govRobotDaReachPV.add_callback(self.govRobotDaReachChangedCB) - - self.govRobotBlReachPV = PV(daq_utils.pvLookupDict["govRobotBlReach"]) - self.govRobotBlReachSignal.connect(self.processGovRobotBlReach) - self.govRobotBlReachPV.add_callback(self.govRobotBlReachChangedCB) - - self.detectorMessagePV = PV(daq_utils.pvLookupDict["eigerStatMessage"]) - self.detMessageSignal.connect(self.processDetMessage) - self.detectorMessagePV.add_callback(self.detMessageChangedCB) - - self.sampleFluxSignal.connect(self.processSampleFlux) - self.sampleFluxPV.add_callback(self.sampleFluxChangedCB) - - self.stillModeStateSignal.connect(self.processStillModeState) - self.stillModeStatePV.add_callback(self.stillModeStateChangedCB) - - self.zebraPulsePV = PV(daq_utils.pvLookupDict["zebraPulseStatus"]) - self.zebraPulseStateSignal.connect(self.processZebraPulseState) - self.zebraPulsePV.add_callback(self.zebraPulseStateChangedCB) - - self.zebraDownloadPV = PV(daq_utils.pvLookupDict["zebraDownloading"]) - self.zebraDownloadStateSignal.connect(self.processZebraDownloadState) - self.zebraDownloadPV.add_callback(self.zebraDownloadStateChangedCB) - - self.zebraSentTriggerPV = PV(daq_utils.pvLookupDict["zebraSentTriggerStatus"]) - self.zebraSentTriggerStateSignal.connect(self.processZebraSentTriggerState) - self.zebraSentTriggerPV.add_callback(self.zebraSentTriggerStateChangedCB) - - self.zebraReturnedTriggerPV = PV( - daq_utils.pvLookupDict["zebraTriggerReturnStatus"] - ) - self.zebraReturnedTriggerStateSignal.connect( - self.processZebraReturnedTriggerState - ) - self.zebraReturnedTriggerPV.add_callback( - self.zebraReturnedTriggerStateChangedCB + self._signal_bridges: list = [] + + def _bridge(source, slot, **kwargs): + bridge = custom_pv.SignalQtBridge(source, slot, **kwargs) + self._signal_bridges.append(bridge) + return bridge + + _bridge(self.beam_size_mode, self.processBeamSize, transform=lambda v, cv, **kw: float(v)) + + _bridge( + self.queue_change_signal, + lambda _: self.dewarTree.refreshTreeThreaded(), + predicate=lambda v, cv, **kw: self.processID != self.queue_change_signal.get(), ) - self.controlMaster_pv = PV(daq_utils.beamlineComm + "zinger_flag") - self.controlMasterSignal.connect(self.processControlMaster) - self.controlMaster_pv.add_callback(self.controlMasterChangedCB) - - self.beamCenterX_pv = PV(daq_utils.pvLookupDict["beamCenterX"]) - self.beamCenterY_pv = PV(daq_utils.pvLookupDict["beamCenterY"]) - - self.choochResultFlag_pv = PV(daq_utils.beamlineComm + "choochResultFlag") - self.choochResultSignal.connect(self.processChoochResult) - self.choochResultFlag_pv.add_callback(self.processChoochResultsCB) - self.xrecRasterFlag_pv = PV(daq_utils.beamlineComm + "xrecRasterFlag") - self.xrecRasterFlag_pv.put("0") - self.xrecRasterSignal.connect(self.displayXrecRaster) - self.xrecRasterFlag_pv.add_callback(self.processXrecRasterCB) - self.message_string_pv = PV(daq_utils.beamlineComm + "message_string") - self.serverMessageSignal.connect(self.printServerMessage) - self.message_string_pv.add_callback(self.serverMessageCB) - self.popup_message_string_pv = PV( - daq_utils.beamlineComm + "gui_popup_message_string" + _bridge(self.mounted_pin_signal, self.processMountedPin, use_char=True) + + _bridge(self.zebra_armed, self.processZebraArmState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.gov_robot_se_reach, self.processGovRobotSeReach, transform=lambda v, cv, **kw: int(v)) + _bridge(self.gov_robot_sa_reach, self.processGovRobotSaReach, transform=lambda v, cv, **kw: int(v)) + _bridge(self.gov_robot_da_reach, self.processGovRobotDaReach, transform=lambda v, cv, **kw: int(v)) + _bridge(self.gov_robot_bl_reach, self.processGovRobotBlReach, transform=lambda v, cv, **kw: int(v)) + _bridge(self.detector_message, self.processDetMessage, use_char=True) + _bridge(self.sample_flux, self.processSampleFlux, transform=lambda v, cv, **kw: float(v)) + _bridge(self.still_mode_status, self.processStillModeState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.zebra_pulse, self.processZebraPulseState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.zebra_download, self.processZebraDownloadState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.zebra_sent_trigger, self.processZebraSentTriggerState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.zebra_returned_trigger, self.processZebraReturnedTriggerState, transform=lambda v, cv, **kw: int(v)) + _bridge(self.control_master_signal, self.processControlMaster, transform=lambda v, cv, **kw: int(v)) + _bridge(self.chooch_result_flag, self.processChoochResult, use_char=True, predicate=lambda v, cv, **kw: cv != "0") + + self.xrec_raster_flag.put("0") + _bridge(self.xrec_raster_flag, self.displayXrecRaster, use_char=True, predicate=lambda v, cv, **kw: cv != "0") + + _bridge(self.server_message_signal, self.printServerMessage, use_char=True) + _bridge(self.popup_message_signal, self.popupServerMessage, use_char=True) + _bridge(self.program_state_signal, self.colorProgramState, use_char=True) + _bridge(self.pause_button_state_signal, self.changePauseButtonState, use_char=True) + _bridge(self.energy_readback, self.processEnergyChange, transform=lambda v, cv, **kw: float(v)) + + for motor_signal, motor_id in [ + (self.gon.x.readback, "x"), + (self.gon.y.readback, "y"), + (self.gon.z.readback, "z"), + ]: + _bridge( + motor_signal, + lambda t: self.processSampMove(*t), + transform=lambda v, cv, _m=motor_id, **kw: (int(v), _m), + ) + + _bridge( + self.gon.omega.readback, + lambda t: self.processSampMove(*t), + transform=lambda v, cv, **kw: (int(v), "omega"), + # label="gon.omega", ) - self.serverPopupMessageSignal.connect(self.popupServerMessage) - self.popup_message_string_pv.add_callback(self.serverPopupMessageCB) - self.program_state_pv = PV(daq_utils.beamlineComm + "program_state") - self.programStateSignal.connect(self.colorProgramState) - self.program_state_pv.add_callback(self.programStateCB) - self.pause_button_state_pv = PV(daq_utils.beamlineComm + "pause_button_state") - self.pauseButtonStateSignal.connect(self.changePauseButtonState) - self.pause_button_state_pv.add_callback(self.pauseButtonStateCB) - - self.energyChangeSignal.connect(self.processEnergyChange) - self.energy_pv.add_callback(self.processEnergyChangeCB, motID="x") - - self.sampx_pv = PV(self.gon.x.readback.pvname) - self.sampMoveSignal.connect(self.processSampMove) - self.sampx_pv.add_callback(self.processSampMoveCB, motID="x") - self.sampy_pv = PV(self.gon.y.readback.pvname) - self.sampy_pv.add_callback(self.processSampMoveCB, motID="y") - self.sampz_pv = PV(self.gon.z.readback.pvname) - self.sampz_pv.add_callback(self.processSampMoveCB, motID="z") - - if self.scannerType == "PI": - self.sampFineX_pv = PV(daq_utils.motor_dict["fineX"] + ".RBV") - self.sampFineX_pv.add_callback(self.processSampMoveCB, motID="fineX") - self.sampFineY_pv = PV(daq_utils.motor_dict["fineY"] + ".RBV") - self.sampFineY_pv.add_callback(self.processSampMoveCB, motID="fineY") - self.sampFineZ_pv = PV(daq_utils.motor_dict["fineZ"] + ".RBV") - self.sampFineZ_pv.add_callback(self.processSampMoveCB, motID="fineZ") - - self.omega_pv = PV(self.gon.omega.setpoint.pvname) - self.omegaTweak_pv = PV(self.gon.omega.setpoint.pvname) - self.sampyTweak_pv = PV(self.gon.y.setpoint.pvname) - #if daq_utils.beamline == "nyx": - # self.sampzTweak_pv = PV(self.gon.x.setpoint.pvname + ".RLV") - #else: - self.sampzTweak_pv = PV(self.gon.z.setpoint.pvname) - self.omegaRBV_pv = PV(self.gon.omega.readback.pvname) - self.omegaRBV_pv.add_callback( - self.processSampMoveCB, motID="omega" - ) # I think monitoring this allows for the textfield to monitor val and this to deal with the graphics. Else next line has two callbacks on same thing. - self.photonShutterOpen_pv = PV(daq_utils.pvLookupDict["photonShutterOpen"]) - self.photonShutterClose_pv = PV(daq_utils.pvLookupDict["photonShutterClose"]) - self.fastShutterRBV_pv = PV(daq_utils.motor_dict["fastShutter"] + ".RBV") - self.fastShutterSignal.connect(self.processFastShutter) - self.fastShutterRBV_pv.add_callback(self.shutterChangedCB) - self.gripTempSignal.connect(self.processGripTemp) - self.gripTemp_pv.add_callback(self.gripTempChangedCB) + + _bridge(self.fast_shutter_rbv, self.processFastShutter, transform=lambda v, cv, **kw: float(v)) + _bridge(self.gripper_temp, self.processGripTemp, transform=lambda v, cv, **kw: float(v)) if getBlConfig(CRYOSTREAM_ONLINE): - self.cryostreamTempSignal.connect(self.processCryostreamTemp) - self.cryostreamTemp_pv.add_callback(self.cryostreamTempChangedCB) - self.ringCurrentSignal.connect(self.processRingCurrent) - self.ringCurrent_pv.add_callback(self.ringCurrentChangedCB) - self.threeClickSignal.connect(self.processThreeClickCentering) - self.beamAvailable_pv.add_callback(self.beamAvailableChangedCB) - self.sampleExposedSignal.connect(self.processSampleExposed) - self.sampleExposed_pv.add_callback(self.sampleExposedChangedCB) - self.highMagCursorChangeSignal.connect(self.processHighMagCursorChange) - self.highMagCursorX_pv.add_callback(self.processHighMagCursorChangeCB, ID="x") - self.highMagCursorY_pv.add_callback(self.processHighMagCursorChangeCB, ID="y") - self.lowMagCursorChangeSignal.connect(self.processLowMagCursorChange) - self.lowMagCursorX_pv.add_callback(self.processLowMagCursorChangeCB, ID="x") - self.lowMagCursorY_pv.add_callback(self.processLowMagCursorChangeCB, ID="y") + _bridge(self.cryostream_temp, self.processCryostreamTemp, transform=lambda v, cv, **kw: float(v)) + + _bridge(self.ring_current, self.processRingCurrent, transform=lambda v, cv, **kw: v) + _bridge(self.beam_available, self.processThreeClickCentering, use_char=True) + _bridge(self.sample_exposed, self.processSampleExposed, transform=lambda v, cv, **kw: v) + + for cursor_signal, axis_id in [ + (self.high_mag_cursor_x, "x"), + (self.high_mag_cursor_y, "y"), + ]: + _bridge( + cursor_signal, + lambda t: self.processHighMagCursorChange(*t), + transform=lambda v, cv, _i=axis_id, **kw: (int(v), _i), + ) + + for cursor_signal, axis_id in [ + (self.low_mag_cursor_x, "x"), + (self.low_mag_cursor_y, "y"), + ]: + _bridge( + cursor_signal, + lambda t: self.processLowMagCursorChange(*t), + transform=lambda v, cv, _i=axis_id, **kw: (int(v), _i), + ) + + _bridge(self.governor_message, self.manage_gov_state_change, use_char=True) + _bridge(self.dewar_plate_pos, self.update_dewar_plate_position, transform=lambda v, cv, **kw: int(v)) def popupServerMessage(self, message_s): if self.popUpMessageInit: @@ -5467,7 +5481,42 @@ def popupServerMessage(self, message_s): if message_s == "killMessage": return else: - self.popupMessage.showMessage(message_s) + try: + broadcast_command = json.loads(message_s) + self.processServerCommand(broadcast_command) + except json.JSONDecodeError: + self.popupMessage.showMessage(message_s) + except Exception as e: + logger.error(f"Could not process command: {e}") + + def processServerCommand(self, command: "dict[str, Any]"): + """ + Process a command broadcasted from the server + """ + if "highlight_cells" in command: + if self.rasterList: + raster_item: RasterGroup = self.rasterList[-1]["graphicsItem"] + raster_item.set_highlighted_cells(command["highlight_cells"]) + if "robot_online" in command: + self.staffScreenDialog.robotOnCheckBox.setChecked(command["robot_online"]) + if "enable_mount" in command: + self.staffScreenDialog.enableMountCheckBox.setChecked(command["enable_mount"]) + if "beam_check" in command: + self.staffScreenDialog.beamCheckOnCheckBox.setChecked(command["beam_check"]) + if "queue_collect" in command: + self.staffScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) + self.userScreenDialog.queueCollectOnCheckBox.setChecked(command["queue_collect"]) + # Update status widget and unmount-cold control based on queue_collect state + self.queue_collect_status_widget.setText( + f"Queue Collect: {'ON' if command['queue_collect'] else 'OFF'}" + ) + self.staffScreenDialog.gripperUnmountColdCheckBox.setEnabled(command["queue_collect"]) + if "unmount_cold" in command: + self.staffScreenDialog.gripperUnmountColdCheckBox.setChecked(command["unmount_cold"]) + if "set_beamsize" in command: + self.beamsizeComboBox.setEnabled(True) + current_index = int(self.beam_size_mode.get()) + self.beamsizeComboBox.setCurrentIndex(current_index) def printServerMessage(self, message_s): if self.textWindowMessageInit: @@ -5496,22 +5545,22 @@ def changePauseButtonState(self, buttonState_s): def controlEnabled(self): return ( - self.processID == abs(int(self.controlMaster_pv.get())) + self.processID == abs(int(self.control_master_signal.get())) and self.controlMasterCheckBox.isChecked() ) def send_to_server(self, function_name: str, args: "Optional[List]" = None, kwargs: "Optional[Dict]" = None): if function_name == "lockControl": - self.controlMaster_pv.put(0 - self.processID) + self.control_master_signal.put(0 - self.processID) return if function_name == "unlockControl": - self.controlMaster_pv.put(self.processID) + self.control_master_signal.put(self.processID) return if self.controlEnabled(): time.sleep(0.01) message = self.generate_server_message(function_name, args, kwargs) logger.info(f"send_to_server: {message}") - self.comm_pv.put(message) + self.command_signal.put(message) else: self.popupServerMessage("You don't have control") @@ -5524,6 +5573,7 @@ def generate_server_message( kwargs = {} return json.dumps( { + "id": time.time_ns(), "function": function_name, "args": args, "kwargs": kwargs, @@ -5535,6 +5585,6 @@ def aux_send_to_server(self, function_name: str, args: "Optional[List]" = None, time.sleep(0.01) message = self.generate_server_message(function_name, args, kwargs) logger.info(f"aux_send_to_server: {message}") - self.immediate_comm_pv.put(message) + self.immediate_command_signal.put(message) else: self.popupServerMessage("You don't have control") diff --git a/gui/data_loc_info.py b/gui/data_loc_info.py index 41d1d2b5..3b7ead48 100644 --- a/gui/data_loc_info.py +++ b/gui/data_loc_info.py @@ -1,12 +1,11 @@ import logging -import os import typing -from qtpy import QtCore, QtGui, QtWidgets +from qtpy import QtWidgets, QtCore +from qtpy.QtWidgets import QGroupBox import daq_utils import db_lib -from config_params import VALID_PREFIX_LENGTH, VALID_PREFIX_NAME if typing.TYPE_CHECKING: from lsdcGui import ControlMain @@ -14,7 +13,7 @@ logger = logging.getLogger() -class DataLocInfo(QtWidgets.QGroupBox): +class DataLocInfo(QGroupBox): # type: ignore[misc] def __init__(self, parent: "ControlMain"): QtWidgets.QGroupBox.__init__(self, parent) self.parent = parent @@ -24,7 +23,9 @@ def __init__(self, parent: "ControlMain"): self.basePathLabel = QtWidgets.QLabel("Base Path:") self.base_path_ledit = QtWidgets.QLabel() self.base_path_ledit.setText(daq_utils.getBlConfig("visitDirectory")) - self.base_path_ledit.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.base_path_ledit.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard + ) # self.base_path_ledit.textChanged[str].connect(self.basePathTextChanged) self.browseBasePathButton = QtWidgets.QPushButton("Browse...") self.browseBasePathButton.setEnabled(False) @@ -32,22 +33,8 @@ def __init__(self, parent: "ControlMain"): self.hBoxDPathParams1.addWidget(self.basePathLabel) self.hBoxDPathParams1.addWidget(self.base_path_ledit) self.hBoxDPathParams1.addWidget(self.browseBasePathButton) - self.hBoxDPathParams2 = QtWidgets.QHBoxLayout() - self.dataPrefixLabel = QtWidgets.QLabel( - "Data Prefix:\n(%s Char Limit)" % VALID_PREFIX_LENGTH - ) - self.prefix_ledit = QtWidgets.QLineEdit() - self.prefix_ledit.textChanged[str].connect(self.prefixTextChanged) - self.prefix_ledit.setValidator( - QtGui.QRegExpValidator(QtCore.QRegExp(VALID_PREFIX_NAME), self.prefix_ledit) - ) - self.hBoxDPathParams2.addWidget(self.dataPrefixLabel) - self.hBoxDPathParams2.addWidget(self.prefix_ledit) - self.dataNumstartLabel = QtWidgets.QLabel("File Number Start:") - self.file_numstart_ledit = QtWidgets.QLineEdit() - self.file_numstart_ledit.setText("1") - self.file_numstart_ledit.setValidator(QtGui.QIntValidator(1, 99999, self)) - self.file_numstart_ledit.setFixedWidth(50) + self._file_prefix = "" + self._file_numstart = "1" self.hBoxDPathParams3 = QtWidgets.QHBoxLayout() self.dataPathLabel = QtWidgets.QLabel("Data Path:") self.dataPath_ledit = QtWidgets.QLineEdit() @@ -55,21 +42,19 @@ def __init__(self, parent: "ControlMain"): self.dataPath_ledit.setReadOnly(True) self.hBoxDPathParams3.addWidget(self.dataPathLabel) self.hBoxDPathParams3.addWidget(self.dataPath_ledit) - self.hBoxDPathParams2.addWidget(self.dataNumstartLabel) - self.hBoxDPathParams2.addWidget(self.file_numstart_ledit) self.vBoxDPathParams1.addLayout(self.hBoxDPathParams1) - self.vBoxDPathParams1.addLayout(self.hBoxDPathParams2) self.vBoxDPathParams1.addLayout(self.hBoxDPathParams3) self.setLayout(self.vBoxDPathParams1) def basePathTextChanged(self, text): - prefix = self.prefix_ledit.text() + prefix = self.getFilePrefix() self.setDataPath_ledit( text + "/" + str(daq_utils.getVisitName()) + "/" + prefix + "/#/" ) def prefixTextChanged(self, text): - prefix = self.prefix_ledit.text() + self._file_prefix = str(text) + prefix = self.getFilePrefix() try: runNum = db_lib.getSampleRequestCount(self.parent.selectedSampleID) except KeyError: @@ -102,13 +87,19 @@ def prefixTextChanged(self, text): ) def setFileNumstart_ledit(self, s): - self.file_numstart_ledit.setText(s) + self._file_numstart = str(s) def setFilePrefix_ledit(self, s): - self.prefix_ledit.setText(s) + self.prefixTextChanged(str(s)) def setBasePath_ledit(self, s): self.base_path_ledit.setText(s) def setDataPath_ledit(self, s): self.dataPath_ledit.setText(s) + + def getFileNumstart(self): + return self._file_numstart + + def getFilePrefix(self): + return self._file_prefix diff --git a/gui/dewar_tree.py b/gui/dewar_tree.py index 4fe4a365..17abc700 100644 --- a/gui/dewar_tree.py +++ b/gui/dewar_tree.py @@ -1,6 +1,7 @@ import getpass import logging import os +import time import typing import requests @@ -54,6 +55,8 @@ def __init__(self, parent: "ControlMain"): self._programmatic_status_update = False self.threadPool = QtCore.QThreadPool.globalInstance() self.refresh_running = False + self._min_refresh_interval_s = 1.0 + self._last_refresh_started_monotonic = 0.0 self.expanded.connect(self.on_expanded) def on_expanded(self, index): @@ -65,7 +68,7 @@ def on_expanded(self, index): def toggle_follow_request(self, check_state): if check_state == Qt.CheckState.Checked: self.follow_current_request = True - self.refreshTree() + self.refreshTreeThreaded() else: self.follow_current_request = False @@ -158,35 +161,96 @@ def keyPressEvent(self, event): super(DewarTree, self).keyPressEvent(event) def refreshTree(self): - self._programmatic_status_update = True - self.refreshTreeDewarView() - self._programmatic_status_update = False + self.refreshTreeThreaded() - def refreshTreeThreaded(self): + def refreshTreeThreaded(self, get_latest_pucks=False, hard_refresh=False): if self.refresh_running: - # A refresh is already running, so ignore the new request. return - self.refresh_running = True # Mark that a refresh is in progress - self.runnable = DataFetchRunnable(self.fetchData) + if ( + time.monotonic() - self._last_refresh_started_monotonic + < self._min_refresh_interval_s + ): + return + + self._start_refresh_now(get_latest_pucks, hard_refresh) + + def _start_refresh_now(self, get_latest_pucks, hard_refresh): + self.refresh_running = True + self._last_refresh_started_monotonic = time.monotonic() + self.runnable = DataFetchRunnable( + self.fetchData, + get_latest_pucks=get_latest_pucks, + hard_refresh=hard_refresh, + ) self.runnable.signal.finished.connect(self.update_model) self.threadPool.start(self.runnable) - def fetchData(self): + def _fetch_proposal_membership_updates(self, sample_data): + if IS_STAFF: + return {} + + missing_proposals = { + sample.get("proposalID") + for sample in sample_data.values() + if sample.get("proposalID") not in (None, "") + and sample.get("proposalID") not in self.proposal_membership + } + if not missing_proposals: + return {} + + proposal_api_url = os.environ.get("NSLS2_API_URL") + if not proposal_api_url: + return {proposal_id: False for proposal_id in missing_proposals} + + username = getpass.getuser() + proposal_membership_updates = {} + for proposal_id in missing_proposals: + try: + response = requests.get( + f"{proposal_api_url}/v1/proposal/{proposal_id}", timeout=(2, 5) + ) + response.raise_for_status() + proposal = response.json().get("proposal", {}) + users = proposal.get("users", []) + proposal_membership_updates[proposal_id] = username in [ + user.get("username") for user in users if "username" in user + ] + except Exception as e: + logger.exception(e) + proposal_membership_updates[proposal_id] = False + + return proposal_membership_updates + + def fetchData(self, get_latest_pucks=False, hard_refresh=False): """ This method performs the heavy data retrieval. It must not interact with any GUI elements. """ - dewar_data, puck_data, sample_data, request_data = db_lib.get_dewar_tree_data( - daq_utils.primaryDewarName, daq_utils.beamline, get_latest_pucks=False - ) - # Return the fetched data in a convenient container. - return { - "dewar_data": dewar_data, - "puck_data": puck_data, - "sample_data": sample_data, - "request_data": request_data, - } + try: + dewar_data, puck_data, sample_data, request_data = db_lib.get_dewar_tree_data( + daq_utils.primaryDewarName, + daq_utils.beamline, + get_latest_pucks=get_latest_pucks, + ) + proposal_membership_updates = self._fetch_proposal_membership_updates( + sample_data + ) + return { + "dewar_data": dewar_data, + "puck_data": puck_data, + "sample_data": sample_data, + "request_data": request_data, + "proposal_membership_updates": proposal_membership_updates, + "hard_refresh": hard_refresh, + } + except Exception as e: + logger.exception(e) + return { + "refresh_error": str(e), + "hard_refresh": hard_refresh, + "proposal_membership_updates": {}, + } def set_unmounted_sample(self, item): item.setForeground(QtGui.QColor("black")) @@ -204,65 +268,98 @@ def set_mounted_sample(self, item, sample_name=None): font.setItalic(True) font.setOverline(True) item.setFont(font) - if self.parent.mountedPin_pv.get_pin_state() is not None: - state = self.parent.mountedPin_pv.get_pin_state() + if self.parent.get_mounted_pin_state() is not None: + state = self.parent.get_mounted_pin_state() mount_state = MountState(state) if sample_name is None: sample_name = item.text() item.setText(sample_name + MountState.get_text(mount_state)) - def refreshTreeDewarView(self, get_latest_pucks=False): - puck = "" - #self.model.clear() - dewar_data, puck_data, sample_data, request_data = db_lib.get_dewar_tree_data( - daq_utils.primaryDewarName, daq_utils.beamline, get_latest_pucks + def refreshTreeDewarView(self, get_latest_pucks=False, hard_refresh=False): + self.refreshTreeThreaded( + get_latest_pucks=get_latest_pucks, + hard_refresh=hard_refresh, ) - data = { - "dewar_data": dewar_data, - "puck_data": puck_data, - "sample_data": sample_data, - "request_data": request_data, - } - self.update_model(data) + + def _finish_refresh(self): + self.refresh_running = False + + def _index_signature(self, index): + if not index or not index.isValid(): + return (None, None) + item = self.model.itemFromIndex(index) + if item is None: + return (None, None) + return (item.data(33), item.data(32)) def update_model(self, data): + update_start = time.monotonic() self._programmatic_status_update = True - dewar_data = data["dewar_data"] - puck_data = data["puck_data"] - sample_data = data["sample_data"] - request_data = data["request_data"] - parentItem = self.model.invisibleRootItem() - for i, puck_id in enumerate( - dewar_data["content"] - ): # dewar contents is the list of puck IDs - puck = "" - puckName = "" - if puck_id: - puck = puck_data[puck_id] - puckName = puck["name"] - sector, puck_pos = divmod(i, self.pucksPerDewarSector) - index_s = f"{sector+1}{chr(puck_pos + ord('A'))}" - item = QtGui.QStandardItem(QtGui.QIcon(ICON), f"{index_s} {puckName}") - item.setData(puckName, 32) - item.setData("container", 33) - if parentItem.rowCount() == i: - parentItem.appendRow(item) - elif item.data(32) != parentItem.child(i).data(32): - # parentItem.takeChild(i,0) - parentItem.setChild(i, 0, item) - - updated_item = parentItem.child(i) - if puck != "" and puckName != "private": - puckContents = puck.get("content", []) - self.add_samples_to_puck_tree( - puckContents, updated_item, index_s, sample_data, request_data - ) - #self.setModel(self.model) - if not self.initialized: - self.expandAll() - self.initialized = True - self._programmatic_status_update = False - self.refresh_running = False + try: + if isinstance(data, Exception): + logger.error("Dewar tree fetch failed: %s", data) + return + + proposal_membership_updates = data.get("proposal_membership_updates", {}) + if proposal_membership_updates: + self.proposal_membership.update(proposal_membership_updates) + + if data.get("refresh_error"): + return + + if daq_utils.getBlConfig("special_mount_enabled"): + self.pucksPerDewarSector = 1 + self.dewarSectors = 1 + else: + self.pucksPerDewarSector = PUCKS_PER_DEWAR_SECTOR[daq_utils.beamline] + self.dewarSectors = DEWAR_SECTORS[daq_utils.beamline] + + if data.get("hard_refresh"): + self.model.clear() + + dewar_data = data["dewar_data"] + puck_data = data["puck_data"] + sample_data = data["sample_data"] + request_data = data["request_data"] + parentItem = self.model.invisibleRootItem() + for i, puck_id in enumerate( + dewar_data["content"] + ): # dewar contents is the list of puck IDs + puck = "" + puckName = "" + if puck_id: + puck = puck_data[puck_id] + puckName = puck["name"] + sector, puck_pos = divmod(i, self.pucksPerDewarSector) + index_s = f"{sector+1}{chr(puck_pos + ord('A'))}" + item = QtGui.QStandardItem(QtGui.QIcon(ICON), f"{index_s} {puckName}") + item.setData(puckName, 32) + item.setData("container", 33) + if parentItem.rowCount() == i: + parentItem.appendRow(item) + elif item.data(32) != parentItem.child(i).data(32): + # parentItem.takeChild(i,0) + parentItem.setChild(i, 0, item) + + updated_item = parentItem.child(i) + if puck != "" and puckName != "private": + puckContents = puck.get("content", []) + self.add_samples_to_puck_tree( + puckContents, updated_item, index_s, sample_data, request_data + ) + #self.setModel(self.model) + if not self.initialized: + self.expandAll() + self.initialized = True + + update_duration = time.monotonic() - update_start + if update_duration > 1.0: + logger.warning("DEWAR_TREE_UPDATE_SLOW duration_s=%.3f", update_duration) + else: + logger.info("DEWAR_TREE_UPDATE_DONE duration_s=%.3f", update_duration) + finally: + self._programmatic_status_update = False + self._finish_refresh() def add_samples_to_puck_tree( self, @@ -322,11 +419,11 @@ def add_samples_to_puck_tree( item.setText(position_s) item.setData(sample_id, 32) item.setData("sample", 33) - if hasattr(self.parent, "mountedPin_pv") and sample_id == self.parent.mountedPin_pv.get(): + if sample_id == self.parent.get_mounted_sample_id(): self.set_mounted_sample(item, position_s) else: self.set_unmounted_sample(item) - if hasattr(self.parent, "mountedPin_pv") and sample_id == self.parent.mountedPin_pv.get(): + if sample_id == self.parent.get_mounted_sample_id(): mountedIndex = self.model.indexFromItem(item) # looking for the selected item if sample_id == self.parent.selectedSampleID: @@ -359,8 +456,10 @@ def add_samples_to_puck_tree( current_index = mountedIndex if current_index and self.follow_current_request: - self.setCurrentIndex(current_index) - self.parent.row_clicked(current_index) + if self._index_signature(current_index) != self._index_signature( + self.currentIndex() + ): + self.setCurrentIndex(current_index) def add_requests_to_sample(self, item, base_requests, nested_requests): # Go through the sample requests and add them to the sample @@ -428,23 +527,7 @@ def add_requests_to_sample(self, item, base_requests, nested_requests): self.expand(match_index) def is_proposal_member(self, proposal_id) -> bool: - # Check if the user running LSDC is part of the sample's proposal - try: - if proposal_id not in self.proposal_membership: - r = requests.get(f"{os.environ['NSLS2_API_URL']}/v1/proposal/{proposal_id}") - r.raise_for_status() - response = r.json()['proposal'] - if "users" in response and getpass.getuser() in [ - user["username"] for user in response["users"] if "username" in user - ]: - self.proposal_membership[proposal_id] = True - else: - logger.info(f"Users not found in response: {response}") - self.proposal_membership[proposal_id] = False - except Exception as e: - logger.exception(e) - return False - return self.proposal_membership[proposal_id] + return self.proposal_membership.get(proposal_id, False) def create_request_item(self, request) -> QtGui.QStandardItem: col_item = QtGui.QStandardItem( @@ -499,7 +582,7 @@ def refreshTreePriorityView( )["content"] maxPucks = len(dewarContents) requestedSampleList = [] - mountedPin = self.parent.mountedPin_pv.get() + mountedPin = self.parent.get_mounted_sample_id() for i in range( len(self.orderedRequests) ): # I need a list of samples for parent nodes @@ -588,11 +671,11 @@ def queueSelectedSample(self, item): if item.data(33) == "request": reqID = str(item.data(32)) if item.checkState() == Qt.Checked: - db_lib.updatePriority(reqID, 5000) + db_lib.queueRequest(reqID) else: - db_lib.updatePriority(reqID, 0) + db_lib.dequeueRequest(reqID) item.setBackground(QtGui.QColor("white")) - self.parent.treeChanged_pv.put( + self.parent.queue_change_signal.put( self.parent.processID #1 ) # the idea is touch the pv, but have this gui instance not refresh @@ -609,9 +692,8 @@ def queueAllSelectedCB(self): itemData = str(item.data(32)) itemDataType = str(item.data(33)) if (itemDataType == "request") and item.isCheckable(): - selectedSampleRequest = db_lib.getRequestByID(itemData) - db_lib.updatePriority(itemData, 5000) - self.parent.treeChanged_pv.put(1) + db_lib.queueRequest(itemData) + self.parent.queue_change_signal.put(1) def deQueueAllSelectedCB(self): selmod = self.selectionModel() @@ -622,9 +704,8 @@ def deQueueAllSelectedCB(self): itemData = str(item.data(32)) itemDataType = str(item.data(33)) if (itemDataType == "request") and item.isCheckable(): - selectedSampleRequest = db_lib.getRequestByID(itemData) - db_lib.updatePriority(itemData, 0) - self.parent.treeChanged_pv.put(1) + db_lib.dequeueRequest(itemData) + self.parent.queue_change_signal.put(1) def confirmDelete(self, numReq): if numReq: @@ -668,6 +749,9 @@ def deleteSelectedCB(self, deleteAll): selectedSampleRequest = db_lib.getRequestByID(itemData) self.selectedSampleID = selectedSampleRequest["sample"] db_lib.deleteRequest(selectedSampleRequest["uid"]) + for row in range(item.rowCount()): + child_item = item.child(row) + db_lib.deleteRequest(str(child_item.data(32))) if selectedSampleRequest["request_obj"]["protocol"] in ( CollectionProtocols.RASTER, CollectionProtocols.STEP_RASTER, @@ -687,7 +771,7 @@ def deleteSelectedCB(self, deleteAll): CollectionProtocols.STEP_VECTOR): self.parent.clearVectorCB() self.parent.progressDialog.close() - self.parent.treeChanged_pv.put(1) + self.parent.queue_change_signal.put(1) def expandAllCB(self): self.expandAll() diff --git a/gui/dialog/dewar.py b/gui/dialog/dewar.py index fb510540..195c9294 100644 --- a/gui/dialog/dewar.py +++ b/gui/dialog/dewar.py @@ -77,7 +77,7 @@ def on_button(self, n): self.dewarPos = n db_lib.removePuckFromDewar(daq_utils.beamline, int(n)) self.allButtonList[int(n)].setText("Empty") - self.parent.treeChanged_pv.put(1) + self.parent.queue_change_signal.put(1) else: self.dewarPos = n self.accept() diff --git a/gui/dialog/multicol.py b/gui/dialog/multicol.py index 2b81c357..b7d987f6 100644 --- a/gui/dialog/multicol.py +++ b/gui/dialog/multicol.py @@ -16,7 +16,6 @@ logger = logging.getLogger() class MultiColDialog(QtWidgets.QDialog): - def __init__(self, parent: "ControlMain", raster_req: dict, raster_result: dict): # Pass in the raster request and result for the widget to run a cell selection algorithm super().__init__(parent) @@ -75,12 +74,15 @@ def submit_centers(self): flattened_index = calculate_flattened_index(x, y, self.M, self.N, self.direction) hitFile = self.cell_results[flattened_index]["cellMapKey"] hitCoords = self.raster_map[hitFile] + parent_req_id = self.raster_result['result_obj']["parentReqID"] + #current_omega = self._parent.gon.omega.get() self.addMultiRequestLocation(self.raster_result["request"], hitCoords, flattened_index, float(self._parent.osc_end_ledit.text())) - self._parent.treeChanged_pv.put(1) + self._parent.queue_change_signal.put(1) self.accept() - def addMultiRequestLocation(self, parentReqID, hitCoords, locIndex, wedge=10.0): + def addMultiRequestLocation(self, parentReqID, hitCoords, locIndex, wedge=None): #rough proto of what to pass here for details like how to organize data + print(wedge) parentRequest = db_lib.getRequestByID(parentReqID) sampleID = parentRequest["sample"] @@ -92,6 +94,8 @@ def addMultiRequestLocation(self, parentReqID, hitCoords, locIndex, wedge=10.0): ss = parentRequest["request_obj"]["rasterDef"]["omega"] if "wedge" in parentRequest["request_obj"]: wedge = float(parentRequest["request_obj"]["wedge"]) + elif wedge is None: + wedge = 10 newReqObj = tempnewStratRequest["request_obj"] newReqObj["sweep_start"] = ss - wedge/2 @@ -109,6 +113,6 @@ def addMultiRequestLocation(self, parentReqID, hitCoords, locIndex, wedge=10.0): newReqObj["xia2"] = False newReqObj["runNum"] = runNum newReqObj["parentReqID"] = parentReqID - newReqObj["energy"] = self._parent.energy_pv.get() + newReqObj["energy"] = self._parent.energy_readback.get() newReqObj["wavelength"] = daq_utils.energy2wave(newReqObj["energy"]) - db_lib.addRequesttoSample(sampleID,newReqObj["protocol"],daq_utils.owner,newReqObj,priority=6000,proposalID=daq_utils.getProposalID()) + newRequestUID = db_lib.addRequesttoSample(sampleID,newReqObj["protocol"],daq_utils.owner,newReqObj,priority=6000,proposalID=daq_utils.getProposalID()) diff --git a/gui/dialog/set_energy.py b/gui/dialog/set_energy.py index 66a5f3c1..97711bcf 100644 --- a/gui/dialog/set_energy.py +++ b/gui/dialog/set_energy.py @@ -106,7 +106,7 @@ def unmount_cold_dialog(self): return msg_box def set_full_alignment_energy(self): - if self._parent.mountedPin_pv.get(): + if self._parent.get_mounted_sample_id(): # If sample is mounted, ask user to unmount cold response = self.unmount_cold_dialog().exec_() diff --git a/gui/dialog/staff_screen.py b/gui/dialog/staff_screen.py index 1375f7be..ec520c11 100644 --- a/gui/dialog/staff_screen.py +++ b/gui/dialog/staff_screen.py @@ -43,27 +43,18 @@ def __init__(self, parent: "ControlMain", **kwargs): hBoxColParams0.addWidget(puckToDewarButton) hBoxColParams0.addWidget(removePuckButton) self.robotOnCheckBox = QCheckBox("Robot (On)") - if getBlConfig("robot_online") == 1: - self.robotOnCheckBox.setChecked(True) - else: - self.robotOnCheckBox.setChecked(False) - self.robotOnCheckBox.stateChanged.connect(self.robotOnCheckCB) + self.robotOnCheckBox.setChecked(True if getBlConfig("robot_online") else False) + self.robotOnCheckBox.clicked.connect(self.robotOnCheckCB) self.topViewCheckOnCheckBox = QCheckBox("TopViewCheck (On)") - if getBlConfig(TOP_VIEW_CHECK) == 1: - self.topViewCheckOnCheckBox.setChecked(True) - else: - self.topViewCheckOnCheckBox.setChecked(False) - self.topViewCheckOnCheckBox.stateChanged.connect(self.topViewOnCheckCB) + self.topViewCheckOnCheckBox.setChecked(True if getBlConfig(TOP_VIEW_CHECK) else False) + self.topViewCheckOnCheckBox.clicked.connect(self.topViewOnCheckCB) # BeamCheck check box self.beamCheckOnCheckBox = QCheckBox("BeamCheck (On)") - if getBlConfig(BEAM_CHECK) == 1: - self.beamCheckOnCheckBox.setChecked(True) - else: - self.beamCheckOnCheckBox.setChecked(False) - self.beamCheckOnCheckBox.stateChanged.connect(self.beamCheckOnCheckCB) + self.beamCheckOnCheckBox.setChecked(True if getBlConfig(BEAM_CHECK) else False) + self.beamCheckOnCheckBox.clicked.connect(self.beamCheckOnCheckCB) self.gripperUnmountColdCheckBox = QCheckBox("Unmount Cold") - self.gripperUnmountColdCheckBox.stateChanged.connect(self.unmountColdCheckCB) + self.gripperUnmountColdCheckBox.clicked.connect(self.unmountColdCheckCB) if getBlConfig(UNMOUNT_COLD_CHECK) == 1: self.gripperUnmountColdCheckBox.setEnabled(True) self.gripperUnmountColdCheckBox.setChecked(True) @@ -75,48 +66,33 @@ def __init__(self, parent: "ControlMain", **kwargs): if daq_utils.beamline == "fmx": self.set_energy_checkbox = QCheckBox("Set Energy") hBoxColParams1.addWidget(self.set_energy_checkbox) - if getBlConfig(SET_ENERGY_CHECK) == 1: - self.set_energy_checkbox.setChecked(True) - else: - self.set_energy_checkbox.setChecked(False) - self.set_energy_checkbox.stateChanged.connect(self.set_energy_check_cb) + self.set_energy_checkbox.setChecked(True if getBlConfig(SET_ENERGY_CHECK) else False) + self.set_energy_checkbox.clicked.connect(self.set_energy_check_cb) self.queueCollectOnCheckBox = QCheckBox("Queue Collect") hBoxColParams1.addWidget(self.queueCollectOnCheckBox) self.checkQueueCollect() - self.queueCollectOnCheckBox.stateChanged.connect(self.queueCollectOnCheckCB) + self.queueCollectOnCheckBox.clicked.connect(self.queueCollectOnCheckCB) self.vertRasterOnCheckBox = QCheckBox("Vert. Raster") hBoxColParams1.addWidget(self.vertRasterOnCheckBox) - if getBlConfig("vertRasterOn") == 1: - self.vertRasterOnCheckBox.setChecked(True) - else: - self.vertRasterOnCheckBox.setChecked(False) - self.vertRasterOnCheckBox.stateChanged.connect(self.vertRasterOnCheckCB) + self.vertRasterOnCheckBox.setChecked(True if getBlConfig("vertRasterOn") else False) + self.vertRasterOnCheckBox.clicked.connect(self.vertRasterOnCheckCB) self.procRasterOnCheckBox = QCheckBox("Process Raster") hBoxColParams1.addWidget(self.procRasterOnCheckBox) - if getBlConfig("rasterProcessFlag") == 1: - self.procRasterOnCheckBox.setChecked(True) - else: - self.procRasterOnCheckBox.setChecked(False) - self.procRasterOnCheckBox.stateChanged.connect(self.procRasterOnCheckCB) + self.procRasterOnCheckBox.setChecked(True if getBlConfig("rasterProcessFlag") else False) + self.procRasterOnCheckBox.clicked.connect(self.procRasterOnCheckCB) self.guiRemoteOnCheckBox = QCheckBox("GUI Remote") hBoxColParams1.addWidget(self.guiRemoteOnCheckBox) - if getBlConfig("omegaMonitorPV") == "VAL": - self.guiRemoteOnCheckBox.setChecked(True) - else: - self.guiRemoteOnCheckBox.setChecked(False) - self.guiRemoteOnCheckBox.stateChanged.connect(self.guiRemoteOnCheckCB) + self.guiRemoteOnCheckBox.setChecked(True if getBlConfig("omegaMonitorPV") == "VAL" else False) + self.guiRemoteOnCheckBox.clicked.connect(self.guiRemoteOnCheckCB) self.albulaDispCheckBox = QCheckBox("Display Data (Albula)") self.albulaDispCheckBox.setChecked(True) hBoxColParams1.addWidget(self.albulaDispCheckBox) self.enableMountCheckBox = QCheckBox("Enable Mount") - if getBlConfig("mountEnabled") == 1: - self.enableMountCheckBox.setChecked(True) - else: - self.enableMountCheckBox.setChecked(False) - self.enableMountCheckBox.stateChanged.connect(self.enableMountCheckCB) + self.enableMountCheckBox.setChecked(True if getBlConfig("mountEnabled") else False) + self.enableMountCheckBox.clicked.connect(self.enableMountCheckCB) self.unmountColdButton = QtWidgets.QPushButton("Unmount Cold") self.unmountColdButton.clicked.connect(self.unmountColdCB) self.openPort1Button = QtWidgets.QPushButton("Open Port 1") @@ -161,9 +137,9 @@ def __init__(self, parent: "ControlMain", **kwargs): self.setBeamcenterButton.clicked.connect(self.setBeamcenterCB) hBoxFastDP.addWidget(self.setBeamcenterButton) self.beamcenterX_ledit = QtWidgets.QLineEdit() - self.beamcenterX_ledit.setText(str(self.parent.beamCenterX_pv.get())) + self.beamcenterX_ledit.setText(str(self.parent.beam_center_x.get())) self.beamcenterY_ledit = QtWidgets.QLineEdit() - self.beamcenterY_ledit.setText(str(self.parent.beamCenterY_pv.get())) + self.beamcenterY_ledit.setText(str(self.parent.beam_center_y.get())) hBoxFastDP.addWidget(self.beamcenterX_ledit) hBoxFastDP.addWidget(self.beamcenterY_ledit) self.setSpotNodesButton = QtWidgets.QPushButton("Set Spotfinder Nodes") @@ -225,7 +201,7 @@ def __init__(self, parent: "ControlMain", **kwargs): def refresh_dewar_tree(self): - self.parent.dewarTree.refreshTreeDewarView(get_latest_pucks=True) + self.parent.dewarTree.refreshTreeThreaded(get_latest_pucks=True) def show(self): self.checkQueueCollect() @@ -283,10 +259,10 @@ def clearMountedSampleCB(self): self.parent.send_to_server("clearMountedSample") def recoverRobotCB(self): - self.parent.aux_send_to_server("recoverRobot") + self.parent.send_to_server("recoverRobot") def rebootEMBL_CB(self): - self.parent.aux_send_to_server("rebootEMBL") + self.parent.send_to_server("rebootEMBL") def restartEMBL_CB(self): self.parent.send_to_server("restartEMBL") @@ -300,19 +276,17 @@ def closeGripper_CB(self): def homePinsCB(self): self.parent.send_to_server("homePins") + def checkbox_toggle_cb(self, state: bool, on_function_name: str, off_function_name: str): + # Based on the value of state, call on_function_name if state is true or off_function_name + function_name = on_function_name if state else off_function_name + logger.info(f"Checkbox state: {state}. Running: {function_name}") + self.parent.send_to_server(function_name) + def robotOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("robot_online", 1) - else: - setBlConfig("robot_online", 0) + self.checkbox_toggle_cb(state, "robotOn", "robotOff") def beamCheckOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig(BEAM_CHECK, 1) - logger.debug(f"{BEAM_CHECK} on") - else: - setBlConfig(BEAM_CHECK, 0) - logger.debug(f"{BEAM_CHECK} off") + self.checkbox_toggle_cb(state, "beamCheckOn", "beamCheckOff") def set_energy_check_cb(self, state): if state == QtCore.Qt.Checked: @@ -327,46 +301,34 @@ def set_energy_check_cb(self, state): msg_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok) def unmountColdCheckCB(self, state): - if state == QtCore.Qt.Checked: - logger.info("unmountColdCheckCB On") - setBlConfig(UNMOUNT_COLD_CHECK, 1) - else: - logger.info("unmountColdCheckCB Off") - setBlConfig(UNMOUNT_COLD_CHECK, 0) + self.checkbox_toggle_cb(state, "unmountColdOn", "unmountColdOff") def topViewOnCheckCB(self, state): - if state == QtCore.Qt.Checked: + if state: setBlConfig(TOP_VIEW_CHECK, 1) else: setBlConfig(TOP_VIEW_CHECK, 0) def vertRasterOnCheckCB(self, state): - if state == QtCore.Qt.Checked: + if state: setBlConfig("vertRasterOn", 1) else: setBlConfig("vertRasterOn", 0) def procRasterOnCheckCB(self, state): - if state == QtCore.Qt.Checked: + if state: setBlConfig("rasterProcessFlag", 1) else: setBlConfig("rasterProcessFlag", 0) def guiRemoteOnCheckCB(self, state): - if state == QtCore.Qt.Checked: + if state: setBlConfig("omegaMonitorPV", "VAL") else: setBlConfig("omegaMonitorPV", "RBV") def queueCollectOnCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("queueCollect", 1) - self.gripperUnmountColdCheckBox.setEnabled(True) - self.parent.queue_collect_status_widget.setText("Queue Collect: ON") - else: - setBlConfig("queueCollect", 0) - self.gripperUnmountColdCheckBox.setEnabled(False) - self.parent.queue_collect_status_widget.setText("Queue Collect: OFF") + self.checkbox_toggle_cb(state, "queueCollectOn", "queueCollectOff") self.parent.row_clicked( 0 ) # This is so that appropriate boxes are filled when toggling queue collect @@ -380,10 +342,10 @@ def checkQueueCollect(self): self.gripperUnmountColdCheckBox.setEnabled(False) def enableMountCheckCB(self, state): - if state == QtCore.Qt.Checked: - setBlConfig("mountEnabled", 1) + if state: + self.parent.send_to_server("enableMount") else: - setBlConfig("mountEnabled", 0) + self.parent.send_to_server("disableMount") def screenDefaultsCancelCB(self): self.hide() diff --git a/gui/dialog/user_screen.py b/gui/dialog/user_screen.py index e7e54e04..0b937ee5 100644 --- a/gui/dialog/user_screen.py +++ b/gui/dialog/user_screen.py @@ -2,7 +2,7 @@ import typing from qt_epics.QtEpicsPVLabel import QtEpicsPVLabel -from qtpy import QtCore, QtWidgets +from qtpy import QtCore, QtWidgets, QtGui from qtpy.QtWidgets import QCheckBox from config_params import ON_MOUNT_OPTION import daq_utils @@ -39,7 +39,7 @@ def __init__(self, parent: "ControlMain"): hBoxColParams1.addWidget(self.BLbutton) govLabel2 = QtWidgets.QLabel("Current Governor State:") self.governorMessage = QtEpicsPVLabel( - daq_utils.pvLookupDict["governorMessage"], + self.parent.bl_devices.governor.message.pvname, self, 140, highlight_on_change=False, @@ -67,6 +67,11 @@ def __init__(self, parent: "ControlMain"): self.checkQueueCollect() self.queueCollectOnCheckBox.stateChanged.connect(self.queueCollectOnCheckCB) + self.mini_raster_on_checkbox = QCheckBox("Mini Raster") + hBoxColParams3.addWidget(self.mini_raster_on_checkbox) + self.check_mini_raster() + self.mini_raster_on_checkbox.stateChanged.connect(self.mini_raster_on_cb) + hBoxColParams3.addWidget(self.unmountWarmButton) hBoxColParams3.addWidget(self.testRobotButton) hBoxColParams3.addWidget(self.recoverRobotButton) @@ -121,7 +126,7 @@ def __init__(self, parent: "ControlMain"): slit1XSPLabel = QtWidgets.QLabel("SetPoint:") self.slit1XMotor_ledit = QtWidgets.QLineEdit() self.slit1XMotor_ledit.returnPressed.connect(self.setSlit1XCB) - self.slit1XMotor_ledit.setText(str(self.parent.slit1XGapSP_pv.get())) + self.slit1XMotor_ledit.setText(str(self.parent.slit1_x_gap_setpoint.get())) slit1YLabel = QtWidgets.QLabel("Slit 1 Y Gap:") slit1YLabel.setAlignment(QtCore.Qt.AlignCenter) @@ -131,13 +136,34 @@ def __init__(self, parent: "ControlMain"): ) slit1YSPLabel = QtWidgets.QLabel("SetPoint:") self.slit1YMotor_ledit = QtWidgets.QLineEdit() - self.slit1YMotor_ledit.setText(str(self.parent.slit1YGapSP_pv.get())) + self.slit1YMotor_ledit.setText(str(self.parent.slit1_y_gap_setpoint.get())) self.slit1YMotor_ledit.returnPressed.connect(self.setSlit1YCB) + temp_change_label = QtWidgets.QLabel("Cryo Temp:") + self.temp_change_ledit = QtWidgets.QLineEdit() + self.temp_change_ledit.returnPressed.connect(self.setTemp) + self.temp_change_button = QtWidgets.QPushButton("Set Temp") + self.temp_change_button.clicked.connect(self.setTemp) + + temp_ramp_label = QtWidgets.QLabel("Cryo Ramp Rate:") + self.temp_ramp_ledit = QtWidgets.QLineEdit() + self.temp_ramp_ledit.returnPressed.connect(self.setRamp) + self.temp_ramp_button = QtWidgets.QPushButton("Set Ramp Rate") + self.temp_ramp_button.clicked.connect(self.setRamp) + + if daq_utils.beamline == "fmx": + # Dose Factor controls + self.doseMultiplierLabel = QtWidgets.QLabel("Dose Factor: ") + # self.doseMultiplierLabel.setStyleSheet("color: #228B22;") + self.doseMultiplier_ledit = QtWidgets.QLineEdit("1.0") + self.doseMultiplier_ledit.setValidator(QtGui.QDoubleValidator(0.001, 100.0, 3)) + self.doseMultiplier_ledit.setMaxLength(6) + self.doseMultiplier_ledit.setMaximumWidth(50) # Keep dose factor input compact + self.doseMultiplier_ledit.textChanged.connect(self.parent.calcLifetimeCB) sampleFluxLabelDesc = QtWidgets.QLabel("Sample Flux:") sampleFluxLabelDesc.setFixedWidth(80) self.sampleFluxLabel = QtWidgets.QLabel() - self.sampleFluxLabel.setText("%E" % self.parent.sampleFluxPV.get()) + self.sampleFluxLabel.setText("%E" % self.parent.sample_flux.get()) hBoxBeam3.addWidget(sampleFluxLabelDesc) hBoxBeam3.addWidget(self.sampleFluxLabel) @@ -152,8 +178,22 @@ def __init__(self, parent: "ControlMain"): hBoxBeam2.addWidget(self.slit1YRBVLabel.getEntry()) hBoxBeam2.addWidget(slit1YSPLabel) hBoxBeam2.addWidget(self.slit1YMotor_ledit) + hBoxTemp = QtWidgets.QHBoxLayout() + hBoxTemp.addWidget(temp_change_label) + hBoxTemp.addWidget(self.temp_change_ledit) + hBoxTemp.addWidget(self.temp_change_button) + hBoxRamp = QtWidgets.QHBoxLayout() + hBoxRamp.addWidget(temp_ramp_label) + hBoxRamp.addWidget(self.temp_ramp_ledit) + hBoxRamp.addWidget(self.temp_ramp_button) + hBoxDoseFactor = QtWidgets.QHBoxLayout() + hBoxDoseFactor.addWidget(self.doseMultiplierLabel) + hBoxDoseFactor.addWidget(self.doseMultiplier_ledit) vBoxBeam.addLayout(hBoxBeam1) vBoxBeam.addLayout(hBoxBeam2) + vBoxBeam.addLayout(hBoxTemp) + vBoxBeam.addLayout(hBoxRamp) + vBoxBeam.addLayout(hBoxDoseFactor) vBoxBeam.addLayout(hBoxBeam3) beamGB.setLayout(vBoxBeam) @@ -193,17 +233,6 @@ def __init__(self, parent: "ControlMain"): ) self.buttons.buttons()[0].clicked.connect(self.userScreenOKCB) - if daq_utils.beamline == "nyx": - self.openShutterButton.setDisabled(True) - self.unmountWarmButton.setDisabled(True) - self.testRobotButton.setDisabled(True) - self.recoverRobotButton.setDisabled(True) - self.dryGripperButton.setDisabled(True) - self.resetZebraButton.setDisabled(True) - self.rebootZebraButton.setDisabled(True) - self.stopDetButton.setDisabled(True) - self.rebootDetIocButton.setDisabled(True) - vBoxColParams1.addLayout(hBoxColParams1) vBoxColParams1.addLayout(hBoxColParams2) vBoxColParams1.addLayout(hBoxColParams25) @@ -229,6 +258,12 @@ def setSlit1XCB(self): def setSlit1YCB(self): self.parent.send_to_server("setSlit1Y", [self.slit1YMotor_ledit.text()]) + def setTemp(self): + self.parent.send_to_server("set_cryostream_temp", [self.temp_change_ledit.text()]) + + def setRamp(self): + self.parent.send_to_server("set_cryostream_ramp_rate", [self.temp_ramp_ledit.text()]) + def unmountWarmCB(self): self.parent.send_to_server("unmountSample") @@ -243,21 +278,21 @@ def dryGripperCB(self): def stopDetCB(self): logger.info("stopping detector") - self.parent.stopDet_pv.put(0) + self.parent.stop_detector.put(0) def rebootDetIocCB(self): logger.info("rebooting detector IOC") - self.parent.rebootDetIOC_pv.put( + self.parent.reboot_detector_ioc.put( 1 ) # no differences visible, but zebra IOC reboot works, this doesn't! def resetZebraCB(self): logger.info("resetting zebra") - self.parent.resetZebra_pv.put(1) + self.parent.reset_zebra.put(1) def rebootZebraIOC_CB(self): logger.info("rebooting zebra IOC") - self.parent.rebootZebraIOC_pv.put(1) + self.parent.reboot_zebra_ioc.put(1) def SEgovCB(self): self.parent.send_to_server("setGovState", ["SE"]) @@ -296,3 +331,9 @@ def checkQueueCollect(self): self.queueCollectOnCheckBox.setChecked(True) else: self.queueCollectOnCheckBox.setChecked(False) + + def mini_raster_on_cb(self, state): + daq_utils.setBlConfig("use_mini_raster", (state == QtCore.Qt.Checked)) + + def check_mini_raster(self): + self.mini_raster_on_checkbox.setChecked(daq_utils.getBlConfig("use_mini_raster")) diff --git a/gui/raster.py b/gui/raster.py index cc351265..055379ba 100644 --- a/gui/raster.py +++ b/gui/raster.py @@ -37,7 +37,7 @@ def mousePressEvent(self, e): if self.parent.rasterList[i]["graphicsItem"].isSelected(): logger.info("found selected raster") self.parent.SelectedItemData = self.parent.rasterList[i]["uid"] - self.parent.treeChanged_pv.put(1) + self.parent.queue_change_signal.put(1) if self.parent.vidActionRasterExploreRadio.isChecked(): for cell in self.childItems(): if cell.contains(e.pos()): diff --git a/gui/vector.py b/gui/vector.py index ef14a501..d6daf0f7 100644 --- a/gui/vector.py +++ b/gui/vector.py @@ -155,8 +155,6 @@ def get_length(self) -> "tuple[int, int, int, np.floating[typing.Any] | float]": vec_diff = vec_end - vec_start trans_total = np.linalg.norm(vec_diff) - if daq_utils.beamline == "nyx": - trans_total *= 1000 return x_vec, y_vec, z_vec, trans_total @@ -305,7 +303,7 @@ def update_marker_position(self, point: VectorMarker) -> None: # First convert the distance moved by the point from pixels to microns micron_x = self.main_window.screenXPixels2microns(point.pos().x()) micron_y = self.main_window.screenYPixels2microns(point.pos().y()) - omega = self.main_window.omegaRBV_pv.get() + omega = self.main_window.gon.omega.readback.get() # Then translate the delta from microns in the lab co-ordinate system to gonio ( @@ -317,9 +315,9 @@ def update_marker_position(self, point: VectorMarker) -> None: # Then add the delta to the current gonio co-ordinates gonio_coords = { - "x": self.main_window.sampx_pv.get() + gonio_offset_x, - "y": self.main_window.sampy_pv.get() + gonio_offset_y, - "z": self.main_window.sampz_pv.get() + gonio_offset_z, + "x": self.main_window.gon.x.readback.get() + gonio_offset_x, + "y": self.main_window.gon.y.readback.get() + gonio_offset_y, + "z": self.main_window.gon.z.readback.get() + gonio_offset_z, "omega": omega, } vectorCoords = self.transform_vector_coords(point.coords, gonio_coords) diff --git a/gui/widgets/log_widget.py b/gui/widgets/log_widget.py index cd4af6af..f3b6628d 100644 --- a/gui/widgets/log_widget.py +++ b/gui/widgets/log_widget.py @@ -29,15 +29,16 @@ def get_summary_widget(path: Path): raise Exception(f"Unknown type for summary: {path.suffix}") class LogViewerWidget(QWidget): - def __init__(self, log_file, max_height=500, parent=None): + def __init__(self, log_file=None, max_height=500, parent=None): super().__init__(parent) - self.log_file = Path(log_file) self.initUI(max_height) - self.last_position = 0 - self.timer = QTimer(self) - self.timer.timeout.connect(self.update_log) - self.timer.start(1000) # Update every second + if log_file: + self.log_file = Path(log_file) + self.last_position = 0 + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_log) + self.timer.start(1000) # Update every second self.auto_scroll = True # Auto-scroll is enabled by default diff --git a/lsdcGui.py b/lsdcGui.py index 81984f0f..0d9b27f6 100644 --- a/lsdcGui.py +++ b/lsdcGui.py @@ -2,21 +2,27 @@ The GUI for the LSDC system """ import sys -from qtpy import QtWidgets +import threading +import time +import traceback +from qtpy import QtCore, QtWidgets import daq_utils from utils.healthcheck import perform_checks import logging import platform import traceback +import getpass from logging import handlers from gui.control_main import ControlMain class HostnameFilter(logging.Filter): hostname = platform.node().split(".")[0] + username = getpass.getuser() def filter(self, record): record.hostname = HostnameFilter.hostname + record.username = HostnameFilter.username return True @@ -27,17 +33,111 @@ def filter(self, record): handler1 = handlers.RotatingFileHandler(logging_file, maxBytes=5000000, backupCount=100) handler1.addFilter(HostnameFilter()) myformat = logging.Formatter( - "%(asctime)s %(hostname)s: %(name)-8s %(levelname)-8s %(message)s" + "%(asctime)s %(hostname)s %(username)s: %(name)-8s %(levelname)-8s %(message)s" ) handler1.setFormatter(myformat) logger.addHandler(handler1) + +class UiStallMonitor: + def __init__( + self, + app, + logger, + heartbeat_ms=100, + stall_threshold_s=2.0, + check_interval_s=0.5, + ): + self.app = app + self.logger = logger + self.heartbeat_ms = heartbeat_ms + self.stall_threshold_s = stall_threshold_s + self.check_interval_s = check_interval_s + self._last_tick = time.monotonic() + self._stall_active = False + self._stall_started = 0.0 + self._request_process_events = False + self._running = False + self._watchdog_thread = None + self._timer = QtCore.QTimer() + self._timer.timeout.connect(self._on_ui_heartbeat) + + def start(self): + self._running = True + self._timer.start(self.heartbeat_ms) + self._watchdog_thread = threading.Thread( + target=self._watchdog_loop, + name="ui-stall-watchdog", + daemon=True, + ) + self._watchdog_thread.start() + self.logger.info( + "UI_STALL_MONITOR_STARTED heartbeat_ms=%s threshold_s=%.2f check_interval_s=%.2f", + self.heartbeat_ms, + self.stall_threshold_s, + self.check_interval_s, + ) + + def stop(self): + self._running = False + if self._timer.isActive(): + self._timer.stop() + if self._watchdog_thread is not None: + self._watchdog_thread.join(timeout=1.0) + self._watchdog_thread = None + + def _on_ui_heartbeat(self): + self._last_tick = time.monotonic() + if self._request_process_events: + self._request_process_events = False + QtWidgets.QApplication.processEvents(QtCore.QEventLoop.AllEvents, 25) + self.logger.warning("UI_STALL_PROCESS_EVENTS_CALLED") + + def _watchdog_loop(self): + while self._running: + time.sleep(self.check_interval_s) + now = time.monotonic() + age = now - self._last_tick + if age >= self.stall_threshold_s: + if not self._stall_active: + self._stall_active = True + self._stall_started = now + self._request_process_events = True + self.logger.warning( + "UI_STALL_START age_s=%.3f threshold_s=%.3f", + age, + self.stall_threshold_s, + ) + self._dump_main_thread_stack() + elif self._stall_active: + self._stall_active = False + self.logger.warning( + "UI_STALL_END duration_s=%.3f recovery_age_s=%.3f", + now - self._stall_started, + age, + ) + + def _dump_main_thread_stack(self): + main_thread = threading.main_thread() + main_ident = main_thread.ident + frames = sys._current_frames() + frame = frames.get(main_ident) + if frame is None: + self.logger.warning("UI_STALL_STACK_UNAVAILABLE") + return + stack_s = "".join(traceback.format_stack(frame)) + self.logger.warning("UI_STALL_STACK_BEGIN\n%sUI_STALL_STACK_END", stack_s) + def main(): logger.info("Starting LSDC...") perform_checks() daq_utils.init_environment() daq_utils.readPVDesc() app = QtWidgets.QApplication(sys.argv) + ui_stall_monitor = UiStallMonitor(app, logger) + app.aboutToQuit.connect(ui_stall_monitor.stop) + ui_stall_monitor.start() + app.ui_stall_monitor = ui_stall_monitor ex = ControlMain() sys.exit(app.exec_()) diff --git a/lsdcServer b/lsdcServer index 3574200c..562290e3 100755 --- a/lsdcServer +++ b/lsdcServer @@ -1,3 +1,2 @@ -pkill -KILL -f daq_main2.py source /opt/conda_envs/lsdc-server-2023-2-latest/bin/activate -$LSDCHOME/daq_main2.py +python $LSDCHOME/server.py diff --git a/mxbluesky/devices/__init__.py b/mxbluesky/devices/__init__.py index 70cf77c8..005ae723 100644 --- a/mxbluesky/devices/__init__.py +++ b/mxbluesky/devices/__init__.py @@ -30,4 +30,19 @@ def new_init(self, *args, **kwargs): cls.__init__ = new_init return cls -from .generic import * \ No newline at end of file +from .generic import * +from .governor import Governor +from .goniometer import Goniometer +from .vector_program import VectorProgram +from .cameras import LowMagCamera, HighMagCamera, TopViewCamera +from .detector import Detector +from .attenuation import Attenuation +from .optics import Optics +from .sample_environment import SampleEnvironment +from .cryostream import CryoStream +from .click_center import ClickCenter +from .misc import Misc +from .ioc_control import IOCControl +from .diagnostics import Diagnostics +from .comm import CommIOC +from .beamline_devices import BeamlineDevices diff --git a/mxbluesky/devices/attenuation.py b/mxbluesky/devices/attenuation.py new file mode 100644 index 00000000..ebcb75f2 --- /dev/null +++ b/mxbluesky/devices/attenuation.py @@ -0,0 +1,73 @@ +"""Attenuation device — BCU blade attenuation + FMX RI attenuator + FMX CRL lenses. + +All attenuation PVs share the ``C-OP:FMX`` (FMX) or ``B-OP:AMX`` (AMX) +prefix. RI and CRL signals exist only on FMX; they remain disconnected +on AMX. + +Instantiation:: + + atten = Attenuation("XF:17IDC-OP:FMX", name="attenuation") # FMX + atten = Attenuation("XF:17IDB-OP:AMX", name="attenuation") # AMX +""" + +from ophyd import Component as Cpt, Device, EpicsSignal, EpicsSignalRO + + +class Attenuation(Device): + """BCU transmission attenuation, RI attenuator (FMX), and CRL lenses (FMX).""" + + # --- BCU blade transmission (both beamlines) --- + transmission_rbv = Cpt(EpicsSignalRO, "{Attn:BCU}Trans-I") # transmissionRBV + transmission_set = Cpt(EpicsSignal, "{Attn:BCU}Trans-SP") # transmissionSet + transmission_go = Cpt(EpicsSignal, "{Attn:BCU}Cmd:Set-Cmd.PROC") # transmissionGo + transmission_done = Cpt(EpicsSignalRO, "{Attn:BCU}attenDone") # transmissionDone + + # --- individual BCU blade in/out commands (FMX only; 12 blades) --- + atten_01_out = Cpt(EpicsSignal, "{Attn:01}Cmd:Out-Cmd") # Atten01-0 + atten_02_out = Cpt(EpicsSignal, "{Attn:02}Cmd:Out-Cmd") + atten_03_out = Cpt(EpicsSignal, "{Attn:03}Cmd:Out-Cmd") + atten_04_out = Cpt(EpicsSignal, "{Attn:04}Cmd:Out-Cmd") + atten_05_out = Cpt(EpicsSignal, "{Attn:05}Cmd:Out-Cmd") + atten_06_out = Cpt(EpicsSignal, "{Attn:06}Cmd:Out-Cmd") + atten_07_out = Cpt(EpicsSignal, "{Attn:07}Cmd:Out-Cmd") + atten_08_out = Cpt(EpicsSignal, "{Attn:08}Cmd:Out-Cmd") + atten_09_out = Cpt(EpicsSignal, "{Attn:09}Cmd:Out-Cmd") + atten_10_out = Cpt(EpicsSignal, "{Attn:10}Cmd:Out-Cmd") + atten_11_out = Cpt(EpicsSignal, "{Attn:11}Cmd:Out-Cmd") + atten_12_out = Cpt(EpicsSignal, "{Attn:12}Cmd:Out-Cmd") # Atten12-0 + + atten_01_in = Cpt(EpicsSignal, "{Attn:01}Cmd:In-Cmd") # Atten01-1 + atten_02_in = Cpt(EpicsSignal, "{Attn:02}Cmd:In-Cmd") + atten_03_in = Cpt(EpicsSignal, "{Attn:03}Cmd:In-Cmd") + atten_04_in = Cpt(EpicsSignal, "{Attn:04}Cmd:In-Cmd") + atten_05_in = Cpt(EpicsSignal, "{Attn:05}Cmd:In-Cmd") + atten_06_in = Cpt(EpicsSignal, "{Attn:06}Cmd:In-Cmd") + atten_07_in = Cpt(EpicsSignal, "{Attn:07}Cmd:In-Cmd") + atten_08_in = Cpt(EpicsSignal, "{Attn:08}Cmd:In-Cmd") + atten_09_in = Cpt(EpicsSignal, "{Attn:09}Cmd:In-Cmd") + atten_10_in = Cpt(EpicsSignal, "{Attn:10}Cmd:In-Cmd") + atten_11_in = Cpt(EpicsSignal, "{Attn:11}Cmd:In-Cmd") + atten_12_in = Cpt(EpicsSignal, "{Attn:12}Cmd:In-Cmd") # Atten12-1 + + # --- RI attenuator (FMX only) --- + ri_energy_sp = Cpt(EpicsSignal, "{Attn:RI}Energy-SP") # RIattenEnergySP + ri_trans_sp = Cpt(EpicsSignal, "{Attn:RI}Trans-SP") # RI_Atten_SP + ri_set = Cpt(EpicsSignal, "{Attn:RI}Cmd:Set-Cmd.PROC") # RI_Atten_SET + + # --- CRL lenses (FMX only) --- + crl_vs_in = Cpt(EpicsSignal, "{CRL:02}Cmd:In-Cmd") # CRL_VS_IN + crl_vs_out = Cpt(EpicsSignal, "{CRL:02}Cmd:Out-Cmd") # CRL_VS_OUT + crl_v2a_in = Cpt(EpicsSignal, "{CRL:04}Cmd:In-Cmd") # CRL_V2A_IN + crl_v2a_out = Cpt(EpicsSignal, "{CRL:04}Cmd:Out-Cmd") + crl_v1a_in = Cpt(EpicsSignal, "{CRL:08}Cmd:In-Cmd") # CRL_V1A_IN + crl_v1a_out = Cpt(EpicsSignal, "{CRL:08}Cmd:Out-Cmd") + crl_v1b_in = Cpt(EpicsSignal, "{CRL:06}Cmd:In-Cmd") # CRL_V1B_IN + crl_v1b_out = Cpt(EpicsSignal, "{CRL:06}Cmd:Out-Cmd") + crl_h4a_in = Cpt(EpicsSignal, "{CRL:03}Cmd:In-Cmd") # CRL_H4A_IN + crl_h4a_out = Cpt(EpicsSignal, "{CRL:03}Cmd:Out-Cmd") + crl_h2a_in = Cpt(EpicsSignal, "{CRL:05}Cmd:In-Cmd") # CRL_H2A_IN + crl_h2a_out = Cpt(EpicsSignal, "{CRL:05}Cmd:Out-Cmd") + crl_h1a_in = Cpt(EpicsSignal, "{CRL:07}Cmd:In-Cmd") # CRL_H1A_IN + crl_h1a_out = Cpt(EpicsSignal, "{CRL:07}Cmd:Out-Cmd") + crl_h1b_in = Cpt(EpicsSignal, "{CRL:09}Cmd:In-Cmd") # CRL_H1B_IN + crl_h1b_out = Cpt(EpicsSignal, "{CRL:09}Cmd:Out-Cmd") diff --git a/mxbluesky/devices/auto_recovery.py b/mxbluesky/devices/auto_recovery.py index 468eca8d..3d664d5b 100644 --- a/mxbluesky/devices/auto_recovery.py +++ b/mxbluesky/devices/auto_recovery.py @@ -1,19 +1,71 @@ -from ophyd import Device, EpicsSignalRO, EpicsSignal, Component as Cpt +from ophyd import Device, EpicsSignalRO, EpicsSignal, FormattedComponent as FCpt from ophyd.status import SubscriptionStatus class PYZHomer(Device): + """Pin Y/Z homer using the Sentinel IOC. - status = Cpt(EpicsSignalRO, "XF:17IDB-ES:AMX{Sentinel}Homing_Sts") - home_actuate = Cpt(EpicsSignal, "XF:17ID:AMX{Sentinel}pin_home") + All five signals span two PV roots, so fully-qualified PV strings are + injected at instantiation (prefix=""). Use the factory:: - kill_home = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Sentinel}Homing_Kill") - kill_py = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PY}Cmd:Kill-Cmd") - kill_pz = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PZ}Cmd:Kill-Cmd") + pyz_homer = PYZHomer.from_beamline("fmx", name="pyz_homer") + pyz_homer = PYZHomer.from_beamline("amx", name="pyz_homer") - def __init__(self, *args, **kwargs): + The ``__init__`` kwarg PVs must be set before ``super().__init__`` because + ophyd instantiates non-lazy components inside Device.__init__. + """ + + status = FCpt(EpicsSignalRO, "{self._status_pv}") + home_actuate = FCpt(EpicsSignal, "{self._home_actuate_pv}") + kill_home = FCpt(EpicsSignal, "{self._kill_home_pv}") + kill_py = FCpt(EpicsSignal, "{self._kill_py_pv}") + kill_pz = FCpt(EpicsSignal, "{self._kill_pz_pv}") + + def __init__(self, *args, + status_pv, + home_actuate_pv, + kill_home_pv, + kill_py_pv, + kill_pz_pv, + **kwargs): + # Must be set before super().__init__ so FCpts can format themselves + self._status_pv = status_pv + self._home_actuate_pv = home_actuate_pv + self._kill_home_pv = kill_home_pv + self._kill_py_pv = kill_py_pv + self._kill_pz_pv = kill_pz_pv super().__init__(*args, **kwargs) + @classmethod + def from_beamline(cls, beamline: str, name: str = "pyz_homer", **kwargs) -> "PYZHomer": + """Construct a PYZHomer for the given beamline. + + Parameters + ---------- + beamline : {"fmx", "amx"} + name : ophyd device name + """ + bl = beamline.lower() + if bl == "fmx": + es = "XF:17IDC-ES:FMX" + id_ = "XF:17ID:FMX" + elif bl == "amx": + es = "XF:17IDB-ES:AMX" + id_ = "XF:17ID:AMX" + else: + raise ValueError(f"Unknown beamline {beamline!r}; expected 'fmx' or 'amx'.") + + return cls( + "", + name=name, + status_pv =f"{es}{{Sentinel}}Homing_Sts", + home_actuate_pv =f"{id_}{{Sentinel}}pin_home", + kill_home_pv =f"{es}{{Sentinel}}Homing_Kill", + kill_py_pv =f"{es}{{Gon:1-Ax:PY}}Cmd:Kill-Cmd", + kill_pz_pv =f"{es}{{Gon:1-Ax:PZ}}Cmd:Kill-Cmd", + **kwargs, + ) + def trigger(self): def callback_homed(value, old_value, **kwargs): diff --git a/mxbluesky/devices/beamline_devices.py b/mxbluesky/devices/beamline_devices.py new file mode 100644 index 00000000..ca039ca3 --- /dev/null +++ b/mxbluesky/devices/beamline_devices.py @@ -0,0 +1,392 @@ +"""BeamlineDevices — single-entry-point container for all beamline ophyd devices. + +Usage:: + + bl = BeamlineDevices.from_beamline("fmx", comm_prefix="XF:17IDC-ES:FMX{Comm}", name="bl") + bl = BeamlineDevices.from_beamline("amx", comm_prefix="XF:17IDB-ES:AMX{Comm}", name="bl") + +After construction every subsystem is accessible as an attribute, e.g.:: + + bl.governor.robot_status.get() + bl.goniometer.omega.move(45) + bl.attenuation.transmission_set.put(0.5) + bl.misc.beam_available.get() + bl.flux.get() # beamline-specific PV, always at bl.flux + bl.optics.energy.move(12.7) + +Sub-devices +----------- + governor : Governor + goniometer : Goniometer + vector : VectorProgram + low_mag : LowMagCamera + high_mag : HighMagCamera + top_view : TopViewCamera + detector : Detector + attenuation : Attenuation + optics : Optics + cryostream : CryoStream + sample_env : SampleEnvironment + click_center : ClickCenter + misc : Misc + ioc_control : IOCControl + diagnostics : Diagnostics + zebra : Zebra + comm : CommIOC + +Top-level signals (beamline-specific PVs that don't fit a single prefix): + flux : EpicsSignalRO — instantaneous photon flux + sample_lifetime : EpicsSignalRO — AMX only (None on FMX) +""" + +from ophyd import EpicsSignal, EpicsSignalRO + +from mxbluesky.devices.governor import Governor +from mxbluesky.devices.goniometer import Goniometer +from mxbluesky.devices.vector_program import VectorProgram +from mxbluesky.devices.cameras import LowMagCamera, HighMagCamera, TopViewCamera +from mxbluesky.devices.detector import Detector +from mxbluesky.devices.attenuation import Attenuation +from mxbluesky.devices.optics import Optics +from mxbluesky.devices.cryostream import CryoStream +from mxbluesky.devices.sample_environment import SampleEnvironment +from mxbluesky.devices.click_center import ClickCenter +from mxbluesky.devices.misc import Misc +from mxbluesky.devices.ioc_control import IOCControl +from mxbluesky.devices.diagnostics import Diagnostics +from mxbluesky.devices.zebra import Zebra +from mxbluesky.devices.comm import CommIOC + + +class BeamlineDevices: + """Plain-Python container for all ophyd device instances on one beamline. + + Not an ophyd Device — use ``from_beamline`` to construct. + """ + + def __init__(self) -> None: + self._beamline = "" + + # Sub-device type hints (for IDE support) + _beamline: str + governor: Governor + goniometer: Goniometer + vector: VectorProgram + low_mag: LowMagCamera + high_mag: HighMagCamera + top_view: TopViewCamera + detector: Detector + attenuation: Attenuation + optics: Optics + cryostream: CryoStream + sample_env: SampleEnvironment + click_center: ClickCenter + misc: Misc + ioc_control: IOCControl + diagnostics: Diagnostics + zebra: Zebra + comm: CommIOC + flux: EpicsSignalRO + sample_lifetime: "EpicsSignalRO | None" + + # ------------------------------------------------------------------ + @classmethod + def from_beamline( + cls, + beamline: str, + comm_prefix: str, + name: str = "bl", + ) -> "BeamlineDevices": + """Instantiate every sub-device for the given beamline. + + Parameters + ---------- + beamline : {"fmx", "amx"} + comm_prefix : beamline communication IOC prefix (daq_utils.beamlineComm) + name : prefix used when constructing ophyd device names. + """ + bl = beamline.lower() + if bl not in ("fmx", "amx"): + raise ValueError(f"Unknown beamline {beamline!r}; expected 'fmx' or 'amx'.") + + obj = cls() + obj._beamline = bl + + if bl == "fmx": + _build_fmx(obj, name, comm_prefix) + else: + _build_amx(obj, name, comm_prefix) + + return obj + + def __repr__(self): + return f"" + + +# --------------------------------------------------------------------------- +# Private builders +# --------------------------------------------------------------------------- + +def _build_fmx(obj: BeamlineDevices, name: str, comm_prefix: str) -> None: + """Populate *obj* with FMX devices.""" + _BL = "XF:17ID" + es = f"{_BL}C-ES:FMX" # XF:17IDC-ES:FMX + a_op = f"{_BL}A-OP:FMX" # XF:17IDA-OP:FMX + c_op = f"{_BL}C-OP:FMX" # XF:17IDC-OP:FMX + ct = f"{_BL}C-CT:FMX" # XF:17IDC-CT:FMX + bi = f"{_BL}C-BI:FMX" # XF:17IDC-BI:FMX + pps = f"{_BL}A-PPS:FMX" # XF:17IDA-PPS:FMX + id_op = f"{_BL}-OP:FMX" # XF:17ID-OP:FMX + id_es = f"{_BL}-ES:FMX" # XF:17ID-ES:FMX + + obj.governor = Governor( + es, + name=f"{name}_governor", + ) + + obj.goniometer = Goniometer( + f"{es}{{Gon:1", + name=f"{name}_goniometer", + ) + + obj.vector = VectorProgram( + f"{es}{{Gon:1-Vec}}", + name=f"{name}_vector", + ) + + obj.low_mag = LowMagCamera( + f"{es}{{Cam:7}}", + zoom_roi=3, + name=f"{name}_low_mag", + ) + + obj.high_mag = HighMagCamera( + f"{es}{{Cam:8}}", + name=f"{name}_high_mag", + ) + + obj.top_view = TopViewCamera( + f"{es}{{Cam:11}}", + name=f"{name}_top_view", + ) + + obj.detector = Detector( + es, + eig_token="{Det:Eig16M}", + cover_token="{Det:FMX-Cover}", + photon_energy_suffix="{Det:Eig16M}cam1:PhotonEnergy", + photon_energy_rbv_suffix="{Det:Eig16M}cam1:PhotonEnergy_RBV", + name=f"{name}_detector", + ) + + obj.attenuation = Attenuation( + c_op, + name=f"{name}_attenuation", + ) + + obj.optics = Optics( + a_op, + name=f"{name}_optics", + ) + + obj.cryostream = CryoStream( + f"{es}{{CS:1}}", + atol=0.1, + name=f"{name}_cryostream", + ) + + obj.sample_env = SampleEnvironment( + es, + name=f"{name}_sample_env", + ) + + obj.click_center = ClickCenter( + f"{es}{{ClkCtr}}", + name=f"{name}_click_center", + ) + + obj.misc = Misc( + "", + beam_available_pv =f"{id_op}{{BeamAvail}}", + exposing_pv =f"{id_op}{{Exposing}}", + still_mode_pv =f"{es}{{Stills}}", + still_mode_status_pv =f"{es}{{Stills}}-Stat", + standard_mode_pv =f"{es}{{Standard}}", + beam_center_x_pv =f"{id_es}{{Misc-BeamCenter:X}}Pos-I", + beam_center_y_pv =f"{id_es}{{Misc-BeamCenter:Y}}Pos-I", + low_mag_cursor_x_pv =f"{id_es}{{Misc-AuxCursor2:X}}Pos-SP", + low_mag_cursor_y_pv =f"{id_es}{{Misc-AuxCursor2:Y}}Pos-SP", + high_mag_cursor_x_pv =f"{id_es}{{Misc-AuxCursor3:X}}Pos-SP", + high_mag_cursor_y_pv =f"{id_es}{{Misc-AuxCursor3:Y}}Pos-SP", + vector_delay_pv =f"{id_es}{{Misc-Vector:Delay}}Pos-SP", + photon_shutter_open_pv =f"{pps}{{PSh}}Cmd:Opn-Cmd", + photon_shutter_close_pv =f"{pps}{{PSh}}Cmd:Cls-Cmd", + name=f"{name}_misc", + ) + + obj.ioc_control = IOCControl( + ct, + eiger_ioc_suffix="{IOC:DET01}:SysReset", + zebra_ioc_suffix="{IOC:Zeb3}:SysReset", + name=f"{name}_ioc_control", + ) + + obj.diagnostics = Diagnostics( + "", + total_current_pv=f"{bi}{{BPM:4}}SumAll:MeanValue_RBV", + ring_current_pv="SR:C03-BI{DCCT:1}I:Real-I", + home_pin_y_pv =f"{ct}{{SDC:04-Ax:5}}StartHome", + home_pin_z_pv =f"{ct}{{SDC:04-Ax:6}}StartHome", + pi_commands_pv =f"{ct}{{MC:21}}Asyn.AOUT", + name=f"{name}_diagnostics", + ) + + obj.zebra = Zebra( + f"{es}{{Zeb:3}}:", + name=f"{name}_zebra", + ) + + obj.comm = CommIOC( + comm_prefix, + name=f"{name}_comm", + ) + + # Beamline-specific top-level signals + obj.flux = EpicsSignalRO( + f"{a_op}{{Mono:DCM-flux}}", + name=f"{name}_flux", + ) + obj.sample_lifetime = None # not present on FMX + + +def _build_amx(obj: BeamlineDevices, name: str, comm_prefix: str) -> None: + """Populate *obj* with AMX devices.""" + _BL = "XF:17ID" + es = f"{_BL}B-ES:AMX" # XF:17IDB-ES:AMX + a_op = f"{_BL}A-OP:AMX" # XF:17IDA-OP:AMX + b_op = f"{_BL}B-OP:AMX" # XF:17IDB-OP:AMX + ct = f"{_BL}B-CT:AMX" # XF:17IDB-CT:AMX + bi = f"{_BL}B-BI:AMX" # XF:17IDB-BI:AMX + pps = f"{_BL}A-PPS:AMX" # XF:17IDA-PPS:AMX + id_op = f"{_BL}-OP:AMX" # XF:17ID-OP:AMX + id_es = f"{_BL}-ES:AMX" # XF:17ID-ES:AMX + + obj.governor = Governor( + es, + name=f"{name}_governor", + ) + + obj.goniometer = Goniometer( + f"{es}{{Gon:1", + name=f"{name}_goniometer", + ) + + obj.vector = VectorProgram( + f"{es}{{Gon:1-Vec}}", + name=f"{name}_vector", + ) + + obj.low_mag = LowMagCamera( + f"{es}{{Cam:6}}", + zoom_roi=1, + name=f"{name}_low_mag", + ) + + obj.high_mag = HighMagCamera( + f"{es}{{Cam:7}}", + name=f"{name}_high_mag", + ) + + obj.top_view = TopViewCamera( + f"{es}{{Cam:9}}", + name=f"{name}_top_view", + ) + + obj.detector = Detector( + es, + eig_token="{Det:Eig9M}", + cover_token="{Det:AMX-Cover}", + name=f"{name}_detector", + ) + + obj.attenuation = Attenuation( + b_op, + name=f"{name}_attenuation", + ) + + obj.optics = Optics( + a_op, + name=f"{name}_optics", + ) + + obj.cryostream = CryoStream( + f"{es}{{CS:1}}", + atol=0.1, + name=f"{name}_cryostream", + ) + + obj.sample_env = SampleEnvironment( + es, + name=f"{name}_sample_env", + ) + + obj.click_center = ClickCenter( + f"{es}{{ClkCtr}}", + name=f"{name}_click_center", + ) + + obj.misc = Misc( + "", + beam_available_pv =f"{id_op}{{BeamAvail}}", + exposing_pv =f"{id_op}{{Exposing}}", + still_mode_pv =f"{es}{{Stills}}", + still_mode_status_pv =f"{es}{{Stills}}-Stat", + standard_mode_pv =f"{es}{{Standard}}", + beam_center_x_pv =f"{id_es}{{Misc-BeamCenter:X}}Pos-I", + beam_center_y_pv =f"{id_es}{{Misc-BeamCenter:Y}}Pos-I", + low_mag_cursor_x_pv =f"{id_es}{{Misc-AuxCursor2:X}}Pos-SP", + low_mag_cursor_y_pv =f"{id_es}{{Misc-AuxCursor2:Y}}Pos-SP", + high_mag_cursor_x_pv =f"{id_es}{{Misc-AuxCursor3:X}}Pos-SP", + high_mag_cursor_y_pv =f"{id_es}{{Misc-AuxCursor3:Y}}Pos-SP", + vector_delay_pv =f"{id_es}{{Misc-Vector:Delay}}Pos-SP", + photon_shutter_open_pv =f"{pps}{{PSh}}Cmd:Opn-Cmd", + photon_shutter_close_pv =f"{pps}{{PSh}}Cmd:Cls-Cmd", + name=f"{name}_misc", + ) + + obj.ioc_control = IOCControl( + ct, + eiger_ioc_suffix="{IOC:DET02}:SysReset.VAL", + zebra_ioc_suffix="{IOC:Zeb2}:SysReset", + name=f"{name}_ioc_control", + ) + + obj.diagnostics = Diagnostics( + "", + total_current_pv=f"{bi}{{BPM:3}}SumAll:MeanValue_RBV", + ring_current_pv="SR:C03-BI{DCCT:1}I:Real-I", + home_pin_y_pv =f"{ct}{{SDC:04-Ax:5}}StartHome", + home_pin_z_pv =f"{ct}{{SDC:04-Ax:6}}StartHome", + # pi_commands_pv intentionally omitted (AMX has no PIcommands) + name=f"{name}_diagnostics", + ) + + obj.zebra = Zebra( + f"{es}{{Zeb:2}}:", + name=f"{name}_zebra", + ) + + obj.comm = CommIOC( + comm_prefix, + name=f"{name}_comm", + ) + + # Beamline-specific top-level signals + obj.flux = EpicsSignalRO( + f"{id_es}{{Misc-BeamFlux}}Pos-SP", + name=f"{name}_flux", + ) + obj.sample_lifetime = EpicsSignalRO( + f"{a_op}{{Mono:DCM-life}}", + name=f"{name}_sample_lifetime", + ) diff --git a/mxbluesky/devices/cameras.py b/mxbluesky/devices/cameras.py new file mode 100644 index 00000000..36a5156a --- /dev/null +++ b/mxbluesky/devices/cameras.py @@ -0,0 +1,186 @@ +"""Camera devices — low-mag, high-mag, and top-view cameras. + +Each class takes the full camera PV base as prefix. + +FMX camera assignments: + low-mag → Cam:7 (XF:17IDC-ES:FMX{Cam:7}) + high-mag → Cam:8 (XF:17IDC-ES:FMX{Cam:8}) + top-view → Cam:11 (XF:17IDC-ES:FMX{Cam:11}) + +AMX camera assignments: + low-mag → Cam:6 (XF:17IDB-ES:AMX{Cam:6}) + high-mag → Cam:7 (XF:17IDB-ES:AMX{Cam:7}) + top-view → Cam:9 (XF:17IDB-ES:AMX{Cam:9}) + +The zoom ROI number differs between beamlines for LowMagCamera: + FMX: zoom=ROI3, full=ROI2 + AMX: zoom=ROI1, full=ROI2 +Pass zoom_roi=3 (FMX, default) or zoom_roi=1 (AMX) at instantiation. +HighMagCamera uses ROI1 (zoom) and ROI2 (full) on both beamlines. + +Note on FormattedComponent (FCpt): + FCpt produces the FULL PV string (device prefix is NOT auto-prepended). + Full PV strings are therefore pre-computed in __init__ and stored as + instance attributes so the FCpt template is a simple attribute lookup. +""" + +from ophyd import Component as Cpt, FormattedComponent as FCpt, Device +from ophyd import EpicsSignal, EpicsSignalRO + + +class LowMagCamera(Device): + """Low-magnification sample camera. + + Parameters + ---------- + zoom_roi : int + ROI plugin number used for the zoom (cropped) view. + Default 3 (FMX). Use 1 for AMX. + """ + + # --- acquisition --- + acquire = Cpt(EpicsSignal, "cam1:Acquire") + acquire_time = Cpt(EpicsSignal, "cam1:AcquireTime") + gain = Cpt(EpicsSignal, "cam1:Gain") + image_mode = Cpt(EpicsSignal, "cam1:ImageMode") + trigger_mode = Cpt(EpicsSignal, "cam1:TriggerMode") + num_images = Cpt(EpicsSignal, "cam1:NumImages") + det_state = Cpt(EpicsSignalRO, "cam1:DetectorState_RBV") + + # --- JPEG writer --- + jpeg_num_images = Cpt(EpicsSignal, "JPEG1:NumCapture") + jpeg_file_number = Cpt(EpicsSignal, "JPEG1:FileNumber") + jpeg_capture = Cpt(EpicsSignal, "JPEG1:Capture") + jpeg_file_path = Cpt(EpicsSignal, "JPEG1:FilePath", string=True) + jpeg_file_name = Cpt(EpicsSignal, "JPEG1:FileName", string=True) + jpeg_enable_callbacks = Cpt(EpicsSignal, "JPEG1:EnableCallbacks") + + # --- MJPG stream frame rates --- + zoom_frame_rate = Cpt(EpicsSignalRO, "MJPG1:ArrayRate_RBV") # zoom2FrameRate + full_frame_rate = Cpt(EpicsSignalRO, "MJPG2:ArrayRate_RBV") # zoom1FrameRate + + # --- zoom ROI (full PV pre-computed in __init__; ROI3 FMX, ROI1 AMX) --- + zoom_roi_min_x_rbv = FCpt(EpicsSignalRO, "{self._zoom_roi_min_x_rbv}") + zoom_roi_min_y_rbv = FCpt(EpicsSignalRO, "{self._zoom_roi_min_y_rbv}") + zoom_roi_min_x = FCpt(EpicsSignal, "{self._zoom_roi_min_x}") + zoom_roi_min_y = FCpt(EpicsSignal, "{self._zoom_roi_min_y}") + zoom_roi_size_x_rbv = FCpt(EpicsSignalRO, "{self._zoom_roi_size_x_rbv}") + zoom_roi_size_y_rbv = FCpt(EpicsSignalRO, "{self._zoom_roi_size_y_rbv}") + zoom_roi_max_size_x = FCpt(EpicsSignalRO, "{self._zoom_roi_max_size_x}") + zoom_roi_max_size_y = FCpt(EpicsSignalRO, "{self._zoom_roi_max_size_y}") + zoom_roi_array_size_x = FCpt(EpicsSignalRO, "{self._zoom_roi_array_size_x}") + zoom_roi_array_size_y = FCpt(EpicsSignalRO, "{self._zoom_roi_array_size_y}") + zoom_roi_bin_x = FCpt(EpicsSignalRO, "{self._zoom_roi_bin_x}") + zoom_roi_bin_y = FCpt(EpicsSignalRO, "{self._zoom_roi_bin_y}") + + # --- full ROI (ROI2 on both beamlines — regular Cpt, no ROI ambiguity) --- + full_roi_min_x = Cpt(EpicsSignal, "ROI2:MinX") + full_roi_min_y = Cpt(EpicsSignal, "ROI2:MinY") + full_roi_min_x_rbv = Cpt(EpicsSignalRO, "ROI2:MinX_RBV") + full_roi_min_y_rbv = Cpt(EpicsSignalRO, "ROI2:MinY_RBV") + full_roi_size_x_rbv = Cpt(EpicsSignalRO, "ROI2:SizeX_RBV") + full_roi_size_y_rbv = Cpt(EpicsSignalRO, "ROI2:SizeY_RBV") + full_roi_max_size_x = Cpt(EpicsSignalRO, "ROI2:MaxSizeX_RBV") + full_roi_max_size_y = Cpt(EpicsSignalRO, "ROI2:MaxSizeY_RBV") + full_roi_array_size_x = Cpt(EpicsSignalRO, "ROI2:ArraySizeX_RBV") + full_roi_array_size_y = Cpt(EpicsSignalRO, "ROI2:ArraySizeY_RBV") + full_roi_bin_x = Cpt(EpicsSignalRO, "ROI2:BinX_RBV") + full_roi_bin_y = Cpt(EpicsSignalRO, "ROI2:BinY_RBV") + + def __init__(self, prefix, *args, zoom_roi=3, **kwargs): + self._zoom_roi = zoom_roi + r = f"ROI{zoom_roi}" + # Pre-compute full PV strings so FCpt templates need only a simple attr lookup. + # (FCpt produces the FULL PV — the device prefix is NOT auto-prepended.) + self._zoom_roi_min_x_rbv = f"{prefix}{r}:MinX_RBV" + self._zoom_roi_min_y_rbv = f"{prefix}{r}:MinY_RBV" + self._zoom_roi_min_x = f"{prefix}{r}:MinX" + self._zoom_roi_min_y = f"{prefix}{r}:MinY" + self._zoom_roi_size_x_rbv = f"{prefix}{r}:SizeX_RBV" + self._zoom_roi_size_y_rbv = f"{prefix}{r}:SizeY_RBV" + self._zoom_roi_max_size_x = f"{prefix}{r}:MaxSizeX_RBV" + self._zoom_roi_max_size_y = f"{prefix}{r}:MaxSizeY_RBV" + self._zoom_roi_array_size_x = f"{prefix}{r}:ArraySizeX_RBV" + self._zoom_roi_array_size_y = f"{prefix}{r}:ArraySizeY_RBV" + self._zoom_roi_bin_x = f"{prefix}{r}:BinX_RBV" + self._zoom_roi_bin_y = f"{prefix}{r}:BinY_RBV" + super().__init__(prefix, *args, **kwargs) + + +class HighMagCamera(Device): + """High-magnification sample camera. + + Both beamlines use ROI1 (zoom) and ROI2 (full). + """ + + # --- acquisition --- + acquire = Cpt(EpicsSignal, "cam1:Acquire") + acquire_time = Cpt(EpicsSignal, "cam1:AcquireTime") + gain = Cpt(EpicsSignal, "cam1:Gain") + image_mode = Cpt(EpicsSignal, "cam1:ImageMode") + trigger_mode = Cpt(EpicsSignal, "cam1:TriggerMode") + num_images = Cpt(EpicsSignal, "cam1:NumImages") + det_state = Cpt(EpicsSignalRO, "cam1:DetectorState_RBV") + + # --- JPEG writer --- + jpeg_num_images = Cpt(EpicsSignal, "JPEG1:NumCapture") + jpeg_file_number = Cpt(EpicsSignal, "JPEG1:FileNumber") + jpeg_capture = Cpt(EpicsSignal, "JPEG1:Capture") + jpeg_file_path = Cpt(EpicsSignal, "JPEG1:FilePath", string=True) + jpeg_file_name = Cpt(EpicsSignal, "JPEG1:FileName", string=True) + jpeg_enable_callbacks = Cpt(EpicsSignal, "JPEG1:EnableCallbacks") + + # --- MJPG stream frame rates --- + zoom_frame_rate = Cpt(EpicsSignalRO, "MJPG1:ArrayRate_RBV") # zoom4FrameRate + full_frame_rate = Cpt(EpicsSignalRO, "MJPG2:ArrayRate_RBV") # zoom3FrameRate + + # --- zoom ROI (ROI1 on both beamlines) --- + zoom_roi_min_x_rbv = Cpt(EpicsSignalRO, "ROI1:MinX_RBV") + zoom_roi_min_y_rbv = Cpt(EpicsSignalRO, "ROI1:MinY_RBV") + zoom_roi_min_x = Cpt(EpicsSignal, "ROI1:MinX") + zoom_roi_min_y = Cpt(EpicsSignal, "ROI1:MinY") + zoom_roi_size_x_rbv = Cpt(EpicsSignalRO, "ROI1:SizeX_RBV") + zoom_roi_size_y_rbv = Cpt(EpicsSignalRO, "ROI1:SizeY_RBV") + zoom_roi_max_size_x = Cpt(EpicsSignalRO, "ROI1:MaxSizeX_RBV") + zoom_roi_max_size_y = Cpt(EpicsSignalRO, "ROI1:MaxSizeY_RBV") + zoom_roi_array_size_x = Cpt(EpicsSignalRO, "ROI1:ArraySizeX_RBV") + zoom_roi_array_size_y = Cpt(EpicsSignalRO, "ROI1:ArraySizeY_RBV") + zoom_roi_bin_x = Cpt(EpicsSignalRO, "ROI1:BinX_RBV") + zoom_roi_bin_y = Cpt(EpicsSignalRO, "ROI1:BinY_RBV") + + # --- full ROI (ROI2 on both beamlines) --- + full_roi_min_x = Cpt(EpicsSignal, "ROI2:MinX") + full_roi_min_y = Cpt(EpicsSignal, "ROI2:MinY") + full_roi_min_x_rbv = Cpt(EpicsSignalRO, "ROI2:MinX_RBV") + full_roi_min_y_rbv = Cpt(EpicsSignalRO, "ROI2:MinY_RBV") + full_roi_size_x_rbv = Cpt(EpicsSignalRO, "ROI2:SizeX_RBV") + full_roi_size_y_rbv = Cpt(EpicsSignalRO, "ROI2:SizeY_RBV") + full_roi_max_size_x = Cpt(EpicsSignalRO, "ROI2:MaxSizeX_RBV") + full_roi_max_size_y = Cpt(EpicsSignalRO, "ROI2:MaxSizeY_RBV") + full_roi_array_size_x = Cpt(EpicsSignalRO, "ROI2:ArraySizeX_RBV") + full_roi_array_size_y = Cpt(EpicsSignalRO, "ROI2:ArraySizeY_RBV") + full_roi_bin_x = Cpt(EpicsSignalRO, "ROI2:BinX_RBV") + full_roi_bin_y = Cpt(EpicsSignalRO, "ROI2:BinY_RBV") + + +class TopViewCamera(Device): + """Top-view (overhead) sample camera.""" + + # --- acquisition --- + acquire = Cpt(EpicsSignal, "cam1:Acquire") + acquire_time = Cpt(EpicsSignal, "cam1:AcquireTime") + data_type = Cpt(EpicsSignal, "cam1:DataType") + gain = Cpt(EpicsSignal, "cam1:Gain") + image_mode = Cpt(EpicsSignal, "cam1:ImageMode") + trigger_mode = Cpt(EpicsSignal, "cam1:TriggerMode") + num_images = Cpt(EpicsSignal, "cam1:NumImages") + det_state = Cpt(EpicsSignalRO, "cam1:DetectorState_RBV") + + # --- JPEG writer --- + jpeg_num_images = Cpt(EpicsSignal, "JPEG1:NumCapture") + jpeg_file_number = Cpt(EpicsSignal, "JPEG1:FileNumber") + jpeg_capture = Cpt(EpicsSignal, "JPEG1:Capture") + jpeg_file_path = Cpt(EpicsSignal, "JPEG1:FilePath", string=True) + jpeg_file_name = Cpt(EpicsSignal, "JPEG1:FileName", string=True) + jpeg_enable_callbacks = Cpt(EpicsSignal, "JPEG1:EnableCallbacks") + jpeg_write_file = Cpt(EpicsSignal, "JPEG1:WriteFile") diff --git a/mxbluesky/devices/click_center.py b/mxbluesky/devices/click_center.py new file mode 100644 index 00000000..880e04b6 --- /dev/null +++ b/mxbluesky/devices/click_center.py @@ -0,0 +1,34 @@ +"""ClickCenter device — click-to-center goniometer alignment. + +Covers all 11 ``{ClkCtr}`` descriptors from both DB files. + +Instantiation:: + + cc = ClickCenter("XF:17IDC-ES:FMX{ClkCtr}", name="click_center") # FMX + cc = ClickCenter("XF:17IDB-ES:AMX{ClkCtr}", name="click_center") # AMX +""" + +from ophyd import Component as Cpt, Device, EpicsSignal, EpicsSignalRO + + +class ClickCenter(Device): + """Click-to-center alignment controller.""" + + # --- image scale calibration --- + image_x_scale_pix = Cpt(EpicsSignal, "image_X_scale.B") # image_X_scalePix + image_x_scale_mm = Cpt(EpicsSignal, "image_X_scale.C") # image_X_scaleMM + image_y_scale_pix = Cpt(EpicsSignal, "image_Y_scale.B") # image_Y_scalePix + image_y_scale_mm = Cpt(EpicsSignal, "image_Y_scale.C") # image_Y_scaleMM + + # --- image center (pixels) --- + image_x_center_pix = Cpt(EpicsSignal, "image_X_center.A") # image_X_centerPix + image_y_center_pix = Cpt(EpicsSignal, "image_Y_center.A") # image_Y_centerPix + + # --- click target coordinates --- + target_x = Cpt(EpicsSignal, "image_X_target.A") # C2C_TargetX + target_y = Cpt(EpicsSignal, "image_Y_target.A") # C2C_TargetY + omega = Cpt(EpicsSignal, "OmegaPos.A") # C2C_Omega + + # --- execution --- + go = Cpt(EpicsSignal, "click_center.PROC") # C2C_Go + gonio_done = Cpt(EpicsSignalRO, "gonioDone") # gonioDone diff --git a/mxbluesky/devices/comm.py b/mxbluesky/devices/comm.py new file mode 100644 index 00000000..f2b82862 --- /dev/null +++ b/mxbluesky/devices/comm.py @@ -0,0 +1,119 @@ +"""CommIOC device — beamline communication IOC channels. + +This device models PVs under ``daq_utils.beamlineComm`` so GUI/server code can +use ophyd signals directly instead of ad-hoc ``epics.PV`` objects. + +The signal list is split into: +1) channels actively referenced in current code paths, and +2) channels declared in ``daq_lib.var_list`` but currently unused. +""" + +from ophyd import Component as Cpt +from ophyd import Device, EpicsSignal + + +# --- actively referenced comm channels --- +ACTIVE_COMM_SIGNAL_SPECS = { + # Explicit beamlineComm + "..." usages + "command_s": ("command_s", {"string": True}), + "immediate_command_s": ("immediate_command_s", {"string": True}), + "live_q_change_flag": ("live_q_change_flag", {}), + "zinger_flag": ("zinger_flag", {}), + "message_string": ("message_string", {"string": True}), + "gui_popup_message_string": ("gui_popup_message_string", {"string": True}), + "restart_server_signal": ("RestartServerSignal", {}), + # daq_lib.set_field/get_field usages + "beam_check_flag": ("beam_check_flag", {}), + "beamline_merit": ("beamline_merit", {}), + "chooch_result_flag": ("choochResultFlag", {"string": True}), + "datum_kappa": ("datum_kappa", {}), + "datum_omega": ("datum_omega", {}), + "datum_phi": ("datum_phi", {}), + "distance": ("distance", {}), + "group_name": ("group_name", {"string": True}), + "kappa": ("kappa", {}), + "mounted_pin": ("mounted_pin", {"string": True}), + "omega": ("omega", {}), + "overwrite_check_flag": ("overwrite_check_flag", {}), + "pause_button_state": ("pause_button_state", {"string": True}), + "phi": ("phi", {}), + "program_state": ("program_state", {"string": True}), + "size_mode": ("size_mode", {}), + "spcgrp": ("spcgrp", {}), + "state": ("state", {"string": True}), + "xrec_raster_flag": ("xrecRasterFlag", {"string": True}), + "xtal_id": ("xtal_id", {"string": True}), +} + + +# --- declared in daq_lib.var_list, currently unused in code --- +CURRENTLY_UNUSED_COMM_SIGNAL_SPECS = { + "active_sweep": ("active_sweep", {}), + "col_end0": ("col_end0", {}), + "col_start0": ("col_start0", {}), + "current_pinpos": ("current_pinpos", {}), + "datafilename": ("datafilename", {"string": True}), + "edna_aimed_isig": ("edna_aimed_ISig", {}), + "edna_aimed_completeness": ("edna_aimed_completeness", {}), + "edna_aimed_multiplicity": ("edna_aimed_multiplicity", {"string": True}), + "edna_aimed_resolution": ("edna_aimed_resolution", {"string": True}), + "energy_fall": ("energy_fall", {}), + "energy_inflection": ("energy_inflection", {}), + "energy_peak": ("energy_peak", {}), + "exptime0": ("exptime0", {}), + "f2prime_infl": ("f2prime_infl", {}), + "f2prime_peak": ("f2prime_peak", {}), + "file_prefix0": ("file_prefix0", {"string": True}), + "filter": ("filter", {}), + "fprime_infl": ("fprime_infl", {}), + "fprime_peak": ("fprime_peak", {}), + "grid_exptime": ("grid_exptime", {}), + "grid_imwidth": ("grid_imwidth", {}), + "html_logging": ("html_logging", {}), + "inc0": ("inc0", {}), + "mono_energy_current": ("mono_energy_current", {}), + "mono_energy_scan_step": ("mono_energy_scan_step", {}), + "mono_energy_target": ("mono_energy_target", {}), + "mono_scan_points": ("mono_scan_points", {}), + "mono_wave_current": ("mono_wave_current", {}), + "mono_wave_target": ("mono_wave_target", {}), + "numstart0": ("numstart0", {}), + "px_id": ("px_id", {"string": True}), + "rot_dist0": ("rot_dist0", {}), + "scan_axis": ("scan_axis", {"string": True}), + "state_percent": ("state_percent", {}), + "sweep_count": ("sweep_count", {}), + "take_xtal_pics": ("take_xtal_pics", {}), + "theta": ("theta", {}), + "vector_fpp": ("vector_fpp", {}), + "vector_on": ("vector_on", {}), + "vector_step": ("vector_step", {}), + "vector_translation": ("vector_translation", {}), + "wave_fall": ("wave_fall", {}), + "wave_inflection": ("wave_inflection", {}), + "wave_peak": ("wave_peak", {}), + "wavelength0": ("wavelength0", {}), + "xia2_on": ("xia2_on", {}), +} + + +ALL_COMM_SIGNAL_SPECS = { + **ACTIVE_COMM_SIGNAL_SPECS, + **CURRENTLY_UNUSED_COMM_SIGNAL_SPECS, +} + +COMM_SUFFIX_BY_ATTR = { + attr: suffix for attr, (suffix, _kwargs) in ALL_COMM_SIGNAL_SPECS.items() +} + + +class CommIOC(Device): + """Beamline communication IOC channels (prefix == beamlineComm).""" + + for _attr, (_suffix, _kwargs) in ACTIVE_COMM_SIGNAL_SPECS.items(): + locals()[_attr] = Cpt(EpicsSignal, _suffix, **_kwargs) + + for _attr, (_suffix, _kwargs) in CURRENTLY_UNUSED_COMM_SIGNAL_SPECS.items(): + locals()[_attr] = Cpt(EpicsSignal, _suffix, **_kwargs) + + del _attr, _suffix, _kwargs diff --git a/mxbluesky/devices/cryostream.py b/mxbluesky/devices/cryostream.py index 4582c760..11332d6f 100644 --- a/mxbluesky/devices/cryostream.py +++ b/mxbluesky/devices/cryostream.py @@ -10,9 +10,10 @@ class CryoStreamCmd(IntEnum): class CryoStream(PVPositionerIsClose): - readback = Cpt(EpicsSignalRO, 'TEMP') - setpoint = Cpt(EpicsSignal, 'RTEMP') - actuate = Cpt(EpicsSignal, "RAMP.PROC") + readback = Cpt(EpicsSignalRO, 'SAMPLE_TEMP_RBV') + setpoint = Cpt(EpicsSignal, 'RAMP:TARGET_TEMP') + actuate = Cpt(EpicsSignal, "RAMP:EXECUTE") actuate_value = CryoStreamCmd.START_RAMP - stop_signal = Cpt(EpicsSignal, "RAMP.PROC") + stop_signal = Cpt(EpicsSignal, "RAMP:EXECUTE") stop_value = CryoStreamCmd.STOP_RAMP + ramp_rate = Cpt(EpicsSignal, "RAMP:RAMP_RATE") diff --git a/mxbluesky/devices/detector.py b/mxbluesky/devices/detector.py new file mode 100644 index 00000000..4ccb832e --- /dev/null +++ b/mxbluesky/devices/detector.py @@ -0,0 +1,89 @@ +"""Detector device — Eiger detector, cover, mercury MCA, and detector Z axis. + +The Eiger model token (e.g. ``{Det:Eig16M}``) and cover token +(e.g. ``{Det:FMX-Cover}``) differ between beamlines and are injected +at instantiation via keyword arguments. + +Photon energy signals exist only on FMX; pass ``photon_energy_suffix`` +and ``photon_energy_rbv_suffix`` for FMX, omit (empty string) for AMX. +Those two components carry ``lazy=True`` so AMX instantiation succeeds. + +Note on FormattedComponent (FCpt): + FCpt produces the FULL PV string — the device prefix is NOT auto-prepended. + Full PV strings are therefore pre-computed in ``__init__`` from the supplied + prefix + token arguments and stored as instance attributes. + +Instantiation examples:: + + # FMX + det = Detector( + "XF:17IDC-ES:FMX", + eig_token="{Det:Eig16M}", + cover_token="{Det:FMX-Cover}", + photon_energy_suffix="{Det:Eig16M}cam1:PhotonEnergy", + photon_energy_rbv_suffix="{Det:Eig16M}cam1:PhotonEnergy_RBV", + name="detector", + ) + + # AMX + det = Detector( + "XF:17IDB-ES:AMX", + eig_token="{Det:Eig9M}", + cover_token="{Det:AMX-Cover}", + name="detector", + ) +""" + +from ophyd import Component as Cpt, FormattedComponent as FCpt, Device +from ophyd import EpicsMotor, EpicsSignal, EpicsSignalRO + + +class Detector(Device): + """Eiger detector + detector-Z motor + cover + mercury MCA.""" + + # --- detector Z motor (same token on both beamlines) --- + dist = Cpt(EpicsMotor, "{Det-Ax:Z}Mtr") # detectorDist + + # --- Eiger control / status (full PVs pre-computed in __init__) --- + acquire = FCpt(EpicsSignal, "{self._acquire_pv}") + status_message = FCpt(EpicsSignalRO, "{self._status_message_pv}", string=True) + nx = FCpt(EpicsSignalRO, "{self._nx_pv}") + ny = FCpt(EpicsSignalRO, "{self._ny_pv}") + description = FCpt(EpicsSignalRO, "{self._description_pv}", string=True) + roi_mode = FCpt(EpicsSignal, "{self._roi_mode_pv}") + + # --- photon energy (FMX only; lazy so AMX instantiation succeeds) --- + photon_energy = FCpt(EpicsSignal, "{self._photon_energy_pv}", lazy=True) + photon_energy_rbv = FCpt(EpicsSignalRO, "{self._photon_energy_rbv_pv}", lazy=True) + + # --- detector cover (full PVs pre-computed in __init__) --- + cover_status = FCpt(EpicsSignalRO, "{self._cover_status_pv}") + cover_open = FCpt(EpicsSignal, "{self._cover_open_pv}") + + # --- Mercury MCA (same token on both beamlines) --- + mercury_erase = Cpt(EpicsSignal, "{Det:Mer}mca1.ERST") # mercuryEraseStart + mercury_spectrum = Cpt(EpicsSignalRO, "{Det:Mer}mca1.VAL") # mercurySpectrum + mercury_read_stat = Cpt(EpicsSignalRO, "{Det:Mer}mca1.READ") # mercuryReadStat + mca_roi_lo = Cpt(EpicsSignal, "{Det:Mer}mca1.R0LO") # mcaRoiLo + mca_roi_hi = Cpt(EpicsSignal, "{Det:Mer}mca1.R0HI") # mcaRoiHi + + def __init__(self, prefix, *args, + eig_token, + cover_token, + photon_energy_suffix="", + photon_energy_rbv_suffix="", + **kwargs): + # Pre-compute full PV strings before super().__init__ triggers FCpt resolution. + # (FCpt produces the FULL PV; device prefix is NOT auto-prepended.) + self._acquire_pv = f"{prefix}{eig_token}cam1:Acquire" + self._status_message_pv = f"{prefix}{eig_token}cam1:StatusMessage_RBV" + self._nx_pv = f"{prefix}{eig_token}cam1:ArraySizeX_RBV" + self._ny_pv = f"{prefix}{eig_token}cam1:ArraySizeY_RBV" + self._description_pv = f"{prefix}{eig_token}cam1:Description_RBV" + self._roi_mode_pv = f"{prefix}{eig_token}cam1:ROIMode" + self._cover_status_pv = f"{prefix}{cover_token}Pos-Sts" + self._cover_open_pv = f"{prefix}{cover_token}Cmd:Opn-Cmd" + # Photon energy: FMX only; suffix already includes the eig_token + self._photon_energy_pv = f"{prefix}{photon_energy_suffix}" + self._photon_energy_rbv_pv = f"{prefix}{photon_energy_rbv_suffix}" + super().__init__(prefix, *args, **kwargs) diff --git a/mxbluesky/devices/diagnostics.py b/mxbluesky/devices/diagnostics.py new file mode 100644 index 00000000..01fb195b --- /dev/null +++ b/mxbluesky/devices/diagnostics.py @@ -0,0 +1,53 @@ +"""Diagnostics device — BPM current, pin-homer, and PI commands. + +Signals span the BI and CT IOC families, so fully-qualified PV strings +are injected at instantiation (prefix=""). + +``pi_commands`` is FMX-only; pass an empty string on AMX and the +component will simply remain disconnected. + +Instantiation:: + + diag = Diagnostics( + "", + total_current_pv="XF:17IDC-BI:FMX{BPM:4}SumAll:MeanValue_RBV", + home_pin_y_pv ="XF:17IDC-CT:FMX{SDC:04-Ax:5}StartHome", + home_pin_z_pv ="XF:17IDC-CT:FMX{SDC:04-Ax:6}StartHome", + pi_commands_pv ="XF:17IDC-CT:FMX{MC:21}Asyn.AOUT", + name="diagnostics", + ) # FMX + + diag = Diagnostics( + "", + total_current_pv="XF:17IDB-BI:AMX{BPM:3}SumAll:MeanValue_RBV", + home_pin_y_pv ="XF:17IDB-CT:AMX{SDC:04-Ax:5}StartHome", + home_pin_z_pv ="XF:17IDB-CT:AMX{SDC:04-Ax:6}StartHome", + name="diagnostics", + ) # AMX +""" + +from ophyd import FormattedComponent as FCpt, Device, EpicsSignal, EpicsSignalRO + + +class Diagnostics(Device): + """BPM total current, pin-homer triggers, and PI command channel.""" + + total_current_bcu = FCpt(EpicsSignalRO, "{self._total_current_pv}") # totalCurrentBCU + ring_current = FCpt(EpicsSignalRO, "{self._ring_current_pv}") # ringCurrent + home_pin_y = FCpt(EpicsSignal, "{self._home_pin_y_pv}") # homePinY + home_pin_z = FCpt(EpicsSignal, "{self._home_pin_z_pv}") # homePinZ + pi_commands = FCpt(EpicsSignal, "{self._pi_commands_pv}", lazy=True) # PIcommands (FMX only) + + def __init__(self, *args, + total_current_pv, + ring_current_pv, + home_pin_y_pv, + home_pin_z_pv, + pi_commands_pv="", + **kwargs): + self._total_current_pv = total_current_pv + self._ring_current_pv = ring_current_pv + self._home_pin_y_pv = home_pin_y_pv + self._home_pin_z_pv = home_pin_z_pv + self._pi_commands_pv = pi_commands_pv + super().__init__(*args, **kwargs) diff --git a/mxbluesky/devices/generic.py b/mxbluesky/devices/generic.py index 6ed3aa1a..ad63e937 100644 --- a/mxbluesky/devices/generic.py +++ b/mxbluesky/devices/generic.py @@ -97,3 +97,16 @@ def is_full_speed(self): return False return True +class SmartMagnet(Device): + magnet = Cpt(EpicsSignal, "{Wago:1}MagnetOn-Sel") + boost = Cpt(EpicsSignal, "{Wago:1}Boost-Sel") + + def toggle(self): + # If current state is 1 then set it to 0 + magnet_next_state = 0 if self.magnet.get() else 1 + self.magnet.set(magnet_next_state).wait() + self.magnet.set(0).wait() + + boost_next_state = 0 if self.boost.get() else 1 + self.boost.set(boost_next_state).wait() + self.boost.set(0).wait() diff --git a/mxbluesky/devices/goniometer.py b/mxbluesky/devices/goniometer.py new file mode 100644 index 00000000..0d408f6c --- /dev/null +++ b/mxbluesky/devices/goniometer.py @@ -0,0 +1,54 @@ +"""Goniometer device — extends GoniometerStack with DB-mapped signals. + +Instantiate with the ``{Gon:1`` prefix, e.g.:: + + gonio = Goniometer("XF:17IDC-ES:FMX{Gon:1", name="gonio") # FMX + gonio = Goniometer("XF:17IDB-ES:AMX{Gon:1", name="gonio") # AMX + +Fine-axis motors (fine_x/y/z) and their waveform-control signals are +FMX-only; they will remain disconnected on AMX. +""" + +from ophyd import Component as Cpt, EpicsMotor, EpicsSignal, EpicsSignalRO +from mxbluesky.devices.generic import GoniometerStack + + +class Goniometer(GoniometerStack): + """GoniometerStack augmented with limit, sync-pin, shutter-pos, and + FMX fine-axis signals.""" + + # --- goniometer X limits (both beamlines) --- + x_high_limit = Cpt(EpicsSignalRO, "-Ax:GX}Mtr.HLM") # gonXHiLim + x_low_limit = Cpt(EpicsSignalRO, "-Ax:GX}Mtr.LLM") # gonXLoLim + + # --- pin-sync flags (both beamlines) --- + sync_pin_y = Cpt(EpicsSignal, "-Ax:PY}Mtr.SYNC") # syncPinY + sync_pin_z = Cpt(EpicsSignal, "-Ax:PZ}Mtr.SYNC") # syncPinZ + + # --- fast shutter calibrated positions (both beamlines) --- + fast_shutter_open_pos = Cpt(EpicsSignalRO, "-Sht}Pos:Opn-I") # fastShutterOpenPos + fast_shutter_close_pos = Cpt(EpicsSignalRO, "-Sht}Pos:Cls-I") # fastShutterClosePos + + # --- FMX-only: fine axis motors --- + fine_x = Cpt(EpicsMotor, "-Ax:Xf}Mtr") # fineX + fine_y = Cpt(EpicsMotor, "-Ax:Yf}Mtr") # fineY + fine_z = Cpt(EpicsMotor, "-Ax:Zf}Mtr") # fineZ + + # --- FMX-only: fine axis waveform parameters --- + fine_x_points = Cpt(EpicsSignal, "-Ax:Xf}Vec.A") # fineXPoints + fine_x_amp = Cpt(EpicsSignal, "-Ax:Xf}Vec.B") # fineXAmp + fine_x_offset = Cpt(EpicsSignal, "-Ax:Xf}Vec.C") # fineXOffset + fine_y_points = Cpt(EpicsSignal, "-Ax:Yf}Vec.A") # fineYPoints + fine_y_amp = Cpt(EpicsSignal, "-Ax:Yf}Vec.B") # fineYAmp + fine_y_offset = Cpt(EpicsSignal, "-Ax:Yf}Vec.C") # fineYOffset + fine_z_points = Cpt(EpicsSignal, "-Ax:Zf}Vec.A") # fineZPoints + fine_z_amp = Cpt(EpicsSignal, "-Ax:Zf}Vec.B") # fineZAmp + fine_z_offset = Cpt(EpicsSignal, "-Ax:Zf}Vec.C") # fineZOffset + + # --- FMX-only: fine axis waveform send / go triggers --- + fine_x_send_wave = Cpt(EpicsSignal, "-Ax:Xf}Vec.PROC") # fineXSendWave + fine_y_send_wave = Cpt(EpicsSignal, "-Ax:Yf}Vec.PROC") # fineYSendWave + fine_z_send_wave = Cpt(EpicsSignal, "-Ax:Zf}Vec.PROC") # fineZSendWave + fine_x_vec_go = Cpt(EpicsSignal, "-Ax:Xf}VecGo.PROC") # fineXVecGo + fine_y_vec_go = Cpt(EpicsSignal, "-Ax:Yf}VecGo.PROC") # fineYVecGo + fine_z_vec_go = Cpt(EpicsSignal, "-Ax:Zf}VecGo.PROC") # fineZVecGo diff --git a/mxbluesky/devices/governor.py b/mxbluesky/devices/governor.py new file mode 100644 index 00000000..082293d9 --- /dev/null +++ b/mxbluesky/devices/governor.py @@ -0,0 +1,72 @@ +"""Governor device — robot and human governor state machines. + +Instantiate with the C-ES (or B-ES) beamline prefix, e.g.:: + + gov = Governor("XF:17IDC-ES:FMX", name="governor") + gov = Governor("XF:17IDB-ES:AMX", name="governor") + +All 38 governor PV descriptors from the DB are covered. +""" + +from ophyd import Component as Cpt, Device, EpicsSignal, EpicsSignalRO + + +class Governor(Device): + """Robot and human governor state-machine signals.""" + + # Governor top-level + reboot = Cpt(EpicsSignal, "{Gov}Cmd:Kill-Cmd") # rebootGovernor + config = Cpt(EpicsSignal, "{Gov}Config-Sel") # robotGovConfig + active = Cpt(EpicsSignal, "{Gov}Active-Sel") # robotGovActive + + # Robot governor + robot_status = Cpt(EpicsSignalRO, "{Gov:Robot}Sts:Status-Sts") # robotGovStatus + robot_go = Cpt(EpicsSignal, "{Gov:Robot}Cmd:Go-Cmd") # robotGovGo + message = Cpt(EpicsSignalRO, "{Gov:Robot}Sts:Msg-Sts", + string=True) # governorMessage + + # Human governor + human_status = Cpt(EpicsSignalRO, "{Gov:Human}Sts:Status-Sts") # humanGovStatus + human_go = Cpt(EpicsSignal, "{Gov:Human}Cmd:Go-Cmd") # humanGovGo + + # Robot governor — state active flags + robot_se_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:SE}Sts:Active-Sts") # robotSeActive + robot_xf_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:XF}Sts:Active-Sts") # robotXfActive + robot_bl_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:BL}Sts:Active-Sts") # robotBlActive + robot_sa_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:SA}Sts:Active-Sts") # robotSaActive + robot_da_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:DA}Sts:Active-Sts") # robotDaActive + robot_di_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:DI}Sts:Active-Sts") # robotDiActive + robot_m_active = Cpt(EpicsSignalRO, "{Gov:Robot-St:M}Sts:Active-Sts") # robotMActive + + # Human governor — state active flags + human_se_active = Cpt(EpicsSignalRO, "{Gov:Human-St:SE}Sts:Active-Sts") # humanSeActive + human_xf_active = Cpt(EpicsSignalRO, "{Gov:Human-St:XF}Sts:Active-Sts") # humanXfActive + human_bl_active = Cpt(EpicsSignalRO, "{Gov:Human-St:BL}Sts:Active-Sts") # humanBlActive + human_sa_active = Cpt(EpicsSignalRO, "{Gov:Human-St:SA}Sts:Active-Sts") # humanSaActive + human_da_active = Cpt(EpicsSignalRO, "{Gov:Human-St:DA}Sts:Active-Sts") # humanDaActive + human_m_active = Cpt(EpicsSignalRO, "{Gov:Human-St:M}Sts:Active-Sts") # humanMActive + + # Robot governor — state reach flags (SA state) + robot_se_reach = Cpt(EpicsSignalRO, "{Gov:Robot-St:SE}Sts:Reach-Sts") # govRobotSeReach + robot_sa_reach = Cpt(EpicsSignalRO, "{Gov:Robot-St:SA}Sts:Reach-Sts") # govRobotSaReach + robot_bl_reach = Cpt(EpicsSignalRO, "{Gov:Robot-St:BL}Sts:Reach-Sts") # govRobotBlReach + robot_da_reach = Cpt(EpicsSignalRO, "{Gov:Robot-St:DA}Sts:Reach-Sts") # govRobotDaReach + + # Robot governor — SA state goniometer limits + robot_x_mount_low_lim = Cpt(EpicsSignalRO, "{Gov:Robot-St:SA}LLim:gx-Pos") # robotXMountLowLim + robot_x_mount_hi_lim = Cpt(EpicsSignalRO, "{Gov:Robot-St:SA}HLim:gx-Pos") # robotXMountHiLim + + # Robot governor — device positions + robot_x_mount_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gx}Pos:Mount-Pos") # robotXMountPos + robot_x_work_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gx}Pos:Work-Pos") # robotXWorkPos + robot_y_mount_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gpy}Pos:Mount-Pos") # robotYMountPos + robot_y_work_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gpy}Pos:Work-Pos") # robotYWorkPos + robot_z_mount_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gpz}Pos:Mount-Pos") # robotZMountPos + robot_z_work_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:gpz}Pos:Work-Pos") # robotZWorkPos + robot_omega_work_pos = Cpt(EpicsSignal, "{Gov:Robot-Dev:go}Pos:Work-Pos") # robotOmegaWorkPos + + # Detector distance positions (human and robot) + human_det_dist = Cpt(EpicsSignal, "{Gov:Human-Dev:dz}Pos:In-Pos") # govHumanDetDist + human_det_dist_out = Cpt(EpicsSignal, "{Gov:Human-Dev:dz}Pos:Out-Pos") # govHumanDetDistOut + robot_det_dist = Cpt(EpicsSignal, "{Gov:Robot-Dev:dz}Pos:In-Pos") # govRobotDetDist + robot_det_dist_out = Cpt(EpicsSignal, "{Gov:Robot-Dev:dz}Pos:Out-Pos") # govRobotDetDistOut diff --git a/mxbluesky/devices/ioc_control.py b/mxbluesky/devices/ioc_control.py new file mode 100644 index 00000000..aa140082 --- /dev/null +++ b/mxbluesky/devices/ioc_control.py @@ -0,0 +1,39 @@ +"""IOCControl device — IOC reboot commands for Eiger and Zebra. + +The IOC names differ between beamlines so they are injected at instantiation. + +Note on FormattedComponent (FCpt): + FCpt produces the FULL PV string — the device prefix is NOT auto-prepended. + Full PV strings are therefore pre-computed in ``__init__``. + +Instantiation:: + + ioc = IOCControl( + "XF:17IDC-CT:FMX", + eiger_ioc_suffix="{IOC:DET01}:SysReset", + zebra_ioc_suffix="{IOC:Zeb3}:SysReset", + name="ioc_control", + ) # FMX + + ioc = IOCControl( + "XF:17IDB-CT:AMX", + eiger_ioc_suffix="{IOC:DET02}:SysReset.VAL", + zebra_ioc_suffix="{IOC:Zeb2}:SysReset", + name="ioc_control", + ) # AMX +""" + +from ophyd import FormattedComponent as FCpt, Device, EpicsSignal + + +class IOCControl(Device): + """IOC soft-reboot commands.""" + + eiger_ioc_reboot = FCpt(EpicsSignal, "{self._eiger_ioc_pv}") # eigerIOC_reboot + zebra_ioc_reboot = FCpt(EpicsSignal, "{self._zebra_ioc_pv}") # zebraRebootIOC + + def __init__(self, prefix, *args, eiger_ioc_suffix, zebra_ioc_suffix, **kwargs): + # Pre-compute full PV strings (FCpt does NOT auto-prepend device prefix). + self._eiger_ioc_pv = f"{prefix}{eiger_ioc_suffix}" + self._zebra_ioc_pv = f"{prefix}{zebra_ioc_suffix}" + super().__init__(prefix, *args, **kwargs) diff --git a/mxbluesky/devices/md2.py b/mxbluesky/devices/md2.py deleted file mode 100644 index 7fa98c59..00000000 --- a/mxbluesky/devices/md2.py +++ /dev/null @@ -1,392 +0,0 @@ -import os -import socket - -from ophyd import Component as Cpt -from ophyd import Device, EpicsSignal, EpicsSignalRO, PVPositioner -from ophyd.status import SubscriptionStatus - - -class MD2Positioner(PVPositioner): - setpoint = Cpt(EpicsSignal, "Position", name="setpoint") - readback = Cpt(EpicsSignal, "Position", name="readback") - state = Cpt(EpicsSignalRO, "State", name="state") - done = Cpt(EpicsSignalRO, "State", name="done") - precision = Cpt(EpicsSignalRO, "Precision", name="precision") - done_value = 4 # MD2 Enum, 4 = Ready - # TODO: Add limits, settle_time, timeout or defaults for each - - def val(self): - return self.get().readback - - -class ExporterComponent(Cpt): - def __init__(self, address, port, name, **kwargs): - super().__init__(self, **kwargs) - self.name = name - self.address = address - self.port = port - self.sock = None # socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # self.sock.settimeout(5) - - def get(self): - return () - - def connect(self): - # for establishing connection, using context manager is preferred - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.address, self.port)) - - def disconnect(self): - self.sock.close() - self.sock = None - - def send_data(self, data): - STX = chr(2) - ETX = chr(3) - data = STX + data + ETX - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((self.address, self.port)) - s.sendto(data.encode(), (self.address, self.port)) - state = s.recv(4096) - print(f"state: {self.decipher_reply(state)}") - ret = None - while ret is None: - output = s.recv(4096) - output = self.decipher_reply(output) - ret = self.process_ret(output) - return ret - - def read_stream(self): - output = None - while output is None: - output, addr = self.sock.recvfrom(4096) - print(f"output:{self.decipher_reply(output)}") - output = None - - def write(self, attribute, value): - return self.send_data("WRTE " + attribute + " " + str(value)) - - def read(self, attribute): - return (self.send_data("READ " + attribute + " ")).split(" ")[0][4:] - - def cmd(self, method, parameters): - parameters = map(str, parameters) - params = "\t".join(parameters) - return self.send_data("EXEC " + method + " " + str(params)) - - def decipher_reply(self, reply): - # Specifically for decoding MD2 Exporter replies - reply = str(reply) - reply = reply.replace("\\x03", "") - reply = reply.replace("\\x1f", ", ") - reply = reply.replace("\\x02", "\n") - reply = reply.replace("\\t", " ") - return reply[2:-1] - - def process_ret(self, ret): - # Returns only when an error or return value is found, - # ignores events to ensure flyer kickoff functions as expected - for line in ret.split("\n"): - if "ERR:" in line: - print(f"error: {line}") - return line - elif "RET:" in line: - print(f"returned: {line}") - return line - elif "NULL" in line: - print(f"null error: {line}") - return line - elif "EVT:" in line: # print here if you want to see the events - pass # return self.process_evt(line) - - -class LightDevice(Device): - control = Cpt(EpicsSignal, "LightIsOn", name="control") - factor = Cpt(EpicsSignal, "LightFactor", name="factor") - level = Cpt(EpicsSignal, "LightLevel", name="level") - - def is_on(self): - return self.control.get() == 1 - - def turn_on(self): - self.control.set(1) - - def turn_off(self): - self.control.set(0) - - def set_factor(self, factor): - self.factor.set(factor) - - def set_level(self, level): - self.level.set(level) - - -class BeamstopDevice(Device): - distance = Cpt(MD2Positioner, "BeamstopDistance", name="distance") - distance_preset = Cpt(EpicsSignal, "BeamstopDistance", name="distance_preset") - x = Cpt(MD2Positioner, "BeamstopX", name="x") - y = Cpt(MD2Positioner, "BeamstopY", name="y") - z = Cpt(MD2Positioner, "BeamstopZ", name="z") - pos = Cpt(EpicsSignal, "BeamstopPosition", name="pos") - - -class MD2SimpleHVDevice(Device): - horizontal = Cpt(MD2Positioner, "HVHorizontal", name="horizontal") - vertical = Cpt(MD2Positioner, "HVVertical", name="vertical") - pos = Cpt(EpicsSignal, "HVPosition", name="pos") - # Current aperture/scintillator/capillary predifined position. - # Enum: the aperture position: - # 0: PARK, under cover. - # 1: BEAM, selected aperture aligned with beam. - # 2: OFF, just below the OAV. - # 3: UNKNOWN, not in a predefined position (this cannot be set). - - -class GonioDevice(Device): - omega = Cpt(MD2Positioner, "Omega", name="omega") - x = Cpt(MD2Positioner, "AlignmentX", name="x") - y = Cpt(MD2Positioner, "AlignmentY", name="y") - z = Cpt(MD2Positioner, "AlignmentZ", name="z") - cx = Cpt(MD2Positioner, "CentringX", name="cx") - cy = Cpt(MD2Positioner, "CentringY", name="cy") - - -class MD2Device(GonioDevice): - # TODO: Enum for MD2 phases and states - cx = Cpt(MD2Positioner, "CentringX", name="cx") - cy = Cpt(MD2Positioner, "CentringY", name="cy") - center_pixel_x = Cpt(EpicsSignalRO, "BeamPositionHorizontal", name="center_pixel_x") - center_pixel_y = Cpt(EpicsSignalRO, "BeamPositionVertical", name="center_pixel_y") - centring_click = Cpt(EpicsSignal, "setCentringClick", name="centring_click") - centring_save = Cpt(EpicsSignal, "saveCentringPositions", name="centring_save") - state = Cpt(EpicsSignalRO, "State", name="state") - phase = Cpt(EpicsSignal, "CurrentPhase", name="phase") - phase_index = Cpt(EpicsSignalRO, "CurrentPhaseIndex", name="phase_index") - detector_state = Cpt(EpicsSignal, "DetectorState", name="det_state") - detector_gate_pulse_enabled = Cpt( - EpicsSignal, "DetectorGatePulseEnabled", name="det_gate_pulse_enabled" - ) - exporter = Cpt( - ExporterComponent, - address=os.environ["EXPORTER_HOST"], - port=int(os.environ["EXPORTER_PORT"]), - name="exporter", - ) - - task_info = Cpt(EpicsSignalRO, "LastTaskInfo", name="task_info") - task_output = Cpt(EpicsSignalRO, "LastTaskOutput", name="task_output") - - def save_center(self): - self.centring_save.put("__EMPTY__") - - def task_complete(self): - return self.task_output == "true" - - def task_status(self): - def check_task(*, old_value, value, **kwargs): - "Return True when last task completes" - return value[6] == "1" - - return SubscriptionStatus(self.task_info, check_task) - - def is_ready(self): - return self.state.get() == 4 - - def ready_status(self): - # returns an ophyd status object that monitors the state pv for operations to complete - def check_ready(*, old_value, value, **kwargs): - "Return True when the MD2 is ready" - return value == 4 - - return SubscriptionStatus(self.state, check_ready) - - def phase_transition(self, phase): - # returns an ophyd status object that monitors the phase pv for operations to complete - self.phase.put(phase) - - def check_transition_state(*, old_value, value, **kwargs): - "Return True when the MD2 is ready" - return value == 4 and self.phase.get() == phase - - return SubscriptionStatus(self.state, check_transition_state) - - def standard_scan( - self, - frame_number=0, # int: frame ID just for logging purposes. - num_images=1, # int: number of frames. Needed solely when the detector use gate enabled trigger. - start_angle=0, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=0.1, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=0.1, # double: exposure time (sec) to control shutter command. - num_passes=1, # int: number of moves forward and reverse between start angle and stop angle - ): - command = "startScanEx2" - if start_angle is None: - start_angle = self.omega.get() - return self.exporter.cmd( - command, - [ - frame_number, - num_images, - start_angle, - scan_range, - exposure_time, - num_passes, - ], - ) - - def vector_scan( - self, - start_angle=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - scan_range=10, # double: omega relative move angle (deg) before closing the shutter. - exposure_time=1, # double: exposure time (sec) to control shutter command. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - stop_y=None, # double: PhiY axis position at the end of the exposure. - stop_z=None, # double: PhiZ axis position at the end of the exposure. - stop_cx=None, # double: CentringX axis position at the end of the exposure. - stop_cy=None, # double: CentringY axis position at the end of the exposure. - ): - command = "startScan4DEx" - if start_angle is None: - start_angle = self.omega.val() - if start_y is None: - start_y = self.y.val() - if start_z is None: - start_z = self.z.val() - if start_cx is None: - start_cx = self.cx.val() - if start_cy is None: - start_cy = self.cy.val() - if stop_y is None: - stop_y = self.y.val() - if stop_z is None: - stop_z = self.z.val() - if stop_cx is None: - stop_cx = self.cx.val() - if stop_cy is None: - stop_cy = self.cy.val() - - # List of scan parameters values, comma separated. The axes start values define the beginning - # of the exposure, that is when all the axes have a steady speed and when the shutter/detector - # are triggered. - # The axes stop values are for the end of detector exposure and define the position at the - # beginning of the deceleration. - # Inputs names: "start_angle", "scan_range", "exposure_time", "start_y", "start_z", "start_cx", - # "start_cy", "stop_y", "stop_z", "stop_cx", "stop_cy" - param_list = [ - start_angle, - scan_range, - exposure_time, - start_y, - start_z, - start_cx, - start_cy, - stop_y, - stop_z, - stop_cx, - stop_cy, - ] - return self.exporter.cmd(command, param_list) - - def raster_scan( - self, - omega_range=0, # double: omega relative move angle (deg) before closing the shutter. - line_range=0.1, # double: horizontal range of the grid (mm). - total_uturn_range=0.1, # double: vertical range of the grid (mm). - start_omega=None, # double: angle (deg) at which the shutter opens and omega speed is stable. - start_y=None, # double: PhiY axis position at the beginning of the exposure. - start_z=None, # double: PhiZ axis position at the beginning of the exposure. - start_cx=None, # double: CentringX axis position at the beginning of the exposure. - start_cy=None, # double: CentringY axis position at the beginning of the exposure. - number_of_lines=5, # int: number of frames on the vertical range. - frames_per_line=5, # int: number of frames on the horizontal range. - exposure_time=1.2, # double: exposure time (sec) to control shutter command. +1, based on the exaples given - invert_direction=True, # boolean: true to enable passes in the reverse direction. - use_centring_table=True, # boolean: true to use the centring table to do the pitch movements. - use_fast_mesh_scans=True, # boolean: true to use the fast raster scan if available (power PMAC). - ): - - command = "startRasterScanEx" - if start_omega is None: - start_omega = self.omega.val() - if start_y is None: - start_y = self.y.val() - if start_z is None: - start_z = self.z.val() - if start_cx is None: - start_cx = self.cx.val() - if start_cy is None: - start_cy = self.cy.val() - # List of scan parameters values, "/t" separated. The axes start values define the beginning - # of the exposure, that is when all the axes have a steady speed and when the shutter/detector - # are triggered. - # The axes stop values are for the end of detector exposure and define the position at the - # beginning of the deceleration. - # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", - # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", - # "invert_direction", "use_centring_table", "use_fast_mesh_scans" - param_list = [ - omega_range, - line_range, - total_uturn_range, - start_omega, - start_y, - start_z, - start_cx, - start_cy, - number_of_lines, - frames_per_line, - exposure_time, - invert_direction, - use_centring_table, - use_fast_mesh_scans, - ] - return self.exporter.cmd(command, param_list) - - -class MD2ApertureDevice(Device): - # this device needs additional signals for "CurrentApertureDiameterIndex" and "ApertureDiameters" - current_index = Cpt( - EpicsSignal, "CurrentApertureDiameterIndex", name="current_index" - ) - diameters = Cpt(EpicsSignalRO, "ApertureDiameters", name="diameters") - - def get_diameter_list(self): - # must format list for GUI - # also, can't currently get from PV - return self.diameters.get() - - def set_diameter(self, diameter): - if diameter in self.get_diameter_list(): - self.current_index.put(self.get_diameter_list().index(diameter)) - - -class ShutterDevice(Device): - control = Cpt( - EpicsSignal, "{MD2}:FastShutterIsOpen", name="control" - ) # PV to send control signal - pos_opn = Cpt(EpicsSignalRO, "{Gon:1-Sht}Pos:Opn-I", name="pos_opn") - pos_cls = Cpt(EpicsSignalRO, "{Gon:1-Sht}Pos:Cls-I", name="pos_cls") - - def is_open(self): - return self.control.get() == 1 # self.pos_opn.get() - - def open_shutter(self): - self.control.set( - 1 - ) # self.pos_opn.get()) iocs are down, so just setting it to 1 - - def close_shutter(self): - self.control.set(0) # self.pos_cls.get()) - - -class CameraDevice(Device): - # MD2 camera has the following attributes: - # CoaxCamScaleX - # CoaxCamScaleY - # CoaxialCameraZoomValue - scale_x = Cpt(EpicsSignalRO, "CoaxCamScaleX", name="scale_x") - scale_y = Cpt(EpicsSignalRO, "CoaxCamScaleY", name="scale_y") - zoom = Cpt(EpicsSignal, "CoaxialCameraZoomValue", name="zoom") diff --git a/mxbluesky/devices/md2_flyers.py b/mxbluesky/devices/md2_flyers.py deleted file mode 100644 index 01021d65..00000000 --- a/mxbluesky/devices/md2_flyers.py +++ /dev/null @@ -1,284 +0,0 @@ -import logging -import os -from collections import deque -import getpass -import grp -from ophyd.sim import NullStatus -from ophyd.status import SubscriptionStatus -from ophyd.utils import WaitTimeoutError - -logger = logging.getLogger(__name__) - -DEFAULT_DATUM_DICT = {"data": None, "omega": None} - -INTERNAL_SERIES = 0 -INTERNAL_ENABLE = 1 -EXTERNAL_SERIES = 2 -EXTERNAL_ENABLE = 3 - -class MD2StandardFlyer(): - def __init__(self, md2, detector=None) -> None: - self.name = "MD2StandardFlyer" - self.detector = detector - self.md2 = md2 - self.collection_params = {} - - self._asset_docs_cache = deque() - self._resource_uids = [] - self._datum_counter = None - self._datum_ids = DEFAULT_DATUM_DICT - self._master_file = None - self._master_metadata = [] - - self._collection_dictionary = None - - def kickoff(self): - md2_msg = self.md2.standard_scan(num_images=self.collection_params["total_num_images"], - start_angle=self.collection_params["start_angle"], - scan_range=self.collection_params["scan_range"], - exposure_time=self.collection_params["exposure_time"]) - logger.info(f"md2 KICKOFF msg: {md2_msg}") - return NullStatus() - - def update_parameters(self, total_num_images, start_angle, scan_range, exposure_time): - self.collection_params = { - "total_num_images": total_num_images, - "start_angle": start_angle, - "scan_range": scan_range, - "exposure_time": exposure_time, - } - - def configure_detector(self, file_prefix, data_directory_name): - self.detector.file.external_name.put(file_prefix) - self.detector.file.write_path_template = data_directory_name - - def detector_arm(self, angle_start, img_width, total_num_images, exposure_per_image, - file_prefix, data_directory_name, file_number_start, x_beam, y_beam, - wavelength, det_distance_m): - self.detector.cam.save_files.put(1) - self.detector.cam.sequence_id.put(file_number_start) - self.detector.cam.det_distance.put(det_distance_m) - self.detector.cam.file_owner.put(getpass.getuser()) - self.detector.cam.file_owner_grp.put(grp.getgrgid(os.getgid())[0]) - self.detector.cam.file_perms.put(420) - file_prefix_minus_directory = str(file_prefix) - file_prefix_minus_directory = file_prefix_minus_directory.split("/")[-1] - self.detector.cam.acquire_time.put(exposure_per_image) - self.detector.cam.acquire_period.put(exposure_per_image) - self.detector.cam.num_triggers.put(1) - self.detector.cam.num_images.put(total_num_images) - self.detector.cam.trigger_mode.put( - EXTERNAL_SERIES - ) # must be external_enable to get the correct number of triggers and stop acquire - self.detector.cam.file_path.put(data_directory_name) - self.detector.cam.fw_name_pattern.put(f"{file_prefix_minus_directory}_$id") - self.detector.cam.beam_center_x.put(x_beam) - self.detector.cam.beam_center_y.put(y_beam) - self.detector.cam.omega_incr.put(img_width) - self.detector.cam.omega_start.put(angle_start) - self.detector.cam.wavelength.put(wavelength) - self.detector.file.file_write_images_per_file.put(500) - - #def armed_callback(value, old_value, **kwargs): - # if old_value == 0 and value == 1: - # return True - # return False - - #status = SubscriptionStatus(self.detector.cam.armed, armed_callback, run=False) - #self.detector.cam.acquire.put(1) - #yield status - - def complete(self): - # monitor md2 status, wait for ready or timeout and then return - #ready_status = self.md2.ready_status() - - #logger.info(f"TASK INFO[6]: {self.md2.task_info[6]}") - #logger.info(f"TASK OUTPUT: {self.md2.task_output}") - logger.debug(f"FLYER COMPLETE FUNCTION") - task_status = self.md2.task_status() - logger.debug(f"assigning task status") - timeout = (self.collection_params["exposure_time"] * 3) + 80 - logger.info(f"TASK TIMEOUT: {timeout}") - #ready_status.wait(timeout=timeout) - try: - task_status.wait(timeout=timeout) - except WaitTimeoutError: - logger.info("reached task timeout") - logger.info(f"TASK INFO: {self.md2.task_info}") - logger.info(f"TASK OUTPUT: {self.md2.task_output}") - return - return task_status - - def describe_collect(self): - return {"stream_name": {}} - #return {self.name: self._collection_dictionary} - - def collect(self): - logger.debug("raster_flyer.collect(): going to unstage now") - yield {"data": {}, "timestamps": {}, "time": 0, "seq_num": 0} - #return self._collection_dictionary - - def unstage(self): - logger.debug("flyer unstaging") - self.collection_params = {} - - def read_configuration(self): - return {} - - def describe_configuration(self): - return {} - - # def collect_asset_docs(self): - # for _ in (): - # yield _ - - def collect_asset_docs(self): - asset_docs_cache = [] - - # Get the Resource which was produced when the detector was staged. - ((name, resource),) = self.detector.file.collect_asset_docs() - - asset_docs_cache.append(("resource", resource)) - self._datum_ids = DEFAULT_DATUM_DICT - # Generate Datum documents from scratch here, because the detector was - # triggered externally by the DeltaTau, never by ophyd. - resource_uid = resource["uid"] - # num_points = int(math.ceil(self.detector.cam.num_images.get() / - # self.detector.cam.fw_num_images_per_file.get())) - - # We are currently generating only one datum document for all frames, that's why - # we use the 0th index below. - # - # Uncomment & update the line below if more datum documents are needed: - # for i in range(num_points): - - seq_id = self.detector.cam.sequence_id.get() - - self._master_file = f"{resource['root']}/{resource['resource_path']}_{seq_id}_master.h5" - if not os.path.isfile(self._master_file): - raise RuntimeError(f"File {self._master_file} does not exist") - - # The pseudocode below is from Tom Caswell explaining the relationship between resource, datum, and events. - # - # resource = { - # "resource_id": "RES", - # "resource_kwargs": {}, # this goes to __init__ - # "spec": "AD-EIGER-MX", - # ...: ..., - # } - # datum = { - # "datum_id": "a", - # "datum_kwargs": {"data_key": "data"}, # this goes to __call__ - # "resource": "RES", - # ...: ..., - # } - # datum = { - # "datum_id": "b", - # "datum_kwargs": {"data_key": "omega"}, - # "resource": "RES", - # ...: ..., - # } - - # event = {...: ..., "data": {"detector_img": "a", "omega": "b"}} - - for data_key in self._datum_ids.keys(): - datum_id = f"{resource_uid}/{data_key}" - self._datum_ids[data_key] = datum_id - datum = { - "resource": resource_uid, - "datum_id": datum_id, - "datum_kwargs": {"data_key": data_key}, - } - asset_docs_cache.append(("datum", datum)) - return tuple(asset_docs_cache) - - def _extract_metadata(self, field="omega"): - with h5py.File(self._master_file, "r") as hf: - return hf.get(f"entry/sample/goniometer/{field}")[()] - -class MD2VectorFlyer(MD2StandardFlyer): - def __init__(self, md2, detector=None) -> None: - super().__init__(md2, detector) - self.name = "MD2VectorFlyer" - - def kickoff(self): - # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z - md2_msg = self.md2.vector_scan(start_angle=self.collection_params["start_angle"], - scan_range=self.collection_params["scan_range"], - exposure_time=self.collection_params["exposure_time"], - start_cx=self.collection_params["start_cx"], - start_cy=self.collection_params["start_cy"], - start_y=self.collection_params["start_y"], - start_z=self.collection_params["start_z"], - stop_cx=self.collection_params["stop_cx"], - stop_cy=self.collection_params["stop_cy"], - stop_y=self.collection_params["stop_y"], - stop_z=self.collection_params["stop_z"],) - logger.info(f"md2 VEC KICKOFF msg: {md2_msg}") - return NullStatus() - - def update_parameters(self, start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z, start_cx, start_cy, stop_cx, stop_cy): - self.collection_params = { - "start_angle": start_angle, - "scan_range": scan_range, - "exposure_time": exposure_time, - "start_cx": start_cx, - "start_cy": start_cy, - "start_y": start_y, - "start_z": start_z, - "stop_cx": stop_cx, - "stop_cy": stop_cy, - "stop_y": stop_y, - "stop_z": stop_z, - } - -class MD2RasterFlyer(MD2StandardFlyer): - # List of scan parameters values, "/t" separated. The axes start values define the beginning - # of the exposure, that is when all the axes have a steady speed and when the shutter/detector - # are triggered. - # The axes stop values are for the end of detector exposure and define the position at the - # beginning of the deceleration. - # Inputs names: "omega_range", "line_range", "total_uturn_range", "start_omega", "start_y", - # "start_z", "start_cx", "start_cy", "number_of_lines", "frames_per_lines", "exposure_time", - # "invert_direction", "use_centring_table", "use_fast_mesh_scans" - - def __init__(self, md2, detector=None) -> None: - super().__init__(md2, detector) - self.name = "MD2RasterFlyer" - - def kickoff(self): - # params used are start_angle, scan_range, exposure_time, start_y, start_z, stop_y, stop_z - md2_msg = self.md2.raster_scan(omega_range=self.collection_params["omega_range"], - line_range=self.collection_params["line_range"], - total_uturn_range=self.collection_params["total_uturn_range"], - start_omega=self.collection_params["start_omega"], - start_y=self.collection_params["start_y"], - start_z=self.collection_params["start_z"], - start_cx=self.collection_params["start_cx"], - start_cy=self.collection_params["start_cy"], - number_of_lines=self.collection_params["number_of_lines"], - frames_per_line=self.collection_params["frames_per_line"], - exposure_time=self.collection_params["exposure_time"], - invert_direction=self.collection_params["invert_direction"], - use_centring_table=self.collection_params["use_centring_table"], - use_fast_mesh_scans=self.collection_params["use_fast_mesh_scans"]) - logger.info(f"md2 RASTER KICKOFF msg: {md2_msg}") - return NullStatus() - - def update_parameters(self, omega_range, line_range, total_uturn_range, start_omega, start_y, start_z, start_cx, start_cy, number_of_lines, frames_per_line, exposure_time, invert_direction, use_centring_table, use_fast_mesh_scans): - self.collection_params = { - "omega_range": omega_range, - "line_range": line_range, - "total_uturn_range": total_uturn_range, - "start_omega": start_omega, - "start_y": start_y, - "start_z": start_z, - "start_cx": start_cx, - "start_cy": start_cy, - "number_of_lines": number_of_lines, - "frames_per_line": frames_per_line, - "exposure_time": exposure_time, - "invert_direction": invert_direction, - "use_centring_table": use_centring_table, - "use_fast_mesh_scans": use_fast_mesh_scans, - } diff --git a/mxbluesky/devices/misc.py b/mxbluesky/devices/misc.py new file mode 100644 index 00000000..109d1215 --- /dev/null +++ b/mxbluesky/devices/misc.py @@ -0,0 +1,95 @@ +"""Misc device — miscellaneous beamline-wide signals spanning multiple IOC +prefixes. + +Because these PVs live across several different IOC prefix domains, the +device uses ``FormattedComponent`` with ``prefix=""`` and accepts fully- +qualified PV strings at instantiation. The ``BeamlineDevices.from_beamline`` +factory provides the correct PV strings. + +Direct instantiation (example, FMX):: + + from mxbluesky.devices.misc import Misc + misc = Misc( + "", + beam_available_pv ="XF:17ID-OP:FMX{BeamAvail}", + exposing_pv ="XF:17ID-OP:FMX{Exposing}", + still_mode_pv ="XF:17IDC-ES:FMX{Stills}", + still_mode_status_pv="XF:17IDC-ES:FMX{Stills}-Stat", + standard_mode_pv ="XF:17IDC-ES:FMX{Standard}", + beam_center_x_pv ="XF:17ID-ES:FMX{Misc-BeamCenter:X}Pos-I", + beam_center_y_pv ="XF:17ID-ES:FMX{Misc-BeamCenter:Y}Pos-I", + low_mag_cursor_x_pv ="XF:17ID-ES:FMX{Misc-AuxCursor2:X}Pos-SP", + low_mag_cursor_y_pv ="XF:17ID-ES:FMX{Misc-AuxCursor2:Y}Pos-SP", + high_mag_cursor_x_pv="XF:17ID-ES:FMX{Misc-AuxCursor3:X}Pos-SP", + high_mag_cursor_y_pv="XF:17ID-ES:FMX{Misc-AuxCursor3:Y}Pos-SP", + vector_delay_pv ="XF:17ID-ES:FMX{Misc-Vector:Delay}Pos-SP", + photon_shutter_open_pv ="XF:17IDA-PPS:FMX{PSh}Cmd:Opn-Cmd", + photon_shutter_close_pv="XF:17IDA-PPS:FMX{PSh}Cmd:Cls-Cmd", + name="misc", + ) +""" + +from ophyd import FormattedComponent as FCpt, Device +from ophyd import EpicsSignal, EpicsSignalRO + + +class Misc(Device): + """Miscellaneous beamline-wide signals (beam status, cursors, shutters).""" + + # --- beam availability / exposure state --- + beam_available = FCpt(EpicsSignalRO, "{self._beam_available_pv}") # beamAvailable + exposing = FCpt(EpicsSignalRO, "{self._exposing_pv}") # exposing + + # --- still / standard mode toggle --- + still_mode = FCpt(EpicsSignal, "{self._still_mode_pv}") # stillMode + still_mode_status = FCpt(EpicsSignalRO, "{self._still_mode_status_pv}") # stillModeStatus + standard_mode = FCpt(EpicsSignal, "{self._standard_mode_pv}") # standardMode + + # --- beam centre (detector geometry) --- + beam_center_x = FCpt(EpicsSignalRO, "{self._beam_center_x_pv}") # beamCenterX + beam_center_y = FCpt(EpicsSignalRO, "{self._beam_center_y_pv}") # beamCenterY + + # --- on-screen cursor positions --- + low_mag_cursor_x = FCpt(EpicsSignal, "{self._low_mag_cursor_x_pv}") # lowMagCursorX + low_mag_cursor_y = FCpt(EpicsSignal, "{self._low_mag_cursor_y_pv}") # lowMagCursorY + high_mag_cursor_x = FCpt(EpicsSignal, "{self._high_mag_cursor_x_pv}") # highMagCursorX + high_mag_cursor_y = FCpt(EpicsSignal, "{self._high_mag_cursor_y_pv}") # highMagCursorY + + # --- vector scan trigger delay --- + vector_delay = FCpt(EpicsSignal, "{self._vector_delay_pv}") # vectorDelay + + # --- photon shutter --- + photon_shutter_open = FCpt(EpicsSignal, "{self._photon_shutter_open_pv}") # photonShutterOpen + photon_shutter_close = FCpt(EpicsSignal, "{self._photon_shutter_close_pv}") # photonShutterClose + + def __init__(self, *args, + beam_available_pv, + exposing_pv, + still_mode_pv, + still_mode_status_pv, + standard_mode_pv, + beam_center_x_pv, + beam_center_y_pv, + low_mag_cursor_x_pv, + low_mag_cursor_y_pv, + high_mag_cursor_x_pv, + high_mag_cursor_y_pv, + vector_delay_pv, + photon_shutter_open_pv, + photon_shutter_close_pv, + **kwargs): + self._beam_available_pv = beam_available_pv + self._exposing_pv = exposing_pv + self._still_mode_pv = still_mode_pv + self._still_mode_status_pv = still_mode_status_pv + self._standard_mode_pv = standard_mode_pv + self._beam_center_x_pv = beam_center_x_pv + self._beam_center_y_pv = beam_center_y_pv + self._low_mag_cursor_x_pv = low_mag_cursor_x_pv + self._low_mag_cursor_y_pv = low_mag_cursor_y_pv + self._high_mag_cursor_x_pv = high_mag_cursor_x_pv + self._high_mag_cursor_y_pv = high_mag_cursor_y_pv + self._vector_delay_pv = vector_delay_pv + self._photon_shutter_open_pv = photon_shutter_open_pv + self._photon_shutter_close_pv = photon_shutter_close_pv + super().__init__(*args, **kwargs) diff --git a/mxbluesky/devices/optics.py b/mxbluesky/devices/optics.py new file mode 100644 index 00000000..bba4ca2d --- /dev/null +++ b/mxbluesky/devices/optics.py @@ -0,0 +1,36 @@ +"""Optics device — DCM energy/wavelength motors, sample flux. + +All signals share the ``A-OP:FMX`` / ``A-OP:AMX`` prefix. + +Slit motors (slit1_x_gap, slit1_y_gap) are FMX-only; they will be +disconnected on AMX. + +The ``flux`` PV differs between beamlines (see BeamlineDevices) and is +NOT included here. Access it as ``bl.flux`` on the top-level container. + +``sample_lifetime`` (AMX-only) is similarly handled at the container level. + +Instantiation:: + + optics = Optics("XF:17IDA-OP:FMX", name="optics") # FMX + optics = Optics("XF:17IDA-OP:AMX", name="optics") # AMX +""" + +from ophyd import Component as Cpt, Device, EpicsMotor, EpicsSignal, EpicsSignalRO + + +class Optics(Device): + """DCM energy/wavelength motors and beam flux readbacks.""" + + # --- monochromator motors (both beamlines) --- + wavelength = Cpt(EpicsMotor, "{Mono:DCM-Ax:W}Mtr") # wavelength + energy = Cpt(EpicsMotor, "{Mono:DCM-Ax:E}Mtr") # energy + + # --- flux readbacks (both beamlines via DCM-dflux-MA) --- + sample_flux = Cpt(EpicsSignalRO, "{Mono:DCM-dflux-MA}") # sampleFlux + + # --- FMX-only: slits --- + slit1_x_gap = Cpt(EpicsMotor, "{Slt:1-Ax:XGap}Mtr") # slit1XGap (FMX only) + slit1_y_gap = Cpt(EpicsMotor, "{Slt:1-Ax:YGap}Mtr") # slit1YGap (FMX only) + slit1_x_gap_setpoint = Cpt(EpicsSignal, "{Slt:1-Ax:XGap}Mtr.VAL", lazy=True) + slit1_y_gap_setpoint = Cpt(EpicsSignal, "{Slt:1-Ax:YGap}Mtr.VAL", lazy=True) diff --git a/mxbluesky/devices/pv_maps.py b/mxbluesky/devices/pv_maps.py new file mode 100644 index 00000000..ee947134 --- /dev/null +++ b/mxbluesky/devices/pv_maps.py @@ -0,0 +1,630 @@ +""" +Internal PV lookup tables for FMX and AMX. + +Each dict maps the legacy descriptor name (from epx.db / epxAMX.db) to the +full EPICS PV string (beamline_designation + DB prefix + suffix). + +This module is **private to mxbluesky.devices** — application code must +never import it directly. It exists to: + 1. Provide a single source of truth during the migration from pvLookupDict. + 2. Enable coverage checks that verify every DB descriptor is reachable via + the new ophyd device layer. + 3. Be dropped once the device layer is feature-complete. + +PV string construction: + beamline_designation ("XF:17ID") + DB_entry = full PV + e.g. "XF:17ID" + "C-ES:FMX{Gon:1-Ax:GX}Mtr" = "XF:17IDC-ES:FMX{Gon:1-Ax:GX}Mtr" +""" + +_BL = "XF:17ID" # beamline_designation shared by both beamlines + +# --------------------------------------------------------------------------- +# Helper: expand raw DB entries to full PV strings +# --------------------------------------------------------------------------- +def _expand(entries): + """Turn a list of (descriptor, db_entry) pairs into {descriptor: full_pv}.""" + return {desc: _BL + entry for desc, entry in entries} + + +# --------------------------------------------------------------------------- +# FMX (epx.db) +# --------------------------------------------------------------------------- +PV_MAPS = { + "fmx": _expand([ + # --- real motors --- + ("fineX", "C-ES:FMX{Gon:1-Ax:Xf}Mtr"), + ("fineY", "C-ES:FMX{Gon:1-Ax:Yf}Mtr"), + ("fineZ", "C-ES:FMX{Gon:1-Ax:Zf}Mtr"), + ("omega", "C-ES:FMX{Gon:1-Ax:O}Mtr"), + ("sampleX", "C-ES:FMX{Gon:1-Ax:GX}Mtr"), + ("sampleY", "C-ES:FMX{Gon:1-Ax:PY}Mtr"), + ("sampleZ", "C-ES:FMX{Gon:1-Ax:PZ}Mtr"), + ("detectorDist", "C-ES:FMX{Det-Ax:Z}Mtr"), + ("fastShutter", "C-ES:FMX{Sht:1-Ax:R}Mtr"), + ("dewarRot", "C-ES:FMX{Dew:1-Ax:R}Mtr"), + ("slit1XGap", "A-OP:FMX{Slt:1-Ax:XGap}Mtr"), + ("slit1YGap", "A-OP:FMX{Slt:1-Ax:YGap}Mtr"), + ("cryoXY", "C-ES:FMX{CS:1-Ax:XY}Mtr"), + # --- virtual motors --- + ("wavelength", "A-OP:FMX{Mono:DCM-Ax:W}Mtr"), + ("energy", "A-OP:FMX{Mono:DCM-Ax:E}Mtr"), + # --- control PVs --- + ("RIattenEnergySP", "C-OP:FMX{Attn:RI}Energy-SP"), + ("beamAvailable", "-OP:FMX{BeamAvail}"), + ("exposing", "-OP:FMX{Exposing}"), + ("mercuryEraseStart", "C-ES:FMX{Det:Mer}mca1.ERST"), + ("mercurySpectrum", "C-ES:FMX{Det:Mer}mca1.VAL"), + ("mercuryReadStat", "C-ES:FMX{Det:Mer}mca1.READ"), + ("zoom2FrameRate", "C-ES:FMX{Cam:7}MJPG1:ArrayRate_RBV"), + ("zoom1FrameRate", "C-ES:FMX{Cam:7}MJPG2:ArrayRate_RBV"), + ("zoom4FrameRate", "C-ES:FMX{Cam:8}MJPG1:ArrayRate_RBV"), + ("zoom3FrameRate", "C-ES:FMX{Cam:8}MJPG2:ArrayRate_RBV"), + ("stillMode", "C-ES:FMX{Stills}"), + ("standardMode", "C-ES:FMX{Standard}"), + ("stillModeStatus", "C-ES:FMX{Stills}-Stat"), + ("detCoverRBV", "C-ES:FMX{Det:FMX-Cover}Pos-Sts"), + ("detCoverOpen", "C-ES:FMX{Det:FMX-Cover}Cmd:Opn-Cmd"), + ("annealIn", "C-ES:FMX{Wago:1}AnnealerAir-Sel"), + ("annealStatus", "C-ES:FMX{Wago:1}AnnealerIn-Sts"), + ("sampleProtect", "C-ES:FMX{SPSEnable}"), + ("warmupThresholdRBV","C-ES:FMX{CS8}WarmupThreshold_RBV"), + ("warmupThreshold", "C-ES:FMX{CS8}WarmupThreshold"), + ("gripperClosed", "C-ES:FMX{Wago:1}BimbaClosed-Sts"), + ("gripperOpen", "C-ES:FMX{Wago:1}BimbaOpen-Sts"), + ("robotForceX", "C-ES:FMX{FTS:1}ForceX-I"), + ("robotForceY", "C-ES:FMX{FTS:1}ForceY-I"), + ("robotForceZ", "C-ES:FMX{FTS:1}ForceZ-I"), + ("robotFTSensorStat", "C-ES:FMX{FTS:1}Status-I"), + ("eigerIOC_reboot", "C-CT:FMX{IOC:DET01}:SysReset"), + ("rebootGovernor", "C-ES:FMX{Gov}Cmd:Kill-Cmd"), + ("stopEiger", "C-ES:FMX{Det:Eig16M}cam1:Acquire"), + ("eigerStatMessage", "C-ES:FMX{Det:Eig16M}cam1:StatusMessage_RBV"), + ("RI_Atten_SP", "C-OP:FMX{Attn:RI}Trans-SP"), + ("RI_Atten_SET", "C-OP:FMX{Attn:RI}Cmd:Set-Cmd.PROC"), + ("CRL_VS_IN", "C-OP:FMX{CRL:02}Cmd:In-Cmd"), + ("CRL_VS_OUT", "C-OP:FMX{CRL:02}Cmd:Out-Cmd"), + ("CRL_V2A_IN", "C-OP:FMX{CRL:04}Cmd:In-Cmd"), + ("CRL_V2A_OUT", "C-OP:FMX{CRL:04}Cmd:Out-Cmd"), + ("CRL_V1A_IN", "C-OP:FMX{CRL:08}Cmd:In-Cmd"), + ("CRL_V1A_OUT", "C-OP:FMX{CRL:08}Cmd:Out-Cmd"), + ("CRL_V1B_IN", "C-OP:FMX{CRL:06}Cmd:In-Cmd"), + ("CRL_V1B_OUT", "C-OP:FMX{CRL:06}Cmd:Out-Cmd"), + ("CRL_H4A_IN", "C-OP:FMX{CRL:03}Cmd:In-Cmd"), + ("CRL_H4A_OUT", "C-OP:FMX{CRL:03}Cmd:Out-Cmd"), + ("CRL_H2A_IN", "C-OP:FMX{CRL:05}Cmd:In-Cmd"), + ("CRL_H2A_OUT", "C-OP:FMX{CRL:05}Cmd:Out-Cmd"), + ("CRL_H1A_IN", "C-OP:FMX{CRL:07}Cmd:In-Cmd"), + ("CRL_H1A_OUT", "C-OP:FMX{CRL:07}Cmd:Out-Cmd"), + ("CRL_H1B_IN", "C-OP:FMX{CRL:09}Cmd:In-Cmd"), + ("CRL_H1B_OUT", "C-OP:FMX{CRL:09}Cmd:Out-Cmd"), + ("robotXMountLowLim", "C-ES:FMX{Gov:Robot-St:SA}LLim:gx-Pos"), + ("robotXMountHiLim", "C-ES:FMX{Gov:Robot-St:SA}HLim:gx-Pos"), + ("robotXMountPos", "C-ES:FMX{Gov:Robot-Dev:gx}Pos:Mount-Pos"), + ("robotXWorkPos", "C-ES:FMX{Gov:Robot-Dev:gx}Pos:Work-Pos"), + ("robotYMountPos", "C-ES:FMX{Gov:Robot-Dev:gpy}Pos:Mount-Pos"), + ("robotYWorkPos", "C-ES:FMX{Gov:Robot-Dev:gpy}Pos:Work-Pos"), + ("robotZMountPos", "C-ES:FMX{Gov:Robot-Dev:gpz}Pos:Mount-Pos"), + ("robotZWorkPos", "C-ES:FMX{Gov:Robot-Dev:gpz}Pos:Work-Pos"), + ("robotOmegaWorkPos", "C-ES:FMX{Gov:Robot-Dev:go}Pos:Work-Pos"), + ("gonXHiLim", "C-ES:FMX{Gon:1-Ax:GX}Mtr.HLM"), + ("gonXLoLim", "C-ES:FMX{Gon:1-Ax:GX}Mtr.LLM"), + ("governorMessage", "C-ES:FMX{Gov:Robot}Sts:Msg-Sts"), + ("transmissionRBV", "C-OP:FMX{Attn:BCU}Trans-I"), + ("transmissionSet", "C-OP:FMX{Attn:BCU}Trans-SP"), + ("transmissionGo", "C-OP:FMX{Attn:BCU}Cmd:Set-Cmd.PROC"), + ("transmissionDone", "C-OP:FMX{Attn:BCU}attenDone"), + ("totalCurrentBCU", "C-BI:FMX{BPM:4}SumAll:MeanValue_RBV"), + ("syncPinY", "C-ES:FMX{Gon:1-Ax:PY}Mtr.SYNC"), + ("syncPinZ", "C-ES:FMX{Gon:1-Ax:PZ}Mtr.SYNC"), + ("sampleDetected", "C-ES:FMX{Wago:1}SampleDetected1-Sts"), + ("boostSelect", "C-ES:FMX{Wago:1}Boost-Sel"), + ("boostStatus", "C-ES:FMX{Wago:1}Boost-Sts"), + ("gripTemp", "C-ES:FMX{Wago:1}GripperTemp-I"), + ("homePinY", "C-CT:FMX{SDC:04-Ax:5}StartHome"), + ("homePinZ", "C-CT:FMX{SDC:04-Ax:6}StartHome"), + ("homePins", ":FMX{Sentinel}pin_home"), + ("pinsHomed", "C-ES:FMX{Sentinel}Validate_Sts"), + ("mcaRoiLo", "C-ES:FMX{Det:Mer}mca1.R0LO"), + ("mcaRoiHi", "C-ES:FMX{Det:Mer}mca1.R0HI"), + ("vectorBufferTime", "C-ES:FMX{Gon:1-Vec}Val:BufferTime-SP"), + ("fineXPoints", "C-ES:FMX{Gon:1-Ax:Xf}Vec.A"), + ("fineXAmp", "C-ES:FMX{Gon:1-Ax:Xf}Vec.B"), + ("fineXOffset", "C-ES:FMX{Gon:1-Ax:Xf}Vec.C"), + ("fineYPoints", "C-ES:FMX{Gon:1-Ax:Yf}Vec.A"), + ("fineYAmp", "C-ES:FMX{Gon:1-Ax:Yf}Vec.B"), + ("fineYOffset", "C-ES:FMX{Gon:1-Ax:Yf}Vec.C"), + ("fineZPoints", "C-ES:FMX{Gon:1-Ax:Zf}Vec.A"), + ("fineZAmp", "C-ES:FMX{Gon:1-Ax:Zf}Vec.B"), + ("fineZOffset", "C-ES:FMX{Gon:1-Ax:Zf}Vec.C"), + ("fineXSendWave", "C-ES:FMX{Gon:1-Ax:Xf}Vec.PROC"), + ("fineYSendWave", "C-ES:FMX{Gon:1-Ax:Yf}Vec.PROC"), + ("fineZSendWave", "C-ES:FMX{Gon:1-Ax:Zf}Vec.PROC"), + ("fineXVecGo", "C-ES:FMX{Gon:1-Ax:Xf}VecGo.PROC"), + ("fineYVecGo", "C-ES:FMX{Gon:1-Ax:Yf}VecGo.PROC"), + ("fineZVecGo", "C-ES:FMX{Gon:1-Ax:Zf}VecGo.PROC"), + ("PIcommands", "C-CT:FMX{MC:21}Asyn.AOUT"), + ("backLightVal", "C-ES:FMX{BL:1}Ch1Value"), + ("govRobotSeReach", "C-ES:FMX{Gov:Robot-St:SE}Sts:Reach-Sts"), + ("govRobotSaReach", "C-ES:FMX{Gov:Robot-St:SA}Sts:Reach-Sts"), + ("govRobotBlReach", "C-ES:FMX{Gov:Robot-St:BL}Sts:Reach-Sts"), + ("govRobotDaReach", "C-ES:FMX{Gov:Robot-St:DA}Sts:Reach-Sts"), + ("govHumanDetDist", "C-ES:FMX{Gov:Human-Dev:dz}Pos:In-Pos"), + ("govRobotDetDist", "C-ES:FMX{Gov:Robot-Dev:dz}Pos:In-Pos"), + ("govHumanDetDistOut","C-ES:FMX{Gov:Human-Dev:dz}Pos:Out-Pos"), + ("govRobotDetDistOut","C-ES:FMX{Gov:Robot-Dev:dz}Pos:Out-Pos"), + ("lowMagAcquire", "C-ES:FMX{Cam:7}cam1:Acquire"), + ("lowMagAcquireTime", "C-ES:FMX{Cam:7}cam1:AcquireTime"), + ("lowMagGain", "C-ES:FMX{Cam:7}cam1:Gain"), + ("lowMagImMode", "C-ES:FMX{Cam:7}cam1:ImageMode"), + ("lowMagTrigMode", "C-ES:FMX{Cam:7}cam1:TriggerMode"), + ("lowMagNumImages", "C-ES:FMX{Cam:7}cam1:NumImages"), + ("lowMagJpegNumImages","C-ES:FMX{Cam:7}JPEG1:NumCapture"), + ("lowMagJpegFileNumber","C-ES:FMX{Cam:7}JPEG1:FileNumber"), + ("lowMagJpegCapture", "C-ES:FMX{Cam:7}JPEG1:Capture"), + ("lowMagJpegFilePath","C-ES:FMX{Cam:7}JPEG1:FilePath"), + ("lowMagJpegFileName","C-ES:FMX{Cam:7}JPEG1:FileName"), + ("lowMagJpegEnableCallbacks","C-ES:FMX{Cam:7}JPEG1:EnableCallbacks"), + ("lowMagDetStateRBV", "C-ES:FMX{Cam:7}cam1:DetectorState_RBV"), + ("topViewAcquire", "C-ES:FMX{Cam:11}cam1:Acquire"), + ("topViewAcquireTime","C-ES:FMX{Cam:11}cam1:AcquireTime"), + ("topViewDataType", "C-ES:FMX{Cam:11}cam1:DataType"), + ("topViewGain", "C-ES:FMX{Cam:11}cam1:Gain"), + ("topViewImMode", "C-ES:FMX{Cam:11}cam1:ImageMode"), + ("topViewTrigMode", "C-ES:FMX{Cam:11}cam1:TriggerMode"), + ("topViewNumImages", "C-ES:FMX{Cam:11}cam1:NumImages"), + ("topViewJpegNumImages","C-ES:FMX{Cam:11}JPEG1:NumCapture"), + ("topViewJpegFileNumber","C-ES:FMX{Cam:11}JPEG1:FileNumber"), + ("topViewJpegCapture","C-ES:FMX{Cam:11}JPEG1:Capture"), + ("topViewJpegFilePath","C-ES:FMX{Cam:11}JPEG1:FilePath"), + ("topViewJpegFileName","C-ES:FMX{Cam:11}JPEG1:FileName"), + ("topViewJpegEnableCallbacks","C-ES:FMX{Cam:11}JPEG1:EnableCallbacks"), + ("topViewWriteFile", "C-ES:FMX{Cam:11}JPEG1:WriteFile"), + ("topViewDetStateRBV","C-ES:FMX{Cam:11}cam1:DetectorState_RBV"), + ("robotGovStatus", "C-ES:FMX{Gov:Robot}Sts:Status-Sts"), + ("humanGovStatus", "C-ES:FMX{Gov:Human}Sts:Status-Sts"), + ("humanGovGo", "C-ES:FMX{Gov:Human}Cmd:Go-Cmd"), + ("robotGovGo", "C-ES:FMX{Gov:Robot}Cmd:Go-Cmd"), + ("humanSeActive", "C-ES:FMX{Gov:Human-St:SE}Sts:Active-Sts"), + ("humanXfActive", "C-ES:FMX{Gov:Human-St:XF}Sts:Active-Sts"), + ("humanBlActive", "C-ES:FMX{Gov:Human-St:BL}Sts:Active-Sts"), + ("humanSaActive", "C-ES:FMX{Gov:Human-St:SA}Sts:Active-Sts"), + ("humanDaActive", "C-ES:FMX{Gov:Human-St:DA}Sts:Active-Sts"), + ("humanMActive", "C-ES:FMX{Gov:Human-St:M}Sts:Active-Sts"), + ("robotSeActive", "C-ES:FMX{Gov:Robot-St:SE}Sts:Active-Sts"), + ("robotXfActive", "C-ES:FMX{Gov:Robot-St:XF}Sts:Active-Sts"), + ("robotBlActive", "C-ES:FMX{Gov:Robot-St:BL}Sts:Active-Sts"), + ("robotSaActive", "C-ES:FMX{Gov:Robot-St:SA}Sts:Active-Sts"), + ("robotDaActive", "C-ES:FMX{Gov:Robot-St:DA}Sts:Active-Sts"), + ("robotDiActive", "C-ES:FMX{Gov:Robot-St:DI}Sts:Active-Sts"), + ("robotMActive", "C-ES:FMX{Gov:Robot-St:M}Sts:Active-Sts"), + ("robotGovConfig", "C-ES:FMX{Gov}Config-Sel"), + ("robotGovActive", "C-ES:FMX{Gov}Active-Sel"), + ("zebraTriggering", "C-ES:FMX{Zeb:3}:AND1_INP1:STA"), + ("zebraDownloading", "C-ES:FMX{Zeb:3}:ARRAY_ACQ"), + ("zebraDownloadCount","C-ES:FMX{Zeb:3}:PC_NUM_DOWN"), + ("zebraDisarm", "C-ES:FMX{Zeb:3}:PC_DISARM"), + ("zebraPulseDelay", "C-ES:FMX{Zeb:3}:PC_PULSE_DLY"), + ("zebraEncX", "C-ES:FMX{Zeb:3}:PC_ENC1"), + ("zebraEncY", "C-ES:FMX{Zeb:3}:PC_ENC2"), + ("zebraEncZ", "C-ES:FMX{Zeb:3}:PC_ENC3"), + ("zebraEncOmega", "C-ES:FMX{Zeb:3}:PC_ENC4"), + ("zebraReset", "C-ES:FMX{Zeb:3}:SYS_RESET.PROC"), + ("zebraRebootIOC", "C-CT:FMX{IOC:Zeb3}:SysReset"), + ("zebraSentTriggerStatus","C-ES:FMX{Zeb:3}:AND1_INP1:STA"), + ("zebraArmStatus", "C-ES:FMX{Zeb:3}:PC_ARM_INP:STA"), + ("zebraPulseStatus", "C-ES:FMX{Zeb:3}:PC_PULSE_INP:STA"), + ("zebraTriggerReturnStatus","C-ES:FMX{Zeb:3}:AND1_INP2:STA"), + ("zebraM1SetPosProc", "C-ES:FMX{Zeb:3}:M1:SETPOS.PROC"), + ("zebraM2SetPosProc", "C-ES:FMX{Zeb:3}:M2:SETPOS.PROC"), + ("zebraM3SetPosProc", "C-ES:FMX{Zeb:3}:M3:SETPOS.PROC"), + ("zebraM4SetPosProc", "C-ES:FMX{Zeb:3}:M4:SETPOS.PROC"), + ("zebraPCBITCAP0", "C-ES:FMX{Zeb:3}:PC_BIT_CAP:B0"), + ("zebraPCBITCAP1", "C-ES:FMX{Zeb:3}:PC_BIT_CAP:B1"), + ("zebraPCBITCAP2", "C-ES:FMX{Zeb:3}:PC_BIT_CAP:B2"), + ("zebraPCBITCAP3", "C-ES:FMX{Zeb:3}:PC_BIT_CAP:B3"), + ("zebraEncoder", "C-ES:FMX{Zeb:3}:PC_ENC"), + ("zebraDirection", "C-ES:FMX{Zeb:3}:PC_DIR"), + ("zebraGateSelect", "C-ES:FMX{Zeb:3}:PC_GATE_SEL"), + ("zebraGateStart", "C-ES:FMX{Zeb:3}:PC_GATE_START"), + ("zebraGateWidth", "C-ES:FMX{Zeb:3}:PC_GATE_WID"), + ("zebraGateStep", "C-ES:FMX{Zeb:3}:PC_GATE_STEP"), + ("zebraGateNumGates", "C-ES:FMX{Zeb:3}:PC_GATE_NGATE"), + ("zebraTTlSel", "C-ES:FMX{Zeb:3}:OUT1_TTL"), + ("zebraPulseTriggerSource","C-ES:FMX{Zeb:3}:PC_PULSE_SEL"), + ("zebraPulseStart", "C-ES:FMX{Zeb:3}:PC_PULSE_START"), + ("zebraPulseStep", "C-ES:FMX{Zeb:3}:PC_PULSE_STEP"), + ("zebraPulseWidth", "C-ES:FMX{Zeb:3}:PC_PULSE_WID"), + ("zebraPulseMax", "C-ES:FMX{Zeb:3}:PC_PULSE_MAX"), + ("zebraArm", "C-ES:FMX{Zeb:3}:PC_ARM"), + ("zebraArmOut", "C-ES:FMX{Zeb:3}:PC_ARM_OUT"), + ("zebraArmTrigSource","C-ES:FMX{Zeb:3}:PC_ARM_SEL"), + ("vectorHold", "C-ES:FMX{Gon:1-Vec}Hold-Sel"), + ("vectorExpose", "C-ES:FMX{Gon:1-Vec}Expose-Sel"), + ("vectorGo", "C-ES:FMX{Gon:1-Vec}Cmd:Go-Cmd"), + ("vectorProceed", "C-ES:FMX{Gon:1-Vec}Cmd:Proceed-Cmd"), + ("vectorAbort", "C-ES:FMX{Gon:1-Vec}Cmd:Abort-Cmd"), + ("photonShutterOpen", "A-PPS:FMX{PSh}Cmd:Opn-Cmd"), + ("photonShutterClose","A-PPS:FMX{PSh}Cmd:Cls-Cmd"), + ("fastShutterOpenPos","C-ES:FMX{Gon:1-Sht}Pos:Opn-I"), + ("fastShutterClosePos","C-ES:FMX{Gon:1-Sht}Pos:Cls-I"), + ("vectorStartX", "C-ES:FMX{Gon:1-Vec}Pos:XStart-SP"), + ("vectorStartY", "C-ES:FMX{Gon:1-Vec}Pos:YStart-SP"), + ("vectorStartZ", "C-ES:FMX{Gon:1-Vec}Pos:ZStart-SP"), + ("vectorEndX", "C-ES:FMX{Gon:1-Vec}Pos:XEnd-SP"), + ("vectorEndY", "C-ES:FMX{Gon:1-Vec}Pos:YEnd-SP"), + ("vectorEndZ", "C-ES:FMX{Gon:1-Vec}Pos:ZEnd-SP"), + ("vectorStartOmega", "C-ES:FMX{Gon:1-Vec}Pos:OStart-SP"), + ("vectorEndOmega", "C-ES:FMX{Gon:1-Vec}Pos:OEnd-SP"), + ("vectorSync", "C-ES:FMX{Gon:1-Vec}Cmd:Sync-Cmd"), + ("vectorframeExptime","C-ES:FMX{Gon:1-Vec}Val:Exposure-SP"), + ("vectorNumFrames", "C-ES:FMX{Gon:1-Vec}Val:NumSamples-SP"), + ("VectorActive", "C-ES:FMX{Gon:1-Vec}Sts:Running-Sts"), + ("VectorState", "C-ES:FMX{Gon:1-Vec}Sts:State-Sts"), + ("Atten01-0", "C-OP:FMX{Attn:01}Cmd:Out-Cmd"), + ("Atten02-0", "C-OP:FMX{Attn:02}Cmd:Out-Cmd"), + ("Atten03-0", "C-OP:FMX{Attn:03}Cmd:Out-Cmd"), + ("Atten04-0", "C-OP:FMX{Attn:04}Cmd:Out-Cmd"), + ("Atten05-0", "C-OP:FMX{Attn:05}Cmd:Out-Cmd"), + ("Atten06-0", "C-OP:FMX{Attn:06}Cmd:Out-Cmd"), + ("Atten07-0", "C-OP:FMX{Attn:07}Cmd:Out-Cmd"), + ("Atten08-0", "C-OP:FMX{Attn:08}Cmd:Out-Cmd"), + ("Atten09-0", "C-OP:FMX{Attn:09}Cmd:Out-Cmd"), + ("Atten10-0", "C-OP:FMX{Attn:10}Cmd:Out-Cmd"), + ("Atten11-0", "C-OP:FMX{Attn:11}Cmd:Out-Cmd"), + ("Atten12-0", "C-OP:FMX{Attn:12}Cmd:Out-Cmd"), + ("Atten01-1", "C-OP:FMX{Attn:01}Cmd:In-Cmd"), + ("Atten02-1", "C-OP:FMX{Attn:02}Cmd:In-Cmd"), + ("Atten03-1", "C-OP:FMX{Attn:03}Cmd:In-Cmd"), + ("Atten04-1", "C-OP:FMX{Attn:04}Cmd:In-Cmd"), + ("Atten05-1", "C-OP:FMX{Attn:05}Cmd:In-Cmd"), + ("Atten06-1", "C-OP:FMX{Attn:06}Cmd:In-Cmd"), + ("Atten07-1", "C-OP:FMX{Attn:07}Cmd:In-Cmd"), + ("Atten08-1", "C-OP:FMX{Attn:08}Cmd:In-Cmd"), + ("Atten09-1", "C-OP:FMX{Attn:09}Cmd:In-Cmd"), + ("Atten10-1", "C-OP:FMX{Attn:10}Cmd:In-Cmd"), + ("Atten11-1", "C-OP:FMX{Attn:11}Cmd:In-Cmd"), + ("Atten12-1", "C-OP:FMX{Attn:12}Cmd:In-Cmd"), + ("image_X_scalePix", "C-ES:FMX{ClkCtr}image_X_scale.B"), + ("image_X_scaleMM", "C-ES:FMX{ClkCtr}image_X_scale.C"), + ("image_Y_scalePix", "C-ES:FMX{ClkCtr}image_Y_scale.B"), + ("image_Y_scaleMM", "C-ES:FMX{ClkCtr}image_Y_scale.C"), + ("image_X_centerPix", "C-ES:FMX{ClkCtr}image_X_center.A"), + ("image_Y_centerPix", "C-ES:FMX{ClkCtr}image_Y_center.A"), + ("C2C_TargetX", "C-ES:FMX{ClkCtr}image_X_target.A"), + ("C2C_TargetY", "C-ES:FMX{ClkCtr}image_Y_target.A"), + ("C2C_Omega", "C-ES:FMX{ClkCtr}OmegaPos.A"), + ("C2C_Go", "C-ES:FMX{ClkCtr}click_center.PROC"), + ("gonioDone", "C-ES:FMX{ClkCtr}gonioDone"), + ("beamCenterX", "-ES:FMX{Misc-BeamCenter:X}Pos-I"), + ("beamCenterY", "-ES:FMX{Misc-BeamCenter:Y}Pos-I"), + ("lowMagCursorX", "-ES:FMX{Misc-AuxCursor2:X}Pos-SP"), + ("lowMagCursorY", "-ES:FMX{Misc-AuxCursor2:Y}Pos-SP"), + ("highMagCursorX", "-ES:FMX{Misc-AuxCursor3:X}Pos-SP"), + ("highMagCursorY", "-ES:FMX{Misc-AuxCursor3:Y}Pos-SP"), + ("vectorDelay", "-ES:FMX{Misc-Vector:Delay}Pos-SP"), + ("flux", "A-OP:FMX{Mono:DCM-flux}"), + ("sampleFlux", "A-OP:FMX{Mono:DCM-dflux-MA}"), + ("lowMagZoomMinXRBV", "C-ES:FMX{Cam:7}ROI3:MinX_RBV"), + ("lowMagZoomMinYRBV", "C-ES:FMX{Cam:7}ROI3:MinY_RBV"), + ("lowMagZoomMinX", "C-ES:FMX{Cam:7}ROI3:MinX"), + ("lowMagZoomMinY", "C-ES:FMX{Cam:7}ROI3:MinY"), + ("lowMagMinX", "C-ES:FMX{Cam:7}ROI2:MinX"), + ("lowMagMinY", "C-ES:FMX{Cam:7}ROI2:MinY"), + ("lowMagZoomROISizeXRBV","C-ES:FMX{Cam:7}ROI3:SizeX_RBV"), + ("lowMagZoomROISizeYRBV","C-ES:FMX{Cam:7}ROI3:SizeY_RBV"), + ("lowMagROISizeXRBV", "C-ES:FMX{Cam:7}ROI2:SizeX_RBV"), + ("lowMagROISizeYRBV", "C-ES:FMX{Cam:7}ROI2:SizeY_RBV"), + ("highMagZoomROISizeXRBV","C-ES:FMX{Cam:8}ROI1:SizeX_RBV"), + ("highMagZoomROISizeYRBV","C-ES:FMX{Cam:8}ROI1:SizeY_RBV"), + ("highMagROISizeXRBV","C-ES:FMX{Cam:8}ROI2:SizeX_RBV"), + ("highMagROISizeYRBV","C-ES:FMX{Cam:8}ROI2:SizeY_RBV"), + ("lowMagZoomMaxSizeXRBV","C-ES:FMX{Cam:7}ROI3:MaxSizeX_RBV"), + ("lowMagZoomMaxSizeYRBV","C-ES:FMX{Cam:7}ROI3:MaxSizeY_RBV"), + ("lowMagMaxSizeXRBV", "C-ES:FMX{Cam:7}ROI2:MaxSizeX_RBV"), + ("lowMagMaxSizeYRBV", "C-ES:FMX{Cam:7}ROI2:MaxSizeY_RBV"), + ("highMagZoomMaxSizeXRBV","C-ES:FMX{Cam:8}ROI1:MaxSizeX_RBV"), + ("highMagZoomMaxSizeYRBV","C-ES:FMX{Cam:8}ROI1:MaxSizeY_RBV"), + ("highMagMaxSizeXRBV","C-ES:FMX{Cam:8}ROI2:MaxSizeX_RBV"), + ("highMagMaxSizeYRBV","C-ES:FMX{Cam:8}ROI2:MaxSizeY_RBV"), + ("lowMagZoomSizeXRBV","C-ES:FMX{Cam:7}ROI3:ArraySizeX_RBV"), + ("lowMagZoomSizeYRBV","C-ES:FMX{Cam:7}ROI3:ArraySizeY_RBV"), + ("lowMagZoomBinXRBV", "C-ES:FMX{Cam:7}ROI3:BinX_RBV"), + ("lowMagZoomBinYRBV", "C-ES:FMX{Cam:7}ROI3:BinY_RBV"), + ("lowMagMinXRBV", "C-ES:FMX{Cam:7}ROI2:MinX_RBV"), + ("lowMagMinYRBV", "C-ES:FMX{Cam:7}ROI2:MinY_RBV"), + ("lowMagSizeXRBV", "C-ES:FMX{Cam:7}ROI2:ArraySizeX_RBV"), + ("lowMagSizeYRBV", "C-ES:FMX{Cam:7}ROI2:ArraySizeY_RBV"), + ("lowMagBinXRBV", "C-ES:FMX{Cam:7}ROI2:BinX_RBV"), + ("lowMagBinYRBV", "C-ES:FMX{Cam:7}ROI2:BinY_RBV"), + ("highMagZoomMinXRBV","C-ES:FMX{Cam:8}ROI1:MinX_RBV"), + ("highMagZoomMinYRBV","C-ES:FMX{Cam:8}ROI1:MinY_RBV"), + ("highMagZoomMinX", "C-ES:FMX{Cam:8}ROI1:MinX"), + ("highMagZoomMinY", "C-ES:FMX{Cam:8}ROI1:MinY"), + ("highMagMinX", "C-ES:FMX{Cam:8}ROI2:MinX"), + ("highMagMinY", "C-ES:FMX{Cam:8}ROI2:MinY"), + ("highMagZoomSizeXRBV","C-ES:FMX{Cam:8}ROI1:ArraySizeX_RBV"), + ("highMagZoomSizeYRBV","C-ES:FMX{Cam:8}ROI1:ArraySizeY_RBV"), + ("highMagZoomBinXRBV","C-ES:FMX{Cam:8}ROI1:BinX_RBV"), + ("highMagZoomBinYRBV","C-ES:FMX{Cam:8}ROI1:BinY_RBV"), + ("highMagMinXRBV", "C-ES:FMX{Cam:8}ROI2:MinX_RBV"), + ("highMagMinYRBV", "C-ES:FMX{Cam:8}ROI2:MinY_RBV"), + ("highMagSizeXRBV", "C-ES:FMX{Cam:8}ROI2:ArraySizeX_RBV"), + ("highMagSizeYRBV", "C-ES:FMX{Cam:8}ROI2:ArraySizeY_RBV"), + ("highMagBinXRBV", "C-ES:FMX{Cam:8}ROI2:BinX_RBV"), + ("highMagBinYRBV", "C-ES:FMX{Cam:8}ROI2:BinY_RBV"), + ("detectorNy", "C-ES:FMX{Det:Eig16M}cam1:ArraySizeY_RBV"), + ("detectorDescription","C-ES:FMX{Det:Eig16M}cam1:Description_RBV"), + ("detectorNx", "C-ES:FMX{Det:Eig16M}cam1:ArraySizeX_RBV"), + ("detectorROIMode", "C-ES:FMX{Det:Eig16M}cam1:ROIMode"), + ("detectorPhotonEnergyRBV","C-ES:FMX{Det:Eig16M}cam1:PhotonEnergy_RBV"), + ("detectorPhotonEnergy","C-ES:FMX{Det:Eig16M}cam1:PhotonEnergy"), + ("dewarPlatePos", "C-ES:FMX{CS8}PlatePosition"), + ]), + + # --------------------------------------------------------------------------- + # AMX (epxAMX.db) + # --------------------------------------------------------------------------- + "amx": _expand([ + # --- real motors --- + ("omega", "B-ES:AMX{Gon:1-Ax:O}Mtr"), + ("sampleX", "B-ES:AMX{Gon:1-Ax:GX}Mtr"), + ("sampleY", "B-ES:AMX{Gon:1-Ax:PY}Mtr"), + ("sampleZ", "B-ES:AMX{Gon:1-Ax:PZ}Mtr"), + ("detectorDist", "B-ES:AMX{Det-Ax:Z}Mtr"), + ("fastShutter", "B-ES:AMX{Sht:1-Ax:R}Mtr"), + ("dewarRot", "B-ES:AMX{Dew:1-Ax:R}Mtr"), + ("cryoXY", "B-ES:AMX{CS:1-Ax:XY}Mtr"), + # --- virtual motors --- + ("wavelength", "A-OP:AMX{Mono:DCM-Ax:W}Mtr"), + ("energy", "A-OP:AMX{Mono:DCM-Ax:E}Mtr"), + # --- control PVs --- + ("beamAvailable", "-OP:AMX{BeamAvail}"), + ("exposing", "-OP:AMX{Exposing}"), + ("mercuryEraseStart", "B-ES:AMX{Det:Mer}mca1.ERST"), + ("mercurySpectrum", "B-ES:AMX{Det:Mer}mca1.VAL"), + ("mercuryReadStat", "B-ES:AMX{Det:Mer}mca1.READ"), + ("zoom2FrameRate", "B-ES:AMX{Cam:6}MJPG1:ArrayRate_RBV"), + ("zoom1FrameRate", "B-ES:AMX{Cam:6}MJPG2:ArrayRate_RBV"), + ("zoom3FrameRate", "B-ES:AMX{Cam:7}MJPG2:ArrayRate_RBV"), + ("zoom4FrameRate", "B-ES:AMX{Cam:7}MJPG1:ArrayRate_RBV"), + ("stillMode", "B-ES:AMX{Stills}"), + ("standardMode", "B-ES:AMX{Standard}"), + ("stillModeStatus", "B-ES:AMX{Stills}-Stat"), + ("detCoverRBV", "B-ES:AMX{Det:AMX-Cover}Pos-Sts"), + ("detCoverOpen", "B-ES:AMX{Det:AMX-Cover}Cmd:Opn-Cmd"), + ("annealIn", "B-ES:AMX{Wago:1}AnnealerAir-Sel"), + ("annealStatus", "B-ES:AMX{Wago:1}AnnealerIn-Sts"), + ("warmupThresholdRBV","B-ES:AMX{CS8}WarmupThreshold_RBV"), + ("warmupThreshold", "B-ES:AMX{CS8}WarmupThreshold"), + ("gripperClosed", "B-ES:AMX{Wago:1}BimbaClosed-Sts"), + ("gripperOpen", "B-ES:AMX{Wago:1}BimbaOpen-Sts"), + ("robotForceX", "B-ES:AMX{FTS:1}ForceX-I"), + ("robotForceY", "B-ES:AMX{FTS:1}ForceY-I"), + ("robotForceZ", "B-ES:AMX{FTS:1}ForceZ-I"), + ("robotFTSensorStat", "B-ES:AMX{FTS:1}Status-I"), + ("rebootGovernor", "B-ES:AMX{Gov}Cmd:Kill-Cmd"), + ("eigerIOC_reboot", "B-CT:AMX{IOC:DET02}:SysReset.VAL"), + ("stopEiger", "B-ES:AMX{Det:Eig9M}cam1:Acquire"), + ("eigerStatMessage", "B-ES:AMX{Det:Eig9M}cam1:StatusMessage_RBV"), + ("robotXMountLowLim", "B-ES:AMX{Gov:Robot-St:SA}LLim:gx-Pos"), + ("robotXMountHiLim", "B-ES:AMX{Gov:Robot-St:SA}HLim:gx-Pos"), + ("robotXMountPos", "B-ES:AMX{Gov:Robot-Dev:gx}Pos:Mount-Pos"), + ("robotXWorkPos", "B-ES:AMX{Gov:Robot-Dev:gx}Pos:Work-Pos"), + ("robotYMountPos", "B-ES:AMX{Gov:Robot-Dev:gpy}Pos:Mount-Pos"), + ("robotYWorkPos", "B-ES:AMX{Gov:Robot-Dev:gpy}Pos:Work-Pos"), + ("robotZMountPos", "B-ES:AMX{Gov:Robot-Dev:gpz}Pos:Mount-Pos"), + ("robotZWorkPos", "B-ES:AMX{Gov:Robot-Dev:gpz}Pos:Work-Pos"), + ("robotOmegaWorkPos", "B-ES:AMX{Gov:Robot-Dev:go}Pos:Work-Pos"), + ("gonXHiLim", "B-ES:AMX{Gon:1-Ax:GX}Mtr.HLM"), + ("gonXLoLim", "B-ES:AMX{Gon:1-Ax:GX}Mtr.LLM"), + ("governorMessage", "B-ES:AMX{Gov:Robot}Sts:Msg-Sts"), + ("sampleLifetime", "A-OP:AMX{Mono:DCM-life}"), + ("transmissionRBV", "B-OP:AMX{Attn:BCU}Trans-I"), + ("transmissionSet", "B-OP:AMX{Attn:BCU}Trans-SP"), + ("transmissionGo", "B-OP:AMX{Attn:BCU}Cmd:Set-Cmd.PROC"), + ("transmissionDone", "B-OP:AMX{Attn:BCU}attenDone"), + ("totalCurrentBCU", "B-BI:AMX{BPM:3}SumAll:MeanValue_RBV"), + ("syncPinY", "B-ES:AMX{Gon:1-Ax:PY}Mtr.SYNC"), + ("syncPinZ", "B-ES:AMX{Gon:1-Ax:PZ}Mtr.SYNC"), + ("sampleDetected", "B-ES:AMX{Wago:1}SampleDetected1-Sts"), + ("boostSelect", "B-ES:AMX{Wago:1}Boost-Sel"), + ("boostStatus", "B-ES:AMX{Wago:1}Boost-Sts"), + ("gripTemp", "B-ES:AMX{Wago:1}GripperTemp-I"), + ("homePinY", "B-CT:AMX{SDC:04-Ax:5}StartHome"), + ("homePinZ", "B-CT:AMX{SDC:04-Ax:6}StartHome"), + ("homePins", ":AMX{Sentinel}pin_home"), + ("pinsHomed", "B-ES:AMX{Sentinel}Validate_Sts"), + ("mcaRoiLo", "B-ES:AMX{Det:Mer}mca1.R0LO"), + ("mcaRoiHi", "B-ES:AMX{Det:Mer}mca1.R0HI"), + ("vectorBufferTime", "B-ES:AMX{Gon:1-Vec}Val:BufferTime-SP"), + ("backLightVal", "B-ES:AMX{BL:1}Ch1Value"), + ("govRobotSeReach", "B-ES:AMX{Gov:Robot-St:SE}Sts:Reach-Sts"), + ("govRobotSaReach", "B-ES:AMX{Gov:Robot-St:SA}Sts:Reach-Sts"), + ("govRobotBlReach", "B-ES:AMX{Gov:Robot-St:BL}Sts:Reach-Sts"), + ("govRobotDaReach", "B-ES:AMX{Gov:Robot-St:DA}Sts:Reach-Sts"), + ("govRobotDetDist", "B-ES:AMX{Gov:Robot-Dev:dz}Pos:In-Pos"), + ("govHumanDetDist", "B-ES:AMX{Gov:Human-Dev:dz}Pos:In-Pos"), + ("govRobotDetDistOut","B-ES:AMX{Gov:Robot-Dev:dz}Pos:Out-Pos"), + ("govHumanDetDistOut","B-ES:AMX{Gov:Human-Dev:dz}Pos:Out-Pos"), + ("lowMagAcquire", "B-ES:AMX{Cam:6}cam1:Acquire"), + ("lowMagAcquireTime", "B-ES:AMX{Cam:6}cam1:AcquireTime"), + ("lowMagGain", "B-ES:AMX{Cam:6}cam1:Gain"), + ("lowMagImMode", "B-ES:AMX{Cam:6}cam1:ImageMode"), + ("lowMagTrigMode", "B-ES:AMX{Cam:6}cam1:TriggerMode"), + ("lowMagNumImages", "B-ES:AMX{Cam:6}cam1:NumImages"), + ("lowMagJpegNumImages","B-ES:AMX{Cam:6}JPEG1:NumCapture"), + ("lowMagJpegFileNumber","B-ES:AMX{Cam:6}JPEG1:FileNumber"), + ("lowMagJpegCapture", "B-ES:AMX{Cam:6}JPEG1:Capture"), + ("lowMagJpegFilePath","B-ES:AMX{Cam:6}JPEG1:FilePath"), + ("lowMagJpegFileName","B-ES:AMX{Cam:6}JPEG1:FileName"), + ("lowMagJpegEnableCallbacks","B-ES:AMX{Cam:6}JPEG1:EnableCallbacks"), + ("lowMagDetStateRBV", "B-ES:AMX{Cam:6}cam1:DetectorState_RBV"), + ("topViewAcquire", "B-ES:AMX{Cam:9}cam1:Acquire"), + ("topViewAcquireTime","B-ES:AMX{Cam:9}cam1:AcquireTime"), + ("topViewDataType", "B-ES:AMX{Cam:9}cam1:DataType"), + ("topViewGain", "B-ES:AMX{Cam:9}cam1:Gain"), + ("topViewImMode", "B-ES:AMX{Cam:9}cam1:ImageMode"), + ("topViewTrigMode", "B-ES:AMX{Cam:9}cam1:TriggerMode"), + ("topViewNumImages", "B-ES:AMX{Cam:9}cam1:NumImages"), + ("topViewJpegNumImages","B-ES:AMX{Cam:9}JPEG1:NumCapture"), + ("topViewJpegFileNumber","B-ES:AMX{Cam:9}JPEG1:FileNumber"), + ("topViewJpegCapture","B-ES:AMX{Cam:9}JPEG1:Capture"), + ("topViewJpegFilePath","B-ES:AMX{Cam:9}JPEG1:FilePath"), + ("topViewJpegFileName","B-ES:AMX{Cam:9}JPEG1:FileName"), + ("topViewJpegEnableCallbacks","B-ES:AMX{Cam:9}JPEG1:EnableCallbacks"), + ("topViewWriteFile", "B-ES:AMX{Cam:9}JPEG1:WriteFile"), + ("topViewDetStateRBV","B-ES:AMX{Cam:9}cam1:DetectorState_RBV"), + ("robotGovStatus", "B-ES:AMX{Gov:Robot}Sts:Status-Sts"), + ("humanGovStatus", "B-ES:AMX{Gov:Human}Sts:Status-Sts"), + ("humanGovGo", "B-ES:AMX{Gov:Human}Cmd:Go-Cmd"), + ("robotGovGo", "B-ES:AMX{Gov:Robot}Cmd:Go-Cmd"), + ("humanSeActive", "B-ES:AMX{Gov:Human-St:SE}Sts:Active-Sts"), + ("humanXfActive", "B-ES:AMX{Gov:Human-St:XF}Sts:Active-Sts"), + ("humanBlActive", "B-ES:AMX{Gov:Human-St:BL}Sts:Active-Sts"), + ("humanSaActive", "B-ES:AMX{Gov:Human-St:SA}Sts:Active-Sts"), + ("humanDaActive", "B-ES:AMX{Gov:Human-St:DA}Sts:Active-Sts"), + ("humanMActive", "B-ES:AMX{Gov:Human-St:M}Sts:Active-Sts"), + ("robotSeActive", "B-ES:AMX{Gov:Robot-St:SE}Sts:Active-Sts"), + ("robotXfActive", "B-ES:AMX{Gov:Robot-St:XF}Sts:Active-Sts"), + ("robotBlActive", "B-ES:AMX{Gov:Robot-St:BL}Sts:Active-Sts"), + ("robotSaActive", "B-ES:AMX{Gov:Robot-St:SA}Sts:Active-Sts"), + ("robotDaActive", "B-ES:AMX{Gov:Robot-St:DA}Sts:Active-Sts"), + ("robotDiActive", "B-ES:AMX{Gov:Robot-St:DI}Sts:Active-Sts"), + ("robotMActive", "B-ES:AMX{Gov:Robot-St:M}Sts:Active-Sts"), + ("robotGovActive", "B-ES:AMX{Gov}Active-Sel"), + ("robotGovConfig", "B-ES:AMX{Gov}Config-Sel"), + ("zebraDownloading", "B-ES:AMX{Zeb:2}:ARRAY_ACQ"), + ("zebraDownloadCount","B-ES:AMX{Zeb:2}:PC_NUM_DOWN"), + ("zebraDisarm", "B-ES:AMX{Zeb:2}:PC_DISARM"), + ("zebraEncX", "B-ES:AMX{Zeb:2}:PC_ENC1"), + ("zebraEncY", "B-ES:AMX{Zeb:2}:PC_ENC2"), + ("zebraEncZ", "B-ES:AMX{Zeb:2}:PC_ENC3"), + ("zebraEncOmega", "B-ES:AMX{Zeb:2}:PC_ENC4"), + ("zebraReset", "B-ES:AMX{Zeb:2}:SYS_RESET.PROC"), + ("zebraRebootIOC", "B-CT:AMX{IOC:Zeb2}:SysReset"), + ("zebraSentTriggerStatus","B-ES:AMX{Zeb:2}:AND1_INP1:STA"), + ("zebraArmStatus", "B-ES:AMX{Zeb:2}:PC_ARM_INP:STA"), + ("zebraPulseStatus", "B-ES:AMX{Zeb:2}:PC_PULSE_INP:STA"), + ("zebraTriggerReturnStatus","B-ES:AMX{Zeb:2}:AND1_INP2:STA"), + ("zebraM1SetPosProc", "B-ES:AMX{Zeb:2}:M1:SETPOS.PROC"), + ("zebraM2SetPosProc", "B-ES:AMX{Zeb:2}:M2:SETPOS.PROC"), + ("zebraM3SetPosProc", "B-ES:AMX{Zeb:2}:M3:SETPOS.PROC"), + ("zebraM4SetPosProc", "B-ES:AMX{Zeb:2}:M4:SETPOS.PROC"), + ("zebraPCBITCAP0", "B-ES:AMX{Zeb:2}:PC_BIT_CAP:B0"), + ("zebraPCBITCAP1", "B-ES:AMX{Zeb:2}:PC_BIT_CAP:B1"), + ("zebraPCBITCAP2", "B-ES:AMX{Zeb:2}:PC_BIT_CAP:B2"), + ("zebraPCBITCAP3", "B-ES:AMX{Zeb:2}:PC_BIT_CAP:B3"), + ("zebraEncoder", "B-ES:AMX{Zeb:2}:PC_ENC"), + ("zebraDirection", "B-ES:AMX{Zeb:2}:PC_DIR"), + ("zebraGateSelect", "B-ES:AMX{Zeb:2}:PC_GATE_SEL"), + ("zebraGateStart", "B-ES:AMX{Zeb:2}:PC_GATE_START"), + ("zebraGateWidth", "B-ES:AMX{Zeb:2}:PC_GATE_WID"), + ("zebraGateStep", "B-ES:AMX{Zeb:2}:PC_GATE_STEP"), + ("zebraGateNumGates", "B-ES:AMX{Zeb:2}:PC_GATE_NGATE"), + ("zebraTTlSel", "B-ES:AMX{Zeb:2}:OUT1_TTL"), + ("zebraPulseTriggerSource","B-ES:AMX{Zeb:2}:PC_PULSE_SEL"), + ("zebraPulseStart", "B-ES:AMX{Zeb:2}:PC_PULSE_START"), + ("zebraPulseStep", "B-ES:AMX{Zeb:2}:PC_PULSE_STEP"), + ("zebraPulseDelay", "B-ES:AMX{Zeb:2}:PC_PULSE_DLY"), + ("zebraPulseWidth", "B-ES:AMX{Zeb:2}:PC_PULSE_WID"), + ("zebraPulseMax", "B-ES:AMX{Zeb:2}:PC_PULSE_MAX"), + ("zebraArm", "B-ES:AMX{Zeb:2}:PC_ARM"), + ("zebraArmOut", "B-ES:AMX{Zeb:2}:PC_ARM_OUT"), + ("zebraArmTrigSource","B-ES:AMX{Zeb:2}:PC_ARM_SEL"), + ("vectorHold", "B-ES:AMX{Gon:1-Vec}Hold-Sel"), + ("vectorExpose", "B-ES:AMX{Gon:1-Vec}Expose-Sel"), + ("vectorGo", "B-ES:AMX{Gon:1-Vec}Cmd:Go-Cmd"), + ("vectorProceed", "B-ES:AMX{Gon:1-Vec}Cmd:Proceed-Cmd"), + ("vectorAbort", "B-ES:AMX{Gon:1-Vec}Cmd:Abort-Cmd"), + ("vectorSync", "B-ES:AMX{Gon:1-Vec}Cmd:Sync-Cmd"), + ("photonShutterOpen", "A-PPS:AMX{PSh}Cmd:Opn-Cmd"), + ("photonShutterClose","A-PPS:AMX{PSh}Cmd:Cls-Cmd"), + ("fastShutterOpenPos","B-ES:AMX{Gon:1-Sht}Pos:Opn-I"), + ("fastShutterClosePos","B-ES:AMX{Gon:1-Sht}Pos:Cls-I"), + ("vectorStartX", "B-ES:AMX{Gon:1-Vec}Pos:XStart-SP"), + ("vectorStartY", "B-ES:AMX{Gon:1-Vec}Pos:YStart-SP"), + ("vectorStartZ", "B-ES:AMX{Gon:1-Vec}Pos:ZStart-SP"), + ("vectorEndX", "B-ES:AMX{Gon:1-Vec}Pos:XEnd-SP"), + ("vectorEndY", "B-ES:AMX{Gon:1-Vec}Pos:YEnd-SP"), + ("vectorEndZ", "B-ES:AMX{Gon:1-Vec}Pos:ZEnd-SP"), + ("vectorStartOmega", "B-ES:AMX{Gon:1-Vec}Pos:OStart-SP"), + ("vectorEndOmega", "B-ES:AMX{Gon:1-Vec}Pos:OEnd-SP"), + ("vectorframeExptime","B-ES:AMX{Gon:1-Vec}Val:Exposure-SP"), + ("vectorNumFrames", "B-ES:AMX{Gon:1-Vec}Val:NumSamples-SP"), + ("VectorActive", "B-ES:AMX{Gon:1-Vec}Sts:Running-Sts"), + ("VectorState", "B-ES:AMX{Gon:1-Vec}Sts:State-Sts"), + ("image_X_scalePix", "B-ES:AMX{ClkCtr}image_X_scale.B"), + ("image_X_scaleMM", "B-ES:AMX{ClkCtr}image_X_scale.C"), + ("image_Y_scalePix", "B-ES:AMX{ClkCtr}image_Y_scale.B"), + ("image_Y_scaleMM", "B-ES:AMX{ClkCtr}image_Y_scale.C"), + ("image_X_centerPix", "B-ES:AMX{ClkCtr}image_X_center.A"), + ("image_Y_centerPix", "B-ES:AMX{ClkCtr}image_Y_center.A"), + ("C2C_TargetX", "B-ES:AMX{ClkCtr}image_X_target.A"), + ("C2C_TargetY", "B-ES:AMX{ClkCtr}image_Y_target.A"), + ("C2C_Omega", "B-ES:AMX{ClkCtr}OmegaPos.A"), + ("C2C_Go", "B-ES:AMX{ClkCtr}click_center.PROC"), + ("gonioDone", "B-ES:AMX{ClkCtr}gonioDone"), + ("lowMagCursorX", "-ES:AMX{Misc-AuxCursor2:X}Pos-SP"), + ("lowMagCursorY", "-ES:AMX{Misc-AuxCursor2:Y}Pos-SP"), + ("highMagCursorX", "-ES:AMX{Misc-AuxCursor3:X}Pos-SP"), + ("highMagCursorY", "-ES:AMX{Misc-AuxCursor3:Y}Pos-SP"), + ("beamCenterX", "-ES:AMX{Misc-BeamCenter:X}Pos-I"), + ("beamCenterY", "-ES:AMX{Misc-BeamCenter:Y}Pos-I"), + ("vectorDelay", "-ES:AMX{Misc-Vector:Delay}Pos-SP"), + ("flux", "-ES:AMX{Misc-BeamFlux}Pos-SP"), + ("sampleFlux", "A-OP:AMX{Mono:DCM-dflux-MA}"), + ("lowMagZoomMinXRBV", "B-ES:AMX{Cam:6}ROI1:MinX_RBV"), + ("lowMagZoomMinYRBV", "B-ES:AMX{Cam:6}ROI1:MinY_RBV"), + ("lowMagMinX", "B-ES:AMX{Cam:6}ROI2:MinX"), + ("lowMagMinY", "B-ES:AMX{Cam:6}ROI2:MinY"), + ("lowMagZoomMinX", "B-ES:AMX{Cam:6}ROI1:MinX"), + ("lowMagZoomMinY", "B-ES:AMX{Cam:6}ROI1:MinY"), + ("lowMagZoomROISizeXRBV","B-ES:AMX{Cam:6}ROI1:SizeX_RBV"), + ("lowMagZoomROISizeYRBV","B-ES:AMX{Cam:6}ROI1:SizeY_RBV"), + ("lowMagROISizeXRBV", "B-ES:AMX{Cam:6}ROI2:SizeX_RBV"), + ("lowMagROISizeYRBV", "B-ES:AMX{Cam:6}ROI2:SizeY_RBV"), + ("lowMagZoomMaxSizeXRBV","B-ES:AMX{Cam:6}ROI1:MaxSizeX_RBV"), + ("lowMagZoomMaxSizeYRBV","B-ES:AMX{Cam:6}ROI1:MaxSizeY_RBV"), + ("lowMagMaxSizeXRBV", "B-ES:AMX{Cam:6}ROI2:MaxSizeX_RBV"), + ("lowMagMaxSizeYRBV", "B-ES:AMX{Cam:6}ROI2:MaxSizeY_RBV"), + ("lowMagZoomSizeXRBV","B-ES:AMX{Cam:6}ROI1:ArraySizeX_RBV"), + ("lowMagZoomSizeYRBV","B-ES:AMX{Cam:6}ROI1:ArraySizeY_RBV"), + ("lowMagSizeXRBV", "B-ES:AMX{Cam:6}ROI2:ArraySizeX_RBV"), + ("lowMagSizeYRBV", "B-ES:AMX{Cam:6}ROI2:ArraySizeY_RBV"), + ("lowMagZoomBinXRBV", "B-ES:AMX{Cam:6}ROI1:BinX_RBV"), + ("lowMagZoomBinYRBV", "B-ES:AMX{Cam:6}ROI1:BinY_RBV"), + ("lowMagMinXRBV", "B-ES:AMX{Cam:6}ROI2:MinX_RBV"), + ("lowMagMinYRBV", "B-ES:AMX{Cam:6}ROI2:MinY_RBV"), + ("lowMagBinXRBV", "B-ES:AMX{Cam:6}ROI2:BinX_RBV"), + ("lowMagBinYRBV", "B-ES:AMX{Cam:6}ROI2:BinY_RBV"), + ("highMagZoomMinXRBV","B-ES:AMX{Cam:7}ROI1:MinX_RBV"), + ("highMagZoomMinYRBV","B-ES:AMX{Cam:7}ROI1:MinY_RBV"), + ("highMagMinX", "B-ES:AMX{Cam:7}ROI2:MinX"), + ("highMagMinY", "B-ES:AMX{Cam:7}ROI2:MinY"), + ("highMagZoomMinX", "B-ES:AMX{Cam:7}ROI1:MinX"), + ("highMagZoomMinY", "B-ES:AMX{Cam:7}ROI1:MinY"), + ("highMagZoomROISizeXRBV","B-ES:AMX{Cam:7}ROI1:SizeX_RBV"), + ("highMagZoomROISizeYRBV","B-ES:AMX{Cam:7}ROI1:SizeY_RBV"), + ("highMagROISizeXRBV","B-ES:AMX{Cam:7}ROI2:SizeX_RBV"), + ("highMagROISizeYRBV","B-ES:AMX{Cam:7}ROI2:SizeY_RBV"), + ("highMagZoomMaxSizeXRBV","B-ES:AMX{Cam:7}ROI1:MaxSizeX_RBV"), + ("highMagZoomMaxSizeYRBV","B-ES:AMX{Cam:7}ROI1:MaxSizeY_RBV"), + ("highMagMaxSizeXRBV","B-ES:AMX{Cam:7}ROI2:MaxSizeX_RBV"), + ("highMagMaxSizeYRBV","B-ES:AMX{Cam:7}ROI2:MaxSizeY_RBV"), + ("highMagZoomSizeXRBV","B-ES:AMX{Cam:7}ROI1:ArraySizeX_RBV"), + ("highMagZoomSizeYRBV","B-ES:AMX{Cam:7}ROI1:ArraySizeY_RBV"), + ("highMagSizeXRBV", "B-ES:AMX{Cam:7}ROI2:ArraySizeX_RBV"), + ("highMagSizeYRBV", "B-ES:AMX{Cam:7}ROI2:ArraySizeY_RBV"), + ("highMagZoomBinXRBV","B-ES:AMX{Cam:7}ROI1:BinX_RBV"), + ("highMagZoomBinYRBV","B-ES:AMX{Cam:7}ROI1:BinY_RBV"), + ("highMagMinXRBV", "B-ES:AMX{Cam:7}ROI2:MinX_RBV"), + ("highMagMinYRBV", "B-ES:AMX{Cam:7}ROI2:MinY_RBV"), + ("highMagBinXRBV", "B-ES:AMX{Cam:7}ROI2:BinX_RBV"), + ("highMagBinYRBV", "B-ES:AMX{Cam:7}ROI2:BinY_RBV"), + ("detectorNy", "B-ES:AMX{Det:Eig9M}cam1:ArraySizeY_RBV"), + ("detectorDescription","B-ES:AMX{Det:Eig9M}cam1:Description_RBV"), + ("detectorNx", "B-ES:AMX{Det:Eig9M}cam1:ArraySizeX_RBV"), + ("detectorROIMode", "B-ES:AMX{Det:Eig9M}cam1:ROIMode"), + ("dewarPlatePos", "B-ES:AMX{CS8}PlatePosition"), + ]), +} diff --git a/mxbluesky/devices/sample_environment.py b/mxbluesky/devices/sample_environment.py new file mode 100644 index 00000000..77493e84 --- /dev/null +++ b/mxbluesky/devices/sample_environment.py @@ -0,0 +1,51 @@ +"""SampleEnvironment device — Wago I/O, FTS force sensor, CS8 cryo, +back light, and sample-handling motors. + +All signals share the ``C-ES:FMX`` / ``B-ES:AMX`` ES prefix. + +``sample_protect`` (FMX-only SPSEnable) will be disconnected on AMX. + +Instantiation:: + + env = SampleEnvironment("XF:17IDC-ES:FMX", name="sample_env") # FMX + env = SampleEnvironment("XF:17IDB-ES:AMX", name="sample_env") # AMX +""" + +from ophyd import Component as Cpt, Device, EpicsMotor, EpicsSignal, EpicsSignalRO + + +class SampleEnvironment(Device): + """Wago I/O, FTS force sensor, CS8 cryo controller, back-light, + and sample-handling motors (fast shutter, dewar rotation, cryo XY).""" + + # --- Wago I/O --- (both beamlines unless noted) + anneal_in = Cpt(EpicsSignal, "{Wago:1}AnnealerAir-Sel") # annealIn + anneal_status = Cpt(EpicsSignalRO, "{Wago:1}AnnealerIn-Sts") # annealStatus + gripper_closed = Cpt(EpicsSignalRO, "{Wago:1}BimbaClosed-Sts") # gripperClosed + gripper_open = Cpt(EpicsSignalRO, "{Wago:1}BimbaOpen-Sts") # gripperOpen + sample_detected = Cpt(EpicsSignalRO, "{Wago:1}SampleDetected1-Sts") # sampleDetected + boost_select = Cpt(EpicsSignal, "{Wago:1}Boost-Sel") # boostSelect + boost_status = Cpt(EpicsSignalRO, "{Wago:1}Boost-Sts") # boostStatus + grip_temp = Cpt(EpicsSignalRO, "{Wago:1}GripperTemp-I") # gripTemp + + # --- FTS force/torque sensor --- + robot_force_x = Cpt(EpicsSignalRO, "{FTS:1}ForceX-I") # robotForceX + robot_force_y = Cpt(EpicsSignalRO, "{FTS:1}ForceY-I") # robotForceY + robot_force_z = Cpt(EpicsSignalRO, "{FTS:1}ForceZ-I") # robotForceZ + robot_fts_status = Cpt(EpicsSignalRO, "{FTS:1}Status-I") # robotFTSensorStat + + # --- CS8 cryo controller --- + warmup_threshold_rbv = Cpt(EpicsSignalRO, "{CS8}WarmupThreshold_RBV") # warmupThresholdRBV + warmup_threshold = Cpt(EpicsSignal, "{CS8}WarmupThreshold") # warmupThreshold + dewar_plate_pos = Cpt(EpicsSignal, "{CS8}PlatePosition") # dewarPlatePos + + # --- back-light value --- + back_light_val = Cpt(EpicsSignal, "{BL:1}Ch1Value") # backLightVal + + # --- FMX-only: sample protection interlock --- + sample_protect = Cpt(EpicsSignal, "{SPSEnable}") # sampleProtect + + # --- sample-handling motors (both beamlines) --- + fast_shutter = Cpt(EpicsMotor, "{Sht:1-Ax:R}Mtr") # fastShutter + dewar_rot = Cpt(EpicsMotor, "{Dew:1-Ax:R}Mtr") # dewarRot + cryo_xy = Cpt(EpicsMotor, "{CS:1-Ax:XY}Mtr") # cryoXY diff --git a/mxbluesky/devices/top_align.py b/mxbluesky/devices/top_align.py index a0b8e238..4532cc12 100644 --- a/mxbluesky/devices/top_align.py +++ b/mxbluesky/devices/top_align.py @@ -90,6 +90,8 @@ class TopAlignCam(StandardProsilica): out10_reset = Cpt(EpicsSignal, "Out10:compress.RES") out9_buffer = Cpt(EpicsSignalRO, "Out9:compress") out9_reset = Cpt(EpicsSignal, "Out9:compress.RES") + out8_buffer = Cpt(EpicsSignalRO, "Out8:compress") + out8_reset = Cpt(EpicsSignal, "Out8:compress.RES") def __init__(self, *args, **kwargs): super().__init__( @@ -119,7 +121,7 @@ def _update_stage_sigs(self, *args, **kwargs): ("cv1.inputs.input2", 3), ("cv1.inputs.input3", 1), ("cv1.inputs.input4", 3), - ("cv1.inputs.input5", 13), + ("cv1.inputs.input5", 6), ("cv1.inputs.input6", 1), ("trans1.nd_array_port", "PROC1"), ] @@ -233,7 +235,8 @@ def _configure_device(self, *args, **kwargs): ] ) - self.topcam.read_attrs = ["out9_buffer", "out10_buffer"] + self.topcam.read_attrs = [ "out8_buffer", + "out9_buffer", "out10_buffer"] self.zebra.read_attrs = ["pos_capt.data.enc4"] def _update_stage_sigs(self, *args, **kwargs): diff --git a/mxbluesky/devices/vector_program.py b/mxbluesky/devices/vector_program.py new file mode 100644 index 00000000..9491e0b5 --- /dev/null +++ b/mxbluesky/devices/vector_program.py @@ -0,0 +1,44 @@ +"""VectorProgram device — vector-scan motion controller signals. + +Covers all 19 ``{Gon:1-Vec}`` descriptors from both DB files. + +Instantiate with the full ``{Gon:1-Vec}`` PV base, e.g.:: + + vector = VectorProgram("XF:17IDC-ES:FMX{Gon:1-Vec}", name="vector") # FMX + vector = VectorProgram("XF:17IDB-ES:AMX{Gon:1-Vec}", name="vector") # AMX +""" + +from ophyd import Component as Cpt, Device, EpicsSignal, EpicsSignalRO + + +class VectorProgram(Device): + """Vector-scan motion controller.""" + + # --- timing --- + buffer_time = Cpt(EpicsSignal, "Val:BufferTime-SP") # vectorBufferTime + + # --- control commands --- + hold = Cpt(EpicsSignal, "Hold-Sel") # vectorHold + expose = Cpt(EpicsSignal, "Expose-Sel") # vectorExpose + go = Cpt(EpicsSignal, "Cmd:Go-Cmd") # vectorGo + proceed = Cpt(EpicsSignal, "Cmd:Proceed-Cmd") # vectorProceed + abort = Cpt(EpicsSignal, "Cmd:Abort-Cmd") # vectorAbort + sync = Cpt(EpicsSignal, "Cmd:Sync-Cmd") # vectorSync + + # --- start / end positions --- + start_x = Cpt(EpicsSignal, "Pos:XStart-SP") # vectorStartX + start_y = Cpt(EpicsSignal, "Pos:YStart-SP") # vectorStartY + start_z = Cpt(EpicsSignal, "Pos:ZStart-SP") # vectorStartZ + end_x = Cpt(EpicsSignal, "Pos:XEnd-SP") # vectorEndX + end_y = Cpt(EpicsSignal, "Pos:YEnd-SP") # vectorEndY + end_z = Cpt(EpicsSignal, "Pos:ZEnd-SP") # vectorEndZ + start_omega = Cpt(EpicsSignal, "Pos:OStart-SP") # vectorStartOmega + end_omega = Cpt(EpicsSignal, "Pos:OEnd-SP") # vectorEndOmega + + # --- collection parameters --- + frame_exp_time = Cpt(EpicsSignal, "Val:Exposure-SP") # vectorframeExptime + num_frames = Cpt(EpicsSignal, "Val:NumSamples-SP") # vectorNumFrames + + # --- status readbacks --- + active = Cpt(EpicsSignalRO, "Sts:Running-Sts") # VectorActive + state = Cpt(EpicsSignalRO, "Sts:State-Sts") # VectorState diff --git a/mxbluesky/devices/zebra.py b/mxbluesky/devices/zebra.py index b27677c7..7b80fca9 100644 --- a/mxbluesky/devices/zebra.py +++ b/mxbluesky/devices/zebra.py @@ -517,6 +517,10 @@ class ZebraBase(Device): download_status = Cpt(EpicsSignalRO, 'ARRAY_ACQ') reset = Cpt(EpicsSignal, 'SYS_RESET.PROC') + # AND1 input status signals (zebraSentTriggerStatus / zebraTriggerReturnStatus) + sent_trigger_status = Cpt(EpicsSignalRO, 'AND1_INP1:STA') + trigger_return_status = Cpt(EpicsSignalRO, 'AND1_INP2:STA') + addresses = ZebraAddresses def __init__(self, prefix, *, configuration_attrs=None, read_attrs=None, diff --git a/mxbluesky/plans/__init__.py b/mxbluesky/plans/__init__.py index c9d52bed..e69de29b 100644 --- a/mxbluesky/plans/__init__.py +++ b/mxbluesky/plans/__init__.py @@ -1,2 +0,0 @@ -from .loop_detection import detect_loop -from .top_view import topview_optimized \ No newline at end of file diff --git a/mxbluesky/plans/loop_detection.py b/mxbluesky/plans/loop_detection.py index 8a706381..fadf4053 100644 --- a/mxbluesky/plans/loop_detection.py +++ b/mxbluesky/plans/loop_detection.py @@ -52,7 +52,6 @@ def detect_loop(sample_detection: "Dict[str, float|int]"): loop_detector.filename.set(two_click_low.jpeg.full_file_name.get()) scan_uid = yield from bp.count([loop_detector], 1) - #box_coords_face: "list[int]" = db[scan_uid].table()['loop_detector_box'][1] box_coords_face: "list[int]" = loop_detector.box.get() logger.info("Got loop predictions") if len(box_coords_face) != 4: @@ -91,10 +90,6 @@ def detect_loop(sample_detection: "Dict[str, float|int]"): yield from bps.mv(gonio.o, sample_detection["face_on_omega"]+90) yield from trigger_two_click() - try: - yield from bp.count([two_click_low], 1) - except FailedStatus: - yield from bp.count([two_click_low], 1) loop_detector.get_threshold.set(True) loop_detector.x_start.set(int(box_coords_face[0]-mean_x)) @@ -102,8 +97,8 @@ def detect_loop(sample_detection: "Dict[str, float|int]"): loop_detector.filename.set(two_click_low.jpeg.full_file_name.get()) scan_uid = yield from bp.count([loop_detector], 1) - box_coords_ortho = db[scan_uid].table()['loop_detector_box'][1] - box_coords_ortho_threshold = db[scan_uid].table()['loop_detector_thresholded_box'][1] + box_coords_ortho = loop_detector.box.get() + box_coords_ortho_threshold = loop_detector.thresholded_box.get() mean_y_threshold = (box_coords_ortho_threshold[1] + box_coords_ortho_threshold[3]) / 2 small_box_height_threshold = (box_coords_ortho_threshold[3] - box_coords_ortho_threshold[1]) * 2 * two_click_low.pix_per_um.get() diff --git a/mxbluesky/plans/top_view.py b/mxbluesky/plans/top_view.py index c3aee728..ffffae44 100644 --- a/mxbluesky/plans/top_view.py +++ b/mxbluesky/plans/top_view.py @@ -36,9 +36,10 @@ def cleanup_topcam(): def inner_pseudo_fly_scan(*args, **kwargs): scan_uid = yield from bp.count(*args, **kwargs) - omegas = db[scan_uid].table()[ - top_aligner_fast.zebra.pos_capt.data.enc4.name - ][1] + scan_table = db[scan_uid].table() + omegas = scan_table[ + top_aligner_fast.zebra.pos_capt.data.enc4.name + ][1] d = np.pi / 180 @@ -47,7 +48,7 @@ def inner_pseudo_fly_scan(*args, **kwargs): omegas_rad = np.array(omegas) * d X = np.vstack([np.cos(omegas_rad), np.sin(omegas_rad)]).T - y = np.array(db[scan_uid].table()[top_aligner_fast.topcam.out9_buffer.name][ + y = np.array(scan_table[top_aligner_fast.topcam.out9_buffer.name][ 1 ]).reshape(-1,1) try: @@ -70,7 +71,7 @@ def inner_pseudo_fly_scan(*args, **kwargs): ) # face on calculation - b = db[scan_uid].table()[top_aligner_fast.topcam.out10_buffer.name][1] + b = scan_table[top_aligner_fast.topcam.out10_buffer.name][1] sample = 300 f_splines = interp1d(omegas, b) diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 00000000..193dd5da --- /dev/null +++ b/pixi.lock @@ -0,0 +1,10045 @@ +version: 7 +platforms: +- name: linux-64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/adbc-driver-manager-1.12.0-py313h7033f15_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.16.1-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.14.1-pl5321h039972f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/astropy-base-8.0.1-py313h29aa505_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/asyncpg-0.31.0-py313h54dd161_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/at-spi2-atk-2.38.0-h0630a04_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/at-spi2-core-2.40.3-h0630a04_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-h04ea711_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/awkward-cpp-54-py313hd3cbb54_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.4-hb7a77c6_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.14-h2aa3ae6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.14.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h720e601_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.1-h6ffeea8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.11.0-h38ae05a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.27.3-h6f4d18d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.16.0-h21f4ec5_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.8-h46fcd08_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.7-h720e601_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h720e601_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.40.1-h102d43b_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.833-hc7390e0_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.3-h206d751_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-h71f81a8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.18.0-h74b55db_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.14.0-hf596fc9_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.16.0-h1f05bef_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-3.1.5-hc31b594_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.0-py313hf46b229_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313hc8edb43_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-49.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hac629b4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/epics-base-7.0.9.0-pl5321h2669dad_12.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/epoxy-1.5.10-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fast-histogram-0.14-py313h29aa505_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fastar-0.11.0-py313hcf592b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-8.1.2-gpl_h1bf8424_901.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fisx-1.3.2-py313h08cd8bf_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.2-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.63.0-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.16-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.44.7-h2b0a6b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.3.1-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-ha257d8a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glib-2.88.2-hbe0478d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.88.2-h8094192_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hb7133d2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glslang-16.3.0-h96af755_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py313h86d8783_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/google-crc32c-1.8.0-py313h74173ec_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.15-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphviz-14.1.2-h8b86629_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.5.4-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.26.11-h6d08254_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.26.11-h29cf534_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gtk3-3.24.52-ha5ea40c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h977cf35_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py313h22c32d4_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.1-ha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h654f344_110.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5plugin-7.0.0-py313hd8f4053_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hicolor-icon-theme-0.17-ha770c72_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/httptools-0.8.0-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h54a6638_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2026.6.26-py313ha402da8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/imath-3.2.2-hde8ca8f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/intel-gmmlib-22.10.0-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/intel-media-driver-26.1.6-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py313hc8edb43_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbde042b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/level-zero-1.29.0-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260526.0-cxx17_h7b12aa8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libadbc-driver-postgresql-1.12.0-hedb09cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libadbc-driver-sqlite-1.12.0-hcea63bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-25.0.0-h32b7842_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-25.0.0-h635bf11_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-25.0.0-h53684a4_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-25.0.0-h635bf11_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-25.0.0-h66fbfdd_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libass-0.17.4-h96ad9f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.4.2-h6d93d65_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.78-hd0affe5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp22.1-22.1.8-default_h6c227bf_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.8-default_h9692865_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.21.0-hae6b9f4_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdovi-3.4.0-ha23c83e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libflac-1.5.0-he200343_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.1.0-ha9f2e26_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.1.0-h69a702a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5fbf134_12.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.1.0-h69a702a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.1.0-h79bb938_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.2-h0d30a3d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.1.0-he0feb66_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.7.0-hbc29df5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.7.0-hdbdcf42_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.82.1-h792040b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-14.2.1-h17a8019_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-devel-14.2.1-h17a8019_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h3a9caae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-h174a0a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.11.0-8_h6ae95b6_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.8-hf7376ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopencv-5.0.0-qt6_hd4a7afd_602.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.27.0-h3133023_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.27.0-ha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-2026.2.1-h1f0fae8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-batch-plugin-2026.2.1-h7e124b3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-plugin-2026.2.1-h7e124b3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-hetero-plugin-2026.2.1-hd41364c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-cpu-plugin-2026.2.1-h1f0fae8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-gpu-plugin-2026.2.1-h1f0fae8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-npu-plugin-2026.2.1-h1f0fae8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-ir-frontend-2026.2.1-hd41364c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-onnx-frontend-2026.2.1-h607c73d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-paddle-frontend-2026.2.1-h607c73d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-pytorch-frontend-2026.2.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-frontend-2026.2.1-h21c0c73_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-lite-frontend-2026.2.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopus-1.6.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-25.0.0-h7376487_3_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libplacebo-7.360.1-h9eeb4b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.4-hd5a49e9_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-7.35.1-h2840a7c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpsl-0.22.0-hf670292_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libraqm-0.11.0-h6406941_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/librdkafka-2.15.0-h560e972_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h60473fc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.62.3-h4c96295_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.2-hc7d488a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.1.0-h934c35e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.1.0-hdf11a46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.13-h084b8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-h9d88235_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.13-h084b8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libunwind-1.8.3-h65a8314_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liburing-2.14-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libusb-1.0.29-h73b1eb8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libva-2.24.1-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h54a6638_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvpl-2.16.0-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvpx-1.15.2-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h5279c79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-hca5e8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzopfli-1.0.3-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvmlite-0.48.0-py313h6b4ec16_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.5-py313h28739b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.11.1-py313h134213a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.4.0-he0a73b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.2-he0a73b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.9-hc50e24c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.1-py313hc8edb43_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ndindex-1.10.1-py313h7033f15_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netifaces-0.11.0-py313h07c4f96_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numba-0.66.0-py313h7afe1cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numcodecs-0.16.5-py313h08cd8bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.2-py313h24ae7f9_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py313hf6604e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/obstore-0.11.0-py313h5c7d99a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.4-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/opencv-5.0.0-qt6_h12f39d8_602.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openexr-3.4.13-hf734b31_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openh264-2.6.0-h65dd3cf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjph-0.30.1-h8d634f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openpyxl-3.1.5-py313ha4be090_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.1-h443056b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.9-py313h541fbb8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.5-py313hbfd7664_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pango-1.58.0-hda50119_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py313h80991f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pugixml-1.15-h3f63f65_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-h9a6aba3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/py-opencv-5.0.0-qt6_h6945e2d_602.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-25.0.0-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-25.0.0-py313h98bfbea_0_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycryptodome-3.23.0-py313h6123c0d_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyepics-3.5.10-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyerfa-2.0.1.5-py310h32771cd_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pymca-5.9.6-py313h29aa505_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pymongo-4.17.0-py313h7033f15_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.11-py313h938ae94_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.17.0-py313h7033f15_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.14-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-blosc2-4.7.0-py313h4559b1c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-confluent-kafka-2.15.0-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-duckdb-1.3.2-py313h7033f15_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.15-h0c412b5_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.1-pl5321h16c4a6b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.8.1-h1fbca29_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h94463f1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.7.19-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-2026.6.3-py313hafbe609_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.5-h7e3ee7f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-image-0.26.0-np2py313hb172dc5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.9.0-np2py313h16d504d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py313h4b8bb8b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sdl2-2.32.56-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sdl3-3.4.12-hdeec2a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.5.0-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/shaderc-2026.2-h718be3e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py313had47c43_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sip-6.10.0-py313h7033f15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spirv-tools-2026.2-hb700be7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.51-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-4.0.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.0.0-hab88423_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.7-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ujson-5.13.0-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/uvloop-0.22.1-py313h07c4f96_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-1.2.0-py313hafbe609_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-hd6090a7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/websockets-16.1.1-py313hc25a8b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-2.2.2-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/x264-1!164.3095-h166bdaf_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.48-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxinerama-1.1.6-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxscrnsaver-1.2.4-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxshmfence-1.3.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xorgproto-2025.1-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h909a3a2_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_x86_64-microarch-level-1-4_level1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/adbc-driver-postgresql-1.12.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/adbc-driver-sqlite-1.12.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/aiofiles-25.1.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/aiosqlite-0.22.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/amostra-0.2.4-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/analysisstore-0.0.6-pyhd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-doc-0.0.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/area-detector-handlers-0.0.10-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asgi-correlation-id-5.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/astropy-iers-data-0.2026.7.27.0.56.29-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-timeout-5.0.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/awkward-2.11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-base-1.15.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-kafka-0.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-tiled-plugins-2.0.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.1.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/canonicaljson-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/caproto-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conftrak-0.0.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.14-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2026.7.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2026.7.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/databroker-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/deprecated-1.3.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dill-0.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2026.7.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dnspython-2.8.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/doct-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/donfig-0.8.1.post1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dpkt-1.9.8-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ecdsa-0.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/echo-0.15.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/email-validator-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/et_xmlfile-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/event-model-1.24.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.140.13-h2fa8227_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-cli-0.0.32-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-core-0.140.13-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/glue-core-1.27.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h5netcdf-1.8.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/historydict-1.2.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.16.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imageio-2.37.0-pyhfb79c49_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.7.1-h09eb056_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.15.0-pyh53cf698_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.4.0-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.context-6.1.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.functools-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jmespath-1.1.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json-merge-patch-0.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyha804496_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lazy-loader-0.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ldap3-2.9.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/linkify-it-py-2.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/mako-1.3.12-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/minio-7.2.20-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mongomock-4.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mongoquery-1.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-11.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpl-scatter-density-0.8-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/msgpack-numpy-0.4.8-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/nslsii-0.11.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/opentelemetry-api-1.44.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ophyd-1.11.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parsimonious-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pims-0.7-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/plotext-5.3.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prettytable-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyasn1-0.6.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-extra-types-2.11.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.11.0-pyh3cfb1c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyolog-4.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.14-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-jose-3.5.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.32-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/qt_epics-0.0.4-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/qtpy-2.4.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ragged-0.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/recordwhat-0.4-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/redis-json-dict-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/redis-py-8.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-toolkit-0.20.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rsa-4.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sentinels-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-83.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.4-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/shortuuid-1.0.13-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/slicerator-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sparse-0.19.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stamina-26.1.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.3.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/suitcase-mongo-0.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/super_state_machine-2.0.2-py_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tenacity-9.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/textual-8.2.8-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/textual-plotext-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tiled-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tiled-base-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tiled-client-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tiled-formats-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tiled-server-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhcf101f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typer-0.27.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzlocal-5.4.4-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uc-micro-py-2.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.51.0-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.51.0-h28ae30b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.49-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xlrd-2.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.2.1-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - pypi: git+https://github.com/vshekar/mxtools?branch=add-raster-assets#2d2ae532037cdc41ad21150997d617e63ad15b6d + - pypi: https://files.pythonhosted.org/packages/14/81/54435f2763c475390da7746405c1732819994c2030f1acf3ad38f82276ac/summarytable-0.1.8.tar.gz + - pypi: https://files.pythonhosted.org/packages/ae/ce/7f538891722a4f06921419d55bac6f41729258d54442861edb2de0dbdf5e/opencv_python_headless-4.14.0.94-cp37-abi3-manylinux_2_28_x86_64.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/adbc-driver-manager-1.12.0-py313h7033f15_0.conda + sha256: 605b6a4f81e3f76f29169435c769e899d012864a804320c82aac9aa8df1c798d + md5: 02fd748c55dbca6697071ef1e688d218 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - pyarrow >=8.0.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/adbc-driver-manager?source=hash-mapping + run_exports: {} + size: 427560 + timestamp: 1785224195950 +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.16.1-hb03c661_0.conda + sha256: cf93ca0f1f107e95a35969a4622684e08fcb8cf37f8cf4a1e9e424828386c921 + md5: 8904e09bda369377b3dd07e2ac828c5d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - alsa-lib >=1.2.16.1,<1.3.0a0 + size: 592377 + timestamp: 1781521980743 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.14.1-pl5321h039972f_1.conda + sha256: b1d972a9b949a88babee681437535550b3ca5dbca6a23a40dffeb7900fec19fd + md5: 5a78a69eb3b50f24b379e9d2a93163ae + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - aom >=3.14.1,<3.15.0a0 + size: 3103347 + timestamp: 1780752473089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + sha256: ad188ccc06a06c633dc124b09e9e06fb9df4c32ffc38acc96ecc86e506062090 + md5: 27bbec9f2f3a15d32b60ec5734f5b41c + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.0.1 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + run_exports: {} + size: 35943 + timestamp: 1762509452935 +- conda: https://conda.anaconda.org/conda-forge/linux-64/astropy-base-8.0.1-py313h29aa505_0.conda + sha256: fece23982ec7979150891bd3fabc08084b376ae111c154a566aa8f4134326175 + md5: 226249992c4342eb4fbc7567f9416b14 + depends: + - __glibc >=2.17,<3.0.a0 + - astropy-iers-data >=0.2026.6.22.1.23.34 + - libgcc >=14 + - numpy >=1.25,<3 + - numpy >=2.0 + - packaging >=25.0 + - pyerfa >=2.0.1.3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - pyyaml >=6.0.0 + constrains: + - astropy >=7.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/astropy?source=hash-mapping + run_exports: {} + size: 9992976 + timestamp: 1783447999606 +- conda: https://conda.anaconda.org/conda-forge/linux-64/asyncpg-0.31.0-py313h54dd161_1.conda + sha256: 7bc4874a25ab5e4508db7a885d47716d62e2a0a9d592882f157564bfd8da6182 + md5: c65459a6764b53107358faed99c042a7 + depends: + - python + - async-timeout >=4.0.3 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/asyncpg?source=hash-mapping + run_exports: {} + size: 751821 + timestamp: 1768733524478 +- conda: https://conda.anaconda.org/conda-forge/linux-64/at-spi2-atk-2.38.0-h0630a04_3.tar.bz2 + sha256: 26ab9386e80bf196e51ebe005da77d57decf6d989b4f34d96130560bc133479c + md5: 6b889f174df1e0f816276ae69281af4d + depends: + - at-spi2-core >=2.40.0,<2.41.0a0 + - atk-1.0 >=2.36.0 + - dbus >=1.13.6,<2.0a0 + - libgcc-ng >=9.3.0 + - libglib >=2.68.1,<3.0a0 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - at-spi2-atk >=2.38.0,<3.0a0 + size: 339899 + timestamp: 1619122953439 +- conda: https://conda.anaconda.org/conda-forge/linux-64/at-spi2-core-2.40.3-h0630a04_0.tar.bz2 + sha256: c4f9b66bd94c40d8f1ce1fad2d8b46534bdefda0c86e3337b28f6c25779f258d + md5: 8cb2fc4cd6cc63f1369cfa318f581cc3 + depends: + - dbus >=1.13.6,<2.0a0 + - libgcc-ng >=9.3.0 + - libglib >=2.68.3,<3.0a0 + - xorg-libx11 + - xorg-libxi + - xorg-libxtst + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - at-spi2-core >=2.40.3,<2.41.0a0 + size: 658390 + timestamp: 1625848454791 +- conda: https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-h04ea711_2.conda + sha256: df682395d05050cd1222740a42a551281210726a67447e5258968dd55854302e + md5: f730d54ba9cd543666d7220c9f7ed563 + depends: + - libgcc-ng >=12 + - libglib >=2.80.0,<3.0a0 + - libstdcxx-ng >=12 + constrains: + - atk-1.0 2.38.0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - atk-1.0 >=2.38.0 + size: 355900 + timestamp: 1713896169874 +- conda: https://conda.anaconda.org/conda-forge/linux-64/awkward-cpp-54-py313hd3cbb54_0.conda + sha256: d7e06d978e1e866f3d955a63fca74484c9eeadf26ff4fb2965646fb72990b2a5 + md5: 4c000a329a0de5055f60d7c7ef7e80f9 + depends: + - python + - numpy >=1.21.3 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - _x86_64-microarch-level >=1 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/awkward-cpp?source=hash-mapping + run_exports: {} + size: 659971 + timestamp: 1783197703355 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.10.4-hb7a77c6_1.conda + sha256: 845fe32ee750d4a4d3efdb1d95a8c29c3388e3ea9787b77341ec4abe4e198b11 + md5: 4347d5ffdf34adf9c5edd10837727d96 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-cal >=0.9.14,<0.9.15.0a0 + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-auth >=0.10.4,<0.10.5.0a0 + size: 135073 + timestamp: 1784086842394 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.14-h2aa3ae6_4.conda + sha256: a67c944be1728f576c02fe8f28b0cbb78b5353415075db3da4ef71bc9dd8c00c + md5: 9c6072e7b882d35ac956c07e493d6a9e + depends: + - __glibc >=2.17,<3.0.a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + - libgcc >=14 + - openssl >=3.5.7,<4.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - aws-c-cal >=0.9.14,<0.9.15.0a0 + size: 56752 + timestamp: 1784043396713 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.14.2-hb03c661_0.conda + sha256: 701398f1c9978c41e93cb0947869a66b7f8004e9d6e548d63d11aedae83cfb02 + md5: f3e0b2e044485ae90d4a59c77e7a0182 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - aws-c-common >=0.14.2,<0.14.3.0a0 + size: 246263 + timestamp: 1783637234072 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h720e601_4.conda + sha256: 8e0a5513cfc42df8bd16adbd8e83a37d3ca89b8e04cb20eb04eb177bbbfea365 + md5: c9be3f5854f349ae77a1a174b59e11a8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-compression >=0.3.2,<0.3.3.0a0 + size: 22301 + timestamp: 1784042853709 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.7.1-h6ffeea8_4.conda + sha256: b1f35f7b7a5e745e2d56cf93212770bb56f5207fa9f0e38f98b0c05a1b898a90 + md5: 204d1e8d60c3ae839bd1898e4c7742c8 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-event-stream >=0.7.1,<0.7.2.0a0 + size: 59633 + timestamp: 1784075699558 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.11.0-h38ae05a_4.conda + sha256: 4b939b4a76a11c9ec50c891ae9bf232da8dcaf8797701df1e79ae51c988be2d4 + md5: 97f2799ae6ff7b6f52dfa321fef9676b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-compression >=0.3.2,<0.3.3.0a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-cal >=0.9.14,<0.9.15.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-http >=0.11.0,<0.11.1.0a0 + size: 231294 + timestamp: 1784075685646 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.27.3-h6f4d18d_1.conda + sha256: 94c32ca672ce290e250aea1cfb92582cca4658bdc3ec7cb1ceef254f34451595 + md5: bd19b685b88557830f1a617a6d404eb2 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-cal >=0.9.14,<0.9.15.0a0 + - s2n >=1.7.5,<1.7.6.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-io >=0.27.3,<0.27.4.0a0 + size: 183100 + timestamp: 1784054829913 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.16.0-h21f4ec5_2.conda + sha256: 0252f072e2f493f8348575938c69b48b2799f29f0489c4ad2c5a919ab7e3d02e + md5: 9728195c2f600ef427a1964ee193e707 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-mqtt >=0.16.0,<0.16.1.0a0 + size: 224933 + timestamp: 1784087527918 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.12.8-h46fcd08_1.conda + sha256: a423bffbb0e6cacb5e2a0861b918ba3180e5132509afb1faf225ee9f0ec8f981 + md5: 706aa99414d18db5b23475b45cc93a0b + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - openssl >=3.5.7,<4.0a0 + - aws-c-cal >=0.9.14,<0.9.15.0a0 + - aws-c-auth >=0.10.4,<0.10.5.0a0 + - aws-checksums >=0.2.10,<0.2.11.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-s3 >=0.12.8,<0.12.9.0a0 + size: 156174 + timestamp: 1784100985258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.7-h720e601_2.conda + sha256: e419e179e04021fc680d98af23fac4b4c2369cea61171087e57a734e968dd9bd + md5: 816f62fa82532118ebb2398382090945 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + size: 68515 + timestamp: 1784044260935 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h720e601_4.conda + sha256: 6cc8617854a43040291dea791fe111ba408e7a5bbd9ee9e5a99375da7a16846b + md5: 24ee781effc5779206a80139323c9caf + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-checksums >=0.2.10,<0.2.11.0a0 + size: 101904 + timestamp: 1784044248506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.40.1-h102d43b_3.conda + sha256: e0c3a119c7035a00e0da325187ee723e07e0e6337e50362349d178ab40961f97 + md5: 3315ce09371baf6d70248b8927aa9866 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + - aws-c-mqtt >=0.16.0,<0.16.1.0a0 + - aws-c-auth >=0.10.4,<0.10.5.0a0 + - aws-c-s3 >=0.12.8,<0.12.9.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-event-stream >=0.7.1,<0.7.2.0a0 + - aws-c-cal >=0.9.14,<0.9.15.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-crt-cpp >=0.40.1,<0.40.2.0a0 + size: 416399 + timestamp: 1784113232967 +- conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.833-hc7390e0_9.conda + sha256: 0c15c036c3b71471eecc58bbe08a68c66fd6a051b548dca7675c7c924ed3972b + md5: c65efa87d532339a090c87093097bf09 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - aws-c-event-stream >=0.7.1,<0.7.2.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + - libcurl >=8.21.0,<9.0a0 + - aws-crt-cpp >=0.40.1,<0.40.2.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - aws-sdk-cpp >=1.11.833,<1.11.834.0a0 + size: 3394321 + timestamp: 1784137257627 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.3-h206d751_0.conda + sha256: fffc66e9be8806a92b314e27129c42b1298ea7065028c9e5d175dacb07829261 + md5: 0cabc152dc8896c3a4c4aebf2b308627 + depends: + - __glibc >=2.17,<3.0.a0 + - libcurl >=8.19.0,<9.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + size: 349147 + timestamp: 1775238757304 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-h71f81a8_2.conda + sha256: ebca774f1ebaa24c150730603b418b33fc7862811a16d097716b1a29f34798c5 + md5: f4fc754ec17176046988c46a54962a8c + depends: + - __glibc >=2.17,<3.0.a0 + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - azure-identity-cpp >=1.13.3,<1.13.4.0a0 + size: 250737 + timestamp: 1781268163278 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.18.0-h74b55db_1.conda + sha256: 04bb27dbf1d426b4447b28c3dc92ec01c807fa784eea86ffa1f8c2bd9b9e8076 + md5: 43151a3b0a8e037747d79a82dff9f967 + depends: + - __glibc >=2.17,<3.0.a0 + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + - azure-storage-common-cpp >=12.14.0,<12.14.1.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - azure-storage-blobs-cpp >=12.18.0,<12.18.1.0a0 + size: 589716 + timestamp: 1781283775801 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.14.0-hf596fc9_1.conda + sha256: 8a806f9852d280926d7613df548889b49e2a1c5d836385bcb2cedb24e7b08c64 + md5: 7896f4a6ee78538e2d0261e3b36dfa69 + depends: + - __glibc >=2.17,<3.0.a0 + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - azure-storage-common-cpp >=12.14.0,<12.14.1.0a0 + size: 159394 + timestamp: 1781268171097 +- conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.16.0-h1f05bef_1.conda + sha256: 13e2e6eb942f65bf81e9089bf6b5926534d9245c781288c5270beb3a25c9156b + md5: 70972c9cb0893d6499dd4118415a966b + depends: + - __glibc >=2.17,<3.0.a0 + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + - azure-storage-blobs-cpp >=12.18.0,<12.18.1.0a0 + - azure-storage-common-cpp >=12.14.0,<12.14.1.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - azure-storage-files-datalake-cpp >=12.16.0,<12.16.1.0a0 + size: 308699 + timestamp: 1781538835962 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda + sha256: d7aca2b34335035ef80eaaebff4e5a0ae524b6f3792a82a144d38c4a81f42916 + md5: fbc7d3707f09cae6648e6eb1b6203a5c + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + run_exports: {} + size: 242845 + timestamp: 1781450812380 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d + md5: 2c2fae981fd2afd00812c92ac47d023d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - snappy >=1.2.1,<1.3.0a0 + - zstd >=1.5.6,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - blosc >=1.21.6,<2.0a0 + size: 48427 + timestamp: 1733513201413 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + sha256: e511644d691f05eb12ebe1e971fd6dc3ae55a4df5c253b4e1788b789bdf2dfa6 + md5: 8ccf913aaba749a5496c17629d859ed1 + depends: + - __glibc >=2.17,<3.0.a0 + - brotli-bin 1.2.0 hb03c661_1 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 20103 + timestamp: 1764017231353 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + sha256: 64b137f30b83b1dd61db6c946ae7511657eead59fdf74e84ef0ded219605aa94 + md5: af39b9a8711d4a8d437b52c1d78eb6a1 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 21021 + timestamp: 1764017221344 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + sha256: dadec2879492adede0a9af0191203f9b023f788c18efd45ecac676d424c458ae + md5: 6c4d3597cf43f3439a51b2b13e29a4ba + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + run_exports: {} + size: 367721 + timestamp: 1764017371123 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda + sha256: b4831ac06bb65561342cedf3d219cf9b096f20b8d62cda74f0177dffed79d4d5 + md5: 5948f4fead433c6e5c46444dbfb01162 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - brunsli >=0.1,<1.0a0 + size: 168501 + timestamp: 1761758949420 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hb03c661_0.conda + sha256: dee93a82d045f9aa8277829aa465224afa3c7a6ac18388de66099b2be7f705e7 + md5: 6130ad6705adc993b5d8482b7f66e01f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - c-ares >=1.34.8,<2.0a0 + size: 210929 + timestamp: 1784091008551 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-3.1.5-hc31b594_0.conda + sha256: 6e42591b70252b4e91a3391894c570e45d7f1f21b20937c8f57f16e3cf659e5d + md5: 76ea4a0e8f1600f89cbb2d3d62ac2b04 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - lz4-c >=1.10.0,<1.11.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - c-blosc2 >=3.1.5,<3.2.0a0 + size: 390948 + timestamp: 1782481386840 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + sha256: 06525fa0c4e4f56e771a3b986d0fdf0f0fc5a3270830ee47e127a5105bde1b9a + md5: bb6c4808bfa69d6f7f6b07e5846ced37 + depends: + - __glibc >=2.17,<3.0.a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libstdcxx >=14 + - libxcb >=1.17.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + run_exports: + weak: + - cairo >=1.18.4,<2.0a0 + size: 989514 + timestamp: 1766415934926 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.0-py313hf46b229_0.conda + sha256: 8f8ee27b9f450fb2a2b568356d19c812ec4a511fe6f2042c1f0a04f2fa425fb9 + md5: 575efaa378a66d0f2c6891092267edf7 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + run_exports: {} + size: 304647 + timestamp: 1783424174920 +- conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.4-hecca717_0.conda + sha256: 285bb88f6229a1eb4772ab805fbc61ef786e27b5e9ca0b6434b13d2a728790bb + md5: dc7072d888ba25c06d706d9dd4bd48ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - charls >=2.4.4,<2.5.0a0 + size: 162240 + timestamp: 1780948654621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313hc8edb43_4.conda + sha256: 7f86eb205d2d7fcf2c82654a08c6a240623ac34cb406206b4b1f1afa5cda8e49 + md5: 33639459bc29437315d4bff9ed5bc7a7 + depends: + - numpy >=1.25 + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/contourpy?source=hash-mapping + run_exports: {} + size: 321850 + timestamp: 1769155964333 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-49.0.0-py313heb322e3_0.conda + sha256: c031e3cbf55c8a52740ba6dccd3c43f85585d4f898a3f0ac077ab46a031629d8 + md5: b1ef340bebb63fcf9c52070e8b818c6d + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=2.0 + - libgcc >=14 + - openssl >=3.5.7,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + run_exports: {} + size: 1912624 + timestamp: 1781385646989 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hac629b4_1.conda + sha256: 7684da83306bb69686c0506fb09aa7074e1a55ade50c3a879e4e5df6eebb1009 + md5: af491aae930edc096b58466c51c4126c + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=13 + - libntlm >=1.8,<2.0a0 + - libstdcxx >=13 + - libxcrypt >=4.4.36 + - openssl >=3.5.5,<4.0a0 + license: BSD-3-Clause-Attribution + license_family: BSD + purls: [] + run_exports: + weak: + - cyrus-sasl >=2.1.28,<3.0a0 + size: 210103 + timestamp: 1771943128249 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.1.0-py313h07c4f96_2.conda + sha256: fd33f531288fb08afef72a65414d29caefbba31cb398533534794475af35b1b3 + md5: 7e7e3c5a8a28c6b8eb430183e0554adf + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - toolz >=0.10.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cytoolz?source=hash-mapping + run_exports: {} + size: 620281 + timestamp: 1771855841837 +- conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda + sha256: 22053a5842ca8ee1cf8e1a817138cdb5e647eb2c46979f84153f6ad7bde73020 + md5: 418c6ca5929a611cbd69204907a83995 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - dav1d >=1.2.1,<1.2.2.0a0 + size: 760229 + timestamp: 1685695754230 +- conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + sha256: 8bb557af1b2b7983cf56292336a1a1853f26555d9c6cecf1e5b2b96838c9da87 + md5: ce96f2f470d39bd96ce03945af92e280 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - libglib >=2.86.2,<3.0a0 + - libexpat >=2.7.3,<3.0a0 + license: AFL-2.1 OR GPL-2.0-or-later + purls: [] + run_exports: + weak: + - dbus >=1.16.2,<2.0a0 + size: 447649 + timestamp: 1764536047944 +- conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda + sha256: 40cdd1b048444d3235069d75f9c8e1f286db567f6278a93b4f024e5642cfaecc + md5: dbe3ec0f120af456b3477743ffd99b74 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - double-conversion >=3.4.0,<3.5.0a0 + size: 71809 + timestamp: 1765193127016 +- conda: https://conda.anaconda.org/conda-forge/linux-64/epics-base-7.0.9.0-pl5321h2669dad_12.conda + sha256: c76b6eea3d80b4a0252a823f729aa5b0ce82705e60c342872de8f1f58c6603b8 + md5: 4de64e05e52a48931310201778c04c43 + depends: + - perl + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - readline >=8.3,<9.0a0 + license: EPICS + purls: [] + run_exports: + weak: + - epics-base >=7.0.9.0,<7.0.9.1.0a0 + size: 33506486 + timestamp: 1785221978026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/epoxy-1.5.10-hb03c661_2.conda + sha256: a5b51e491fec22bcc1765f5b2c8fff8a97428e9a5a7ee6730095fb9d091b0747 + md5: 057083b06ccf1c2778344b6dabace38b + depends: + - __glibc >=2.17,<3.0.a0 + - libdrm >=2.4.125,<2.5.0a0 + - libegl >=1.7.0,<2.0a0 + - libegl-devel + - libgcc >=14 + - libgl >=1.7.0,<2.0a0 + - libgl-devel + - libglx >=1.7.0,<2.0a0 + - libglx-devel + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + - xorg-libxxf86vm >=1.1.6,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - epoxy >=1.5.10,<1.6.0a0 + size: 411735 + timestamp: 1758743520805 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fast-histogram-0.14-py313h29aa505_4.conda + sha256: 0d08b7ccc44a403459e9b115668af569efc32f692ce7ce2bad0124cb98d2cf15 + md5: 5dbeafc74ea48bd8a3a738d35f05cdb0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/fast-histogram?source=hash-mapping + run_exports: {} + size: 38227 + timestamp: 1761124421117 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fastar-0.11.0-py313hcf592b0_0.conda + sha256: 8e9513886e30a542d239c37894a834f6738899586ff751c7dcd2bcea06eb6156 + md5: ed1cf0cfc0ae19eb436a897adb60fdab + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/fastar?source=hash-mapping + run_exports: {} + size: 422578 + timestamp: 1776177827225 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-8.1.2-gpl_h1bf8424_901.conda + sha256: 2d56b0d90a1e581e296a41aa0a0443c85f918a94780e779a23005be9128627be + md5: 0c457f1b2384bb0aa984831a79021a66 + depends: + - __glibc >=2.17,<3.0.a0 + - alsa-lib >=1.2.16.1,<1.3.0a0 + - aom >=3.14.1,<3.15.0a0 + - bzip2 >=1.0.8,<2.0a0 + - dav1d >=1.2.1,<1.2.2.0a0 + - fontconfig >=2.18.1,<3.0a0 + - fonts-conda-ecosystem + - gmp >=6.3.0,<7.0a0 + - harfbuzz >=14.2.1 + - lame >=3.100,<3.101.0a0 + - libass >=0.17.4,<0.17.5.0a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - libjxl >=0.11,<0.12.0a0 + - liblzma >=5.8.3,<6.0a0 + - libopenvino >=2026.2.1,<2026.2.2.0a0 + - libopenvino-auto-batch-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-auto-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-hetero-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-cpu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-gpu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-npu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-ir-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-onnx-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-paddle-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-pytorch-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-tensorflow-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-tensorflow-lite-frontend >=2026.2.1,<2026.2.2.0a0 + - libopus >=1.6.1,<2.0a0 + - libplacebo >=7.360.1,<7.361.0a0 + - librsvg >=2.62.3,<3.0a0 + - libstdcxx >=14 + - libva >=2.23.0,<3.0a0 + - libvorbis >=1.3.7,<1.4.0a0 + - libvpl >=2.16.0,<2.17.0a0 + - libvpx >=1.15.2,<1.16.0a0 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libxcb >=1.17.0,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 + - openh264 >=2.6.0,<2.6.1.0a0 + - openssl >=3.5.7,<4.0a0 + - pulseaudio-client >=17.0,<17.1.0a0 + - sdl2 >=2.32.56,<3.0a0 + - shaderc >=2026.2,<2026.3.0a0 + - svt-av1 >=4.0.1,<4.0.2.0a0 + - x264 >=1!164.3095,<1!165 + - x265 >=3.5,<3.6.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + constrains: + - __cuda >=12.8 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - ffmpeg >=8.1.2,<9.0a0 + size: 13078770 + timestamp: 1782260267617 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fisx-1.3.2-py313h08cd8bf_2.conda + sha256: 01e68eb979ac04b9e6be0bdc62212c28c8603963852213cd442ec61d09aa3398 + md5: 1551134da6e7dabbf80bcbf7a3f5601e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/fisx?source=hash-mapping + run_exports: {} + size: 4027488 + timestamp: 1756710521487 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.2-h27c8c51_0.conda + sha256: d77a47bc2b340b997c680241751e581672d1d0377a680eaf0b19026f013f56b7 + md5: 3c702747058a5d0af93fe71e559327f3 + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libuuid >=2.42.2,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fontconfig >=2.18.2,<3.0a0 + - fonts-conda-ecosystem + size: 292684 + timestamp: 1784754430789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.63.0-py313h3dea7bd_0.conda + sha256: e0029a390d7aef29bd6e7c12a3759f5e0b989930b5781e544ca9bac0abcd8442 + md5: ae83c999b4cfc4c171ce88b99c8b43cc + depends: + - __glibc >=2.17,<3.0.a0 + - brotli + - libgcc >=14 + - munkres + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/fonttools?source=hash-mapping + run_exports: {} + size: 2995315 + timestamp: 1778770432258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + sha256: c934c385889c7836f034039b43b05ccfa98f53c900db03d8411189892ced090b + md5: 8462b5322567212beeb025f3519fb3e2 + depends: + - libfreetype 2.14.3 ha770c72_0 + - libfreetype6 2.14.3 h73754d4_0 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: + weak: + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + size: 173839 + timestamp: 1774298173462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.16-hb03c661_0.conda + sha256: 858283ff33d4c033f4971bf440cebff217d5552a5222ba994c49be990dacd40d + md5: f9f81ea472684d75b9dd8d0b328cf655 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - fribidi >=1.0.16,<2.0a0 + size: 61244 + timestamp: 1757438574066 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.44.7-h2b0a6b4_0.conda + sha256: 1c22e37f9d7e06e9e0582ee5a55c2ddd19ea75f71f44eb13b56f504ef5c37aa5 + md5: 5d355db3e937086e22cf4cb5fe19787c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libglib >=2.88.2,<3.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - gdk-pixbuf >=2.44.7,<3.0a0 + size: 581631 + timestamp: 1782591374199 +- conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda + sha256: 08896dcd94e14a83f247e91748444e610f344ab42d80cbf2b6082b481c3f8f4b + md5: 4d4efd0645cd556fab54617c4ad477ef + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - geos >=3.14.1,<3.14.2.0a0 + size: 1974942 + timestamp: 1761593471198 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.3.1-h54a6638_0.conda + sha256: f7026323ac769ef432c747a6c22abd39c583d0d4f6985a7100fb371e2167f214 + md5: 04be12e7dd050aaf364ece2393988e85 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - gflags >=2.3.1,<2.4.0a0 + size: 130991 + timestamp: 1785044715896 +- conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-ha257d8a_1.conda + sha256: 3611c5d605cd56017704e6c01c7bd849e74a656a546d4c49d655fde49ddfeecc + md5: bd7c3a8586ed0101f094962100d30a63 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - giflib >=5.2.2,<5.3.0a0 + size: 77403 + timestamp: 1784694210187 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glib-2.88.2-hbe0478d_0.conda + sha256: 84448dc33e66a2c74c58fb2a95d9e84547bc65cf44dcebf447b8d236c0ccc68b + md5: 7ac6295be13fafd7f20869b30b47ec2f + depends: + - python * + - packaging + - libglib ==2.88.2 h0d30a3d_0 + - glib-tools ==2.88.2 h8094192_0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libglib >=2.88.2,<3.0a0 + size: 85433 + timestamp: 1782463895250 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.88.2-h8094192_0.conda + sha256: 079757e4e0497d67505b52062668e4cc0d2a86ec065ae2c0c57487a178206061 + md5: cfe66c21ae651b84a3d25a1dbb641a54 + depends: + - libglib ==2.88.2 h0d30a3d_0 + - libffi + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: {} + size: 238517 + timestamp: 1782463895250 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hb7133d2_1.conda + sha256: f24feeda3aeec3a2cbb9fe2e0fabc4785372e70b6cc838c96023e08e17f4bfa0 + md5: 6941f96b8a8056677d79b4f5a2c4aaca + depends: + - __glibc >=2.17,<3.0.a0 + - gflags >=2.3.0,<2.4.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - glog >=0.7.1,<0.8.0a0 + size: 149031 + timestamp: 1784094370091 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glslang-16.3.0-h96af755_0.conda + sha256: 3c9b6a90937a96ad27d160304cdbe5e9961db613aba2b84ff673429f0c61d48e + md5: d175cb2c14104728ada04883786a309d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - spirv-tools >=2026,<2027.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - glslang >=16,<17.0a0 + size: 1366082 + timestamp: 1777747028121 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c + md5: c94a5994ef49749880a8139cf9afcbe1 + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: GPL-2.0-or-later OR LGPL-3.0-or-later + purls: [] + run_exports: + weak: + - gmp >=6.3.0,<7.0a0 + size: 460055 + timestamp: 1718980856608 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.0-py313h86d8783_1.conda + sha256: 911dcf5c1c810b92ae4811505af46672a1b52718bb1c1d1f200d694562362294 + md5: 047e3ea395eab013f918b338fecc19a0 + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=14 + - mpc >=1.3.1,<2.0a0 + - mpfr >=4.2.1,<5.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/gmpy2?source=hash-mapping + run_exports: {} + size: 255065 + timestamp: 1773245107465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/google-crc32c-1.8.0-py313h74173ec_1.conda + sha256: c0e39ebce8b9d8bc4e734bb5d2538e60f7c8c85ec04f9b0690fc6e1cb9656869 + md5: d358850e37a98739224fdc265d7d8eb7 + depends: + - __glibc >=2.17,<3.0.a0 + - libcrc32c >=1.1.2,<1.2.0a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/google-crc32c?source=hash-mapping + run_exports: {} + size: 25326 + timestamp: 1768549200259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.15-hecca717_0.conda + sha256: 885fa7d1d7e2ad9ed0a700ee0d81ceb49de278253082d517959b22d6336eecce + md5: cf09e9fc938518e91d0706572cadf17a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - graphite2 >=1.3.15,<2.0a0 + size: 100054 + timestamp: 1780454302233 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphviz-14.1.2-h8b86629_0.conda + sha256: 48d4aae8d2f7dd038b8c2b6a1b68b7bca13fa6b374b78c09fcc0757fa21234a1 + md5: 341fc61cfe8efa5c72d24db56c776f44 + depends: + - __glibc >=2.17,<3.0.a0 + - adwaita-icon-theme + - cairo >=1.18.4,<2.0a0 + - fonts-conda-ecosystem + - gdk-pixbuf >=2.44.4,<3.0a0 + - gtk3 >=3.24.43,<4.0a0 + - gts >=0.7.6,<0.8.0a0 + - libexpat >=2.7.3,<3.0a0 + - libgcc >=14 + - libgd >=2.3.3,<2.4.0a0 + - libglib >=2.86.3,<3.0a0 + - librsvg >=2.60.0,<3.0a0 + - libstdcxx >=14 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pango >=1.56.4,<2.0a0 + license: EPL-1.0 + license_family: Other + purls: [] + run_exports: + weak: + - graphviz >=14.1.2,<15.0a0 + size: 2426455 + timestamp: 1769427102743 +- conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.5.4-py313h5d5ffb9_0.conda + sha256: 9ad9df6806ac5688eabb1a194c0ed7fcc9452509be857aa9c60c34d4bad9d318 + md5: 749fc819c01069f94201789e40b0116a + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/greenlet?source=hash-mapping + run_exports: {} + size: 276100 + timestamp: 1784885443049 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.26.11-h6d08254_0.conda + sha256: 5a227ac457b9cc7a70b14008df1f91fd5dd2b3fda9641e5445c950b06d04be9e + md5: 971da16e7fc43161329213557688d315 + depends: + - gstreamer ==1.26.11 h29cf534_0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - xorg-libxau >=1.0.12,<2.0a0 + - libogg >=1.3.5,<1.4.0a0 + - xorg-libxshmfence >=1.3.3,<2.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - pango >=1.56.4,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + - libxcb >=1.17.0,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + - libopus >=1.6.1,<2.0a0 + - libglib >=2.86.4,<3.0a0 + - libegl >=1.7.0,<2.0a0 + - alsa-lib >=1.2.15.3,<1.3.0a0 + - libgl >=1.7.0,<2.0a0 + - libvorbis >=1.3.7,<1.4.0a0 + - libpng >=1.6.57,<1.7.0a0 + - libdrm >=2.4.125,<2.5.0a0 + - gstreamer >=1.26.11,<1.27.0a0 + - libzlib >=1.3.2,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - gst-plugins-base >=1.26.11,<1.27.0a0 + size: 3199241 + timestamp: 1776268376145 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.26.11-h29cf534_0.conda + sha256: 6b9f6c7de3207b7d3a76741713587dd9f7fe2f00720f9ba047632f0900cfa5e5 + md5: 1e0e854b77451ac918b4a68f28932b1d + depends: + - glib >=2.86.4,<3.0a0 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + - libglib >=2.86.4,<3.0a0 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - gstreamer >=1.26.11,<1.27.0a0 + size: 2281638 + timestamp: 1776268376145 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gtk3-3.24.52-ha5ea40c_0.conda + sha256: c6bb4f06331bcb0a566d84e0f0fad7af4b9035a03b13e2d5ecfaf13be57e6e10 + md5: bcaea22d85999a4f17918acfab877e61 + depends: + - __glibc >=2.17,<3.0.a0 + - at-spi2-atk >=2.38.0,<3.0a0 + - atk-1.0 >=2.38.0 + - cairo >=1.18.4,<2.0a0 + - epoxy >=1.5.10,<1.6.0a0 + - fontconfig >=2.17.1,<3.0a0 + - fonts-conda-ecosystem + - fribidi >=1.0.16,<2.0a0 + - gdk-pixbuf >=2.44.5,<3.0a0 + - glib-tools + - harfbuzz >=13.2.1 + - hicolor-icon-theme + - libcups >=2.3.3,<2.4.0a0 + - libcups >=2.3.3,<3.0a0 + - libexpat >=2.7.4,<3.0a0 + - libfreetype >=2.14.2 + - libfreetype6 >=2.14.2 + - libgcc >=14 + - libglib >=2.86.4,<3.0a0 + - liblzma >=5.8.2,<6.0a0 + - libxkbcommon >=1.13.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - pango >=1.56.4,<2.0a0 + - wayland >=1.25.0,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxcomposite >=0.4.7,<1.0a0 + - xorg-libxcursor >=1.2.3,<2.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + - xorg-libxi >=1.8.2,<2.0a0 + - xorg-libxinerama >=1.1.6,<1.2.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - gtk3 >=3.24.52,<4.0a0 + - adwaita-icon-theme + size: 5939083 + timestamp: 1774288645605 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h977cf35_4.conda + sha256: b5cd16262fefb836f69dc26d879b6508d29f8a5c5948a966c47fe99e2e19c99b + md5: 4d8df0b0db060d33c9a702ada998a8fe + depends: + - libgcc-ng >=12 + - libglib >=2.76.3,<3.0a0 + - libstdcxx-ng >=12 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - gts >=0.7.6,<0.8.0a0 + size: 318312 + timestamp: 1686545244763 +- conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.16.0-nompi_py313h22c32d4_102.conda + sha256: 3def6d562885610497befa9dd81c685c53bb027309407797ad9918d4179dda21 + md5: 37727e0bcda5059c33c83fdf5b13a9a1 + depends: + - __glibc >=2.17,<3.0.a0 + - cached-property + - hdf5 >=2.1.0,<3.0a0 + - libgcc >=14 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/h5py?source=hash-mapping + run_exports: {} + size: 1336531 + timestamp: 1775581276561 +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.1-ha770c72_1.conda + sha256: 853beec89ff91cbbf630f1d305b69153eb28a3cb78af95ddc1fb4d30e524c6f4 + md5: 72e956a71241633d8c80aa198fd08784 + depends: + - libharfbuzz-devel 14.2.1 h17a8019_1 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libharfbuzz >=14.2.1 + size: 11039 + timestamp: 1782800611635 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-2.1.0-nompi_h654f344_110.conda + sha256: 548411cb10baf1122c46cfef555936c385137d3637b75bd322e7574eda933842 + md5: 58484580a0f626dbe7d5145f014dbfd4 + depends: + - __glibc >=2.17,<3.0.a0 + - aws-c-auth >=0.10.4,<0.10.5.0a0 + - aws-c-common >=0.14.2,<0.14.3.0a0 + - aws-c-http >=0.11.0,<0.11.1.0a0 + - aws-c-io >=0.27.3,<0.27.4.0a0 + - aws-c-s3 >=0.12.8,<0.12.9.0a0 + - aws-c-sdkutils >=0.2.7,<0.2.8.0a0 + - libaec >=1.1.5,<2.0a0 + - libcurl >=8.21.0,<9.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - hdf5 >=2.1.0,<3.0a0 + size: 4128283 + timestamp: 1784118352322 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5plugin-7.0.0-py313hd8f4053_0.conda + sha256: 15dc7c9e5be94199b48b12ae5b6a1f0f6df01503bbbf5539fdf6dfa18617b124 + md5: 8c877abea2ce7fcad23f733ec61bb24b + depends: + - __glibc >=2.17,<3.0.a0 + - blosc >=1.21.6,<2.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=3.1.4,<3.2.0a0 + - h5py >=3.0.0 + - hdf5 >=2.1.0,<3.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - packaging + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hdf5plugin?source=hash-mapping + run_exports: {} + size: 3418830 + timestamp: 1782464876055 +- conda: https://conda.anaconda.org/conda-forge/linux-64/hicolor-icon-theme-0.17-ha770c72_3.conda + sha256: 6d7e6e1286cb521059fe69696705100a03b006efb914ffe82a2ae97ecbae66b7 + md5: 129e404c5b001f3ef5581316971e3ea0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: {} + size: 17625 + timestamp: 1771539597968 +- conda: https://conda.anaconda.org/conda-forge/linux-64/httptools-0.8.0-py313h07c4f96_0.conda + sha256: 5549f2956daa49d7c2023917b54510b70e3c246571c306f49fbc88257d01b343 + md5: 4ac47c40548b80590101de0655a29b80 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/httptools?source=hash-mapping + run_exports: {} + size: 100123 + timestamp: 1779757515480 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h54a6638_2.conda + sha256: d7c260b7e1cf22ce04d6ba8a86eabf4e6c50bc96a5c27fe2ecb32298af3e88eb + md5: 4ef4b977bb216a3001a3334696a80850 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14455340 + timestamp: 1784916378180 +- conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2026.6.26-py313ha402da8_0.conda + sha256: 1adb54c4070c56c9617cea54e47291c207f78322486c15769271dbdb795df43b + md5: b5714553e1a8ad0fc2b974312f203fcb + depends: + - __glibc >=2.17,<3.0.a0 + - blosc >=1.21.6,<2.0a0 + - brunsli >=0.1,<1.0a0 + - bzip2 >=1.0.8,<2.0a0 + - c-blosc2 >=3.1.5,<3.2.0a0 + - charls >=2.4.4,<2.5.0a0 + - giflib >=5.2.2,<5.3.0a0 + - jxrlib >=1.1,<1.2.0a0 + - lcms2 >=2.19.1,<3.0a0 + - lerc >=4.1.0,<5.0a0 + - libaec >=1.1.5,<2.0a0 + - libavif16 >=1.4.2,<2.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libjxl >=0.11,<0.12.0a0 + - liblzma >=5.8.3,<6.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - libzopfli >=1.0.3,<1.1.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - numpy >=1.25,<3 + - openjpeg >=2.5.4,<3.0a0 + - openjph >=0.30.1,<0.31.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - snappy >=1.2.2,<1.3.0a0 + - zfp >=1.0.1,<2.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/imagecodecs?source=hash-mapping + run_exports: {} + size: 2356995 + timestamp: 1782686071434 +- conda: https://conda.anaconda.org/conda-forge/linux-64/imath-3.2.2-hde8ca8f_0.conda + sha256: 43f30e6fd8cbe1fef59da760d1847c9ceff3fb69ceee7fd4a34538b0927959dd + md5: c427448c6f3972c76e8a4474e0fe367b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - imath >=3.2.2,<3.2.3.0a0 + size: 160289 + timestamp: 1759983212466 +- conda: https://conda.anaconda.org/conda-forge/linux-64/intel-gmmlib-22.10.0-hb700be7_0.conda + sha256: bc231d69eb6663db0e09738fb916c5e5507147cf1ac60f364f964004e0b29bab + md5: 10909406c1b0e4b57f9f4f0eb0999af8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - intel-gmmlib >=22.10.0,<23.0a0 + size: 1013714 + timestamp: 1774422680665 +- conda: https://conda.anaconda.org/conda-forge/linux-64/intel-media-driver-26.1.6-hecca717_0.conda + sha256: 7cbd7fda22db70c64af64c9173434a4ede58e4f220bda52a044e469aa94c65cb + md5: aaf7c3db8c7c4533deb5449d3ba1c51f + depends: + - __glibc >=2.17,<3.0.a0 + - intel-gmmlib >=22.10.0,<23.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libva >=2.23.0,<3.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - intel-media-driver >=26.1.6,<26.2.0a0 + size: 8782375 + timestamp: 1776080148587 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda + sha256: 2057ca87b313bde5b74b93b0e696f8faab69acd4cb0edebb78469f3f388040c0 + md5: 5aeabe88534ea4169d4c49998f293d6c + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - jxrlib >=1.1,<1.2.0a0 + size: 239104 + timestamp: 1703333860145 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - keyutils >=1.6.3,<2.0a0 + size: 134088 + timestamp: 1754905959823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py313hc8edb43_0.conda + sha256: 0447d2901639f295989c5ccba7b1c367ed78b216e0d2705327a8c8a87a31177e + md5: b81883b9dbf5069821c2fb09a8ba1407 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/kiwisolver?source=hash-mapping + run_exports: {} + size: 76911 + timestamp: 1773067054809 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbde042b_1.conda + sha256: 9b07046870772f28740e3f6149f09ff222843733087a33c5540b169c6289652d + md5: 54157a1c8c0bb70f62dd0b17fba7e7f2 + depends: + - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1388990 + timestamp: 1781859420533 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 + sha256: aad2a703b9d7b038c0f745b853c6bb5f122988fe1a7a096e0e606d9cbec4eaab + md5: a8832b479f93521a9e7b5b743803be51 + depends: + - libgcc-ng >=12 + license: LGPL-2.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - lame >=3.100,<3.101.0a0 + size: 508258 + timestamp: 1664996250081 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda + sha256: 112b5b9462572d970f4abd2912f76a25ee7db158b1e7260163d91dd8a630db84 + md5: 8b3ce45e929cd8e8e5f4d18586b56d8b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - lcms2 >=2.19.1,<3.0a0 + size: 251971 + timestamp: 1780211695895 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 449500f2c089da11c40f5c21312e3e07 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.46.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 745303 + timestamp: 1784214507189 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + sha256: bf9fdebf55d8bc99d83531cdffda00703f4dc5f93a1a956768c147362c72feda + md5: fb9d356b1a57d6d54768be7ebd5fce09 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - lerc >=4.2.0,<5.0a0 + size: 271158 + timestamp: 1785036167977 +- conda: https://conda.anaconda.org/conda-forge/linux-64/level-zero-1.29.0-hb700be7_0.conda + sha256: d87cfc5eaa08eefff97d891ecb49faa958fcfc32a425767796269c4100d4e516 + md5: f3c3bc77c96af553f761af0e78bc8d9d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 875773 + timestamp: 1780142086148 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260526.0-cxx17_h7b12aa8_1.conda + sha256: 32933de2d4fa6e6ffd949052815b49cb65a0649ad70007155c533ab97ea8cefd + md5: c4393db381bffa0a83a8d9e47b238106 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + constrains: + - abseil-cpp =20260526.0 + - libabseil-static =20260526.0=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libabseil >=20260526.0,<20260527.0a0 + - libabseil =*=cxx17* + size: 1437712 + timestamp: 1780524559298 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libadbc-driver-postgresql-1.12.0-hedb09cf_0.conda + sha256: 5ab0c8cde10407bf362d5c70272fa7141b1a5934f976b46f72e60d13d2a9d6a1 + md5: de1e94a7c54b81f2fc61a9548f325d1c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpq >=18.4,<19.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libadbc-driver-postgresql >=1.12.0,<1.13.0a0 + size: 283452 + timestamp: 1785224456508 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libadbc-driver-sqlite-1.12.0-hcea63bf_0.conda + sha256: c0b70bf630a495b66eb7819c18c5611f81750bd6a9c037b9f4e6e3c69eee6ff5 + md5: ddcd056ab2a63ed1a3a6d0fd924b2b58 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libsqlite >=3.53.4,<4.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libadbc-driver-sqlite >=1.12.0,<1.13.0a0 + size: 191823 + timestamp: 1785224476365 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + sha256: 822e4ae421a7e9c04e841323526321185f6659222325e1a9aedec811c686e688 + md5: 86f7414544ae606282352fa1e116b41f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libaec >=1.1.5,<2.0a0 + size: 36544 + timestamp: 1769221884824 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-25.0.0-h32b7842_3_cpu.conda + build_number: 3 + sha256: f73ca07a5fbb52238b2f043ea035ef335db143a6a6ca0a16653809cc6b2fbc1f + md5: afdfa6620932b96e4b74fd2e9c453e6d + depends: + - __glibc >=2.17,<3.0.a0 + - aws-crt-cpp >=0.40.1,<0.40.2.0a0 + - aws-sdk-cpp >=1.11.833,<1.11.834.0a0 + - azure-core-cpp >=1.16.3,<1.16.4.0a0 + - azure-identity-cpp >=1.13.3,<1.13.4.0a0 + - azure-storage-blobs-cpp >=12.18.0,<12.18.1.0a0 + - azure-storage-files-datalake-cpp >=12.16.0,<12.16.1.0a0 + - bzip2 >=1.0.8,<2.0a0 + - glog >=0.7.1,<0.8.0a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libgcc >=14 + - libgoogle-cloud >=3.7.0,<3.8.0a0 + - libgoogle-cloud-storage >=3.7.0,<3.8.0a0 + - libopentelemetry-cpp >=1.27.0,<1.28.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - orc >=2.3.1,<2.3.2.0a0 + - snappy >=1.2.2,<1.3.0a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - arrow-cpp <0.0a0 + - apache-arrow-proc =*=cpu + - parquet-cpp <0.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libarrow >=25.0.0,<25.1.0a0 + size: 6600089 + timestamp: 1784539587494 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-25.0.0-h635bf11_3_cpu.conda + build_number: 3 + sha256: 06daf25ebcd77c823a30f7708c4dc9f6b04168d3701732e4f388610bda5ab390 + md5: eb368348b7519180f55e5b64c24bd6e8 + depends: + - __glibc >=2.17,<3.0.a0 + - libarrow 25.0.0 h32b7842_3_cpu + - libarrow-compute 25.0.0 h53684a4_3_cpu + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libarrow-acero >=25.0.0,<25.1.0a0 + size: 590792 + timestamp: 1784539825135 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-compute-25.0.0-h53684a4_3_cpu.conda + build_number: 3 + sha256: fb6578afbc4cf65138ece29f1e06724bb0622219542dd99d52bc3ab7f3ad9948 + md5: 41af8eb51e72db32521ba04d31b7ed94 + depends: + - __glibc >=2.17,<3.0.a0 + - libarrow 25.0.0 h32b7842_3_cpu + - libgcc >=14 + - libre2-11 >=2025.11.5 + - libstdcxx >=14 + - libutf8proc >=2.11.3,<2.12.0a0 + - re2 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libarrow-compute >=25.0.0,<25.1.0a0 + size: 2977184 + timestamp: 1784539709946 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-25.0.0-h635bf11_3_cpu.conda + build_number: 3 + sha256: 7b0a8ad36ef30346fd2fa61936e4264d65a08b31b1417584f5e58becc81c378d + md5: 90acc7f6b9edace63fa91278694f7344 + depends: + - __glibc >=2.17,<3.0.a0 + - libarrow 25.0.0 h32b7842_3_cpu + - libarrow-acero 25.0.0 h635bf11_3_cpu + - libarrow-compute 25.0.0 h53684a4_3_cpu + - libgcc >=14 + - libparquet 25.0.0 h7376487_3_cpu + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libarrow-dataset >=25.0.0,<25.1.0a0 + size: 591733 + timestamp: 1784539903988 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-25.0.0-h66fbfdd_3_cpu.conda + build_number: 3 + sha256: 9bcf81c7b47c74b8f7ae4530bb5fe4a181a308d89730f58db3728f500cbdeb53 + md5: 56854c4b996dd276be1fe68310a6370f + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libarrow 25.0.0 h32b7842_3_cpu + - libarrow-acero 25.0.0 h635bf11_3_cpu + - libarrow-dataset 25.0.0 h635bf11_3_cpu + - libgcc >=14 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libarrow-substrait >=25.0.0,<25.1.0a0 + size: 500398 + timestamp: 1784539930465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libass-0.17.4-h96ad9f0_0.conda + sha256: 035eb8b54e03e72e42ef707420f9979c7427776ea99e0f1e3c969f92eb573f19 + md5: d3be7b2870bf7aff45b12ea53165babd + depends: + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + - libfreetype >=2.13.3 + - libfreetype6 >=2.13.3 + - fribidi >=1.0.10,<2.0a0 + - libiconv >=1.18,<2.0a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - harfbuzz >=11.0.1 + license: ISC + purls: [] + run_exports: + weak: + - libass >=0.17.4,<0.17.5.0a0 + size: 152179 + timestamp: 1749328931930 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.4.2-h6d93d65_2.conda + sha256: dcc3af160ac1340f21dc46204135b8a482f81af209f392040f0351b11e14ccb0 + md5: 1893869da150ecb924d769c3733e2a9b + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - dav1d >=1.2.1,<1.2.2.0a0 + - svt-av1 >=4.0.1,<4.0.2.0a0 + - rav1e >=0.8.1,<0.9.0a0 + - aom >=3.14.1,<3.15.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libavif16 >=1.4.2,<2.0a0 + size: 165185 + timestamp: 1782983774262 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + build_number: 8 + sha256: b2da6bfd72a1c9cb143ccf64bf5b28790cb4eb58bd1cb978f6537b2322f7d48b + md5: 00fc660ab1b2f5ca07e92b4900d10c79 + depends: + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 + constrains: + - blas 2.308 openblas + - mkl <2027 + - libcblas 3.11.0 8*_openblas + - liblapack 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 18804 + timestamp: 1779859100675 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlienc >=1.2.0,<1.3.0a0 + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.78-hd0affe5_0.conda + sha256: cc8c9fc6ddf0fbd3d1275b558ae9abad6cda23bced268732e2da21a87bb358cd + md5: f9f17eab7f3df1c6fd4b1a548a2f683a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcap >=2.78,<2.79.0a0 + size: 124335 + timestamp: 1775488792584 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + build_number: 8 + sha256: 1a2bc77bb26520255904a3d9b1f40e6bf0bf9d8d3405c7709dd162282820915a + md5: 33a413f1095f8325e5c30fde3b0d2445 + depends: + - libblas 3.11.0 8_h4a7cf45_openblas + constrains: + - blas 2.308 openblas + - liblapacke 3.11.0 8*_openblas + - liblapack 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18778 + timestamp: 1779859107964 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp22.1-22.1.8-default_h6c227bf_3.conda + sha256: ccb8bd0a8f2d57675b6a60dabb0cc8becb35c2748ac4ec920d7f7625b93c16d8 + md5: 864e6d29ec7378b89ff5b5c9c629099e + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang-cpp22.1 >=22.1.8,<22.2.0a0 + size: 24175081 + timestamp: 1782358841320 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.8-default_h9692865_3.conda + sha256: b90ed8467a692788477c06bc0359fac52ed5651d801ddef189ba4b7ecf3ce38c + md5: 2a913525f4201f1adab2711fcf6f89b3 + depends: + - libclang-cpp22.1 ==22.1.8 default_h6c227bf_3 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang13 >=22.1.8 + size: 14283567 + timestamp: 1782358841320 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 + sha256: fd1d153962764433fe6233f34a72cdeed5dcf8a883a85769e8295ce940b5b0c5 + md5: c965a5aa0d5c1c37ffc62dff36e28400 + depends: + - libgcc-ng >=9.4.0 + - libstdcxx-ng >=9.4.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcrc32c >=1.1.2,<1.2.0a0 + size: 20440 + timestamp: 1633683576494 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda + sha256: 205c4f19550f3647832ec44e35e6d93c8c206782bdd620c1d7cf66237580ff9c + md5: 49c553b47ff679a6a1e9fc80b9c5a2d4 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libcups >=2.3.3,<2.4.0a0 + size: 4518030 + timestamp: 1770902209173 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.21.0-hae6b9f4_2.conda + sha256: ba8354084b34fce56d5697982fce4a384c148e972e15c0ab891187617fe7ec29 + md5: f9c59d277a16ec8f272b2d5dd2ec3335 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libnghttp2 >=1.68.1,<2.0a0 + - libpsl >=0.22.0,<0.23.0a0 + - libssh2 >=1.11.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: curl + license_family: MIT + purls: [] + run_exports: + weak: + - libcurl >=8.21.0,<9.0a0 + size: 480327 + timestamp: 1782911787190 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 + md5: 6c77a605a7a689d17d4819c0f8ac9a00 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdeflate >=1.25,<1.26.0a0 + size: 73490 + timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdovi-3.4.0-ha23c83e_0.conda + sha256: cea351b57c30d70e288b53ea69a1dcf6b750992f5d7717a7fc364072fa1209e7 + md5: 4377d220f09344452b227d699cacce4f + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdovi >=3.4.0,<4.0a0 + size: 404998 + timestamp: 1784281566921 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda + sha256: 7d3187c11b7ae66c5595a8afd5a7ce352a490527fdf6614cab129bc7f2c16ba3 + md5: d8d16b9b32a3c5df7e5b3350e2cbe058 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpciaccess >=0.19,<0.20.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdrm >=2.4.127,<2.5.0a0 + size: 311505 + timestamp: 1778975798004 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b + depends: + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_3.conda + sha256: 9a25ea93e8272785405a21d30f84e620befb1d545f6dfaae18f06103b5df0443 + md5: 75e9f795be506c96dd43cb09c7c8d557 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_3 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 46500 + timestamp: 1779728188901 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_3.conda + sha256: e4b46919c9bb65930bce238bd2736110ed7b8c30e5cd5394e4e1edb48de54843 + md5: 5bc6d55503483aabe8a90c5e7f49a2a4 + depends: + - __glibc >=2.17,<3.0.a0 + - libegl 1.7.0 ha4b6fd6_3 + - libgl-devel 1.7.0 ha4b6fd6_3 + - xorg-libx11 + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libegl >=1.7.0,<2.0a0 + size: 31718 + timestamp: 1779728222280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libev >=4.33,<4.34.0a0 + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda + sha256: 2e14399d81fb348e9d231a82ca4d816bf855206923759b69ad006ba482764131 + md5: a1cfcc585f0c42bf8d5546bb1dfb668d + depends: + - libgcc-ng >=12 + - openssl >=3.1.1,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libevent >=2.1.12,<2.1.13.0a0 + size: 427426 + timestamp: 1685725977222 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + sha256: 16feffd9ddbbe5b718515d38ee376c685ba95491cd901244e24671d20b952a77 + md5: b24d3c612f71e7aa74158d92106318b2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 77856 + timestamp: 1781203599810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.5.2,<3.6.0a0 + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libflac-1.5.0-he200343_1.conda + sha256: e755e234236bdda3d265ae82e5b0581d259a9279e3e5b31d745dc43251ad64fb + md5: 47595b9d53054907a00d95e4d47af1d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - libogg >=1.3.5,<1.4.0a0 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libflac >=1.5.0,<1.6.0a0 + size: 424563 + timestamp: 1764526740626 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + sha256: 38f014a7129e644636e46064ecd6b1945e729c2140e21d75bb476af39e692db2 + md5: e289f3d17880e44b633ba911d57a321b + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 8049 + timestamp: 1774298163029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda + sha256: 16f020f96da79db1863fcdd8f2b8f4f7d52f177dd4c58601e38e9182e91adf1d + md5: fb16b4b69e3f1dcfe79d80db8fd0c55d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 384575 + timestamp: 1774298162622 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.1.0-ha9f2e26_0.conda + sha256: c06e9c8c1836dda7ba50a5f427c1d7bdc4bd31426207c4553221c75ae49c3ec2 + md5: ebf54252821c52871cfc5f7d0c4511c6 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==16.1.0=*_0 + - libgomp 16.1.0 he0feb66_0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 1057873 + timestamp: 1785269541697 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.1.0-h69a702a_0.conda + sha256: 2d64b3c46de921c910caac4b8da85be84ac1781ddb5d563d4424267753c122cc + md5: db3cc8bbdcf7e6b19b43e4b50c95b83e + depends: + - libgcc 16.1.0 ha9f2e26_0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - libgcc + size: 28179 + timestamp: 1785269546419 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5fbf134_12.conda + sha256: 245be793e831170504f36213134f4c24eedaf39e634679809fd5391ad214480b + md5: 88c1c66987cd52a712eea89c27104be6 + depends: + - __glibc >=2.17,<3.0.a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + license: GD + license_family: BSD + purls: [] + run_exports: + weak: + - libgd >=2.3.3,<2.4.0a0 + size: 177306 + timestamp: 1766331805898 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.1.0-h69a702a_0.conda + sha256: 563a343efeee9ee04aae2a19710175b40e42d8995ae1117830cc3cd77da34278 + md5: 6d5a1d430e1e23a4fe80e87c9300afb2 + depends: + - libgfortran5 16.1.0 h79bb938_0 + constrains: + - libgfortran-ng ==16.1.0=*_0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 28158 + timestamp: 1785269581361 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.1.0-h79bb938_0.conda + sha256: 44b36269b491870abddc7a02e75a9de31eaca71182df88bfa9211d9958fed1f2 + md5: dda956beb0aa1af376704c77e2ffa824 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=16.1.0 + constrains: + - libgfortran 16.1.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 2538558 + timestamp: 1785269557832 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_3.conda + sha256: ec353b3076ed8e357ed961d0e9ff6997491cade0e603de5bd18a2e301ac78ebd + md5: f25206d7322c0e9648e8b83694d143ab + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_3 + - libglx 1.7.0 ha4b6fd6_3 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 133469 + timestamp: 1779728207669 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_3.conda + sha256: 41d7d864ad1f199bdb06ff6cc3931455c8af62f1d2071a08c6fa08affbcb678f + md5: 63e43d278ee5084813fe3c2edf4834ce + depends: + - __glibc >=2.17,<3.0.a0 + - libgl 1.7.0 ha4b6fd6_3 + - libglx-devel 1.7.0 ha4b6fd6_3 + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libgl >=1.7.0,<2.0a0 + size: 115664 + timestamp: 1779728218325 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.2-h0d30a3d_0.conda + sha256: 4bee10e62796f01e4fa2b5849135b1cc061337fe9cf5eb9bd79e9664922ae0e4 + md5: 889febc66cd9e4190f80ef9718fa239b + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - libffi >=3.5.2,<3.6.0a0 + - pcre2 >=10.47,<10.48.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libglib >=2.88.2,<3.0a0 + size: 4754220 + timestamp: 1782463895250 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_3.conda + sha256: e019ebe4e3f5cdf23e2f5e58ddf7ade27988c53820115b17b98f218ebcc87748 + md5: eb83f3f8cecc3e9bff9e250817fc69b6 + depends: + - __glibc >=2.17,<3.0.a0 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 133586 + timestamp: 1779728183422 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_3.conda + sha256: 2f74713c9ca408ea84e88a30a9028153e7b553e8bb42e06139eac9a753c27da9 + md5: ec3c4350aa0261bf7f87b8ca15c8e80e + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_3 + - xorg-libx11 >=1.8.13,<2.0a0 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 76586 + timestamp: 1779728199059 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_3.conda + sha256: a17ae2d4cb2de04a20882ae14ec3cc1958e868a4dec81e3d7eca30115ee50e94 + md5: 16b6330783ce0d1ae8d22782173b32c9 + depends: + - __glibc >=2.17,<3.0.a0 + - libglx 1.7.0 ha4b6fd6_3 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-xorgproto + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libglx >=1.7.0,<2.0a0 + size: 27363 + timestamp: 1779728211402 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.1.0-he0feb66_0.conda + sha256: 24b6406687ad439e989071e05e7ddbe5980e048650165e61fff40f906c55cc83 + md5: 3dd76c45d9e416a3c6e849c731068bde + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 641292 + timestamp: 1785269468130 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-3.7.0-hbc29df5_0.conda + sha256: 5e6186a18c868651f9f7d8ed5b9ff0c808294b33f5ae56f7f97ccd3d75f96c9d + md5: 767b70cdcd4bd5547be6db59a5f8c897 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libcurl >=8.21.0,<9.0a0 + - libgcc >=14 + - libgrpc >=1.82.1,<1.83.0a0 + - libopentelemetry-cpp >=1.27.0,<1.28.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + - openssl >=3.5.7,<4.0a0 + constrains: + - libgoogle-cloud 3.7.0 *_0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libgoogle-cloud >=3.7.0,<3.8.0a0 + size: 2698983 + timestamp: 1784211975522 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-3.7.0-hdbdcf42_0.conda + sha256: 6d373d7131ed0e2d99f7506b1aa9de6721e7dc159c42212744f0af2c4cdf70ae + md5: 44823876c0c3d9a9d522c891929037e8 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil + - libcrc32c >=1.1.2,<1.2.0a0 + - libcurl + - libgcc >=14 + - libgoogle-cloud 3.7.0 hbc29df5_0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libgoogle-cloud-storage >=3.7.0,<3.8.0a0 + size: 784267 + timestamp: 1784212113124 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.82.1-h792040b_0.conda + sha256: 1e657c1b802c111178bc1845fe06488db5f69a85e34e970ce0989810aa562d45 + md5: aa4571fc3bcf18ad12370429aa991223 + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.6,<2.0a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libre2-11 >=2025.11.5 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - re2 + constrains: + - grpc-cpp =1.82.1 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libgrpc >=1.82.1,<1.83.0a0 + size: 6957755 + timestamp: 1783588701960 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-14.2.1-h17a8019_1.conda + sha256: 821c315f6b5171aa89e735be2ad84e74eb3f898fc7610ee36cfecd95dfa789e8 + md5: fb4669c3990b94ea32fbb81f433e9aa6 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.15,<2.0a0 + - icu >=78.3,<79.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libglib >=2.88.2,<3.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 1297668 + timestamp: 1782800580119 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-devel-14.2.1-h17a8019_1.conda + sha256: 6d8e793042affd18ca1784b7794fab14d16f54f984777ce6ab86bacaa465ab0d + md5: 99cf21100441e51272f1cd6fe0632a20 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - freetype + - graphite2 >=1.3.15,<2.0a0 + - icu >=78.3,<79.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libglib >=2.88.2,<3.0a0 + - libharfbuzz 14.2.1 h17a8019_1 + - libpng >=1.6.58,<1.7.0a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libharfbuzz >=14.2.1 + size: 1970690 + timestamp: 1782800603897 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + sha256: 5041d295813dfb84652557839825880aae296222ab725972285c5abe3b6e4288 + md5: c197985b58bc813d26b42881f0021c82 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libhwloc >=2.13.0,<2.13.1.0a0 + size: 2436378 + timestamp: 1770953868164 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h3a9caae_0.conda + sha256: 02ab5e50c3921e88ad4dd0bc8f3fe282d2d7d03a20203d3281954b94641deb3d + md5: 9544a7225c8366ea2c397aad5fb53470 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 OR BSD-3-Clause + purls: [] + run_exports: + weak: + - libhwy >=1.4.0,<1.5.0a0 + size: 1431901 + timestamp: 1784325535334 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 790176 + timestamp: 1754908768807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_0.conda + sha256: 716332fd31b3808da7a4235a05212a9e9da05e854864c3def2dea0b5d2bf7610 + md5: 466badda5536d85ddc63ee9404f29735 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + run_exports: + weak: + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 652868 + timestamp: 1783731886811 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-h174a0a3_1.conda + sha256: 0c8a78c6a42a6e4c6de3a5e82d692f60400d43f4cc80591745f28b37daad9c70 + md5: 850f48943d6b4589800a303f0de6a816 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libhwy >=1.4.0,<1.5.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libjxl >=0.11,<1.0a0 + size: 1846962 + timestamp: 1777065125966 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + build_number: 8 + sha256: 168e327d737059553e15cc6ec36d76b9bbb3931c2a7721555fd68b4c9348b247 + md5: 809be8ba8712c77bc7d44c2d99390dc4 + depends: + - libblas 3.11.0 8_h4a7cf45_openblas + constrains: + - blas 2.308 openblas + - libcblas 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18790 + timestamp: 1779859115086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.11.0-8_h6ae95b6_openblas.conda + build_number: 8 + sha256: 0b982865888a73fe7c2e7d8e8ee3738ac378b83012a7da1a12264a473cb2382b + md5: da17457f689df5c0f246d2f0b3798fd2 + depends: + - libblas 3.11.0 8_h4a7cf45_openblas + - libcblas 3.11.0 8_h0358290_openblas + - liblapack 3.11.0 8_h47877c9_openblas + constrains: + - blas 2.308 openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapacke >=3.11.0,<3.12.0a0 + size: 18824 + timestamp: 1779859122364 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.8-hf7376ad_1.conda + sha256: e9b5f301d6b001a9b8ce782157f56b75c92c4fbc9eba95dc6345c1139251d13b + md5: 298bb2483fc7d15396147cf1c1465359 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libllvm22 >=22.1.8,<22.2.0a0 + size: 44320272 + timestamp: 1781788728739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d + md5: b88d90cad08e6bc8ad540cb310a761fb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 113478 + timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.6,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libnghttp2 >=1.68.1,<2.0a0 + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + sha256: 3b3f19ced060013c2dd99d9d46403be6d319d4601814c772a3472fe2955612b0 + md5: 7c7927b404672409d9917d49bff5f2d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libntlm >=1.8,<2.0a0 + size: 33418 + timestamp: 1734670021371 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + sha256: ffb066ddf2e76953f92e06677021c73c85536098f1c21fcd15360dbc859e22e4 + md5: 68e52064ed3897463c0e958ab5c8f91b + depends: + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libogg >=1.3.5,<1.4.0a0 + size: 218500 + timestamp: 1745825989535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + sha256: 3d9aa85648e5e18a6d66db98b8c4317cc426721ad7a220aa86330d1ccedc8903 + md5: 2d3278b721e40468295ca755c3b84070 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + constrains: + - openblas >=0.3.33,<0.3.34.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libopenblas >=0.3.33,<1.0a0 + size: 5931919 + timestamp: 1776993658641 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopencv-5.0.0-qt6_hd4a7afd_602.conda + sha256: d63d4f6ea65a31646d9bbc7dff7f237a4d6162bf73e942144ca3b5d4dbbab5b6 + md5: 2bb2082ad9ae0e013e8b4babc2722090 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - ffmpeg >=8.1.2,<9.0a0 + - harfbuzz >=14.2.1 + - hdf5 >=2.1.0,<3.0a0 + - imath >=3.2.2,<3.2.3.0a0 + - libavif16 >=1.4.2,<2.0a0 + - libcblas >=3.9.0,<4.0a0 + - libegl >=1.7.0,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libgl >=1.7.0,<2.0a0 + - libiconv >=1.18,<2.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libjxl >=0.11,<0.12.0a0 + - liblapack >=3.9.0,<4.0a0 + - liblapacke >=3.9.0,<4.0a0 + - libopenvino >=2026.2.1,<2026.2.2.0a0 + - libopenvino-auto-batch-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-auto-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-hetero-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-cpu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-gpu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-intel-npu-plugin >=2026.2.1,<2026.2.2.0a0 + - libopenvino-ir-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-onnx-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-paddle-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-pytorch-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-tensorflow-frontend >=2026.2.1,<2026.2.2.0a0 + - libopenvino-tensorflow-lite-frontend >=2026.2.1,<2026.2.2.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - openexr >=3.4.13,<3.5.0a0 + - openjpeg >=2.5.4,<3.0a0 + - qt6-main >=6.11.1,<7.0a0 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/opencv-python?source=compressed-mapping + run_exports: + weak: + - libopencv >=5.0.0,<5.0.1.0a0 + size: 35543251 + timestamp: 1782349674739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_3.conda + sha256: 90777039b48529283df5f16383fc399866024257a8bd93de583f4730db1ab30a + md5: c2bd8055a2e2dce7a7f32cfd02101fb6 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_3 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 51767 + timestamp: 1779728204026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.27.0-h3133023_1.conda + sha256: 8f9281133322e9eb0bd4a5b11330db1c14f3b40c409639dd3805251151ca52e2 + md5: f749f223bede1bac7724e49d686ac598 + depends: + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libcurl >=8.21.0,<9.0a0 + - libgrpc >=1.82.0,<1.83.0a0 + - libopentelemetry-cpp-headers 1.27.0 ha770c72_1 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libzlib >=1.3.2,<2.0a0 + - nlohmann_json + - prometheus-cpp >=1.3.0,<1.4.0a0 + constrains: + - cpp-opentelemetry-sdk =1.27.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopentelemetry-cpp >=1.27.0,<1.28.0a0 + size: 948783 + timestamp: 1783133058845 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.27.0-ha770c72_1.conda + sha256: 31349543e3c59c64f17e24494939a22b0e1d611bdb19d98d115775dba423cbf7 + md5: 6cc68cbbe88746be8beaf027d6c64ad3 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 394726 + timestamp: 1783133038109 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-2026.2.1-h1f0fae8_1.conda + sha256: 7941fb9ba8c3a5a0a2401dc4120e8fcb561b96d928c43374eb93f545019a2858 + md5: ea41753f926f73966629d81fdf20ec6f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - pugixml >=1.15,<1.16.0a0 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino >=2026.2.1,<2026.2.2.0a0 + size: 6823841 + timestamp: 1782219077259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-batch-plugin-2026.2.1-h7e124b3_1.conda + sha256: 3ac14d36fa890840ae8474b8a9f0a094b8542fd8fbc409faf3d465c68f20aff0 + md5: 5698a64698e14e8a2e9e16f8f0de0e2e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 114628 + timestamp: 1782219097820 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-plugin-2026.2.1-h7e124b3_1.conda + sha256: 499a472fc7b598ad3753b8f2afe60eb5a277d48eca9362e8aca094b2862587a7 + md5: 2ce088ef09292930d4cb3262ce7e144d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 250912 + timestamp: 1782219111223 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-hetero-plugin-2026.2.1-hd41364c_1.conda + sha256: bec24379598a4405de171ad151945e79743c6bd049aceabf190b753c3f7a11da + md5: 02e71250f7ca786c4b183d0a39ef63ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - pugixml >=1.15,<1.16.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 215488 + timestamp: 1782219123433 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-cpu-plugin-2026.2.1-h1f0fae8_1.conda + sha256: eecc040a7838752a2dff9b4435a4c59bbc67b83e0c880457935b968206cb20b5 + md5: 7288f979a74cfe3fd4b32d8a0dc7baa4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - pugixml >=1.15,<1.16.0a0 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 13637410 + timestamp: 1782219135415 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-gpu-plugin-2026.2.1-h1f0fae8_1.conda + sha256: a47442ce578b022e19a306f963536a108cc79385f4e09d57a14a849b6a864604 + md5: c0a258b12f0c18c476b8344dbd6db8d5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - ocl-icd >=2.3.4,<3.0a0 + - pugixml >=1.15,<1.16.0a0 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 12367381 + timestamp: 1782219178219 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-npu-plugin-2026.2.1-h1f0fae8_1.conda + sha256: 45a91feb68ccce90ad0fa86520572233ca20be56deae0c920f86133d020ad1e8 + md5: c214b149e108e92672e0ee097ebe16f7 + depends: + - __glibc >=2.17,<3.0.a0 + - level-zero >=1.29.0,<2.0a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - pugixml >=1.15,<1.16.0a0 + - tbb >=2023.0.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 2630818 + timestamp: 1782219217519 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-ir-frontend-2026.2.1-hd41364c_1.conda + sha256: ebeba9a3ac9505ee69b556865b7d1b9fbbad01ca1ebe6a4249ff62c3dc677b47 + md5: 2d946aebcf06e9ba438880987050e975 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + - pugixml >=1.15,<1.16.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-ir-frontend >=2026.2.1,<2026.2.2.0a0 + size: 201061 + timestamp: 1782219232657 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-onnx-frontend-2026.2.1-h607c73d_1.conda + sha256: 7b105c0102356352d6d9518a112ff6343dab6b8f32c837809117cd26cbf006df + md5: 3bd3599825189418ea14b2c9da3a6d87 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-onnx-frontend >=2026.2.1,<2026.2.2.0a0 + size: 1944558 + timestamp: 1782219246849 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-paddle-frontend-2026.2.1-h607c73d_1.conda + sha256: af45c03d41ebe0b48c28b68be31ee919cb801ac5077164808a66db515ad6a316 + md5: 91e198085bff9d8fa02d4d947f026ba8 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-paddle-frontend >=2026.2.1,<2026.2.2.0a0 + size: 690240 + timestamp: 1782219261154 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-pytorch-frontend-2026.2.1-hecca717_1.conda + sha256: e6353874a36143ffb7db7ec2c3767fd5e3434a8eeff41a569bc46e68259f668f + md5: 152d6694f1d05b53319b8376cdd811e4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-pytorch-frontend >=2026.2.1,<2026.2.2.0a0 + size: 1226625 + timestamp: 1782219274006 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-frontend-2026.2.1-h21c0c73_1.conda + sha256: cffe112815b8eb57528fdfdf8b39f6a0915884291147dab5bc2066d2bf123031 + md5: 89d2455ec2f065786856b0cd2ac1c0c6 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=14 + - snappy >=1.2.2,<1.3.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-tensorflow-frontend >=2026.2.1,<2026.2.2.0a0 + size: 1284650 + timestamp: 1782219287644 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-lite-frontend-2026.2.1-hecca717_1.conda + sha256: 142e7b24173ca8c32dbdb29c60f33a56ffb21a4ed733c9d6ab160c3a213ff52e + md5: c1a50f20847df0a8cb462138153ab46f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libopenvino 2026.2.1 h1f0fae8_1 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libopenvino-tensorflow-lite-frontend >=2026.2.1,<2026.2.2.0a0 + size: 501906 + timestamp: 1782219300706 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopus-1.6.1-h280c20c_0.conda + sha256: f1061a26213b9653bbb8372bfa3f291787ca091a9a3060a10df4d5297aad74fd + md5: 2446ac1fe030c2aa6141386c1f5a6aed + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libopus >=1.6.1,<2.0a0 + size: 324993 + timestamp: 1768497114401 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-25.0.0-h7376487_3_cpu.conda + build_number: 3 + sha256: dd83537b83718d9f882dea1aa3be368ec91bc368a0f3c1096cad506b54e96c39 + md5: 6df15a1aec4afbefc201bdc2b04fc85c + depends: + - __glibc >=2.17,<3.0.a0 + - libarrow 25.0.0 h32b7842_3_cpu + - libgcc >=14 + - libstdcxx >=14 + - libthrift >=0.22.0,<0.22.1.0a0 + - openssl >=3.5.7,<4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libparquet >=25.0.0,<25.1.0a0 + size: 1415435 + timestamp: 1784539797468 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_0.conda + sha256: f41721636a7c2e51bc2c642e1127955ab9c81145470714fdaac44d4d09e4af41 + md5: 33082e13b4769b48cfeb648e15bfe3fc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libpciaccess >=0.19,<0.20.0a0 + size: 29147 + timestamp: 1773533027610 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libplacebo-7.360.1-h9eeb4b2_0.conda + sha256: 26cbbd3d7b91801826c779c3f7e87d071856d5cbe3d55b22777ca0d984fb02ed + md5: e6324dfe6c02e0736bb9235f8ef3c8a6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libdovi >=3.3.2,<4.0a0 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - lcms2 >=2.19,<3.0a0 + - shaderc >=2026.2,<2026.3.0a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libplacebo >=7.360.1,<7.361.0a0 + size: 549348 + timestamp: 1777835950707 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda + sha256: 377cfe037f3eeb3b1bf3ad333f724a64d32f315ee1958581fc671891d63d3f89 + md5: eba48a68a1a2b9d3c0d9511548db85db + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + run_exports: + weak: + - libpng >=1.6.58,<1.7.0a0 + size: 317729 + timestamp: 1776315175087 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.4-hd5a49e9_1.conda + sha256: f7232cb79a31f5a5bcd499aea930b469cde8b96d26db9541022493fd274d2a6e + md5: 6c9103e7ea739a3bb3505da49a4708c1 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - openldap >=2.6.13,<2.7.0a0 + - openssl >=3.5.7,<4.0a0 + license: PostgreSQL + purls: [] + run_exports: + weak: + - libpq >=18.4,<19.0a0 + size: 2709008 + timestamp: 1782580447454 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-7.35.1-h2840a7c_2.conda + sha256: b5ac3938186516c1091b96414c34f90255da2a3bbd736a0712b22bbf4b5ebf02 + md5: 729db8acaadb9a5e5bb2dc3d9ac84ae4 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libprotobuf >=7.35.1,<7.35.2.0a0 + size: 3774596 + timestamp: 1783168720126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpsl-0.22.0-hf670292_2.conda + sha256: a9fcbf3d64be6e0c587dd6135f6d720e1569972692d42bff6c013cd5056ac23d + md5: f4bb2168fb76b16a31ac27e0a372d2e3 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - icu >=78.3,<79.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libpsl >=0.22.0,<0.23.0a0 + size: 72299 + timestamp: 1785342673105 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libraqm-0.11.0-h6406941_0.conda + sha256: ab3ccb9fd5816f76b18ee8974d359cd2605ca87d8ddef55369dc461b9986f95c + md5: 3ac89a48d224409739dbf6200e524373 + depends: + - libgcc >=14 + - __glibc >=2.28,<3.0.a0 + - fribidi >=1.0.16,<2.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libharfbuzz >=14.2.1 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libraqm >=0.11.0,<0.12.0a0 + size: 33983 + timestamp: 1784850267262 +- conda: https://conda.anaconda.org/conda-forge/linux-64/librdkafka-2.15.0-h560e972_0.conda + sha256: 58aca1e9f6d44eac6e77e9af82742c8b9cae2ed05b1ce061f83d6b9ca9bc3c43 + md5: 3f250cb1b8a26d4c3f2397ce1e25541c + depends: + - __glibc >=2.17,<3.0.a0 + - cyrus-sasl >=2.1.28,<3.0a0 + - libcurl >=8.21.0,<9.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - openssl >=3.5.7,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - librdkafka >=2.15.0,<2.16.0a0 + size: 20871633 + timestamp: 1782907483461 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h60473fc_2.conda + sha256: 43132eb45a569b1f63199b0e7d5874d94227e9be950b327a323e7dcc1f8cd58c + md5: ee5c400d37b79db5d32a69ed1a79fc0b + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=14 + - libstdcxx >=14 + constrains: + - re2 2025.11.05.* + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libre2-11 >=2025.11.5 + size: 210598 + timestamp: 1781312565737 +- conda: https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.62.3-h4c96295_0.conda + sha256: 5571bd8239d71961d4e3ce972f865b3ea95a91ce0b53d5749fe2dd24254ddbda + md5: 492c8d9b1c564c2e948b6cb4ba0f8261 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - fontconfig >=2.18.0,<3.0a0 + - fonts-conda-ecosystem + - gdk-pixbuf >=2.44.6,<3.0a0 + - harfbuzz >=14.2.0 + - libgcc >=14 + - libglib >=2.88.1,<3.0a0 + - libxml2-16 >=2.14.6 + - pango >=1.56.4,<2.0a0 + constrains: + - __glibc >=2.17 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - librsvg >=2.62.3,<3.0a0 + size: 3476570 + timestamp: 1780450632624 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.2-hc7d488a_2.conda + sha256: 57cb5f92110324c04498b96563211a1bca6a74b2918b1e8df578bfed03cc32e4 + md5: 067590f061c9f6ea7e61e3b2112ed6b3 + depends: + - __glibc >=2.17,<3.0.a0 + - lame >=3.100,<3.101.0a0 + - libflac >=1.5.0,<1.6.0a0 + - libgcc >=14 + - libogg >=1.3.5,<1.4.0a0 + - libopus >=1.5.2,<2.0a0 + - libstdcxx >=14 + - libvorbis >=1.3.7,<1.4.0a0 + - mpg123 >=1.32.9,<1.33.0a0 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - libsndfile >=1.2.2,<1.3.0a0 + size: 355619 + timestamp: 1765181778282 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-hf4e2dac_0.conda + sha256: 72023efc207fe681e26b65fc9d668062cf0b4f0eacf3431e6eb099b95c1f2efd + md5: df088a279cd5e6fd2790b4c196434da1 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 964200 + timestamp: 1785016112246 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 + md5: eecce068c7e4eddeb169591baac20ac4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.0,<4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libssh2 >=1.11.1,<2.0a0 + size: 304790 + timestamp: 1745608545575 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.1.0-h934c35e_0.conda + sha256: a791d1b4bbdd8b6bfbe315b011d479975ab9524115035f474c70d7e438bca12f + md5: 7f73f9e90fcb0f2f164e75061742c3d0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 16.1.0 ha9f2e26_0 + constrains: + - libstdcxx-ng ==16.1.0=*_0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 6626334 + timestamp: 1785269572774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.1.0-hdf11a46_0.conda + sha256: 1ca98460fe8fb81aa612e039ebcaa9a0ab3ee0de3d9b0e00a55dc396de3c6858 + md5: a6b9e3eb4ef3f1a15f3fdb165f4162b2 + depends: + - libstdcxx 16.1.0 h934c35e_0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - libstdcxx + size: 28268 + timestamp: 1785269614099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.13-h084b8d7_1.conda + sha256: 2293884d59cf0436c37fc0a4bad71011a8de2a6913610d1c701a7703377c1f75 + md5: ea0da9c20bbb221b530810c3c68bbe62 + depends: + - __glibc >=2.17,<3.0.a0 + - libcap >=2.78,<2.79.0a0 + - libgcc >=14 + license: LGPL-2.1-or-later + purls: [] + run_exports: {} + size: 493022 + timestamp: 1780084748140 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda + sha256: af6025aa4a4fc3f4e71334000d2739d927e2f678607b109ec630cc17d716918a + md5: b6e326fbe1e3948da50ec29cee0380db + depends: + - __glibc >=2.17,<3.0.a0 + - libevent >=2.1.12,<2.1.13.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - libthrift >=0.22.0,<0.22.1.0a0 + size: 423861 + timestamp: 1777018957474 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-h9d88235_0.conda + sha256: b31346e1c01ab40a170e91147092ee8fd92b1dee3c66ee47ef025571c879b159 + md5: c1fcb4a88bc15a9f77ad8d27d7af1df9 + depends: + - __glibc >=2.17,<3.0.a0 + - lerc >=4.1.0,<5.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libstdcxx >=14 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + run_exports: + weak: + - libtiff >=4.7.2,<4.8.0a0 + size: 452337 + timestamp: 1783084902636 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.13-h084b8d7_1.conda + sha256: 287d05680e49eea51b8145fbf34bc213c0618b04f32e450e9da5d715e5134e38 + md5: 89e5671a076d99516a6acd72a35b1640 + depends: + - __glibc >=2.17,<3.0.a0 + - libcap >=2.78,<2.79.0a0 + - libgcc >=14 + license: LGPL-2.1-or-later + purls: [] + run_exports: {} + size: 145969 + timestamp: 1780084753104 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libunwind-1.8.3-h65a8314_0.conda + sha256: 71c8b9d5c72473752a0bb6e91b01dd209a03916cb71f36cc6a564e3a2a132d7a + md5: e179a69edd30d75c0144d7a380b88f28 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libunwind >=1.8.3,<1.9.0a0 + size: 75995 + timestamp: 1757032240102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liburing-2.14-hb700be7_0.conda + sha256: 3d17b7aa90610afc65356e9e6149aeac0b2df19deda73a51f0a09cf04fd89286 + md5: 56f65185b520e016d29d01657ac02c0d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - liburing >=2.14,<2.15.0a0 + size: 154203 + timestamp: 1770566529700 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libusb-1.0.29-h73b1eb8_0.conda + sha256: 89c84f5b26028a9d0f5c4014330703e7dff73ba0c98f90103e9cef6b43a5323c + md5: d17e3fb595a9f24fa9e149239a33475d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libudev1 >=257.4 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libusb >=1.0.29,<2.0a0 + size: 89551 + timestamp: 1748856210075 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda + sha256: ecbf4b7520296ed580498dc66a72508b8a79da5126e1d6dc650a7087171288f9 + md5: 1247168fe4a0b8912e3336bccdbf98a5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libutf8proc >=2.11.3,<2.12.0a0 + size: 85969 + timestamp: 1768735071295 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + sha256: 9b1bdce27a7e31f7d241aeecff67a1f3101d52a2b1e33ccc2cdf2613072bf81f + md5: 01bb81d12c957de066ea7362007df642 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libuuid >=2.42.2,<3.0a0 + size: 40017 + timestamp: 1781625522462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + sha256: e28e4519223f78b3163599ca89c3f2d80bfb53e907e7fc74e806e60d1efa578b + md5: 4e33d49bf4fc853855a3b00643aa5484 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libuv >=1.52.1,<2.0a0 + size: 419935 + timestamp: 1779396012261 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libva-2.24.1-he1eb515_0.conda + sha256: 16a76abbb4fd1de4516ac4a3d06cbf1f561bc8049ca72b04dcac395eee74d017 + md5: eb1b7f8bfdea40eef150c4a1d37df09e + depends: + - __glibc >=2.17,<3.0.a0 + - libdrm >=2.4.127,<2.5.0a0 + - libegl >=1.7.0,<2.0a0 + - libgcc >=14 + - libgl >=1.7.0,<2.0a0 + - libglx >=1.7.0,<2.0a0 + - libxcb >=1.17.0,<2.0a0 + - wayland >=1.25.0,<2.0a0 + - wayland-protocols + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libva >=2.24.1,<3.0a0 + size: 222717 + timestamp: 1783519315031 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h54a6638_2.conda + sha256: ca494c99c7e5ecc1b4cd2f72b5584cef3d4ce631d23511184411abcbb90a21a5 + md5: b4ecbefe517ed0157c37f8182768271c + depends: + - libogg + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - libogg >=1.3.5,<1.4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libvorbis >=1.3.7,<1.4.0a0 + size: 285894 + timestamp: 1753879378005 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvpl-2.16.0-h54a6638_0.conda + sha256: 38850657dd6835613ef16b34895a54bea98bc7639db6a649c886b331635714fc + md5: 9f6b0090c3902b2c763a16f7dace7b6e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - intel-media-driver >=26.1.2,<26.2.0a0 + - libva >=2.23.0,<3.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libvpl >=2.16.0,<2.17.0a0 + size: 287992 + timestamp: 1772980546550 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvpx-1.15.2-hecca717_0.conda + sha256: 8e1119977f235b488ab32d540c018d3fd1eccefc3dd3859921a0ff555d8c10d2 + md5: 10f5008f1c89a40b09711b5a9cdbd229 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libvpx >=1.15.2,<1.16.0a0 + size: 1070048 + timestamp: 1762010217363 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h5279c79_0.conda + sha256: 1e30138cff1e6ba5739ce3ec787b24ef22ac6e2008d8a2073df334e9e5f8690b + md5: 9d8c72f797f6f2d1c32897603d70824c + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + constrains: + - libvulkan-headers 1.4.357.0.* + license: Apache-2.0 + purls: [] + run_exports: + weak: + - libvulkan-loader >=1.4.357.0,<2.0a0 + size: 203456 + timestamp: 1785311377294 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b + md5: aea31d2e5b1091feca96fcfe945c3cf9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - libwebp 1.6.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libwebp-base >=1.6.0,<2.0a0 + size: 429011 + timestamp: 1752159441324 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa + md5: 92ed62436b625154323d40d5f2f11dd7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxcb >=1.17.0,<2.0a0 + size: 395888 + timestamp: 1727278577118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libxcrypt >=4.4.36 + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-hca5e8e5_0.conda + sha256: 046f2ff4acebd8729fac03e99c8c307dfb48b6a32894ba8c11576e78f6e76e43 + md5: dc8b067e22b414172bedd8e3f03f3c95 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxcb >=1.17.0,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - xkeyboard-config + - xorg-libxau >=1.0.12,<2.0a0 + license: MIT/X11 Derivative + license_family: MIT + purls: [] + run_exports: + weak: + - libxkbcommon >=1.13.2,<2.0a0 + size: 851166 + timestamp: 1780213397575 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda + sha256: 3d44f737c5ae52d5af32682cc1530df433f401f8e58a7533926536244127572a + md5: e79d2c2f24b027aa8d5ab1b1ba3061e7 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.3 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 559775 + timestamp: 1776376739004 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda + sha256: 3bc5551720c58591f6ea1146f7d1539c734ed1c40e7b9f5cb8cb7e900c509aba + md5: 995d8c8bad2a3cc8db14675a153dec2b + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 hca6bf5a_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 46810 + timestamp: 1776376751152 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzopfli-1.0.3-h9c3ff4c_0.tar.bz2 + sha256: ff94f30b2e86cbad6296cf3e5804d442d9e881f7ba8080d92170981662528c6e + md5: c66fe2d123249af7651ebde8984c51c2 + depends: + - libgcc-ng >=9.3.0 + - libstdcxx-ng >=9.3.0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libzopfli >=1.0.3,<1.1.0a0 + size: 168074 + timestamp: 1607309189989 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvmlite-0.48.0-py313h6b4ec16_1.conda + sha256: 8f676fa146716d18293ad48104095bac694bd9317e9068a3b9eefd8d2e9cbab2 + md5: 225e3e835deabe4e36147ac88d640763 + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/llvmlite?source=hash-mapping + run_exports: {} + size: 40433538 + timestamp: 1784043438796 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.5-py313h28739b2_1.conda + sha256: cbc82f4fa7587376c038d2f0471a73efa7ade4439857b04a0cc839262f1de6e5 + md5: e69ad33075938ba81e43311da86b809c + depends: + - python + - lz4-c + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + - lz4-c >=1.10.0,<1.11.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/lz4?source=hash-mapping + run_exports: {} + size: 44861 + timestamp: 1765026393230 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 + md5: 9de5350a85c4a20c685259b889aa6393 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - lz4-c >=1.10.0,<1.11.0a0 + size: 167055 + timestamp: 1733741040117 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + sha256: 72ed7c0216541d65a17b171bf2eec4a3b81e9158d8ed48e59e1ecd3ae302d263 + md5: aeb9b9da79fd0258b3db091d1fefcd71 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 26100 + timestamp: 1772445154165 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.11.1-py313h134213a_2.conda + sha256: 40e7f144d6a1df2372b7a8b8b42d7f03bcb236dfcb053c32030f9b1635c8f429 + md5: 2c99937357c9476a1f5dc5ecd08d0c75 + depends: + - __glibc >=2.17,<3.0.a0 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.28.2 + - freetype + - kiwisolver >=1.3.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libraqm >=0.11.0,<0.12.0a0 + - libstdcxx >=14 + - numpy >=1.25 + - numpy >=1.25,<3 + - packaging >=20.0 + - pillow >=9 + - pyparsing >=3 + - python >=3.13,<3.14.0a0 + - python-dateutil >=2.7 + - python_abi 3.13.* *_cp313 + - qhull >=2020.2,<2020.3.0a0 + - tk >=8.6.13,<8.7.0a0 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/matplotlib?source=compressed-mapping + run_exports: {} + size: 9005023 + timestamp: 1785211111407 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.4.0-he0a73b1_0.conda + sha256: c1fdeebc9f8e4f51df265efca4ea20c7a13911193cc255db73cccb6e422ae486 + md5: 770d00bf57b5599c4544d61b61d8c6c6 + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=14 + - mpfr >=4.2.2,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - mpc >=1.4.0,<2.0a0 + size: 100245 + timestamp: 1774472435333 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.2-he0a73b1_0.conda + sha256: 8690f550a780f75d9c47f7ffc15f5ff1c149d36ac17208e50eda101ca16611b9 + md5: 85ce2ffa51ab21da5efa4a9edc5946aa + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=14 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - mpfr >=4.2.2,<5.0a0 + size: 730422 + timestamp: 1773413915171 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.9-hc50e24c_0.conda + sha256: 39c4700fb3fbe403a77d8cc27352fa72ba744db487559d5d44bf8411bb4ea200 + md5: c7f302fd11eeb0987a6a5e1f3aed6a21 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: LGPL-2.1-only + license_family: LGPL + purls: [] + run_exports: + weak: + - mpg123 >=1.32.9,<1.33.0a0 + size: 491140 + timestamp: 1730581373280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.1-py313hc8edb43_1.conda + sha256: 6bc2690c67ba7354affa0498a68998fad8f66759c287706e7ff42c2242e14ccb + md5: edd7c461f72756bd3829f836e107c488 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/msgpack?source=compressed-mapping + run_exports: {} + size: 112798 + timestamp: 1782460772873 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 918956 + timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ndindex-1.10.1-py313h7033f15_0.conda + sha256: 7ac6a3718f39bcde66486346b8f2f78b449de1f69dee98ada9aafafc011c358c + md5: f0db808169f9f10e25c6ca4a9a79583f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ndindex?source=hash-mapping + run_exports: {} + size: 245194 + timestamp: 1763658106189 +- conda: https://conda.anaconda.org/conda-forge/linux-64/netifaces-0.11.0-py313h07c4f96_4.conda + sha256: 9c74e152594df9442f0d8d0475863d417c21bd02b43b90b9be09a670a9f8e263 + md5: b32897ac6aee3f529551a55e57a0e362 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/netifaces?source=hash-mapping + run_exports: {} + size: 20500 + timestamp: 1756921185152 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda + sha256: fd2cbd8dfc006c72f45843672664a8e4b99b2f8137654eaae8c3d46dca776f63 + md5: 16c2a0e9c4a166e53632cfca4f68d020 + constrains: + - nlohmann_json-abi ==3.12.0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 136216 + timestamp: 1758194284857 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda + sha256: e3664264bd936c357523b55c71ed5a30263c6ba278d726a75b1eb112e6fb0b64 + md5: e235d5566c9cc8970eb2798dd4ecf62f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + run_exports: + weak: + - nspr >=4.38,<5.0a0 + size: 228588 + timestamp: 1762348634537 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda + sha256: 44dd98ffeac859d84a6dcba79a2096193a42fc10b29b28a5115687a680dd6aea + md5: 567fbeed956c200c1db5782a424e58ee + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libsqlite >=3.51.0,<4.0a0 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - nspr >=4.38,<5.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + run_exports: + weak: + - nss >=3.118,<4.0a0 + size: 2057773 + timestamp: 1763485556350 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numba-0.66.0-py313h7afe1cf_0.conda + sha256: d244bfccb2bfba80462dfc97c015ca760f6bbd0e03438eb24e1579b2428857ea + md5: 28316215e4e032a3b61afd74e8415efc + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - libgcc >=14 + - libstdcxx >=14 + - llvmlite >=0.48.0,<0.49.0a0 + - numpy >=1.22.3,<2.5 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - cudatoolkit >=11.2 + - cuda-python >=11.6 + - libopenblas !=0.3.6 + - scipy >=1.0 + - tbb >=2021.6.0 + - cuda-version >=11.2 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/numba?source=compressed-mapping + run_exports: {} + size: 5804060 + timestamp: 1784209150387 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numcodecs-0.16.5-py313h08cd8bf_0.conda + sha256: 7c35d46639f8638849535a22cb7ae1b7210121be0c7b053d8e2ab7ed485e6bff + md5: 0f394ef25fb81d1dec8ff4fa716f00bd + depends: + - __glibc >=2.17,<3.0.a0 + - deprecated + - libgcc >=14 + - libstdcxx >=14 + - msgpack-python + - numpy >=1.23,<3 + - numpy >=1.24 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - typing_extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/numcodecs?source=hash-mapping + run_exports: {} + size: 808201 + timestamp: 1764782369322 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.2-py313h24ae7f9_100.conda + sha256: f86e2743ffe086f96a627521a8972e99f8900f39c390088b6f10c3a94cba8fa1 + md5: fa4dc45efc136327aad72a80491d72d9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - nomkl + - numpy >=1.23.0 + - numpy >=1.25,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/numexpr?source=hash-mapping + run_exports: {} + size: 219767 + timestamp: 1784389091235 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py313hf6604e3_0.conda + sha256: 3740c9bc562db9c6f252f8697c5c7948bb48784346856f6d6308aba72ea4f00b + md5: a5fdb80595ec7912e6b1634b2abd4b50 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + run_exports: + weak: + - numpy >=1.23,<3 + size: 8864096 + timestamp: 1779169199037 +- conda: https://conda.anaconda.org/conda-forge/linux-64/obstore-0.11.0-py313h5c7d99a_0.conda + sha256: 32749ec2d55933ad5a3e767eb5b17f11581438a45e6df79bb7f7cb21882ed628 + md5: b0895bcc353f6268f5895421116eac3a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/obstore?source=hash-mapping + run_exports: {} + size: 4175718 + timestamp: 1782408521332 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.4-hb03c661_1.conda + sha256: 75f3bf733523a338f73d6c276c4a26634877cd970edb558f2769d9fa52b100a9 + md5: c2871ba95727fd1382c05db66048b64c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - opencl-headers >=2025.6.13 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - ocl-icd >=2.3.4,<3.0a0 + size: 109598 + timestamp: 1780362789611 +- conda: https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-hecca717_0.conda + sha256: 8de2f0cd8a659b01abf86e7fbb8cea4f28ada62fd288429a2bbc040db1b98dd0 + md5: c930c8052d780caa41216af7de472226 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 55754 + timestamp: 1773844383536 +- conda: https://conda.anaconda.org/conda-forge/linux-64/opencv-5.0.0-qt6_h12f39d8_602.conda + noarch: python + sha256: f106695b88be3108d7b580712778ce88221e01390d38085935500645f8800d39 + md5: e90ef2f22d6b63612245d8880f01f3b7 + depends: + - libopencv 5.0.0 qt6_hd4a7afd_602 + - py-opencv 5.0.0 qt6_h6945e2d_602 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: {} + size: 48967 + timestamp: 1782349719319 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openexr-3.4.13-hf734b31_1.conda + sha256: 17e3d2769dfc018748f0c09757b95744a5e942f74325b605d06303a69ee7955d + md5: 9db8aff65eaea058afc9fe93569b4b14 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - imath >=3.2.2,<3.2.3.0a0 + - openjph >=0.30.1,<0.31.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openexr >=3.4.13,<3.5.0a0 + size: 1223929 + timestamp: 1782198396418 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openh264-2.6.0-h65dd3cf_1.conda + sha256: 5317c5c23762f3fe1c8510565a2bb94c645e1470ff73b386315656404f7eb58a + md5: 69894a95220a17a66272daa701c387bc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openh264 >=2.6.0,<2.6.1.0a0 + size: 726478 + timestamp: 1782685945856 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d + md5: 11b3379b191f63139e29c0d19dee24cd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.50,<1.7.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openjpeg >=2.5.4,<3.0a0 + size: 355400 + timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjph-0.30.1-h8d634f6_0.conda + sha256: e405a62cc16604c4274d1d64387fa62ba5466cc65fa087ae45451d0150f4b698 + md5: 6143d4af035262f7e1b954e1cba9156d + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - libtiff >=4.7.1,<4.8.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openjph >=0.30.1,<0.31.0a0 + size: 294315 + timestamp: 1782091151122 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda + sha256: 21c4f6c7f41dc9bec2ea2f9c80440d9a4d45a6f2ac13243e658f10dcf1044146 + md5: 680608784722880fbfe1745067570b00 + depends: + - __glibc >=2.17,<3.0.a0 + - cyrus-sasl >=2.1.28,<3.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.6,<4.0a0 + license: OLDAP-2.8 + license_family: BSD + purls: [] + run_exports: + weak: + - openldap >=2.6.13,<2.7.0a0 + size: 786149 + timestamp: 1775741359582 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openpyxl-3.1.5-py313ha4be090_3.conda + sha256: ee3e071cbc0be5600747631b41da17349be6fd25c982c9a9644cda3953bbf8b5 + md5: 993d27015ca7aa1de3f4a471a9b5309e + depends: + - et_xmlfile + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/openpyxl?source=hash-mapping + run_exports: {} + size: 484606 + timestamp: 1769122088626 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + sha256: d48f5c22b9897c01e4dff3680f1f57ceb02711ab9c62f74339b080419dfad34b + md5: 79dd2074b5cd5c5c6b2930514a11e22d + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.3,<4.0a0 + size: 3159683 + timestamp: 1781069855778 +- conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.3.1-h443056b_0.conda + sha256: ae96bc0895a2279f2ea296879e0475333e838cc88754b0c1a4903dd92a7e1b21 + md5: 1280a5f8270f30b89cc8373ecfe8899d + depends: + - tzdata + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - snappy >=1.2.2,<1.3.0a0 + - libabseil >=20260526.0,<20260527.0a0 + - libabseil * cxx17* + - libzlib >=1.3.2,<2.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - lz4-c >=1.10.0,<1.11.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - orc >=2.3.1,<2.3.2.0a0 + size: 1458252 + timestamp: 1784301236872 +- conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.9-py313h541fbb8_0.conda + sha256: 21697e46371cf46966638fe022995e2ff4d9d188ddab644ae5f75da7203b3c9f + md5: 19483ddd60c9fa7a865b011ed288c3d9 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/orjson?source=hash-mapping + run_exports: {} + size: 366512 + timestamp: 1778694308496 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.5-py313hbfd7664_1.conda + sha256: 3fc0e22d9c4338b2becb6a2cce230a57f94730dfb6d3be395089084411c458e3 + md5: a78866632a871f274cd2015bccdd2ca0 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - numpy >=1.23,<3 + - python_abi 3.13.* *_cp313 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + purls: + - pkg:pypi/pandas?source=hash-mapping + run_exports: {} + size: 15049335 + timestamp: 1785276231848 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pango-1.58.0-hda50119_0.conda + sha256: ec020fd570ba116c06592af8d8ce05f41c20b6d673d1a8a7a5a98fecad0f4b14 + md5: ef685e254006246695cbc36cf3fda159 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - fontconfig >=2.18.2,<3.0a0 + - fonts-conda-ecosystem + - fribidi >=1.0.16,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libglib >=2.88.2,<3.0a0 + - libharfbuzz >=14.2.1 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - pango >=1.58.0,<2.0a0 + size: 468909 + timestamp: 1785174647353 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff + md5: 7a3bff861a6583f1889021facefc08b1 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pcre2 >=10.47,<10.48.0a0 + size: 1222481 + timestamp: 1763655398280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda + build_number: 7 + sha256: 9ec32b6936b0e37bcb0ed34f22ec3116e75b3c0964f9f50ecea5f58734ed6ce9 + md5: f2cfec9406850991f4e3d960cc9e3321 + depends: + - libgcc-ng >=12 + - libxcrypt >=4.4.36 + license: GPL-1.0-or-later OR Artistic-1.0-Perl + purls: [] + run_exports: + weak: + - perl >=5.32.1,<5.33.0a0 *_perl5 + noarch: + - perl >=5.32.1,<6.0a0 *_perl5 + size: 13344463 + timestamp: 1703310653947 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py313h80991f8_0.conda + sha256: cb3e51a639d19e04d0ea197ff6f6805a40eeef92d762642f28fa1cd1d25f672a + md5: 730e462e7e141813192b606cf07ebdd0 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libwebp-base >=1.6.0,<2.0a0 + - lcms2 >=2.19.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - tk >=8.6.13,<8.7.0a0 + - libxcb >=1.17.0,<2.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - python_abi 3.13.* *_cp313 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - openjpeg >=2.5.4,<3.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=compressed-mapping + run_exports: {} + size: 1077037 + timestamp: 1782912080163 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_2.conda + sha256: 9e5b5be056820ade8b09ef73cf9f4bea037eb9887145d0297ac99e0add88878d + md5: 7cd77fef4da3e1ca9484394616cb71f1 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - pixman >=0.46.4,<1.0a0 + size: 376220 + timestamp: 1784286827180 +- conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda + sha256: 013669433eb447548f21c3c6b16b2ed64356f726b5f77c1b39d5ba17a8a4b8bc + md5: a83f6a2fdc079e643237887a37460668 + depends: + - __glibc >=2.17,<3.0.a0 + - libcurl >=8.10.1,<9.0a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + - zlib + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - prometheus-cpp >=1.3.0,<1.4.0a0 + size: 199544 + timestamp: 1730769112346 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + sha256: f19fd682d874689dfde20bf46d7ec1a28084af34583e0405685981363af47c91 + md5: 25fe6e02c2083497b3239e21b49d8093 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + run_exports: {} + size: 228663 + timestamp: 1769678153829 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 + md5: b3c17d95b5a10c6e64a21fa17573e70e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 8252 + timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pugixml-1.15-h3f63f65_0.conda + sha256: 23c98a5000356e173568dc5c5770b53393879f946f3ace716bbdefac2a8b23d2 + md5: b11a4c6bf6f6f44e5e143f759ffa2087 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - pugixml >=1.15,<1.16.0a0 + size: 118488 + timestamp: 1736601364156 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-h9a6aba3_3.conda + sha256: 0a0858c59805d627d02bdceee965dd84fde0aceab03a2f984325eec08d822096 + md5: b8ea447fdf62e3597cb8d2fae4eb1a90 + depends: + - __glibc >=2.17,<3.0.a0 + - dbus >=1.16.2,<2.0a0 + - libgcc >=14 + - libglib >=2.86.1,<3.0a0 + - libiconv >=1.18,<2.0a0 + - libsndfile >=1.2.2,<1.3.0a0 + - libsystemd0 >=257.10 + - libxcb >=1.17.0,<2.0a0 + constrains: + - pulseaudio 17.0 *_3 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - pulseaudio-client >=17.0,<17.1.0a0 + size: 750785 + timestamp: 1763148198088 +- conda: https://conda.anaconda.org/conda-forge/linux-64/py-opencv-5.0.0-qt6_h6945e2d_602.conda + noarch: python + sha256: f74fc21337142f296a44fd975d754bbb87d141420d0f9b5474881f3d11fd64c6 + md5: 25c0cecc27b0fe1cdfc53b78d058d0f0 + depends: + - _python_abi3_support 1.* + - cpython >=3.10 + - libopencv 5.0.0 qt6_hd4a7afd_602 + - numpy >=1.21,<3 + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/opencv-python?source=compressed-mapping + run_exports: + weak: + - py-opencv >=5.0.0,<6.0a0 + size: 3841843 + timestamp: 1782349715473 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-25.0.0-py313h78bf25f_0.conda + sha256: 283926f580a9efd06e438d19f402d37e189cababbc18b86920a66f6441d84b24 + md5: 9cda71e83d2aa135af38edb6b0293d95 + depends: + - libarrow-acero 25.0.0.* + - libarrow-dataset 25.0.0.* + - libarrow-substrait 25.0.0.* + - libparquet 25.0.0.* + - pyarrow-core 25.0.0 *_0_* + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 26039 + timestamp: 1784258353242 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-25.0.0-py313h98bfbea_0_cpu.conda + sha256: 2acc9a98e83a07deeebe295cc3314a9e19261af32415cfaf2dbaaac71bcb1bbd + md5: e44951ecdce8a268052edcac0c8efcc2 + depends: + - __glibc >=2.17,<3.0.a0 + - libarrow 25.0.0.* *cpu + - libarrow-compute 25.0.0.* *cpu + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - numpy >=1.23,<3 + - apache-arrow-proc * cpu + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/pyarrow?source=hash-mapping + run_exports: {} + size: 5442780 + timestamp: 1784258403853 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycryptodome-3.23.0-py313h6123c0d_2.conda + sha256: 8a389621b0bf6bbce6f1732c4d4da09b3dad4c413e029726465f244ff25b55ec + md5: 9515285f632cbc82b783df886713f998 + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pycryptodome?source=hash-mapping + run_exports: {} + size: 1681620 + timestamp: 1768755547718 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + sha256: d6705962afa2655cc22edcd075ce1f7e67c4407c005137d6d63c0dc5eaa76f47 + md5: ef0f9efec6ec28b17e22f787c7672c67 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + run_exports: {} + size: 1893749 + timestamp: 1778084222642 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyepics-3.5.10-py313h78bf25f_0.conda + sha256: 2189b69f51595ac894248e9b1a56ab509e4fed56e8a3b50405d0b36f88912a30 + md5: 061a43fe687bc5ed12f0919ce48421ab + depends: + - epics-base + - importlib_resources + - numpy >=1.26 + - pyparsing + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - setuptools + license: EPICS + purls: + - pkg:pypi/pyepics?source=hash-mapping + run_exports: {} + size: 4255908 + timestamp: 1779312690017 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyerfa-2.0.1.5-py310h32771cd_2.conda + noarch: python + sha256: a3f25f921be09e15ed6ff46a1ec99ce9cca6affa4a086f6f39ad630e21e48fb7 + md5: e6efd9593a25d093b4ce9dd8053c4af7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - numpy >=1.21,<3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyerfa?source=hash-mapping + run_exports: {} + size: 295617 + timestamp: 1756821497270 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pymca-5.9.6-py313h29aa505_0.conda + sha256: de6bfdcec946d32a229031a233e2069d47e0d95bbdf80ffecc43d703a8a33ee2 + md5: d8eccf4a805d13c4aee4c2b67f8fecc9 + depends: + - __glibc >=2.17,<3.0.a0 + - fisx >=1.1.6 + - h5py + - libgcc >=14 + - matplotlib-base >1.0 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pymca5?source=hash-mapping + run_exports: {} + size: 8753009 + timestamp: 1771431336050 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pymongo-4.17.0-py313h7033f15_0.conda + sha256: d6cb716d5cfd2514c86a2aa69a0598981a979f28361d6cc35c3e9e5d0a47bf7e + md5: b97cd214390464333ff7c80f957efe41 + depends: + - __glibc >=2.17,<3.0.a0 + - dnspython <3.0.0,>=2.6.1 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/pymongo?source=hash-mapping + run_exports: {} + size: 2412952 + timestamp: 1776765924991 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.11-py313h938ae94_2.conda + sha256: 6b32a29e7e859b06b75f5a21d35e048d3fae7a83359935bde41784b0f8f74ed7 + md5: 74d3105f70fe612ad22d59e270baf929 + depends: + - __glibc >=2.17,<3.0.a0 + - libegl >=1.7.0,<2.0a0 + - libgcc >=14 + - libgl >=1.7.0,<2.0a0 + - libopengl >=1.7.0,<2.0a0 + - libstdcxx >=14 + - pyqt5-sip 12.17.0 py313h7033f15_2 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - qt-main >=5.15.15,<5.16.0a0 + - sip >=6.10.0,<6.11.0a0 + - xcb-util >=0.4.1,<0.5.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + - xcb-util-wm >=0.4.2,<0.5.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxcomposite >=0.4.6,<1.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxxf86vm >=1.1.6,<2.0a0 + license: GPL-3.0-only + license_family: GPL + purls: + - pkg:pypi/pyqt5?source=hash-mapping + run_exports: + weak: + - pyqt >=5.15.11,<5.16.0a0 + size: 5267324 + timestamp: 1759497949535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.17.0-py313h7033f15_2.conda + sha256: 2b2facbe222f4746d92d09243a253eb1747cba3ed5d9a818c2d644c23967b31b + md5: 9e7b69bd33b197ca3ee87e66e4f01a9b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - packaging + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - sip + - toml + license: GPL-3.0-only + license_family: GPL + purls: + - pkg:pypi/pyqt5-sip?source=hash-mapping + run_exports: {} + size: 85680 + timestamp: 1759495571128 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.14-h6add32d_100_cp313.conda + build_number: 100 + sha256: f2146aff59ce4b571a8f1d1acf94f9bed6cc18ab5632d7dcc940fb48ecdeef99 + md5: 93762cd272814a142cf21d794f8fb0c1 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.53.2,<4.0a0 + - libuuid >=2.42.1,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.7,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.13.* *_cp313 + noarch: + - python + size: 37398694 + timestamp: 1781258934574 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-blosc2-4.7.0-py313h4559b1c_0.conda + sha256: 9814d30eb4c9037857c9331f23b95ee3d2fcb91173267d1b24c313cb9916f720 + md5: ce36be33499c7f18574155d8f2a4a4cd + depends: + - __glibc >=2.17,<3.0.a0 + - c-blosc2 >=3.1.5,<3.2.0a0 + - libgcc >=14 + - libstdcxx >=14 + - msgpack-python + - ndindex + - numexpr >=2.14.1 + - numpy >=1.25,<3 + - numpy >=1.26.0 + - pydantic + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - requests + - rich + - textual + - textual-plotext + - threadpoolctl + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/blosc2?source=hash-mapping + run_exports: {} + size: 2374310 + timestamp: 1782793828391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-confluent-kafka-2.15.0-py313h07c4f96_0.conda + sha256: 58dc19d813fda60b0690650ef179055f3d514f274079738d65effb26b609dc9d + md5: e14fdb25f61610776edb17618f9106d1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - librdkafka >=2.15.0 + - librdkafka >=2.15.0,<2.16.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/confluent-kafka?source=hash-mapping + run_exports: {} + size: 511720 + timestamp: 1783884082232 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-duckdb-1.3.2-py313h7033f15_0.conda + sha256: 7bf8b0d04c7a13fdec84f349a63d857a7655b1cf0c350d0390722a472d28b018 + md5: 2675d531dcba6deeb2338dbdf4cda7ad + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/duckdb?source=hash-mapping + run_exports: {} + size: 24452451 + timestamp: 1752086879946 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 + md5: f256753e840c3cd3766488c9437a8f8b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 201616 + timestamp: 1770223543730 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + sha256: 776363493bad83308ba30bcb88c2552632581b143e8ee25b1982c8c743e73abc + md5: 353823361b1d27eb3960efb076dfcaf6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: LicenseRef-Qhull + purls: [] + run_exports: + weak: + - qhull >=2020.2,<2020.3.0a0 + size: 552937 + timestamp: 1720813982144 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.15-h0c412b5_8.conda + sha256: c0008c97dbfaef709eff044ea2fdcf7cca55b2e061ff992872d71b9b35f7f91b + md5: 80e27e7982af989ebc2e0f0d57c75ea7 + depends: + - __glibc >=2.17,<3.0.a0 + - alsa-lib >=1.2.15.3,<1.3.0a0 + - dbus >=1.16.2,<2.0a0 + - fontconfig >=2.17.1,<3.0a0 + - fonts-conda-ecosystem + - gst-plugins-base >=1.26.10,<1.27.0a0 + - gstreamer >=1.26.10,<1.27.0a0 + - harfbuzz >=13.2.0 + - icu >=78.3,<79.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libclang-cpp22.1 >=22.1.0,<22.2.0a0 + - libclang13 >=22.1.0 + - libcups >=2.3.3,<2.4.0a0 + - libdrm >=2.4.125,<2.5.0a0 + - libegl >=1.7.0,<2.0a0 + - libevent >=2.1.12,<2.1.13.0a0 + - libexpat >=2.7.4,<3.0a0 + - libfreetype >=2.14.2 + - libfreetype6 >=2.14.2 + - libgcc >=13 + - libgl >=1.7.0,<2.0a0 + - libglib >=2.86.4,<3.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libllvm22 >=22.1.0,<22.2.0a0 + - libpng >=1.6.55,<1.7.0a0 + - libpq >=18.3,<19.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libstdcxx >=13 + - libxcb >=1.17.0,<2.0a0 + - libxkbcommon >=1.13.1,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - libzlib >=1.3.1,<2.0a0 + - nspr >=4.38,<5.0a0 + - nss >=3.118,<4.0a0 + - openssl >=3.5.5,<4.0a0 + - pulseaudio-client >=17.0,<17.1.0a0 + - xcb-util >=0.4.1,<0.5.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + - xcb-util-wm >=0.4.2,<0.5.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - qt 5.15.15 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - qt-main >=5.15.15,<5.16.0a0 + size: 52674357 + timestamp: 1773957808615 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.1-pl5321h16c4a6b_1.conda + sha256: aefbc43bde188ff4027d480da99c7fa9e8e6341e9762e065190239cb9b99bb1c + md5: 331d660aef48fec733a878dd1f8f4206 + depends: + - libxcb + - xcb-util + - xcb-util-wm + - xcb-util-keysyms + - xcb-util-image + - xcb-util-renderutil + - xcb-util-cursor + - libgl-devel + - libegl-devel + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - xcb-util >=0.4.1,<0.5.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - fontconfig >=2.18.1,<3.0a0 + - fonts-conda-ecosystem + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + - libsqlite >=3.53.2,<4.0a0 + - libpq >=18.4,<19.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - wayland >=1.25.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + - libpng >=1.6.58,<1.7.0a0 + - harfbuzz >=14.2.1 + - xcb-util-cursor >=0.1.6,<0.2.0a0 + - xorg-libxcursor >=1.2.3,<2.0a0 + - libcups >=2.3.3,<2.4.0a0 + - libxcb >=1.17.0,<2.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libdrm >=2.4.127,<2.5.0a0 + - xorg-libxcomposite >=0.4.7,<1.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - xcb-util-wm >=0.4.2,<0.5.0a0 + - zstd >=1.5.7,<1.6.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + - icu >=78.3,<79.0a0 + - xorg-libxdamage >=1.1.6,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - alsa-lib >=1.2.16,<1.3.0a0 + - openssl >=3.5.6,<4.0a0 + - libglib >=2.88.1,<3.0a0 + - libgl >=1.7.0,<2.0a0 + - libxkbcommon >=1.13.2,<2.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - double-conversion >=3.4.0,<3.5.0a0 + - dbus >=1.16.2,<2.0a0 + - xorg-libxtst >=1.2.5,<2.0a0 + - libegl >=1.7.0,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + constrains: + - qt ==6.11.1 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - qt6-main >=6.11.1,<7.0a0 + size: 60185421 + timestamp: 1780593127053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.8.1-h1fbca29_0.conda + sha256: cf550bbc8e5ebedb6dba9ccaead3e07bd1cb86b183644a4c853e06e4b3ad5ac7 + md5: d83958768626b3c8471ce032e28afcd3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - __glibc >=2.17 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - rav1e >=0.8.1,<0.9.0a0 + size: 5595970 + timestamp: 1772540833621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h94463f1_2.conda + sha256: 7279908454f0c87b84ebc4aec6924f02f41a105d88420fb35dfaf5ecd976e0f5 + md5: d83f2ee212d217a6d745a00e5be84671 + depends: + - libre2-11 2025.11.05 h60473fc_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libre2-11 >=2025.11.5 + - re2 + size: 27397 + timestamp: 1781312576962 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.7.19-py313h07c4f96_0.conda + sha256: 509803a927697426d0774a6a90ca593b0cf0e5744d30088934385f8c677ddbf5 + md5: 8c6ce8a7aa0cda751604a285b5982a75 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 AND CNRI-Python + license_family: PSF + purls: + - pkg:pypi/regex?source=hash-mapping + run_exports: {} + size: 416559 + timestamp: 1784433186983 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-2026.6.3-py313hafbe609_0.conda + sha256: 42f2aec0823d8493682a18993912d725a108b5a27a606ad5cf7b41ac307b9575 + md5: 0ff19d7aef2cd2f61cf12838c3138a2e + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + run_exports: {} + size: 299711 + timestamp: 1782831281126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.5-h7e3ee7f_1.conda + sha256: 7369ac21f3561e2203f5b1600ef0671270057380783ca4b9e15f679ba25db575 + md5: fa1c00d999e83ec20173c9775f71a1f1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - openssl >=3.5.7,<4.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - s2n >=1.7.5,<1.7.6.0a0 + size: 392607 + timestamp: 1783164766248 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-image-0.26.0-np2py313hb172dc5_0.conda + sha256: ec926e3c9ceb6ab8c1494d0ba199db5311c446b4a38e3ccfd18b41e251696087 + md5: 6b11ece96457f4f1bf078dbdba069ce6 + depends: + - imageio >=2.33,!=2.35.0 + - lazy-loader >=0.4 + - networkx >=3.0 + - numpy >=1.24 + - packaging >=21.0 + - pillow >=10.1 + - python + - scipy >=1.11.4 + - tifffile >=2022.8.12 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + - numpy >=1.23,<3 + constrains: + - astropy-base >=6.0 + - dask-core >=2023.2.0,!=2024.8.0 + - matplotlib-base >=3.7 + - pooch >=1.6.0 + - pyamg >=5.2 + - pywavelets >=1.6 + - scikit-learn >=1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scikit-image?source=hash-mapping + run_exports: {} + size: 18597670 + timestamp: 1766684360037 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.9.0-np2py313h16d504d_0.conda + sha256: f73359758333651f01f531ec2ab960ea0445bf89810066f52c3ab4218d755abe + md5: 79dc96526e178928df7363e80169e8c4 + depends: + - python + - numpy >=1.24.1 + - scipy >=1.10.0 + - joblib >=1.4.0 + - threadpoolctl >=3.5.0 + - narwhals >=2.0.1 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - _openmp_mutex >=4.5 + - libstdcxx >=14 + - python_abi 3.13.* *_cp313 + - numpy >=1.23,<3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scikit-learn?source=hash-mapping + run_exports: {} + size: 10208137 + timestamp: 1780401055093 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py313h4b8bb8b_0.conda + sha256: 3ffdca726208a7394b2aa9989e5b1718e28a6285329bbc10bea4e92066e14f49 + md5: a32f88181ff9a3451c71be1f17a7c799 + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=2.0.0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + run_exports: {} + size: 17171066 + timestamp: 1781912954186 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sdl2-2.32.56-h54a6638_0.conda + sha256: 987ad072939fdd51c92ea8d3544b286bb240aefda329f9b03a51d9b7e777f9de + md5: cdd138897d94dc07d99afe7113a07bec + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libgl >=1.7.0,<2.0a0 + - sdl3 >=3.2.22,<4.0a0 + - libegl >=1.7.0,<2.0a0 + license: Zlib + purls: [] + run_exports: + weak: + - sdl2 >=2.32.56,<3.0a0 + size: 589145 + timestamp: 1757842881000 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sdl3-3.4.12-hdeec2a5_0.conda + sha256: 2607340a7adbf7b7ec902892bce00c9fd35ccdb7f1e8d4ef1efd51641ad36a3c + md5: 1c36b749acf532314ad381f6c1663647 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - liburing >=2.14,<2.15.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + - libusb >=1.0.29,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - libgl >=1.7.0,<2.0a0 + - dbus >=1.16.2,<2.0a0 + - libdrm >=2.4.127,<2.5.0a0 + - pulseaudio-client >=17.0,<17.1.0a0 + - xorg-libxi >=1.8.3,<2.0a0 + - wayland >=1.25.0,<2.0a0 + - libegl >=1.7.0,<2.0a0 + - libxkbcommon >=1.13.2,<2.0a0 + - libudev1 >=257.13 + - libunwind >=1.8.3,<1.9.0a0 + - xorg-libxscrnsaver >=1.2.4,<2.0a0 + - libvulkan-loader >=1.4.341.0,<2.0a0 + - xorg-libxcursor >=1.2.3,<2.0a0 + - xorg-libxtst >=1.2.5,<2.0a0 + license: Zlib + purls: [] + run_exports: + weak: + - sdl3 >=3.4.12,<4.0a0 + size: 2156114 + timestamp: 1782948044627 +- conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.5.0-py313h78bf25f_0.conda + sha256: f9dfaf76745306d78f73aaff483faf9d0ba1144aae6f1326b01a67af457b35bf + md5: b127d964bb31b39d3431a063caf91e97 + depends: + - cryptography >=2.0 + - dbus + - jeepney >=0.6 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/secretstorage?source=hash-mapping + run_exports: {} + size: 33486 + timestamp: 1780858508391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/shaderc-2026.2-h718be3e_0.conda + sha256: c6e3280867e54c97996a4fedda0ab72c92d48d1d69258bddf910130df72c169d + md5: 6438976979721e2f60ec47327d8d38df + depends: + - __glibc >=2.17,<3.0.a0 + - glslang >=16,<17.0a0 + - libgcc >=14 + - libstdcxx >=14 + - spirv-tools >=2026,<2027.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - shaderc >=2026.2,<2026.3.0a0 + size: 113684 + timestamp: 1777360595361 +- conda: https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py313had47c43_2.conda + sha256: 0bf96349dd2cccba4faf6b98f2f3e02767cdc8b78a6bc1a0ee4f88bddee84917 + md5: 6e550dd748e9ac9b2925411684e076a1 + depends: + - __glibc >=2.17,<3.0.a0 + - geos >=3.14.1,<3.14.2.0a0 + - libgcc >=14 + - numpy >=1.23,<3 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/shapely?source=hash-mapping + run_exports: {} + size: 648024 + timestamp: 1762523698473 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sip-6.10.0-py313h7033f15_1.conda + sha256: bbb1e932c631a64e80c7462e99545f7a8798fea9ded8a94b7349221d9a3b9ac8 + md5: 10a462623c2d9527ace20f6ceed70bb6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - packaging + - ply + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - setuptools + - tomli + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sip?source=hash-mapping + run_exports: + weak: + - sip >=6.10.0,<6.11.0a0 + size: 686013 + timestamp: 1759437934008 +- conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + sha256: 48f3f6a76c34b2cfe80de9ce7f2283ecb55d5ed47367ba91e8bb8104e12b8f11 + md5: 98b6c9dc80eb87b2519b97bcf7e578dd + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - snappy >=1.2.2,<1.3.0a0 + size: 45829 + timestamp: 1762948049098 +- conda: https://conda.anaconda.org/conda-forge/linux-64/spirv-tools-2026.2-hb700be7_1.conda + sha256: 65a4e2fc5fb0677cff340ca314b25226d7eb94b2c75f733c0cab6176a8f46421 + md5: 6c08f1aeca6e195eaa2b2e9ee18a9891 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + constrains: + - spirv-headers >=1.4.350.1,<1.4.350.2.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - spirv-tools >=2026,<2027.0a0 + size: 2392774 + timestamp: 1784383717730 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.51-py313h54dd161_0.conda + sha256: 15f16c47557a040bcd083776672c1dfb7a2cd79a57b6cfc368ce0da6ded707fa + md5: 28d8cadca137a5cf4407b4a220bae38d + depends: + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sqlalchemy?source=hash-mapping + run_exports: {} + size: 3852066 + timestamp: 1781547880247 +- conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-4.0.1-hecca717_0.conda + sha256: 4a1d2005153b9454fc21c9bad1b539df189905be49e851ec62a6212c2e045381 + md5: 2a2170a3e5c9a354d09e4be718c43235 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - svt-av1 >=4.0.1,<4.0.2.0a0 + size: 2619743 + timestamp: 1769664536467 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.0.0-hab88423_2.conda + sha256: 30cb9355c2fefc20ff1a3d6566b9714d5614086a2524c07721fc344eb20515ae + md5: 7073b15f9364ebc118998601ac6ca6a6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libhwloc >=2.13.0,<2.13.1.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 182331 + timestamp: 1778673758649 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda + build_number: 103 + sha256: 43624eab22f5f29df7d6ffe914cf442f28fd559b55b290906255492826e636e8 + md5: 48a1049e710857572fc2a832aa394d9f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + constrains: + - xorg-libx11 >=1.8.13,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3550916 + timestamp: 1784229071544 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.7-py313h07c4f96_0.conda + sha256: ed2f17b2bc92389187b47e7444aa7d6ab6c70c3f3ba6bf2ced508027e60e82cb + md5: 9665531f18d2bcbc946e3ba0cf53ea91 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=compressed-mapping + run_exports: {} + size: 885824 + timestamp: 1781006802459 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ujson-5.13.0-py313h5d5ffb9_0.conda + sha256: e139122331bcb9b3b7a80fce0fdd976e0c82e677c4a6d9090132184d68eac731 + md5: 4acb20dbdca63a91bfa1ac4a00b9526e + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ujson?source=hash-mapping + run_exports: {} + size: 59893 + timestamp: 1781561596279 +- conda: https://conda.anaconda.org/conda-forge/linux-64/uvloop-0.22.1-py313h07c4f96_1.conda + sha256: 77a220ecf6c1467f94d6adda5fb1296f558f3f3044842dc0a52881eab5908dc0 + md5: 266caaa8701a13482ea924a77897b1e4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libuv >=1.51.0,<2.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT OR Apache-2.0 + purls: + - pkg:pypi/uvloop?source=hash-mapping + run_exports: {} + size: 590601 + timestamp: 1762472969139 +- conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-1.2.0-py313hafbe609_1.conda + sha256: e6d9c92ea12b8b3cd5d9a40ae1f3fec297b3e28c317e6bf16077f8e645a5fb12 + md5: bc58733908867566203fc93f520a3b02 + depends: + - python + - anyio >=3.0.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/watchfiles?source=hash-mapping + run_exports: {} + size: 391814 + timestamp: 1781180265966 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-hd6090a7_0.conda + sha256: 6b9e182021ef3a64ab3bf788ebdab6de6775035612237c639ccafc941639eb13 + md5: b34c5559f45d8996e3bc0b6250a6cc84 + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - wayland >=1.26.0,<2.0a0 + size: 340543 + timestamp: 1784249169392 +- conda: https://conda.anaconda.org/conda-forge/linux-64/websockets-16.1.1-py313hc25a8b5_0.conda + sha256: 2be32fddaa6049111898a25f63f83d5b865e99a83c01cc610d5476cd6de22b84 + md5: fb854989058a0248c81b821841d3409b + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/websockets?source=compressed-mapping + run_exports: {} + size: 372055 + timestamp: 1784377049841 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-2.2.2-py313h07c4f96_0.conda + sha256: 234b4888d4303c29f839408345f52194078716a369680920af09f6a8fef803a9 + md5: d6f92eb04d026a9e5b9c5d52c8cd65d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/wrapt?source=hash-mapping + run_exports: {} + size: 117401 + timestamp: 1782133645625 +- conda: https://conda.anaconda.org/conda-forge/linux-64/x264-1!164.3095-h166bdaf_2.tar.bz2 + sha256: 175315eb3d6ea1f64a6ce470be00fa2ee59980108f246d3072ab8b977cb048a5 + md5: 6c99772d483f566d59e25037fea2c4b1 + depends: + - libgcc-ng >=12 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - x264 >=1!164.3095,<1!165 + size: 897548 + timestamp: 1660323080555 +- conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 + sha256: 76c7405bcf2af639971150f342550484efac18219c0203c5ee2e38b8956fe2a0 + md5: e7f6ed84d4623d52ee581325c1587a6b + depends: + - libgcc-ng >=10.3.0 + - libstdcxx-ng >=10.3.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + weak: + - x265 >=3.5,<3.6.0a0 + size: 3357188 + timestamp: 1646609687141 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + sha256: ad8cab7e07e2af268449c2ce855cbb51f43f4664936eff679b1f3862e6e4b01d + md5: fdc27cb255a7a2cc73b7919a968b48f0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util >=0.4.1,<0.5.0a0 + size: 20772 + timestamp: 1750436796633 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + sha256: c2be9cae786fdb2df7c2387d2db31b285cf90ab3bfabda8fa75a596c3d20fc67 + md5: 4d1fc190b99912ed557a8236e958c559 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.13 + - libxcb >=1.17.0,<2.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-cursor >=0.1.6,<0.2.0a0 + size: 20829 + timestamp: 1763366954390 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + sha256: 94b12ff8b30260d9de4fd7a28cca12e028e572cbc504fd42aa2646ec4a5bded7 + md5: a0901183f08b6c7107aab109733a3c91 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + - xcb-util >=0.4.1,<0.5.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-image >=0.4.0,<0.5.0a0 + size: 24551 + timestamp: 1718880534789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + sha256: 546e3ee01e95a4c884b6401284bb22da449a2f4daf508d038fdfa0712fe4cc69 + md5: ad748ccca349aec3e91743e08b5e2b50 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + size: 14314 + timestamp: 1718846569232 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + sha256: 2d401dadc43855971ce008344a4b5bd804aca9487d8ebd83328592217daca3df + md5: 0e0cbe0564d03a99afd5fd7b362feecd + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + size: 16978 + timestamp: 1718848865819 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + sha256: 31d44f297ad87a1e6510895740325a635dd204556aa7e079194a0034cdd7e66a + md5: 608e0ef8256b81d04456e8d211eee3e8 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-wm >=0.4.2,<0.5.0a0 + size: 51689 + timestamp: 1718844051451 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.48-h280c20c_0.conda + sha256: 3b04afd5d1a65d2d27ac2d49a63b01ab8bcd875776779ec63e337370ed38afdc + md5: b233b41be0bf210989d57160ed39b394 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - xorg-libx11 >=1.8.13,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 441670 + timestamp: 1782027360439 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b + md5: fb901ff28063514abb6046c9ec2c4a45 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libice >=1.1.2,<2.0a0 + size: 58628 + timestamp: 1734227592886 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 + md5: 1c74ff8c35dcadf952a16f752ca5aa49 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libuuid >=2.38.1,<3.0a0 + - xorg-libice >=1.1.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libsm >=1.2.6,<2.0a0 + size: 27590 + timestamp: 1741896361728 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + sha256: 516d4060139dbb4de49a4dcdc6317a9353fb39ebd47789c14e6fe52de0deee42 + md5: 861fb6ccbc677bb9a9fb2468430b9c6a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libx11 >=1.8.13,<2.0a0 + size: 839652 + timestamp: 1770819209719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b + md5: b2895afaf55bf96a8c8282a2e47a5de0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxau >=1.0.12,<2.0a0 + size: 15321 + timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda + sha256: 048c103000af9541c919deef03ae7c5e9c570ffb4024b42ecb58dbde402e373a + md5: f2ba4192d38b6cef2bb2c25029071d90 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxcomposite >=0.4.7,<1.0a0 + size: 14415 + timestamp: 1770044404696 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + sha256: 832f538ade441b1eee863c8c91af9e69b356cd3e9e1350fff4fe36cc573fc91a + md5: 2ccd714aa2242315acaf0a67faea780b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + - xorg-libxrender >=0.9.11,<0.10.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxcursor >=1.2.3,<2.0a0 + size: 32533 + timestamp: 1730908305254 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + sha256: 43b9772fd6582bf401846642c4635c47a9b0e36ca08116b3ec3df36ab96e0ec0 + md5: b5fcc7172d22516e1f965490e65e33a4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdamage >=1.1.6,<2.0a0 + size: 13217 + timestamp: 1727891438799 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 + md5: 1dafce8548e38671bea82e3f5c6ce22f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdmcp >=1.1.5,<2.0a0 + size: 20591 + timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + sha256: 79c60fc6acfd3d713d6340d3b4e296836a0f8c51602327b32794625826bd052f + md5: 34e54f03dfea3e7a2dcf1453a85f1085 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxext >=1.3.7,<2.0a0 + size: 50326 + timestamp: 1769445253162 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda + sha256: 83c4c99d60b8784a611351220452a0a85b080668188dce5dfa394b723d7b64f4 + md5: ba231da7fccf9ea1e768caf5c7099b84 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxfixes >=6.0.2,<7.0a0 + size: 20071 + timestamp: 1759282564045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.3-hb03c661_0.conda + sha256: 495f99c8eacfa4ae2d8fed2a7f2105777af89acdc204df145d2bbbc380ac631b + md5: adba2e334082bb218db806d4c12277c9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxi >=1.8.3,<2.0a0 + size: 47717 + timestamp: 1779111857071 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxinerama-1.1.6-hecca717_0.conda + sha256: 3a9da41aac6dca9d3ff1b53ee18b9d314de88add76bafad9ca2287a494abcd86 + md5: 93f5d4b5c17c8540479ad65f206fea51 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxinerama >=1.1.6,<1.2.0a0 + size: 14818 + timestamp: 1769432261050 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda + sha256: 80ed047a5cb30632c3dc5804c7716131d767089f65877813d4ae855ee5c9d343 + md5: e192019153591938acf7322b6459d36e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxrandr >=1.5.5,<2.0a0 + size: 30456 + timestamp: 1769445263457 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 + md5: 96d57aba173e878a2089d5638016dc5e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxrender >=0.9.12,<0.10.0a0 + size: 33005 + timestamp: 1734229037766 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxscrnsaver-1.2.4-hb9d3cd8_0.conda + sha256: 58e8fc1687534124832d22e102f098b5401173212ac69eb9fd96b16a3e2c8cb2 + md5: 303f7a0e9e0cd7d250bb6b952cecda90 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxscrnsaver >=1.2.4,<2.0a0 + size: 14412 + timestamp: 1727899730073 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxshmfence-1.3.3-hb9d3cd8_0.conda + sha256: c0830fe9fa78d609cd9021f797307e7e0715ef5122be3f784765dad1b4d8a193 + md5: 9a809ce9f65460195777f2f2116bae02 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxshmfence >=1.3.3,<2.0a0 + size: 12302 + timestamp: 1734168591429 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda + sha256: 752fdaac5d58ed863bbf685bb6f98092fe1a488ea8ebb7ed7b606ccfce08637a + md5: 7bbe9a0cc0df0ac5f5a8ad6d6a11af2f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxi >=1.7.10,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxtst >=1.2.5,<2.0a0 + size: 32808 + timestamp: 1727964811275 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda + sha256: 64db17baaf36fa03ed8fae105e2e671a7383e22df4077486646f7dbf12842c9f + md5: 665d152b9c6e78da404086088077c844 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxxf86vm >=1.1.7,<2.0a0 + size: 18701 + timestamp: 1769434732453 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xorgproto-2025.1-hb03c661_0.conda + sha256: 7a8c64938428c2bfd016359f9cb3c44f94acc256c6167dbdade9f2a1f5ca7a36 + md5: aa8d21be4b461ce612d8f5fb791decae + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 570010 + timestamp: 1766154256151 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 85189 + timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h909a3a2_5.conda + sha256: 5fabe6cccbafc1193038862b0b0d784df3dae84bc48f12cac268479935f9c8b7 + md5: 6a0eb48e58684cca4d7acc8b7a0fd3c7 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zfp >=1.0.1,<2.0a0 + size: 277694 + timestamp: 1766549572069 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda + sha256: 245c9ee8d688e23661b95e3c6dd7272ca936fabc03d423cdb3cdee1bbcf9f2f2 + md5: c2a01a08fc991620a74b32420e97868a + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib 1.3.2 h25fd6f3_2 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 95931 + timestamp: 1774072620848 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + sha256: ea4e50c465d70236408cb0bfe0115609fd14db1adcd8bd30d8918e0291f8a75f + md5: 2aadb0d17215603a82a2a6b0afd9a4cb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - zlib-ng >=2.3.3,<2.4.0a0 + size: 122618 + timestamp: 1770167931827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + sha256: e6921de3669e1bbd5d050a3b771b46a887e7f4ffeb1ddd5e4d9fb01062a2f6e9 + md5: 710d4663806d0f72b2fb414e936223b5 + depends: + - python + - cffi >=1.11 + - zstd >=1.5.7,<1.5.8.0a0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + run_exports: {} + size: 471496 + timestamp: 1762512679097 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: 3845f3d75991bae0fb90884662f4327c + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 8144 + timestamp: 1784221492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/_x86_64-microarch-level-1-4_level1.conda + build_number: 4 + sha256: a7110d0259d2d93f9b980fdee8a29b5bbcd8d31bee1e572abe2d7b415dff8844 + md5: 27734571c37b81e1c9464d925fbd9d94 + depends: + - __archspec 1.* ^(core2|k10|nocona|x86_64|bulldozer|ivybridge|nehalem|piledriver|sandybridge|steamroller|westmere|x86_64_v2|broadwell|cannonlake|excavator|haswell|mic_knl|skylake|x86_64_v3|zen|zen2|zen3|cascadelake|icelake|sapphirerapids|skylake_avx512|x86_64_v4|zen4|zen5)$ + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7902 + timestamp: 1781714818968 +- conda: https://conda.anaconda.org/conda-forge/noarch/adbc-driver-postgresql-1.12.0-pyha770c72_0.conda + sha256: 0852e3b1ade3277a276d33d79e673f10574f28fa0e10ddbbede2041d2897439d + md5: 18dd6827f0d0f70d8f3de12a9b59f11f + depends: + - adbc-driver-manager >=1.12.0,<2.0a0 + - importlib_resources + - libadbc-driver-postgresql >=1.12.0,<1.12.1.0a0 + - python >=3.10 + constrains: + - pyarrow >=8.0.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/adbc-driver-postgresql?source=hash-mapping + run_exports: {} + size: 26806 + timestamp: 1785224517310 +- conda: https://conda.anaconda.org/conda-forge/noarch/adbc-driver-sqlite-1.12.0-pyha770c72_0.conda + sha256: 130f0ef1b727e0523aa72e430d6378ed949a80ca6e5ccd2ebc18f075dc831686 + md5: f19f610b832c17ce6c5b7cca4541bad5 + depends: + - adbc-driver-manager >=1.12.0,<2.0a0 + - importlib_resources + - libadbc-driver-sqlite >=1.12.0,<1.12.1.0a0 + - python >=3.10 + constrains: + - pyarrow >=8.0.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/adbc-driver-sqlite?source=hash-mapping + run_exports: {} + size: 25384 + timestamp: 1785224536166 +- conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda + sha256: a362b4f5c96a0bf4def96be1a77317e2730af38915eb9bec85e2a92836501ed7 + md5: b3f0179590f3c0637b7eb5309898f79e + depends: + - __unix + - hicolor-icon-theme + - librsvg + license: LGPL-3.0-or-later OR CC-BY-SA-3.0 + license_family: LGPL + purls: [] + run_exports: {} + size: 631452 + timestamp: 1758743294412 +- conda: https://conda.anaconda.org/conda-forge/noarch/aiofiles-25.1.0-pyhcf101f3_1.conda + sha256: f37288164cf28b916b184b0a5e89225e17af0e0c9bcd4d0dc39e5a597fcfeed1 + md5: a6435dc39a8031d33d6d52859913e939 + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/aiofiles?source=hash-mapping + run_exports: {} + size: 24824 + timestamp: 1767290524894 +- conda: https://conda.anaconda.org/conda-forge/noarch/aiosqlite-0.22.1-pyhcf101f3_1.conda + sha256: 8b23dfe615f958724269733d348139fb7615f29e0d35a9b556fe823d65a941a8 + md5: 9be31ce1f8e613fbb3b140430e109d41 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/aiosqlite?source=hash-mapping + run_exports: {} + size: 22318 + timestamp: 1767730199932 +- conda: https://conda.anaconda.org/conda-forge/noarch/alembic-1.18.5-pyhcf101f3_0.conda + sha256: 3d523e517cb0b2a3cbfaf56a6f0d897062ab741eab1a8a4b3cc415a42bdef20a + md5: 5e2f9d485f398f312a6b0f17e11d9c6f + depends: + - python >=3.10 + - sqlalchemy >=1.4.23 + - mako + - typing_extensions >=4.12 + - tomli + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/alembic?source=compressed-mapping + run_exports: {} + size: 185355 + timestamp: 1782460469542 +- conda: https://conda.anaconda.org/conda-forge/noarch/amostra-0.2.4-pyhd8ed1ab_0.tar.bz2 + sha256: 9ba51432aefd584aa7d17d7172f312ef28181f0ad12e7184ffeb723308049351 + md5: 209295c42b492df027a73830eda5dda4 + depends: + - doct + - jsonschema + - mongoquery + - pymongo + - python >=3.6 + - pyyaml + - requests + - tornado + - ujson + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/amostra?source=hash-mapping + run_exports: {} + size: 21740 + timestamp: 1646685872837 +- conda: https://conda.anaconda.org/conda-forge/noarch/analysisstore-0.0.6-pyhd8ed1ab_1.tar.bz2 + sha256: f60ea53682b1eb790fc98b5e5312d0b42c2acc80ec719e695f4161ce8f3a2c90 + md5: 14c9fe1ecd455179774099254356f7f4 + depends: + - doct + - jsonschema + - mongoquery + - pymongo + - python >=3.6 + - pyyaml + - requests + - six + - tornado >=5 + - ujson + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/analysisstore?source=hash-mapping + run_exports: {} + size: 17881 + timestamp: 1644954404261 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-doc-0.0.5-pyhcf101f3_0.conda + sha256: a0a320c709fded9b5178108dedebf81a1f5a5d9c7720e3af50c1ab058dadd296 + md5: d68ec17cb915cab4642357417ec0a104 + depends: + - python >=3.10 + - python + license: MIT + purls: + - pkg:pypi/annotated-doc?source=compressed-mapping + run_exports: {} + size: 16785 + timestamp: 1785335690199 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + sha256: b8fcb994134d3918d1c64a9d62f4168ff85d5bfb89e698102fe4ed679fe0df24 + md5: 108c928d2a8551832dbc762b535e90bb + depends: + - python >=3.10 + - typing-extensions >=4.0.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/annotated-types?source=hash-mapping + run_exports: {} + size: 19461 + timestamp: 1784935220549 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + sha256: e36998c5e860e26b22e5dbcd5726dd0c4eabad949c84d383cad8512757bbf6a1 + md5: fb568fbae6908ba86a090a85a089d11f + depends: + - exceptiongroup >=1.0.2 + - idna >=2.8 + - python >=3.10 + - typing_extensions >=4.5 + - python + constrains: + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/anyio?source=compressed-mapping + run_exports: {} + size: 164465 + timestamp: 1783889660383 +- conda: https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyhd8ed1ab_1.conda + sha256: 5b9ef6d338525b332e17c3ed089ca2f53a5d74b7a7b432747d29c6466e39346d + md5: f4e90937bbfc3a4a92539545a37bb448 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/appdirs?source=hash-mapping + run_exports: {} + size: 14835 + timestamp: 1733754069532 +- conda: https://conda.anaconda.org/conda-forge/noarch/area-detector-handlers-0.0.10-pyhd8ed1ab_0.tar.bz2 + sha256: 75ea052a3fd16d518612e2165b7fb2b53c91226552b557755949ab301b871550 + md5: e410310445f38d0371ab90f76d27c798 + depends: + - dask + - entrypoints + - h5py + - pandas + - python >=3.6 + - tifffile >=2020.8.25 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/area-detector-handlers?source=hash-mapping + run_exports: {} + size: 21977 + timestamp: 1664603052349 +- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad + md5: 8ac12aff0860280ee0cff7fa2cf63f3b + depends: + - argon2-cffi-bindings + - python >=3.9 + - typing-extensions + constrains: + - argon2_cffi ==999 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi?source=hash-mapping + run_exports: {} + size: 18715 + timestamp: 1749017288144 +- conda: https://conda.anaconda.org/conda-forge/noarch/asgi-correlation-id-5.0.1-pyhd8ed1ab_0.conda + sha256: 156f7055ca5fd3d2a0135eef9385e28be76fd6f18373c83f028d43985aee86a6 + md5: ce492e9567c6393d46c61ecf7ea3b1ab + depends: + - python >=3.10 + - starlette >=0.18 + license: MIT + purls: + - pkg:pypi/asgi-correlation-id?source=hash-mapping + run_exports: {} + size: 18249 + timestamp: 1785264660811 +- conda: https://conda.anaconda.org/conda-forge/noarch/astropy-iers-data-0.2026.7.27.0.56.29-pyhd8ed1ab_0.conda + sha256: 91b9cb5b6e2d9da1ea399f7496c5235aa9b9b9cf0de2ef47b0f5c006426b2b04 + md5: 06d52014a67f75b9e49fab20989c6804 + depends: + - python >=3.10 + license: BSD-3-Clause + purls: + - pkg:pypi/astropy-iers-data?source=hash-mapping + run_exports: {} + size: 1257265 + timestamp: 1785121588668 +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 89d495168582cb00428dad699d149624 + depends: + - python >=3.10 + constrains: + - astroid >=2,<5 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/asttokens?source=hash-mapping + run_exports: {} + size: 34639 + timestamp: 1783975742052 +- conda: https://conda.anaconda.org/conda-forge/noarch/async-timeout-5.0.1-pyhcf101f3_2.conda + sha256: 6638b68ab2675d0bed1f73562a4e75a61863b903be1538282cddb56c8e8f75bd + md5: 0d0ef7e4a0996b2c4ac2175a12b3bf69 + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/async-timeout?source=hash-mapping + run_exports: {} + size: 13559 + timestamp: 1767290444597 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab + md5: c6b0543676ecb1fb2d7643941fe375f2 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/attrs?source=hash-mapping + run_exports: {} + size: 64927 + timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/awkward-2.11.0-pyhcf101f3_0.conda + sha256: 7e1eac785043901bfcda6ec3ec3eb82475194e6197e4551bdea0fc481d29b6d7 + md5: 3c3ee515c2ea48e913ef072829f9f328 + depends: + - python >=3.10 + - awkward-cpp ==54 + - importlib-metadata >=4.13.0 + - numpy >=1.18.0 + - packaging + - typing_extensions >=4.1.0 + - fsspec >=2022.11.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/awkward?source=hash-mapping + run_exports: {} + size: 528608 + timestamp: 1784660128611 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda + sha256: e1c3dc8b5aa6e12145423fed262b4754d70fec601339896b9ccf483178f690a6 + md5: 767d508c1a67e02ae8f50e44cacfadb2 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7069 + timestamp: 1733218168786 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhcf101f3_2.conda + sha256: 25abdb37e186f0d6ac3b774a63c81c5bc4bf554b5096b51343fa5e7c381193b1 + md5: bea46844deb274b2cc2a3a941745fa73 + depends: + - python >=3.10 + - backports + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/backports-tarfile?source=hash-mapping + run_exports: {} + size: 35739 + timestamp: 1767290467820 +- conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-base-1.15.1-pyhd8ed1ab_0.conda + sha256: a2689c53cf103f5b25d47bc8744de9d1789f99630a01657ec00a0ec62f954e77 + md5: 433607e935ea7544fb926b23b4a11613 + depends: + - cycler + - event-model >=1.14.0 + - historydict + - msgpack-numpy + - msgpack-python + - numpy + - opentelemetry-api + - python >=3.10 + - toolz + - tqdm >=4.44 + - zict + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/bluesky?source=hash-mapping + run_exports: {} + size: 287265 + timestamp: 1778080167723 +- conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-kafka-0.10.0-pyhd8ed1ab_1.conda + sha256: 2644b99ec5e38d1cf4114bb075698b804210e80caf0e08168bc1149a37427dab + md5: 101282e69efa43f93ff4d40591adf999 + depends: + - bluesky-base + - msgpack-numpy + - msgpack-python + - python >=3.10 + - python-confluent-kafka + - suitcase-mongo + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/bluesky-kafka?source=hash-mapping + run_exports: {} + size: 31972 + timestamp: 1753970402988 +- conda: https://conda.anaconda.org/conda-forge/noarch/bluesky-tiled-plugins-2.0.9-pyhd8ed1ab_0.conda + sha256: c59912702e7f28bf53ac1e85aa913675e381b091c3da3f4767221b93caec1ddb + md5: ad274191d52527cd66c48c9fe3d46189 + depends: + - dask-core + - event-model + - mongoquery + - python >=3.10 + - pytz + - tiled-client >=0.2.0 + - tzlocal + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/bluesky-tiled-plugins?source=hash-mapping + run_exports: {} + size: 59118 + timestamp: 1784707204742 +- conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.2-pyhd8ed1ab_0.conda + sha256: 577c2614f3a59512697c1ffe2021c5194818b55f734006fcdeb6c6921b514c44 + md5: b2781afb860ae9ed84cb92bdebbc83ea + depends: + - contourpy >=1.2 + - jinja2 >=2.9 + - narwhals >=1.13 + - numpy >=1.16 + - packaging >=16.8 + - pillow >=7.1.0 + - python >=3.10 + - pyyaml >=3.10 + - tornado >=6.2 + - xyzservices >=2021.09.1 + constrains: + - panel >=1.8.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/bokeh?source=compressed-mapping + run_exports: {} + size: 4292921 + timestamp: 1785249803504 +- conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.1.0-pyhd8ed1ab_0.conda + sha256: ca44f906756aaa908645c9bcef31a132fc82e7934777aa6bb45c12edada6a58c + md5: 92c88d22e52af7b672605b12a31bf0bb + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/boltons?source=compressed-mapping + run_exports: {} + size: 309453 + timestamp: 1784706150046 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 + md5: 0f51e2391ade309db462a55611263e9c + depends: + - __unix + license: ISC + purls: [] + run_exports: {} + size: 131780 + timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + noarch: python + sha256: 0d00dd61cb91b1bd1536600c64c9db4f94f3c699fec42864d0a2f4bc2ad8c3e8 + md5: 1990eb3f49022846fbc7fc9624a0ee43 + depends: + - cached_property >=1.5.2,<1.5.3.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=compressed-mapping + run_exports: {} + size: 6836 + timestamp: 1783242914545 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + sha256: b1808a7811b5688d045b204425bb7ee824b340b40025b4ad0a39b4db1f4f1c98 + md5: f71e6840332854fd2eac6c3229768a9c + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=compressed-mapping + run_exports: {} + size: 14752 + timestamp: 1783242913845 +- conda: https://conda.anaconda.org/conda-forge/noarch/cachetools-7.1.6-pyhd8ed1ab_0.conda + sha256: 3705f855a35c8fb466f31d021af9e0400a3fdf48b8b9f989225553b86e71bd2d + md5: b3bb29ee0df44b08bb005ce0f576f84c + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cachetools?source=compressed-mapping + run_exports: {} + size: 21911 + timestamp: 1784863612738 +- conda: https://conda.anaconda.org/conda-forge/noarch/canonicaljson-2.0.0-pyhd8ed1ab_0.conda + sha256: 2b73c926cf83265cf394ba9ba11839b0a7008ad2af1c55f1e8002f81cb682d00 + md5: 7d027ed4883d11a8ba7b27e0dd56df47 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/canonicaljson?source=hash-mapping + run_exports: {} + size: 13344 + timestamp: 1737528464034 +- conda: https://conda.anaconda.org/conda-forge/noarch/caproto-1.3.0-pyhd8ed1ab_0.conda + sha256: 076f0ab61cbfe828e17d2cf0bbd7efaf20b6546828eb2d528379504560f48919 + md5: d99697865151fe2aff7ca4284387be7d + depends: + - dpkt + - netifaces + - numpy + - python >=3.10 + constrains: + - curio >=1.2 + - trio >=0.18.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/caproto?source=hash-mapping + run_exports: {} + size: 280352 + timestamp: 1770670039751 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: 37e13edbe3b48f1095a9d085ef9cd83b + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=compressed-mapping + run_exports: {} + size: 137015 + timestamp: 1784717699092 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.9-pyhd8ed1ab_0.conda + sha256: 8d8813ef655b4e75e4fb897abd83ad548882efad7b4e836b021b797f42780799 + md5: d154b40b109e503430979e8a8d099eaf + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + run_exports: {} + size: 61418 + timestamp: 1783505332569 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyhc90fa1f_0.conda + sha256: ccc4787f511964f9a1f2d2d2859c91c5d571fb60f7f09d4c4e092c9b7a94e671 + md5: 2c4bd6aeb90bb157456841c3270a0d92 + depends: + - __unix + - python + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/click?source=compressed-mapping + run_exports: {} + size: 107155 + timestamp: 1783085363526 +- conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + sha256: 4c287c2721d8a34c94928be8fe0e9a85754e90189dd4384a31b1806856b50a67 + md5: 61b8078a0905b12529abc622406cb62c + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cloudpickle?source=hash-mapping + run_exports: {} + size: 27353 + timestamp: 1765303462831 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + run_exports: {} + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 + md5: 2da13f2b299d8e1995bafbbe9689a2f7 + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/comm?source=hash-mapping + run_exports: {} + size: 14690 + timestamp: 1753453984907 +- conda: https://conda.anaconda.org/conda-forge/noarch/conftrak-0.0.9-pyhd8ed1ab_0.conda + sha256: f104af5847520dd9a866f59bb7389327300005fab6de5295c8943296dcdb5936 + md5: 65c908dcd96b970d71e061f00a270a74 + depends: + - doct + - jsonschema + - pymongo + - python >=3.6 + - pyyaml + - requests + - tornado >=5 + - ujson + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/conftrak?source=hash-mapping + run_exports: {} + size: 20526 + timestamp: 1706558882984 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.14-py313hd8ed1ab_100.conda + noarch: generic + sha256: 42995d97d7e83d2bedbe8173bc9aa022ea412bf33dd2ff0e3db2c01a5242cd0a + md5: 22ff6a23190a29024b0df04b4caa0c66 + depends: + - python >=3.13,<3.14.0a0 + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + run_exports: {} + size: 48337 + timestamp: 1781257766256 +- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + sha256: bb47aec5338695ff8efbddbc669064a3b10fe34ad881fb8ad5d64fbfa6910ed1 + md5: 4c2a8fef270f6c69591889b93f9f55c1 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cycler?source=hash-mapping + run_exports: {} + size: 14778 + timestamp: 1764466758386 +- conda: https://conda.anaconda.org/conda-forge/noarch/dask-2026.7.1-pyhc364b38_0.conda + sha256: 6fe2135c130f5832841d5fa5cd6dbadf82d84f61858ae8e2dfadf71ab1c8fb88 + md5: 07d4f6b76ccd0a3721c0964dac50db99 + depends: + - python >=3.10 + - dask-core >=2026.7.1,<2026.7.2.0a0 + - distributed >=2026.7.1,<2026.7.2.0a0 + - cytoolz >=0.11.2 + - lz4 >=4.3.2 + - numpy >=1.26 + - pandas >=2.0 + - bokeh >=3.1.0 + - jinja2 >=2.10.3 + - pyarrow >=16.0 + - python + constrains: + - openssl !=1.1.1e + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/dask?source=compressed-mapping + run_exports: {} + size: 10399 + timestamp: 1784056179014 +- conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2026.7.1-pyhc364b38_0.conda + sha256: feef96841d636fc35c3fef7c7276fa9c175baf4444896073bee65f08abe6ac91 + md5: 7e9fcc61c119f585f2b7b491147df821 + depends: + - python >=3.10 + - click >=8.1 + - cloudpickle >=3.0.0 + - fsspec >=2021.9.0 + - packaging >=20.0 + - partd >=1.4.0 + - pyyaml >=5.4.1 + - toolz >=0.12.0 + - importlib-metadata >=4.13.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/dask?source=compressed-mapping + run_exports: {} + size: 1074755 + timestamp: 1784031128995 +- conda: https://conda.anaconda.org/conda-forge/noarch/databroker-2.0.1-pyhd8ed1ab_0.conda + sha256: 6b4e065f3ae659f0d26f3a78282eedf6a9e4cef5fd2699a772401dac026a11a1 + md5: af7b895241d969c615797ab5dfcb196d + depends: + - area-detector-handlers + - bluesky-tiled-plugins + - boltons + - cachetools + - doct + - entrypoints + - event-model + - fastapi + - glue-core + - humanize + - jinja2 + - jsonschema + - mongomock + - mongoquery + - msgpack-python >=1.0.0 + - orjson + - pims + - pydantic + - pymongo + - python >=3.10 + - pytz + - requests + - starlette + - suitcase-mongo >=0.6.0 + - tiled >=0.1.0b39 + - tiled-client + - tiled-server + - toolz + - tzlocal + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/databroker?source=hash-mapping + run_exports: {} + size: 141744 + timestamp: 1779940315643 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.3.1-pyhd8ed1ab_0.conda + sha256: 430bd9d731b265f0bedb3183ac3ecfaa1656390c092b6e864ff8cc1229843c8c + md5: 61dcf784d59ef0bd62c57d982b154ace + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/decorator?source=compressed-mapping + run_exports: {} + size: 16102 + timestamp: 1779115228886 +- conda: https://conda.anaconda.org/conda-forge/noarch/deprecated-1.3.1-pyhd8ed1ab_1.conda + sha256: 7d57a7b8266043ffb99d092ebc25e89a0a2490bed4146b9432c83c2c476fa94d + md5: 5498feb783ab29db6ca8845f68fa0f03 + depends: + - python >=3.10 + - wrapt <3,>=1.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/deprecated?source=hash-mapping + run_exports: {} + size: 15896 + timestamp: 1768934186726 +- conda: https://conda.anaconda.org/conda-forge/noarch/dill-0.4.1-pyhcf101f3_0.conda + sha256: 1ef84c0cc4efd0c2d58c3cb365945edbd9ee42a1c54514d1ccba4b641005f757 + md5: 080a808fce955026bf82107d955d32da + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/dill?source=hash-mapping + run_exports: {} + size: 95462 + timestamp: 1768863743943 +- conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2026.7.1-pyhc364b38_0.conda + sha256: bae0d85df264228cbd485b444bd1f5135e32093e32503aa72e4b4b61636d12b2 + md5: f06a1abc946ca9b6107851cf148621f3 + depends: + - python >=3.10 + - click >=8.0 + - cloudpickle >=3.0.0 + - cytoolz >=0.12.0 + - dask-core >=2026.7.1,<2026.7.2.0a0 + - jinja2 >=2.10.3 + - locket >=1.0.0 + - msgpack-python >=1.0.2 + - packaging >=20.0 + - psutil >=5.8.0 + - pyyaml >=5.4.1 + - sortedcontainers >=2.0.5 + - tblib >=1.6.0 + - toolz >=0.12.0 + - tornado >=6.2.0 + - zict >=3.0.0 + - python + constrains: + - openssl !=1.1.1e + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/distributed?source=hash-mapping + run_exports: {} + size: 843078 + timestamp: 1784043304773 +- conda: https://conda.anaconda.org/conda-forge/noarch/dnspython-2.8.0-pyhcf101f3_0.conda + sha256: ef1e7b8405997ed3d6e2b6722bd7088d4a8adf215e7c88335582e65651fb4e05 + md5: d73fdc05f10693b518f52c994d748c19 + depends: + - python >=3.10,<4.0.0 + - sniffio + - python + constrains: + - aioquic >=1.2.0 + - cryptography >=45 + - httpcore >=1.0.0 + - httpx >=0.28.0 + - h2 >=4.2.0 + - idna >=3.10 + - trio >=0.30 + - wmi >=1.5.1 + license: ISC + purls: + - pkg:pypi/dnspython?source=hash-mapping + run_exports: {} + size: 196500 + timestamp: 1757292856922 +- conda: https://conda.anaconda.org/conda-forge/noarch/doct-1.1.0-pyhd8ed1ab_1.conda + sha256: dd9bb5fa0f71bf2abd6cb95cda8bb0d4c85c60d98e798a069e3a88b11fc9530f + md5: 0308aef1583bf38f319746d070802538 + depends: + - humanize + - prettytable + - python >=3.9 + - six + license: BSD 3-Clause + purls: + - pkg:pypi/doct?source=hash-mapping + run_exports: {} + size: 9642 + timestamp: 1734372607001 +- conda: https://conda.anaconda.org/conda-forge/noarch/donfig-0.8.1.post1-pyhd8ed1ab_1.conda + sha256: d58e97d418f71703e822c422af5b9c431e3621a0ecdc8b0334c1ca33e076dfe7 + md5: c56a7fa5597ad78b62e1f5d21f7f8b8f + depends: + - python >=3.9 + - pyyaml + license: MIT + license_family: MIT + purls: + - pkg:pypi/donfig?source=hash-mapping + run_exports: {} + size: 22491 + timestamp: 1734368817583 +- conda: https://conda.anaconda.org/conda-forge/noarch/dpkt-1.9.8-pyhd8ed1ab_1.conda + sha256: 654dc0a018ff3ab47dd7d2ed53cec2878a31668c136202ccc3a0c6ece491a189 + md5: 09e43762ceba1ce023431be7104a5d81 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/dpkt?source=hash-mapping + run_exports: {} + size: 151982 + timestamp: 1734321907078 +- conda: https://conda.anaconda.org/conda-forge/noarch/ecdsa-0.19.2-pyhd8ed1ab_0.conda + sha256: 279bba0bcb2248ec21807fcb1459b52abff42154811b85b4a0c62c54aba6773f + md5: d3422625946166c45d353f5b96fc02da + depends: + - gmpy2 + - python >=3.10 + - six >=1.9.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ecdsa?source=hash-mapping + run_exports: {} + size: 129113 + timestamp: 1774556826565 +- conda: https://conda.anaconda.org/conda-forge/noarch/echo-0.15.0-pyhd8ed1ab_0.conda + sha256: 1bc8ba15b0ba9d7d02ff2a2cd309a4eb0680360d81beb119bb569744588f67d2 + md5: d3445ebf229eefc9abfcf58e0f4f8be2 + depends: + - importlib_metadata + - numpy + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/echo?source=hash-mapping + run_exports: {} + size: 49073 + timestamp: 1775806041517 +- conda: https://conda.anaconda.org/conda-forge/noarch/email-validator-2.3.0-pyhd8ed1ab_0.conda + sha256: c37320864c35ef996b0e02e289df6ee89582d6c8e233e18dc9983375803c46bb + md5: 3bc0ac31178387e8ed34094d9481bfe8 + depends: + - dnspython >=2.0.0 + - idna >=2.0.0 + - python >=3.10 + license: Unlicense + purls: + - pkg:pypi/email-validator?source=hash-mapping + run_exports: {} + size: 46767 + timestamp: 1756221480106 +- conda: https://conda.anaconda.org/conda-forge/noarch/email_validator-2.3.0-hd8ed1ab_0.conda + sha256: 6a518e00d040fcad016fb2dde29672aa3476cd9ae33ea5b7b257222e66037d89 + md5: 2452e434747a6b742adc5045f2182a8e + depends: + - email-validator >=2.3.0,<2.3.1.0a0 + license: Unlicense + purls: [] + run_exports: {} + size: 7077 + timestamp: 1756221480651 +- conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_1.conda + sha256: 80f579bfc71b3dab5bef74114b89e26c85cb0df8caf4c27ab5ffc16363d57ee7 + md5: 3366592d3c219f2731721f11bc93755c + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/entrypoints?source=hash-mapping + run_exports: {} + size: 11259 + timestamp: 1733327239578 +- conda: https://conda.anaconda.org/conda-forge/noarch/et_xmlfile-2.0.0-pyhd8ed1ab_1.conda + sha256: 2209534fbf2f70c20661ff31f57ab6a97b82ee98812e8a2dcb2b36a0d345727c + md5: 71bf9646cbfabf3022c8da4b6b4da737 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/et-xmlfile?source=hash-mapping + run_exports: {} + size: 21908 + timestamp: 1733749746332 +- conda: https://conda.anaconda.org/conda-forge/noarch/event-model-1.24.0-pyhd8ed1ab_0.conda + sha256: 56bd86690e0c517ab38d1634dd9b7f3122ea56894a5e1a6866c95448d0093536 + md5: b343480b1d751a229b31014d54bfb50c + depends: + - importlib-resources + - jsonschema >=3 + - numpy + - python >=3.10 + - typing_extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/event-model?source=hash-mapping + run_exports: {} + size: 58249 + timestamp: 1780492261356 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + run_exports: {} + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad + md5: ff9efb7f7469aed3c4a8106ffa29593c + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/executing?source=hash-mapping + run_exports: {} + size: 30753 + timestamp: 1756729456476 +- conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.140.13-h2fa8227_0.conda + sha256: 5f000929a0074d1b4ae51799bf03adb7a3f29e12c76669f9e42ccaca7983f088 + md5: 8ed1869f390c0661441b148e246c109e + depends: + - fastapi-core ==0.140.13 pyhcf101f3_0 + - email_validator + - fastapi-cli + - fastar + - httpx + - jinja2 + - pydantic-extra-types + - pydantic-settings + - python-multipart + - uvicorn-standard + license: MIT + purls: + - pkg:pypi/fastapi?source=compressed-mapping + run_exports: {} + size: 4856 + timestamp: 1785328932437 +- conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-cli-0.0.32-pyhcf101f3_0.conda + sha256: 904e4b2abe0345ac9a51d2f64b716918ef3a9ad496252f8874864fb0ffbe8d31 + md5: 873835dd2a01dd8844086f1cde2067ce + depends: + - python >=3.10 + - rich-toolkit >=0.14.8 + - tomli >=2.0.0 + - typer >=0.16 + - uvicorn-standard >=0.15.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/fastapi-cli?source=hash-mapping + run_exports: {} + size: 20791 + timestamp: 1784211753962 +- conda: https://conda.anaconda.org/conda-forge/noarch/fastapi-core-0.140.13-pyhcf101f3_0.conda + sha256: 35123d3b50c19efc1d1f27784e8df972081d3052303c92627743f56d493eef5f + md5: 388d8ffe17c6dd778cdd881e9e4c1da8 + depends: + - python >=3.10 + - annotated-doc >=0.0.2 + - starlette >=0.46.0 + - typing_extensions >=4.8.0 + - typing-inspection >=0.4.2 + - pydantic >=2.9.0 + - python + constrains: + - email_validator >=2.0.0 + - fastapi-cli >=0.0.8 + - fastar >=0.9.0 + - httpx >=0.23.0,<1.0.0 + - jinja2 >=3.1.5 + - pydantic-extra-types >=2.0.0 + - pydantic-settings >=2.0.0 + - python-multipart >=0.0.18 + - uvicorn-standard >=0.12.0 + license: MIT + purls: + - pkg:pypi/fastapi?source=compressed-mapping + run_exports: {} + size: 104834 + timestamp: 1785328932437 +- conda: https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda + sha256: acdb7b73d84268773fcc8192965994554411edc488ec3447925a62154e9d3baa + md5: f1e618f2f783427019071b14a111b30d + depends: + - python >=3.9 + - typing-extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/flexcache?source=hash-mapping + run_exports: {} + size: 16674 + timestamp: 1733663669958 +- conda: https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda + sha256: 9bdad0cd9fb6d67e48798c03930d634ea2d33a894d30439d3d7bdffd3c21af7b + md5: 6dc4e43174cd552452fdb8c423e90e69 + depends: + - python >=3.9 + - typing-extensions + - typing_extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/flexparser?source=hash-mapping + run_exports: {} + size: 28686 + timestamp: 1733663636245 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b + md5: 0c96522c6bdaed4b1566d11387caaf45 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 397370 + timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c + md5: 34893075a5c9e55cdafac56607368fc6 + license: OFL-1.1 + license_family: Other + purls: [] + run_exports: {} + size: 96530 + timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 + md5: 4d59c254e01d9cde7957100457e2d5fb + license: OFL-1.1 + license_family: Other + purls: [] + run_exports: {} + size: 700814 + timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 + md5: 49023d73832ef61042f6a237cb2687e7 + license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 + license_family: Other + purls: [] + run_exports: {} + size: 1620504 + timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 + md5: fee5683a3f04bd15cbd8318b096a27ab + depends: + - fonts-conda-forge + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 3667 + timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 + depends: + - font-ttf-ubuntu + - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono + - font-ttf-source-code-pro + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 4059 + timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + sha256: 3cd1c985695d8114bdba2a4a38c87e86d633fadd7cfe7a6733ebb3fe807fdc86 + md5: b9176565976c773a0739bd83deaf06cc + depends: + - python >=3.10 + license: BSD-3-Clause + purls: + - pkg:pypi/fsspec?source=compressed-mapping + run_exports: {} + size: 151868 + timestamp: 1785325238671 +- conda: https://conda.anaconda.org/conda-forge/noarch/glue-core-1.27.0-pyhd8ed1ab_0.conda + sha256: ea9f910ff3887f76730e518fcf8126fde587668ffc2d8e1abc5f8c4cbd928206 + md5: 6b7f7f5fd11fd68536abc49bb0d7fc3a + depends: + - astropy-base >=4.0 + - dill >=0.2 + - echo >=0.12 + - fast-histogram >=0.12 + - h5py >=2.10 + - importlib-metadata >=3.6 + - importlib-resources >=1.3 + - ipython >=4.0 + - matplotlib-base >=3.2 + - mpl-scatter-density >=0.8 + - numpy >=1.17 + - openpyxl >=3.0 + - pandas >=1.2 + - python >=3.10 + - scikit-image + - scipy >=1.1 + - shapely >=2.0 + - xlrd >=1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/glue-core?source=hash-mapping + run_exports: {} + size: 880651 + timestamp: 1782896713294 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 + depends: + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h11?source=hash-mapping + run_exports: {} + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.0-pyhcf101f3_0.conda + sha256: 1bdffcb701cf1f4429733fc2e194995bab5ecc71073d84a434dcfb57049a4265 + md5: aae3214aab65ae635fbb3cc455596a86 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.2,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=compressed-mapping + run_exports: {} + size: 100184 + timestamp: 1784898236952 +- conda: https://conda.anaconda.org/conda-forge/noarch/h5netcdf-1.8.1-pyhd8ed1ab_0.conda + sha256: 5bf081c0f21a57fc84b5000d53f043d63638b77dcc2137f87511a4581838c9f6 + md5: ca7f9ba8762d3e360e47917a10e23760 + depends: + - h5py + - numpy + - packaging + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/h5netcdf?source=hash-mapping + run_exports: {} + size: 57732 + timestamp: 1769241877548 +- conda: https://conda.anaconda.org/conda-forge/noarch/historydict-1.2.6-pyhd8ed1ab_1.conda + sha256: 325656b615af237c60f7596b40d654b489af4d4d128aa936c3ef287a5bd6a8b9 + md5: 3f055e6e8646745be340ce4b08092328 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/historydict?source=hash-mapping + run_exports: {} + size: 11249 + timestamp: 1734382711342 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + sha256: fdcea5d7cb314485d3907192ef024c704311548c5b0cbeb390cd1951051e29d2 + md5: b395909221b9bd1df066e5930e18855b + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=compressed-mapping + run_exports: {} + size: 32884 + timestamp: 1782283986153 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb + depends: + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpcore?source=hash-mapping + run_exports: {} + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 + depends: + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpx?source=hash-mapping + run_exports: {} + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.16.0-pyhd8ed1ab_0.conda + sha256: 521d801b5620e9365a07d00e61be00b3b92ffe1b3ec900a23b98cb103a049f60 + md5: b3f48b389787b514572ccddd67af3ae2 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/humanize?source=compressed-mapping + run_exports: {} + size: 72882 + timestamp: 1782906300604 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + run_exports: {} + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + sha256: c75632ea624aa450a394f570749420c5a2e0997d0216bc29d5d45b0f39df0426 + md5: 577b04680ae422adb86fc60d7b940659 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=compressed-mapping + run_exports: {} + size: 163869 + timestamp: 1781620148226 +- conda: https://conda.anaconda.org/conda-forge/noarch/imageio-2.37.0-pyhfb79c49_0.conda + sha256: 8ef69fa00c68fad34a3b7b260ea774fda9bd9274fd706d3baffb9519fd0063fe + md5: b5577bc2212219566578fd5af9993af6 + depends: + - numpy + - pillow >=8.3.2 + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/imageio?source=hash-mapping + run_exports: {} + size: 293226 + timestamp: 1738273949742 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.1-pyhcf101f3_0.conda + sha256: dd88e15a886d1af698f627d4a393cf0a76369bc014009b1ac12d52236b59f20c + md5: 26d87af49eddabfaabd5986d9310e817 + depends: + - python >=3.10 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=compressed-mapping + run_exports: {} + size: 34727 + timestamp: 1779719562556 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-7.1.0-pyhd8ed1ab_0.conda + sha256: 6a2f86ef0965605d742b5b94229bf8b829258d0a9f640e3651901cc72ef9a0a5 + md5: e3bffa82b874f8b9a2631bddb3869529 + depends: + - importlib_resources >=7.1.0,<7.1.1.0a0 + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 10354 + timestamp: 1776068852701 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.7.1-h09eb056_0.conda + sha256: 04d6af092f98f245b64422a314b73292650d0557383ec79abbf64234005a544f + md5: bd52258d9673b8d5a34f71cfffd0d328 + depends: + - importlib-metadata ==8.7.1 pyhcf101f3_0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 22247 + timestamp: 1779719562556 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + sha256: a563a51aa522998172838e867e6dedcf630bc45796e8612f5a1f6d73e9c8125a + md5: 0ba6225c279baf7ea9473a62ea0ec9ae + depends: + - python >=3.10 + - zipp >=3.1.0 + constrains: + - importlib-resources >=7.1.0,<7.1.1.0a0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-resources?source=hash-mapping + run_exports: {} + size: 34809 + timestamp: 1776068839274 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.15.0-pyh53cf698_0.conda + sha256: d9fa14ec35119901bf702a9a3e8a5570daef2aada7d03878e4c24d9de912302b + md5: 2f4ea7f616b30363d8dde5cc15e7af2e + depends: + - __unix + - decorator >=5.1.0 + - ipython_pygments_lexers >=1.0.0 + - jedi >=0.18.2 + - matplotlib-inline >=0.1.6 + - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 + - pygments >=2.14.0 + - python >=3.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - pexpect >4.6 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython?source=compressed-mapping + run_exports: {} + size: 658801 + timestamp: 1782482716877 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 + md5: bd80ba060603cc228d9d81c257093119 + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython-pygments-lexers?source=hash-mapping + run_exports: {} + size: 13993 + timestamp: 1737123723464 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + sha256: 6bb58afb7eabc8b4ac0c7e92707fb498313cc0164cf04e7ba1090dbf49af514b + md5: d68e3f70d1f068f1b66d94822fdc644e + depends: + - comm >=0.1.3 + - ipython >=6.1.0 + - jupyterlab_widgets >=3.0.15,<3.1.0 + - python >=3.10 + - traitlets >=4.3.1 + - widgetsnbextension >=4.0.14,<4.1.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipywidgets?source=hash-mapping + run_exports: {} + size: 114376 + timestamp: 1762040524661 +- conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.4.0-pyhcf101f3_3.conda + sha256: 3cc991f0f09dfd00d2626e745ba68da03e4f1dcbb7b36dd20f7a7373643cd5d5 + md5: d59568bad316413c89831456e691de29 + depends: + - python >=3.10 + - more-itertools + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jaraco-classes?source=hash-mapping + run_exports: {} + size: 14831 + timestamp: 1767294269456 +- conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.context-6.1.2-pyhcf101f3_0.conda + sha256: 108b3919db8ef81b5585b38a3fedbe9eeccdf8fa576c250a13559a1188f597cc + md5: b9d2b1ffa307961f6bb7d83c8ba8a8be + depends: + - python >=3.10 + - backports.tarfile + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jaraco-context?source=hash-mapping + run_exports: {} + size: 16222 + timestamp: 1776862415793 +- conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.functools-4.6.0-pyhcf101f3_0.conda + sha256: d23b9e8e8d5968699ec2bc9f8d182598e86e494dbe8a3a71b4a53b1be9a3cb16 + md5: 8665b873062a31a90b563bf493f9fb2c + depends: + - python >=3.10 + - more-itertools + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jaraco-functools?source=compressed-mapping + run_exports: {} + size: 18997 + timestamp: 1784015600777 +- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + sha256: 744143551c1c7b528b82533fb641b9d7db20b2203abc4c2635c387fa6c089fc3 + md5: c2b3d37aa1411031126036ee76a8a861 + depends: + - python >=3.10 + - parso >=0.8.6,<0.9.0 + - python + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/jedi?source=compressed-mapping + run_exports: {} + size: 2715215 + timestamp: 1782251948616 +- conda: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.9.0-pyhd8ed1ab_0.conda + sha256: 00d37d85ca856431c67c8f6e890251e7cc9e5ef3724a0302b8d4a101f22aa27f + md5: b4b91eb14fbe2f850dd2c5fc20676c0d + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/jeepney?source=hash-mapping + run_exports: {} + size: 40015 + timestamp: 1740828380668 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d + depends: + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + run_exports: {} + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jmespath-1.1.0-pyhcf101f3_1.conda + sha256: 904d43d5210584004cf8b38f9657c717661ae55b0fb3f60573be974e50653fa1 + md5: cc73a9bd315659dc5307a5270f44786f + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jmespath?source=hash-mapping + run_exports: {} + size: 25946 + timestamp: 1769161799923 +- conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + sha256: 301539229d7be6420c084490b8145583291123f0ce6b92f56be5948a2c83a379 + md5: 615de2a4d97af50c350e5cf160149e77 + depends: + - python >=3.10 + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/joblib?source=hash-mapping + run_exports: {} + size: 226448 + timestamp: 1765794135253 +- conda: https://conda.anaconda.org/conda-forge/noarch/json-merge-patch-0.2-pyhd8ed1ab_2.conda + sha256: dcb8881bd19ed15e321ae35bddd74c22277fbd5f4e47e4d62f40362f9212305d + md5: 4d05d9514233b53fe421c34e6b249c6b + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/json-merge-patch?source=hash-mapping + run_exports: {} + size: 11200 + timestamp: 1734249397662 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + sha256: 304955757d1fedbe344af43b12b5467cca072f83cce6109361ba942e186b3993 + md5: cb60ae9cf02b9fcb8004dec4089e5691 + depends: + - jsonpointer >=1.9 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpatch?source=hash-mapping + run_exports: {} + size: 17311 + timestamp: 1733814664790 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae + md5: 89bf346df77603055d3c8fe5811691e6 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + run_exports: {} + size: 14190 + timestamp: 1774311356147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 + md5: ada41c863af263cc4c5fcbaff7c3e4dc + depends: + - attrs >=22.2.0 + - jsonschema-specifications >=2023.3.6 + - python >=3.10 + - referencing >=0.28.4 + - rpds-py >=0.25.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jsonschema?source=hash-mapping + run_exports: {} + size: 82356 + timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 + md5: 439cd0f567d697b20a8f45cb70a1005a + depends: + - python >=3.10 + - referencing >=0.31.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping + run_exports: {} + size: 19236 + timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + sha256: 5c03de243d7ae6247f39a402f4785d95e61c3be79ef18738e8f17155585d31a8 + md5: dbf8b81974504fa51d34e436ca7ef389 + depends: + - python >=3.10 + - python + constrains: + - jupyterlab >=3,<5 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-widgets?source=hash-mapping + run_exports: {} + size: 216779 + timestamp: 1762267481404 +- conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyha804496_0.conda + sha256: 010718b1b1a35ce72782d38e6d6b9495d8d7d0dbea9a3e42901d030ff2189545 + md5: 9eeb0eaf04fa934808d3e070eebbe630 + depends: + - __linux + - importlib-metadata >=4.11.4 + - importlib_resources + - jaraco.classes + - jaraco.context + - jaraco.functools + - jeepney >=0.4.2 + - python >=3.10 + - secretstorage >=3.2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/keyring?source=hash-mapping + run_exports: {} + size: 37717 + timestamp: 1763320674488 +- conda: https://conda.anaconda.org/conda-forge/noarch/lazy-loader-0.5-pyhd8ed1ab_0.conda + sha256: 1a88069ac61d2756ccaf26a6c206ab4d56610fb054bd2fffb5df4cd0744ab78e + md5: 75932da6f03a6bef32b70a51e991f6eb + depends: + - packaging + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/lazy-loader?source=hash-mapping + run_exports: {} + size: 14883 + timestamp: 1772817374026 +- conda: https://conda.anaconda.org/conda-forge/noarch/ldap3-2.9.1-pyhd8ed1ab_1.conda + sha256: 0816b267189241330b85bbe9524423255cd4daf8dd4d97765213b49991d1c33d + md5: 7319a76eaab1e21f84ad7949ff2f66e7 + depends: + - pyasn1 >=0.4.6 + - python >=3.9 + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/ldap3?source=hash-mapping + run_exports: {} + size: 265456 + timestamp: 1733217280290 +- conda: https://conda.anaconda.org/conda-forge/noarch/linkify-it-py-2.1.0-pyhcf101f3_0.conda + sha256: 991a82fbb64aba6d10719a017ce354e28df02ea5df1d9c7b0221da573c168d27 + md5: 1005e1f39083adad2384772e8e384e43 + depends: + - python >=3.10 + - uc-micro-py + license: MIT + license_family: MIT + purls: + - pkg:pypi/linkify-it-py?source=hash-mapping + run_exports: {} + size: 26062 + timestamp: 1772476821244 +- conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 + sha256: 9afe0b5cfa418e8bdb30d8917c5a6cec10372b037924916f1f85b9f4899a67a6 + md5: 91e27ef3d05cc772ce627e51cff111c4 + depends: + - python >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/locket?source=hash-mapping + run_exports: {} + size: 8250 + timestamp: 1650660473123 +- conda: https://conda.anaconda.org/conda-forge/noarch/mako-1.3.12-pyhcf101f3_0.conda + sha256: d06d02574be3892020262464b49360a749c1d448ed9f0de52fe8a08bc1483261 + md5: a73036dabdd6dfe9679ed893baa8b230 + depends: + - python >=3.10 + - importlib-metadata + - markupsafe >=0.9.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/mako?source=hash-mapping + run_exports: {} + size: 72185 + timestamp: 1777410001911 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + sha256: 0c4c35376fe920714390d46e4b8d31c876d65f18e1655899e0763ec25f2a902f + md5: 6d03368f2b2b0a5fb6839df53b2eb5e0 + depends: + - mdurl >=0.1,<1 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/markdown-it-py?source=hash-mapping + run_exports: {} + size: 69017 + timestamp: 1778169663339 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + sha256: 35b43d7343f74452307fd018a1cca92b8f68961ff8e2ab6a81ce0a703c9a3764 + md5: 9acc1c385be401d533ff70ef5b50dae6 + depends: + - python >=3.10 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/matplotlib-inline?source=hash-mapping + run_exports: {} + size: 15725 + timestamp: 1778264403247 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + sha256: 49db23cbfb1c1d414a14d7540195208b994ebd747beba0f15c903f3a0a2dc446 + md5: ad6821df7a98510117db06e9a833281f + depends: + - markdown-it-py >=2.0.0,<5.0.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mdit-py-plugins?source=hash-mapping + run_exports: {} + size: 50460 + timestamp: 1778692223625 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 + md5: 592132998493b3ff25fd7479396e8351 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mdurl?source=hash-mapping + run_exports: {} + size: 14465 + timestamp: 1733255681319 +- conda: https://conda.anaconda.org/conda-forge/noarch/minio-7.2.20-pyhd8ed1ab_0.conda + sha256: 132cd2ac509a15cb41a1f9c55f190c2c6ab278a8ee4915b178920c3606beb9af + md5: 3244fc3d4bc0be3ea995df133a4d9436 + depends: + - argon2-cffi + - certifi + - pycryptodome + - python >=3.10 + - typing_extensions + - urllib3 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/minio?source=hash-mapping + run_exports: {} + size: 69324 + timestamp: 1764207690145 +- conda: https://conda.anaconda.org/conda-forge/noarch/mongomock-4.3.0-pyhd8ed1ab_0.conda + sha256: 047e58ce472555586386fc3b2121ea95ec25d9f27b570a7adb9ccf8cefcb5796 + md5: e3fc737aa291e3966b1ee004c2f81cbb + depends: + - packaging + - python >=3.9 + - pytz + - sentinels + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/mongomock?source=hash-mapping + run_exports: {} + size: 59687 + timestamp: 1742727718589 +- conda: https://conda.anaconda.org/conda-forge/noarch/mongoquery-1.4.3-pyhd8ed1ab_0.conda + sha256: 8e5fc466a715ef261c44d5965c0bd26507cc99747b0296635b753a7ab998b407 + md5: 404f751bd276eb97293319b9bdd80e38 + depends: + - python >=3.10 + - six + license: Unlicense + purls: + - pkg:pypi/mongoquery?source=hash-mapping + run_exports: {} + size: 12594 + timestamp: 1758059611138 +- conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-11.1.0-pyhcf101f3_0.conda + sha256: 6725c1c8db9d025db763e1bba1acdcff2eb2ae20a7766a71512ea84081033288 + md5: 0e694257dbf916540b749c3ffc808efe + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/more-itertools?source=hash-mapping + run_exports: {} + size: 71270 + timestamp: 1779478190496 +- conda: https://conda.anaconda.org/conda-forge/noarch/mpl-scatter-density-0.8-pyhd8ed1ab_1.conda + sha256: b841728ddbee6a82677efefa9ddd704236d0c1f9c4440527ba11e0a8294b4939 + md5: 15f7a27f590c079a0aed4a8f1cee0dac + depends: + - fast-histogram >=0.3 + - matplotlib-base >=3.0 + - numpy + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/mpl-scatter-density?source=hash-mapping + run_exports: {} + size: 743291 + timestamp: 1745590251486 +- conda: https://conda.anaconda.org/conda-forge/noarch/msgpack-numpy-0.4.8-pyhcf101f3_2.conda + sha256: de324a439c5c0d0455bd54e92b983917aa4218d83f4941f45a829957c8b30423 + md5: bc5d7f293f60a98776c60263dce658b5 + depends: + - python >=3.10 + - numpy >=1.9.0 + - msgpack-python >=0.5.2 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgpack-numpy?source=hash-mapping + run_exports: {} + size: 14436 + timestamp: 1767289592471 +- conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + sha256: d09c47c2cf456de5c09fa66d2c3c5035aa1fa228a1983a433c47b876aa16ce90 + md5: 37293a85a0f4f77bbd9cf7aaefc62609 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/munkres?source=hash-mapping + run_exports: {} + size: 15851 + timestamp: 1749895533014 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.24.0-pyhcf101f3_0.conda + sha256: 57f525a1c55b08c3204fab05d2c74a3e9c2172d7f08f1ecaa07e19865cc1d7cf + md5: 42ef6cbb3e1d0e6689b9dd160f560eae + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/narwhals?source=compressed-mapping + run_exports: {} + size: 289946 + timestamp: 1783943716915 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 + md5: a2c1eeadae7a309daed9d62c96012a2b + depends: + - python >=3.11 + - python + constrains: + - numpy >=1.25 + - scipy >=1.11.2 + - matplotlib-base >=3.8 + - pandas >=2.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/networkx?source=hash-mapping + run_exports: {} + size: 1587439 + timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b + md5: 9a66894dfd07c4510beb6b3f9672ccc0 + constrains: + - mkl <0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 3843 + timestamp: 1582593857545 +- conda: https://conda.anaconda.org/conda-forge/noarch/nslsii-0.11.9-pyhd8ed1ab_0.conda + sha256: 0912fe8c5d8547b6afc9427c794a8eeef518d5d0e678e6352b7ad722f7185c70 + md5: f3c857ff3007753fb45008d278b2fe8e + depends: + - appdirs + - bluesky-base >=1.8.1 + - bluesky-kafka >=0.8.0 + - caproto + - databroker >=2.0.0b59 + - h5py + - httpx + - ipython + - ipywidgets + - ldap3 + - matplotlib-base + - msgpack-numpy + - msgpack-python >=1.0.0 + - numpy + - opencv + - ophyd + - packaging + - pillow + - psutil + - pycryptodome + - pyolog + - python >=3.10 + - recordwhat + - redis-json-dict + - redis-py + - requests + - setuptools + - shortuuid + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nslsii?source=hash-mapping + run_exports: {} + size: 72449 + timestamp: 1777916492649 +- conda: https://conda.anaconda.org/conda-forge/noarch/opentelemetry-api-1.44.0-pyhd8ed1ab_0.conda + sha256: 3185d1dfa8250d1e7c37cc23a09dc2b8a362f6407ca8919a66406e1fef570e45 + md5: cdeabb4c6c80399310749b3697f636ad + depends: + - importlib-metadata >=6.0,<8.8.0 + - python >=3.10 + - typing-extensions >=4.5.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/opentelemetry-api?source=compressed-mapping + run_exports: {} + size: 52149 + timestamp: 1784225783127 +- conda: https://conda.anaconda.org/conda-forge/noarch/ophyd-1.11.2-pyhd8ed1ab_0.conda + sha256: 704251e284e2ed4766442a5c452fe461e48c1745ba199d49b4e6f10bfce50820 + md5: a28f969b6f7b5af60865bcb87bf77c10 + depends: + - caproto !=1.2.0 + - networkx >=2.5 + - numpy + - opentelemetry-api + - packaging + - pint + - pyepics >=3.4.2 + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ophyd?source=hash-mapping + run_exports: {} + size: 217502 + timestamp: 1780613273481 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 + md5: 4c06a92e74452cfa53623a81592e8934 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + run_exports: {} + size: 91574 + timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/noarch/parsimonious-0.11.0-pyhd8ed1ab_0.conda + sha256: ad733579b4c39cd10fe7efebecead2492ec98c6ef63cb72eb29cd99af7d557e0 + md5: 293d719104da1aa4076aa5dd60a3805c + depends: + - python >=3.10 + - regex + license: MIT + license_family: MIT + purls: + - pkg:pypi/parsimonious?source=hash-mapping + run_exports: {} + size: 59797 + timestamp: 1763016422677 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + sha256: 611882f7944b467281c46644ffde6c5145d1a7730388bcde26e7e86819b0998e + md5: 39894c952938276405a1bd30e4ce2caf + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/parso?source=hash-mapping + run_exports: {} + size: 82472 + timestamp: 1777722955579 +- conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda + sha256: 472fc587c63ec4f6eba0cc0b06008a6371e0a08a5986de3cf4e8024a47b4fe6c + md5: 0badf9c54e24cecfb0ad2f99d680c163 + depends: + - locket + - python >=3.9 + - toolz + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/partd?source=hash-mapping + run_exports: {} + size: 20884 + timestamp: 1715026639309 +- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a + md5: d0d408b1f18883a944376da5cf8101ea + depends: + - ptyprocess >=0.5 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/pexpect?source=hash-mapping + run_exports: {} + size: 53561 + timestamp: 1733302019362 +- conda: https://conda.anaconda.org/conda-forge/noarch/pims-0.7-pyhd8ed1ab_1.conda + sha256: cc9521b3a517c9c0f5097a96ed2285b89ba3ee291320a26100261fea2130f8bf + md5: 146adfd93cac5e7c6b5def8f39c917cd + depends: + - imageio + - jinja2 + - numpy >=1.19 + - packaging + - pillow + - python >=3.9 + - slicerator >=1.1.0 + - tifffile + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pims?source=hash-mapping + run_exports: {} + size: 71357 + timestamp: 1734051228623 +- conda: https://conda.anaconda.org/conda-forge/noarch/pint-0.25.3-pyhc364b38_0.conda + sha256: f58a03dd2908c15dc65fab7e03a0212c69043466f7f85a4e345470492841782f + md5: 293c4719f6d62778ffecd18e024a8fcd + depends: + - python >=3.11 + - platformdirs >=2.1.0 + - flexcache >=0.3 + - flexparser >=0.4 + - typing_extensions >=4.0.0 + - python + constrains: + - numpy >=1.23 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pint?source=hash-mapping + run_exports: {} + size: 245230 + timestamp: 1773969991837 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.0-pyhcf101f3_0.conda + sha256: a4b8c7d3b3703d10f93187986d59aec09b22033978945845899beb7f849a7be6 + md5: 1fadaa6dd1d03d062075f84157ac2cc7 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + run_exports: {} + size: 26632 + timestamp: 1784661349391 +- conda: https://conda.anaconda.org/conda-forge/noarch/plotext-5.3.2-pyhff2d567_0.conda + sha256: b3fd4a4526ce0bd0d24c287d54f56fa8caaa3f7190163421db031de07b422c9f + md5: 9e160af9c896f5dccd43815a226a26c8 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/plotext?source=hash-mapping + run_exports: {} + size: 60077 + timestamp: 1731942133024 +- conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda + sha256: bae453e5cecf19cab23c2e8929c6e30f4866d996a8058be16c797ed4b935461f + md5: fd5062942bfa1b0bd5e0d2a4397b099e + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ply?source=hash-mapping + run_exports: {} + size: 49052 + timestamp: 1733239818090 +- conda: https://conda.anaconda.org/conda-forge/noarch/prettytable-3.18.0-pyhd8ed1ab_0.conda + sha256: de4bea093492d7dbb13997aa804ced847588bff59c88db1b3581b75e0e81f2c3 + md5: 5bfa6d6ff0b4dbff82268ddc83626cee + depends: + - python >=3.10 + - wcwidth >=0.3.5 + constrains: + - ptable >=9999 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/prettytable?source=compressed-mapping + run_exports: {} + size: 38907 + timestamp: 1782320930094 +- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + sha256: 794eec057361b41db1b06a9677eb8632adc0de81f7dcfe113bca8f0b04a23553 + md5: 3aa7e2d85645e61627c98082747dfdfe + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/prometheus-client?source=compressed-mapping + run_exports: {} + size: 61554 + timestamp: 1785016068982 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + sha256: efe8def2c93aa34cd8d3c9af1dc4c7d312791cf769d8b2b615e32733e6df6051 + md5: 39c92a39517316e5001d645ae63d9ab9 + depends: + - python >=3.10 + - wcwidth + constrains: + - prompt_toolkit 3.0.53 + license: BSD-3-Clause + purls: + - pkg:pypi/prompt-toolkit?source=hash-mapping + run_exports: {} + size: 276081 + timestamp: 1785160613307 +- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 + md5: 7d9daffbb8d8e0af0f769dbbcd173a54 + depends: + - python >=3.9 + license: ISC + purls: + - pkg:pypi/ptyprocess?source=hash-mapping + run_exports: {} + size: 19457 + timestamp: 1733302371990 +- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 + md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pure-eval?source=hash-mapping + run_exports: {} + size: 16668 + timestamp: 1733569518868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyasn1-0.6.4-pyhcf101f3_0.conda + sha256: 3b85c117363f823749d3f9cc9ce178f5580580f0b65a7ee021b2d08dc25d37fe + md5: 9c1d3582aa39e47e263c46e5026fed0b + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pyasn1?source=hash-mapping + run_exports: {} + size: 66722 + timestamp: 1783572312166 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + sha256: e27e0473fc6723311a0bd48b89b616fa1b996a2f7a2b555338cbbcfb9c640568 + md5: 9c5491066224083c41b6d5635ed7107b + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + run_exports: {} + size: 55886 + timestamp: 1779293633166 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f + md5: 729843edafc0899b3348bd3f19525b9d + depends: + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 + - python >=3.10 + - annotated-types >=0.6.0 + - pydantic-core ==2.46.4 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic?source=hash-mapping + run_exports: {} + size: 346511 + timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-extra-types-2.11.1-pyhcf101f3_0.conda + sha256: 385a900cf5d1d39dafab6088537c58a32d572fd237d7c4598def92c4c1120cb8 + md5: 96513760035d70917819a2840155d23a + depends: + - python >=3.10 + - pydantic >=2.5.2 + - python + constrains: + - phonenumbers >=8,<9 + - pycountry >=23 + - semver >=3.0.2,<4 + - python-ulid >=1,<4 + - pendulum >=3.0.0,<4.0.0 + - pytz >=2024.1 + - tzdata >=2024a + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-extra-types?source=hash-mapping + run_exports: {} + size: 72040 + timestamp: 1773664659868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.11.0-pyh3cfb1c2_0.conda + sha256: 0580a33a0b405e222f9c0294fa5052629e3d69dfdfa93db4438c4a215a5874dc + md5: c4286d133d776242af0793343f867f11 + depends: + - pydantic >=2.7.0 + - python >=3.10 + - python-dotenv >=0.21.0 + - typing-inspection >=0.4.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-settings?source=hash-mapping + run_exports: {} + size: 41378 + timestamp: 1758734155852 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 + md5: 16c18772b340887160c79a6acc022db0 + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=hash-mapping + run_exports: {} + size: 893031 + timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyolog-4.5.1-pyhd8ed1ab_0.conda + sha256: 3e00f14d7bd9e774cd2fe8400f64f9c7ce8727d71b989e6ff18ec480d2280a9f + md5: cc359b6be99e836558cde55350028e6b + depends: + - ipython + - keyring + - python >=3.9 + - requests + - six + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyolog?source=hash-mapping + run_exports: {} + size: 25273 + timestamp: 1737145863269 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + sha256: 417fba4783e528ee732afa82999300859b065dc59927344b4859c64aae7182de + md5: 3687cc0b82a8b4c17e1f0eb7e47163d5 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyparsing?source=hash-mapping + run_exports: {} + size: 110893 + timestamp: 1769003998136 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + run_exports: {} + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + run_exports: {} + size: 233310 + timestamp: 1751104122689 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 + md5: 130584ad9f3a513cdd71b1fdc1244e9c + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/python-dotenv?source=hash-mapping + run_exports: {} + size: 27848 + timestamp: 1772388605021 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.14-h4df99d1_100.conda + sha256: c7a8f98ea1cda5a84377c236ccd4bf1b6e2212c5a258d60bba295fb9f0260235 + md5: 200323d73f85b9c5c411db8c8c4942db + depends: + - cpython 3.13.14.* + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + run_exports: {} + size: 48307 + timestamp: 1781257788601 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-jose-3.5.0-pyhff2d567_0.conda + sha256: 785a3be2b9ce6d2f2f480bf1805c737f17e84c7e6382162eb83aea7d19089b87 + md5: 1b8523e5a0a5809e42c0f53a648efb28 + depends: + - cryptography >=3.4.0 + - ecdsa !=0.15 + - pyasn1 >=0.5.0 + - python >=3.9 + - rsa >=4.0,<5.0,!=4.4,!=4.1.1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/python-jose?source=hash-mapping + run_exports: {} + size: 76008 + timestamp: 1748530600158 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.32-pyhcf101f3_0.conda + sha256: 7dde902994a99993b616d597783fcd0ce3265a550feffbd0890b1329c9dff7e0 + md5: f54d00b369042e8e0e235b1a1a817487 + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-multipart?source=hash-mapping + run_exports: {} + size: 38132 + timestamp: 1780610429919 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7002 + timestamp: 1752805902938 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + sha256: ad774abda71c759baef515983bfedbba34d56d6227974c23715c04612f812a90 + md5: eb2fb9070c0232e96ae5515d8b28e4f9 + depends: + - python >=3.10 + - python + license: MIT + purls: + - pkg:pypi/pytz?source=compressed-mapping + run_exports: {} + size: 201126 + timestamp: 1785077087428 +- conda: https://conda.anaconda.org/conda-forge/noarch/qt_epics-0.0.4-pyhd8ed1ab_0.tar.bz2 + sha256: 7f23da3628ed1c7a55d8a44afd49c0b34e68da9f84ed714eb7f6346864359184 + md5: 4e5b90e15b47b1da78162db751c31f68 + depends: + - pyepics + - python >=3.4 + - qtpy + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/qt-epics?source=hash-mapping + run_exports: {} + size: 11525 + timestamp: 1640199277463 +- conda: https://conda.anaconda.org/conda-forge/noarch/qtpy-2.4.3-pyhd8ed1ab_1.conda + sha256: b17dd9d2ee7a4f60fb13712883cd2664aa1339df4b29eb7ae0f4423b31778b00 + md5: b49c000df5aca26d36b3f078ba85e03a + depends: + - packaging + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/qtpy?source=hash-mapping + run_exports: {} + size: 63041 + timestamp: 1749167192680 +- conda: https://conda.anaconda.org/conda-forge/noarch/ragged-0.3.0-pyhd8ed1ab_0.conda + sha256: 7530d46e7c607ade3284aa77222af6e4502d527c214c98ecd3d62480a0e3a47b + md5: d89fab0a40e24eeb3a47238378a2baa6 + depends: + - awkward >=2.6.7 + - numpy >=1.24.0 + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ragged?source=hash-mapping + run_exports: {} + size: 56506 + timestamp: 1780484568632 +- conda: https://conda.anaconda.org/conda-forge/noarch/recordwhat-0.4-pyhd8ed1ab_0.tar.bz2 + sha256: c58c1235cfbe16822b58da4fa00ef01adae6413f7c8a3afe51c84a1053d2a91c + md5: 267a23a875e8b8acb16455f02012dc31 + depends: + - attrs + - graphviz + - ophyd + - pandas + - parsimonious + - pyepics + - python >=3.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/recordwhat?source=hash-mapping + run_exports: {} + size: 48682 + timestamp: 1626721155514 +- conda: https://conda.anaconda.org/conda-forge/noarch/redis-json-dict-0.2.2-pyhd8ed1ab_0.conda + sha256: 921a5d6800485119e0865fb34632aaf96cb179dea5c8b6f724d39999f9bb2e06 + md5: 93f3d23d296f2d60b38ff3e4671f5ecc + depends: + - orjson >=3.9 + - python >=3.10 + - redis-py >=5.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/redis-json-dict?source=hash-mapping + run_exports: {} + size: 12623 + timestamp: 1768344324167 +- conda: https://conda.anaconda.org/conda-forge/noarch/redis-py-8.0.1-pyhd8ed1ab_0.conda + sha256: 63ac96dccefb16f7328463c0520c26ededbdbb039d36aea5cbc1fcff886bbe35 + md5: 55bebda63a5b9f512c2e19f20bf581b9 + depends: + - async-timeout >=4.0.3 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/redis?source=hash-mapping + run_exports: {} + size: 321576 + timestamp: 1782266366040 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab + depends: + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/referencing?source=hash-mapping + run_exports: {} + size: 51788 + timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da + md5: 4a85203c1d80c1059086ae860836ffb9 + depends: + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=compressed-mapping + run_exports: {} + size: 68709 + timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + sha256: 3d6ba2c0fcdac3196ba2f0615b4104e532525ffa1335b50a2878be5ff488814a + md5: 0242025a3c804966bf71aa04eee82f66 + depends: + - markdown-it-py >=2.2.0 + - pygments >=2.13.0,<3.0.0 + - python >=3.10 + - typing_extensions >=4.0.0,<5.0.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rich?source=hash-mapping + run_exports: {} + size: 208577 + timestamp: 1775991661559 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-toolkit-0.20.3-pyhcf101f3_0.conda + sha256: 104fbf62487954552edb19ee8706a0d2c9383ff1e1e16ab31c6947d20d34a862 + md5: 3296ee717391681ab734f0a01c89ac85 + depends: + - python >=3.10 + - rich >=13.7.1 + - click >=8.1.7 + - typing_extensions >=4.12.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rich-toolkit?source=compressed-mapping + run_exports: {} + size: 34824 + timestamp: 1784024058693 +- conda: https://conda.anaconda.org/conda-forge/noarch/rsa-4.9.1-pyhd8ed1ab_0.conda + sha256: e32e94e7693d4bc9305b36b8a4ef61034e0428f58850ebee4675978e3c2e5acf + md5: 58958bb50f986ac0c46f73b6e290d5fe + depends: + - pyasn1 >=0.1.3 + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/rsa?source=hash-mapping + run_exports: {} + size: 31709 + timestamp: 1744825527634 +- conda: https://conda.anaconda.org/conda-forge/noarch/sentinels-1.1.1-pyhd8ed1ab_0.conda + sha256: e8da2a799025b7530f54ea6c56ad51898172c10c3ff44815e5b6ecbb68feb21a + md5: ca6b76bd85ee078a5c735a855c628467 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/sentinels?source=hash-mapping + run_exports: {} + size: 11095 + timestamp: 1782926071262 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-83.0.0-pyh332efcf_0.conda + sha256: 48a9f96016505debadfc67f06de7ac548decbc38d327409b24b0432ef6f16335 + md5: 6bf6acbab2499830180ec88c3aff2fa4 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=compressed-mapping + run_exports: {} + size: 642081 + timestamp: 1783619174976 +- conda: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.4-pyhd8ed1ab_2.conda + sha256: 1d6534df8e7924d9087bd388fbac5bd868c5bf8971c36885f9f016da0657d22b + md5: 83ea3a2ddb7a75c1b09cea582aa4f106 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/shellingham?source=hash-mapping + run_exports: {} + size: 15018 + timestamp: 1762858315311 +- conda: https://conda.anaconda.org/conda-forge/noarch/shortuuid-1.0.13-pyhd8ed1ab_1.conda + sha256: ad2ca1f82d8caf9c1f65d9d303f1f1b387bb33121094eadddab78c0e3ea3a55f + md5: 9cbf6fc5548c1c07a2491afd6de0f073 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/shortuuid?source=hash-mapping + run_exports: {} + size: 15074 + timestamp: 1734272417278 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + run_exports: {} + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/slicerator-1.1.0-pyhd8ed1ab_1.conda + sha256: 5340c36cb62b7c8a22c267254c037302fea2670a4fb9d29e10ba36565e2a5510 + md5: 102f1100ad3dcbcf57f789600c9c015a + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/slicerator?source=hash-mapping + run_exports: {} + size: 15755 + timestamp: 1734051114500 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/sniffio?source=hash-mapping + run_exports: {} + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + sha256: d1e3e06b5cf26093047e63c8cc77b70d970411c5cbc0cb1fad461a8a8df599f7 + md5: 0401a17ae845fa72c7210e206ec5647d + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/sortedcontainers?source=hash-mapping + run_exports: {} + size: 28657 + timestamp: 1738440459037 +- conda: https://conda.anaconda.org/conda-forge/noarch/sparse-0.19.0-pyhc364b38_0.conda + sha256: 32fbfa88c4ce9f00f7cb274e3a9defb44b521899bb9238113411a8cc7bac3d22 + md5: 2be361cf98960e628815b445ea9631e7 + depends: + - python >=3.11 + - numpy >=1.17 + - numba >=0.49 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/sparse?source=compressed-mapping + run_exports: {} + size: 127433 + timestamp: 1782985176301 +- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 + md5: b1b505328da7a6b246787df4b5a49fbc + depends: + - asttokens + - executing + - pure_eval + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/stack-data?source=hash-mapping + run_exports: {} + size: 26988 + timestamp: 1733569565672 +- conda: https://conda.anaconda.org/conda-forge/noarch/stamina-26.1.0-pyhcf101f3_1.conda + sha256: e85ebb8bc145d925f00d92fda9b9578b7a3fec17a80fdddf416c3ac75f8ac55d + md5: c06c071acce8103b854d9434a249bd80 + depends: + - python >=3.10 + - tenacity + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/stamina?source=hash-mapping + run_exports: {} + size: 24175 + timestamp: 1776172479398 +- conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.3.1-pyhcf101f3_0.conda + sha256: 17e3dc37c0ac9754b782e49285a3d8d223d372f7012d64a4f6ae371466cef143 + md5: bb8e15a6ef1a475545720fa2c29f19c4 + depends: + - anyio >=3.6.2,<5 + - python >=3.10 + - typing_extensions >=4.10.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/starlette?source=hash-mapping + run_exports: {} + size: 64264 + timestamp: 1781447071524 +- conda: https://conda.anaconda.org/conda-forge/noarch/suitcase-mongo-0.7.0-pyhd8ed1ab_0.conda + sha256: 26b42fb653ccb74243d4e1e73950edf2cfc1c79b2f6720797cf17b72d617c36f + md5: 30068d1e506e0c54b9954d44dfcfb1bf + depends: + - event-model >=1.8.0 + - packaging + - pymongo + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/suitcase-mongo?source=hash-mapping + run_exports: {} + size: 26416 + timestamp: 1737651184394 +- conda: https://conda.anaconda.org/conda-forge/noarch/super_state_machine-2.0.2-py_0.tar.bz2 + sha256: 22c5156e7a77f6cdb8498514b90039d0e349e4686a78d797e81407fecdcbb90d + md5: 397455bd7fc1b452949aa404d4ea2a62 + depends: + - python + - six + license: BSD 3-Clause + purls: + - pkg:pypi/super-state-machine?source=hash-mapping + run_exports: {} + size: 10795 + timestamp: 1572708084087 +- conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda + sha256: 6b549360f687ee4d11bf85a6d6a276a30f9333df1857adb0fe785f0f8e9bcd60 + md5: f88bb644823094f436792f80fba3207e + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/tblib?source=hash-mapping + run_exports: {} + size: 19397 + timestamp: 1762956379123 +- conda: https://conda.anaconda.org/conda-forge/noarch/tenacity-9.1.4-pyhcf101f3_0.conda + sha256: 32e75900d6a094ffe4290a8c9f1fa15744d9da8ff617aba4acaa0f057a065c34 + md5: 043f0599dc8aa023369deacdb5ac24eb + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/tenacity?source=hash-mapping + run_exports: {} + size: 31404 + timestamp: 1770510172846 +- conda: https://conda.anaconda.org/conda-forge/noarch/textual-8.2.8-pyhcf101f3_0.conda + sha256: 88997e493c8b74b05d8f9bc48133d297d4b5412809c2a2a041e5b40204fdff02 + md5: 89f2b015d7ee36ceaaf03fe687d1bd61 + depends: + - pygments >=2.19.2,<3.0.0 + - typing_extensions >=4.4.0,<5.0.0 + - platformdirs >=3.6.0,<5 + - python >=3.10,<4.0.0 + - markdown-it-py >=2.1.0 + - linkify-it-py >=1,<3 + - mdit-py-plugins + - rich >=14.2.0 + - python + constrains: + - tree_sitter >=0.25.0 + - tree_sitter_languages 1.10.2.* + license: MIT + license_family: MIT + purls: + - pkg:pypi/textual?source=hash-mapping + run_exports: {} + size: 538351 + timestamp: 1782808451993 +- conda: https://conda.anaconda.org/conda-forge/noarch/textual-plotext-1.0.1-pyhd8ed1ab_1.conda + sha256: cba8986f82052923e1e857a2ce91712189f6d6564547dfb3771a7ae3d1f61ee9 + md5: 96b4ba1d86092f546cbd217350ef3bb3 + depends: + - plotext >=5.2.8,<6.0.0 + - python >=3.9,<4.0.0 + - textual >=0.86.2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/textual-plotext?source=hash-mapping + run_exports: {} + size: 20304 + timestamp: 1735071100022 +- conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + sha256: 6016672e0e72c4cf23c0cf7b1986283bd86a9c17e8d319212d78d8e9ae42fdfd + md5: 9d64911b31d57ca443e9f1e36b04385f + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/threadpoolctl?source=hash-mapping + run_exports: {} + size: 23869 + timestamp: 1741878358548 +- conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.14-pyhd8ed1ab_0.conda + sha256: 8cb9c58cf4b8af42d5388230eeb7a6bf630ff8f0190ccce4bcb7eededad31057 + md5: 4b0a134b830e983e4f33c410892265c7 + depends: + - imagecodecs >=2026.5.10 + - numpy >=2.1 + - python >=3.12 + constrains: + - matplotlib-base >=3.3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tifffile?source=compressed-mapping + run_exports: {} + size: 212043 + timestamp: 1784115322460 +- conda: https://conda.anaconda.org/conda-forge/noarch/tiled-0.2.14-pyhd8ed1ab_0.conda + sha256: 453370792b0dbba3cc62181fb609035aa0711601ce672065b93d5b237fe8a437 + md5: 6291384940b0931e5cb8a11ff23a5e48 + depends: + - python >=3.10 + - tiled-client 0.2.14 pyhd8ed1ab_0 + - tiled-formats 0.2.14 pyhd8ed1ab_0 + - tiled-server 0.2.14 pyhd8ed1ab_0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 8709 + timestamp: 1783548057637 +- conda: https://conda.anaconda.org/conda-forge/noarch/tiled-base-0.2.14-pyhd8ed1ab_0.conda + sha256: 9692c853cab650b477ef9608536fd67b82b23cf78c740f073af3b2d9d23c2244 + md5: a0fb3826aaddb0ac9238ac9dbe5b86b3 + depends: + - appdirs + - awkward + - click !=8.1.0 + - dask + - httpx >=0.20.0 + - json-merge-patch + - jsonpatch + - jsonschema + - lz4 + - msgpack-python >=1.0.0 + - ndindex + - numpy + - orjson + - pandas + - pyarrow + - pydantic-settings >=2,<2.12.0 + - python >=3.10 + - python-blosc2 + - pyyaml + - ragged + - sparse >=0.13.0 + - typer + - xarray + - zstandard + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tiled?source=hash-mapping + run_exports: {} + size: 1500362 + timestamp: 1783548034273 +- conda: https://conda.anaconda.org/conda-forge/noarch/tiled-client-0.2.14-pyhd8ed1ab_0.conda + sha256: b5d846980e6d513ea6890759178c9576f8434095278848b836dc5a62580ce243 + md5: 02b6c6aefb96f619e883b7f0ee9c563d + depends: + - entrypoints + - platformdirs + - pydantic + - python >=3.10 + - rich + - stamina + - tiled-base 0.2.14 pyhd8ed1ab_0 + - websockets + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 8235 + timestamp: 1783548040749 +- conda: https://conda.anaconda.org/conda-forge/noarch/tiled-formats-0.2.14-pyhd8ed1ab_0.conda + sha256: d2dd25615e6095f280f2058da654308ab1da15f9af3cd2886bf0d7c6882a4bc7 + md5: 15790b6327f7cf5f62b47377f5fd55a6 + depends: + - h5netcdf + - h5py + - hdf5plugin + - openpyxl + - pillow + - python >=3.10 + - tifffile + - tiled-base 0.2.14 pyhd8ed1ab_0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 8073 + timestamp: 1783548045776 +- conda: https://conda.anaconda.org/conda-forge/noarch/tiled-server-0.2.14-pyhd8ed1ab_0.conda + sha256: fca9feda563607162f9ad48c84fd66334105d7657ea7523015b552427fdc87e3 + md5: 02cfd2bf046ca7d97f9dc65d7b95f269 + depends: + - adbc-driver-manager + - adbc-driver-postgresql + - adbc-driver-sqlite + - aiofiles + - aiosqlite + - alembic + - anyio + - asgi-correlation-id + - asyncpg + - cachetools + - canonicaljson + - fastapi >=0.122.0 + - jinja2 + - jmespath + - minio + - obstore + - openpyxl + - packaging + - prometheus_client + - pydantic >=2,<3 + - python >=3.10 + - python-dateutil + - python-duckdb <1.4.0 + - python-jose + - python-multipart + - redis-py + - sqlalchemy + - stamina + - starlette >=0.48.0 + - tiled-base 0.2.14 pyhd8ed1ab_0 + - toolz + - uvicorn + - watchfiles + - zarr + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 8523 + timestamp: 1783548050783 +- conda: https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhcf101f3_3.conda + sha256: fd30e43699cb22ab32ff3134d3acf12d6010b5bbaa63293c37076b50009b91f8 + md5: d0fc809fa4c4d85e959ce4ab6e1de800 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/toml?source=hash-mapping + run_exports: {} + size: 24017 + timestamp: 1764486833072 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + run_exports: {} + size: 21561 + timestamp: 1774492402955 +- conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda + sha256: 4e379e1c18befb134247f56021fdf18e112fb35e64dd1691858b0a0f3bea9a45 + md5: c07a6153f8306e45794774cf9b13bd32 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/toolz?source=hash-mapping + run_exports: {} + size: 53978 + timestamp: 1760707830681 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.70.0-pyh8f84b5b_0.conda + sha256: a4d9e9da15b13f1ab047e7db412e2ed564bc615832e80213cf129b973c3abf4a + md5: 00953c3b729e03b0ae21f49fdf3441bb + depends: + - python >=3.10 + - __unix + - python + constrains: + - envwrap >=0.2 + - ipywidgets >=6.0 + license: MPL-2.0 and MIT + purls: + - pkg:pypi/tqdm?source=compressed-mapping + run_exports: {} + size: 98184 + timestamp: 1785172528905 +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.1-pyhcf101f3_0.conda + sha256: b89a823edf524956b94a2a4db974866e4501f05c68976eff458c5dcf07f88431 + md5: 37e3be7b6e2977d37b8fa5da229f5dc0 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/traitlets?source=hash-mapping + run_exports: {} + size: 115158 + timestamp: 1780507822178 +- conda: https://conda.anaconda.org/conda-forge/noarch/typer-0.27.0-pyhcf101f3_0.conda + sha256: f44984374b248c45f7e3d43a2cb0bf73d1441ecea07fb73198e89f643f6c5824 + md5: aa0ef00569728be93656de5f097fc81d + depends: + - annotated-doc >=0.0.2 + - colorama + - python >=3.10 + - rich >=13.8.0 + - shellingham >=1.3.0 + - python + license: MIT AND BSD-3-Clause + purls: + - pkg:pypi/typer?source=hash-mapping + run_exports: {} + size: 187450 + timestamp: 1784167351535 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 94080 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a + md5: 53f5409c5cfd6c5a66417d68e3f0a864 + depends: + - python >=3.10 + - typing_extensions >=4.12.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/typing-inspection?source=hash-mapping + run_exports: {} + size: 20935 + timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=compressed-mapping + run_exports: {} + size: 52631 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: fcb489df604d100968b737f2cb6076c6 + license: LicenseRef-Public-Domain + purls: [] + run_exports: {} + size: 118849 + timestamp: 1784250406640 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzlocal-5.4.4-pyh8f84b5b_0.conda + sha256: 5d693f6f22b584fb69bac52e716872a1432c50527ced54d66064cc27e5946c0b + md5: cbd15812c946aecc2d9479318aca450f + depends: + - python >=3.10 + - __unix + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tzlocal?source=hash-mapping + run_exports: {} + size: 24057 + timestamp: 1782759572367 +- conda: https://conda.anaconda.org/conda-forge/noarch/uc-micro-py-2.0.0-pyhcf101f3_0.conda + sha256: 9e5a0e70e876fca50de075b1cc6641cdc009a16c18f4d52ab03edb777729a1bc + md5: 4fdd327852ffefc83173a7c029690d0c + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/uc-micro-py?source=hash-mapping + run_exports: {} + size: 13378 + timestamp: 1772479008776 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + sha256: feff959a816f7988a0893201aa9727bbb7ee1e9cec2c4f0428269b489eb93fb4 + md5: cbb88288f74dbe6ada1c6c7d0a97223e + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + run_exports: {} + size: 103560 + timestamp: 1778188657149 +- conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.51.0-pyhc90fa1f_0.conda + sha256: e9ca2188416b40eb79e50031649ec669dfbceea5cc5895c6f94f342612159974 + md5: fa008b61010236d744eae4ef867c63ce + depends: + - __unix + - click >=7.0 + - h11 >=0.8 + - python >=3.10 + - typing_extensions >=4.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/uvicorn?source=hash-mapping + run_exports: {} + size: 57699 + timestamp: 1783518883165 +- conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-standard-0.51.0-h28ae30b_0.conda + sha256: 7b123672d8b350089a6ac800dfda53efee981b625dd9f114bf7f73e9a5fbdfc5 + md5: 999eb6aca58e2f2fc3891874aef3f95a + depends: + - __unix + - uvicorn ==0.51.0 pyhc90fa1f_0 + - websockets >=10.4 + - httptools >=0.6.3 + - watchfiles >=0.20 + - python-dotenv >=0.13 + - pyyaml >=5.1 + - uvloop >=0.15.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 4154 + timestamp: 1783518883165 +- conda: https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.49-hd8ed1ab_0.conda + sha256: 04ce686cd187d379344f9b2be7b4da5f431b265dc0944a6b764fab9da9171948 + md5: 0839a3421140d4a9ba93fb988698fc00 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 147954 + timestamp: 1780946721169 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + sha256: 4acf845da404e84cef1acccc66cc0156af1b83a5b5d7077b2ca19b705c561e57 + md5: 99f7755ec8648a042b0dbe906234f888 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wcwidth?source=compressed-mapping + run_exports: {} + size: 132415 + timestamp: 1782771807703 +- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + sha256: 826af5e2c09e5e45361fa19168f46ff524e7a766022615678c3a670c45895d9a + md5: dc257b7e7cad9b79c1dfba194e92297b + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/widgetsnbextension?source=hash-mapping + run_exports: {} + size: 889195 + timestamp: 1762040404362 +- conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2026.7.0-pyhc364b38_0.conda + sha256: 47244c233aeb55de993fe79eebbbb2adc14b388bb0bf2f9dfbaa8aa6610b6345 + md5: f72531e6cf99c98d47306ec917f5e6b9 + depends: + - python >=3.11 + - numpy >=1.26 + - packaging >=24.2 + - pandas >=2.2 + - python + constrains: + - bottleneck >=1.4 + - cartopy >=0.23 + - cftime >=1.6 + - dask-core >=2024.6 + - distributed >=2024.6 + - flox >=0.9 + - h5netcdf >=1.3 + - h5py >=3.11 + - hdf5 >=1.14 + - iris >=3.9 + - matplotlib-base >=3.8 + - nc-time-axis >=1.4 + - netcdf4 >=1.6.0 + - numba >=0.60 + - numbagg >=0.8 + - pint >=0.24 + - pydap >=3.5.0 + - scipy >=1.13 + - seaborn-base >=0.13 + - sparse >=0.15 + - toolz >=0.12 + - zarr >=2.18 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/xarray?source=hash-mapping + run_exports: {} + size: 1027069 + timestamp: 1783633473025 +- conda: https://conda.anaconda.org/conda-forge/noarch/xlrd-2.0.2-pyhd8ed1ab_0.conda + sha256: 64f09069d8b3a3791643230cedc80d9f9422f667e3e328b40d527375352fe8d4 + md5: 91f5637b706492b9e418da1872fd61ce + depends: + - python >=3.10 + license: BSD-3-Clause AND BSD-4-Clause + license_family: BSD + purls: + - pkg:pypi/xlrd?source=hash-mapping + run_exports: {} + size: 93671 + timestamp: 1756170155688 +- conda: https://conda.anaconda.org/conda-forge/noarch/xmltodict-1.0.4-pyhcf101f3_0.conda + sha256: 7588e77a5d3885145e693d8b98493952f6efac8f3fabb1c218cd0cbd1a739fad + md5: 0f02dbcae61967ced21fea829a8ee927 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/xmltodict?source=hash-mapping + run_exports: {} + size: 20673 + timestamp: 1771770296472 +- conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + sha256: 663ea9b00d68c2da309114923924686ab6d3f59ef1b196c5029ba16799e7bb07 + md5: 4487b9c371d0161d54b5c7bbd890c0fc + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/xyzservices?source=hash-mapping + run_exports: {} + size: 51732 + timestamp: 1774900074457 +- conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.2.1-pyhc364b38_0.conda + sha256: 88716d633ac7fdc896ce7aa00efdfc91df6363d51bebabfb60d34399c023a3d0 + md5: b787cd1f01e24b161261438a5589df51 + depends: + - python >=3.12 + - packaging >=22.0 + - numpy >=2 + - numcodecs >=0.14 + - typing_extensions >=4.13 + - donfig >=0.8 + - google-crc32c >=1.5 + - python + constrains: + - fsspec >=2023.10.0 + - obstore >=0.5.1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/zarr?source=hash-mapping + run_exports: {} + size: 367721 + timestamp: 1777989189792 +- conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda + sha256: 5488542dceeb9f2874e726646548ecc5608060934d6f9ceaa7c6a48c61f9cc8d + md5: e52c2ef711ccf31bb7f70ca87d144b9e + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zict?source=hash-mapping + run_exports: {} + size: 36341 + timestamp: 1733261642963 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + sha256: 210bd31c22bb88f5e2a167df24c95bb5f152b2ada7502f9b8c49d1f5366db423 + md5: ba3dcdc8584155c97c648ae9c044b7a3 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/zipp?source=compressed-mapping + run_exports: {} + size: 24190 + timestamp: 1779159948016 +- pypi: git+https://github.com/vshekar/mxtools?branch=add-raster-assets#2d2ae532037cdc41ad21150997d617e63ad15b6d + name: mxtools + version: 0.post230+g2d2ae53 + requires_dist: + - bluesky + - ophyd + - pyepics + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/14/81/54435f2763c475390da7746405c1732819994c2030f1acf3ad38f82276ac/summarytable-0.1.8.tar.gz + name: summarytable + version: 0.1.8 + sha256: a3514d5baa119ce3baf891ca50898977875723b8caafd9affe9e7a58f926e6ed + requires_dist: + - xmltodict + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/ae/ce/7f538891722a4f06921419d55bac6f41729258d54442861edb2de0dbdf5e/opencv_python_headless-4.14.0.94-cp37-abi3-manylinux_2_28_x86_64.whl + name: opencv-python-headless + version: 4.14.0.94 + sha256: 211e581f5a4670acbbe08fff36a35e9946039d2eea28b80394632d036d1be527 + requires_dist: + - numpy<2.0 ; python_full_version < '3.9' + - numpy>=2 ; python_full_version >= '3.9' + requires_python: '>=3.6' diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..e4a5bc34 --- /dev/null +++ b/pixi.toml @@ -0,0 +1,53 @@ +[workspace] +authors = ["vshekar1 "] +channels = ["conda-forge"] +name = "lsdc" +platforms = ["linux-64"] +version = "0.1.0" + +[tasks.fmx-gui] +cmd = "./bin/fmx_env.sh && cd \"$INIT_CWD\" && setsid -f python /nsls2/software/mx/daq/lsdc_fmx/lsdcGui.py" + +[tasks.fmx-remote] +cmd = "./bin/fmx_env.sh && cd \"$INIT_CWD\" && ipython -i /nsls2/software/mx/daq/lsdc_fmx/daq_mainAux.py" + +[tasks.fmx-server] +cmd = "./bin/fmx_env.sh && cd \"$INIT_CWD\" && ipython -i /nsls2/software/mx/daq/lsdc_fmx/server.py" + +[tasks.amx-gui] +cmd = "./bin/amx_env.sh && cd \"$INIT_CWD\" && setsid -f python /nsls2/software/mx/daq/lsdc_amx/lsdcGui.py" + +[tasks.amx-remote] +cmd = "./bin/amx_env.sh && cd \"$INIT_CWD\" && ipython -i /nsls2/software/mx/daq/lsdc_amx/daq_mainAux.py" + +[tasks.amx-server] +cmd = "./bin/amx_env.sh && cd \"$INIT_CWD\" && ipython -i /nsls2/software/mx/daq/lsdc_amx/server.py" + +[dependencies] +python = ">=3.13.5,<3.14" +qtpy = ">=2.4.3,<3" +requests = ">=2.32.4,<3" +amostra = "==0.2.4" +conftrak = ">=0.0.9,<0.0.10" +analysisstore = ">=0.0.6,<0.0.7" +pyqt = ">=5.15.11,<6" +ipython = ">=9.7.0,<10" +ophyd = ">=1.11.0,<2" +xmltodict = ">=1.0.2,<2" +# mxtools = ">=1.0.4,<2" +super_state_machine = ">=2.0.2,<3" +databroker = ">=2.0.0b68,<3" +nslsii = ">=0.11.4,<0.12" +scikit-learn = ">=1.7.2,<2" +glib = ">=2.86.1,<3" +pymca = ">=5.9.4,<6" +qt_epics = ">=0.0.4,<0.0.5" +pydantic = ">=2.12.5,<3" +tiled = ">=0.2.13,<0.3" +bluesky-tiled-plugins = ">=2.0.6,<3" + +[pypi-dependencies] +opencv-python-headless = ">=4.11.0.86, <5" +summarytable = ">=0.1.8, <0.2" +mxtools = { git = "https://github.com/vshekar/mxtools", branch = "add-raster-assets" } +# mxtools = { path = "../mxtools", editable = true } diff --git a/raddoseLib.py b/raddoseLib.py index 4c1ef5a4..48b76d96 100755 --- a/raddoseLib.py +++ b/raddoseLib.py @@ -166,7 +166,7 @@ def rd3d_calc( logger.info("\n=== rd3d_calc summary ===") # append_fields has issues with 1d arrays, use reshape() and [] to make len() work on size 1 array: # https://stackoverflow.com/questions/53137822/adding-a-field-to-a-structured-numpy-array-4 - rd3d_out = rd3d_out.reshape(1) + rd3d_out = rd3d_out.reshape(1)[0] logger.info("Diffraction weighted dose = " + "%.3f" % rd3d_out["DWD"] + " MGy") logger.info("Max dose = " + "%.3f" % rd3d_out["Max_Dose"] + " MGy") if rd3d_out["DWD"]: @@ -175,7 +175,7 @@ def rd3d_calc( ) # Time to Garman limit based on diffraction weighted dose else: t2gl = 0 - rd3d_out = rfn.append_fields(rd3d_out, "t2gl", [t2gl], usemask=False) + rd3d_out = rfn.append_fields(rd3d_out, "t2gl", [t2gl], usemask=False)[0] logger.info("Time to Garman limit = " + "%.3f" % rd3d_out["t2gl"] + " s") return rd3d_out @@ -213,6 +213,8 @@ def fmx_expTime( flux=-1, wedge=180, verbose=False, + dm_user=1.0, # User dose multiplier + beamsize_type="S", # "S" or "L" for beam size multiplier ): """ RD3D output = AWD [MGy] @@ -343,4 +345,15 @@ def fmx_expTime( f"Experiment time to reach an average diffraction weighted dose of {avg_dwd} MGy = {expTimeMGy} s" ) - return expTimeMGy + # Apply dose multipliers + # Calculate beam size multiplier (invisible to user) + dm_beam = 1.0 if beamsize_type == "S" else 8.0 + + # Apply combined dose multiplier + dm_total = dm_user * dm_beam + expTimeMGy_adjusted = expTimeMGy * dm_total + + logger.info(f"Applied dose multipliers: DM_user={dm_user}, DM_beam={dm_beam}, DM_total={dm_total}") + logger.info(f"Adjusted experiment time = {expTimeMGy_adjusted} s") + + return expTimeMGy_adjusted diff --git a/robot_lib.py b/robot_lib.py index e92478c4..260c999f 100644 --- a/robot_lib.py +++ b/robot_lib.py @@ -52,6 +52,12 @@ def recoverRobot(): def dryGripper(): robot.dryGripper() +def recoverRobotFloco(): + robot.recoverRobotFloco() + +def dryGripperFloco(): + robot.dryGripperFloco() + # TODO ask Edwin about these Dewar functions def DewarRefill(hours): diff --git a/runFastDPH5.py b/runFastDPH5.py index 66c845a0..47b4973c 100755 --- a/runFastDPH5.py +++ b/runFastDPH5.py @@ -1,10 +1,23 @@ #!/opt/conda_envs/lsdc-server-2023-2-latest/bin/python import os import sys + + +os.environ.setdefault( + "SSL_CERT_FILE", + "/etc/pki/tls/certs/ca-bundle.crt", +) +os.environ.setdefault( + "REQUESTS_CA_BUNDLE", + "/etc/pki/tls/certs/ca-bundle.crt", +) + import db_lib -from daq_utils import getBlConfig +from daq_utils import getBlConfig, setBlConfig import xmltodict +import json import logging +import subprocess logger = logging.getLogger() logging.getLogger().setLevel(logging.INFO) handler1 = logging.FileHandler('fast_dp.txt') @@ -32,26 +45,58 @@ runDimple = int(sys.argv[6]) dimpleNode = sys.argv[7] ispybDCID = 1 #int(sys.argv[8]) +runAutoProc = int(sys.argv[9]) -comm_s = f"ssh -q {node} \"{os.environ['MXPROCESSINGSCRIPTSDIR']}fast_dp.sh {request_id} {numstart}\"" -logger.info(comm_s) -os.system(comm_s) +try: + comm_s = f"ssh -q {node} \"{os.environ['MXPROCESSINGSCRIPTSDIR']}fast_dp.sh {request_id} {numstart}\"" + logger.info(comm_s) + os.system(comm_s) + fastDPResultFile = runningDir+"/fast_dp.xml" + fd = open(fastDPResultFile) + resultObj = xmltodict.parse(fd.read()) + logger.info(f"finished fast_dp {request_id}") + resultID = db_lib.addResultforRequest("fastDP",request_id,owner,resultObj,beamline=os.environ["BEAMLINE_ID"]) + newResult = db_lib.getResult(resultID) + visitName = getBlConfig("visitName") +except Exception as e: + logger.error("runfastdph5 error running fastdp: %s" % e) -fastDPResultFile = runningDir+"/fast_dp.xml" -fd = open(fastDPResultFile) -resultObj = xmltodict.parse(fd.read()) -logger.info(f"finished fast_dp {request_id}") -resultID = db_lib.addResultforRequest("fastDP",request_id,owner,resultObj,beamline=os.environ["BEAMLINE_ID"]) -newResult = db_lib.getResult(resultID) -visitName = getBlConfig("visitName") try: ispybLib.insertResult(newResult,"fastDP",request,visitName,ispybDCID,fastDPResultFile) except Exception as e: logger.error("runfastdph5 insert result ispyb error: %s" % e) + if (runFastEP): os.system("fast_ep") #looks very bad! running on ca1! + if (runDimple): - dimpleComm = getBlConfig("dimpleComm") - comm_s = f"ssh -q {dimpleNode} \"{os.environ['MXPROCESSINGSCRIPTSDIR']}dimple.sh {request_id} {numstart}\"" - logger.info(f"running dimple: {comm_s}") - os.system(comm_s) + try: + dimpleComm = getBlConfig("dimpleComm") + comm_s = f"ssh -q {dimpleNode} \"{os.environ['MXPROCESSINGSCRIPTSDIR']}dimple.sh {request_id} {numstart}\"" + logger.info(f"running dimple: {comm_s}") + # os.system(comm_s) + subprocess.Popen(comm_s, shell=True) + except Exception as e: + logger.error("runfastdph5 error running dimple: %s" % e) + +if runAutoProc: + logger.info("Running AUTO-AUTOPROC...") + setBlConfig("auto_proc_lock", True) + try: + queue = getBlConfig("auto_proc_queue") + queue.append((directory, request_id)) + setBlConfig("auto_proc_queue", queue) + except Exception as e: + logger.exception("Could not add request to autoproc queue") + finally: + setBlConfig("auto_proc_lock", False) + start_in_proc = None + autoproc_processor_list = json.loads(getBlConfig("autoprocNodes")) + for proc_num in autoproc_processor_list: + if not getBlConfig(proc_num): + start_in_proc = proc_num + break + if start_in_proc: + comm_s = f"ssh {start_in_proc} \"nohup {os.environ['MXPROCESSINGSCRIPTSDIR']}autoproc.sh {start_in_proc} {os.environ['BEAMLINE_ID']} & \" " + logger.info(f"Initializing AUTO-AUTOPROC {comm_s} \n In ({directory}, {request_id})") + subprocess.Popen(comm_s, shell=True) diff --git a/server.py b/server.py new file mode 100644 index 00000000..b4228c53 --- /dev/null +++ b/server.py @@ -0,0 +1,190 @@ +import logging +from logging import handlers +import json +from pathlib import Path +import signal +import threading +import asyncio +from typing import Any, Callable, Dict, Optional + +import daq_lib +import daq_utils +from epics import PV +from threads import run_summary_monitor +from utils.healthcheck import perform_server_checks +from daq_utils import getBlConfig, setBlConfig +from daq_main_common import pybass_init, process_input, whitelisted_functions +from utils.command_execution import is_safe_to_run_in_parallel +import os +from queue import Queue + +# Setup logger +logger = logging.getLogger() +logging.getLogger().setLevel(logging.INFO) +logging.getLogger("ophyd").setLevel(logging.WARN) +logging.getLogger("caproto").setLevel(logging.WARN) +logging.getLogger("httpx").setLevel(logging.WARN) +handler1 = handlers.RotatingFileHandler( + "lsdcServerLog.txt", maxBytes=5000000, backupCount=100 +) +myformat = logging.Formatter("%(asctime)s %(name)-8s %(levelname)-8s %(message)s") +handler1.setFormatter(myformat) +logger.addHandler(handler1) + + +workers: Dict[str, Dict[str, Any]] = { + "normal": { + "queue": Queue(), + "pv_suffix": "command_s", + "label": "command", + }, + "immediate": { + "queue": Queue(), + "pv_suffix": "immediate_command_s", + "label": "immediate", + }, +} + +RUN_QUEUE_COMMAND = "runDCQueue" +run_dc_queue_active = threading.Event() + + +def get_command_name(cmd: str) -> Optional[str]: + try: + command = json.loads(cmd) + except Exception: + return None + return command.get("function") + + +def should_ignore_normal_command(command_name: Optional[str]) -> bool: + return ( + run_dc_queue_active.is_set() + and daq_lib.unpause_evt.is_set() + and not is_safe_to_run_in_parallel(command_name, whitelisted_functions) + ) + + +def worker(queue: Queue, worker_name: str = "worker") -> None: + """ + This worker thread waits for a command to be added to the queue, + then processes it directly in the worker thread + """ + logger.info(f"{worker_name} started") + # Make sure this worker thread has a default asyncio loop for the RunEngine + try: + asyncio.get_event_loop() + except RuntimeError: + asyncio.set_event_loop(asyncio.new_event_loop()) + while True: + cmd = queue.get() # blocks until a command is available + try: + if cmd == "__STOP__": + break + command_name = get_command_name(cmd) + if worker_name == "normal worker" and should_ignore_normal_command(command_name): + logger.info("Ignoring normal command while runDCQueue is active: %s", cmd) + continue + + if command_name == RUN_QUEUE_COMMAND: + run_dc_queue_active.set() + try: + process_input(cmd) + finally: + if command_name == RUN_QUEUE_COMMAND: + run_dc_queue_active.clear() + except Exception: + logger.exception("Error executing %r", cmd) + finally: + queue.task_done() + logger.info(f"{worker_name} exiting") + + +def make_pv_callback(queue_label: str, queue: Queue) -> Callable[..., None]: + def _cb(value: Any = None, char_value: Any = None, **kws: Any) -> None: + s = (char_value or "").strip() + if not s: + return + logger.info("PV %s -> %s", queue_label, s) + queue.put(s) + + return _cb + + +def run_server(prefix: str) -> None: + """ + This server implementation allows multiple "workers" to execute commands in its own thread. + Each worker runs commands from a queue assigned to it and each queue is populated by commands sent to it via Epics PVs. + """ + # Start worker threads + stop_evt = threading.Event() + threads: Dict[str, threading.Thread] = {} + # Start command handling threads + for name, cfg in workers.items(): + t = threading.Thread( + target=worker, + name=f"cmd-worker-{name}", + args=[cfg["queue"], f"{name} worker"], + daemon=True, + ) + t.start() + threads[name] = t + + # Start summary table threads + mx_dir = Path(getBlConfig("visitDirectory")) + visit_name = "" + for part in mx_dir.parts: + if "pass-" in part: + visit_name = f"mx{part.split('-')[1]}-1" + fast_dp_dir = mx_dir / Path(visit_name) / Path("fast_dp_dir") + fast_dp_dir.mkdir(parents=True, exist_ok=True) + autoproc_dir = Path(mx_dir) / Path(visit_name) / Path("autoProc_dir") + autoproc_dir.mkdir(parents=True, exist_ok=True) + for basename, final_dir in {"fast_dp": fast_dp_dir, "autoPROC": autoproc_dir}.items(): + name = f"summary-monitor-{basename}" + t = threading.Thread( + target=run_summary_monitor, + args=(final_dir, basename), + kwargs={"period": 10, "stop_evt": stop_evt}, + daemon=True, + name=name + ) + t.start() + threads[name] = t + + + pvs: Dict[str, PV] = {} + for name, cfg in workers.items(): + pv = PV(f"{prefix}{cfg['pv_suffix']}", auto_monitor=True) + pv.put("", wait=True) + pv.add_callback(make_pv_callback(cfg["label"], cfg["queue"]), run_now=False) + pvs[name] = pv + + # Keep process alive; stop cleanly on Ctrl-C / SIGTERM + def _sig(_s: int, _f: Any) -> None: + stop_evt.set() + + signal.signal(signal.SIGINT, _sig) + signal.signal(signal.SIGTERM, _sig) + + try: + stop_evt.wait() + finally: + # tell workers to exit and wait for them + stop_evt.set() + for cfg in workers.values(): + cfg["queue"].put("__STOP__") + for t in threads.values(): + t.join() + + +def main() -> None: + pybass_init() + perform_server_checks() + setBlConfig("visitDirectory", os.environ.get("CURRENT_VISIT_DIR", os.getcwd())) + logger.info(f"Beamline Comm: {daq_utils.beamlineComm}") + run_server(daq_utils.beamlineComm) + + +if __name__ == "__main__": + main() diff --git a/setenergy_lsdc.py b/setenergy_lsdc.py index 09f5fc13..ae6585f4 100644 --- a/setenergy_lsdc.py +++ b/setenergy_lsdc.py @@ -17,7 +17,8 @@ import time import gov_lib import daq_lib -from start_bs import db, gov_robot, govs +# from start_bs import db, gov_robot, start_bs.govs +import start_bs import logging @@ -335,7 +336,14 @@ def find_peak(det, mot, start, stop, steps): sp = '_gap_user_setpoint' if mot is ivu_gap else '_user_setpoint' output = '_sum_all' if det is bpm1 else '' - data = np.array(db[uid].table()[[det.name+output, mot.name+sp]])[1:] + # data = np.array(db[uid].table()[[det.name+output, mot.name+sp]])[1:] + d = start_bs.tiled_client[uid]["primary"]["data"] + col_det = det.name + output + col_mot = mot.name + sp + # np.array(table()[[det, mot]]) preserves column order [det, mot]; + # [1:] drops the first (settling) scan point, matching databroker. + data = np.stack([d[col_det].read(), d[col_mot].read()], axis=1)[1:] + peak_idx = np.argmax(data[:, 0]) peak_x = data[peak_idx, 1] @@ -715,7 +723,7 @@ def setELsdc(energy, """ desired_states = ("SA", "AB") - if not gov_robot.state.get() in desired_states: + if not start_bs.gov_robot.state.get() in desired_states: print(f'Governor state not in one of {desired_states}, exiting') return -1 @@ -941,7 +949,7 @@ def beam_center_align(transSet='All'): return -1 desired_states = ("SA", "AB") - if not gov_robot.state.get() in desired_states: + if not start_bs.gov_robot.state.get() in desired_states: print(f'Governor state not in one of {desired_states}, exiting.') return -1 @@ -956,7 +964,7 @@ def beam_center_align(transSet='All'): detectorCoverClose() # Transition to Governor state AB (Auto-align Beam) - gov_lib.setGovRobot(gov_robot, 'AB') + gov_lib.setGovRobot(start_bs.gov_robot, 'AB') # Set beam transmission that avoids scintillator saturation # Default values are defined in settings as lookup table @@ -977,7 +985,7 @@ def beam_center_align(transSet='All'): yield from trans_set(transDefault, trans=trans_bcu) # Retract backlight - yield from bps.mv(light.y,govs.gov.Robot.dev.li.target_Out.get()) + yield from bps.mv(light.y,start_bs.govs.gov.Robot.dev.li.target_Out.get()) print('Light Y Out') # TODO: use "yield from bps.mv(...)" instead of .put(...) below. @@ -1050,17 +1058,17 @@ def beam_center_align(transSet='All'): # Adjust Gonio Y so rotation axis is again aligned to beam gonioYDiff = beamHiMagDiffY * hiMagCal - posGyOld = govs.gov.Robot.dev.gy.target_Work.get() + posGyOld = start_bs.govs.gov.Robot.dev.gy.target_Work.get() posGyNew = posGyOld + gonioYDiff yield from bps.mv(gonio.gy, posGyNew) # Move Gonio Y to new position - govs.gov.Robot.dev.gy.target_Work.set(posGyNew) # Set Governor Gonio Y Work position to new value + start_bs.govs.gov.Robot.dev.gy.target_Work.set(posGyNew) # Set Governor Gonio Y Work position to new value print('Gonio Y difference = %.3f' % gonioYDiff) yield from bps.mv(shutter_bcu.close, 1) print('BCU Shutter Closed') # Transition to Governor state SA (Sample Alignment) - gov_lib.setGovRobot(gov_robot, 'SA') + gov_lib.setGovRobot(start_bs.gov_robot, 'SA') # Set previous beam transmission if transSet != 'None': @@ -1188,7 +1196,7 @@ def fmx_flux_reference(slit1GapList = [2000, 1000, 600, 400], slit1GapDefault = # Put in diode yield from bps.mv(light.y, - govs.gov.Robot.dev.li.target_Diode.get()) + start_bs.govs.gov.Robot.dev.li.target_Diode.get()) # Open BCU shutter @@ -1215,7 +1223,7 @@ def fmx_flux_reference(slit1GapList = [2000, 1000, 600, 400], slit1GapDefault = # Retract diode yield from bps.mv(light.y, - govs.gov.Robot.dev.li.target_In.get()) + start_bs.govs.gov.Robot.dev.li.target_In.get()) # Set previous beam transmission if transSet != 'None': @@ -1259,17 +1267,17 @@ def fmx_reference(slit1GapDefault = 1000, transSet='All'): fmx_reference() """ - if not gov_robot.state.get() == "SA": + if not start_bs.gov_robot.state.get() == "SA": print('Not in Governor state SA, exiting') return # Transition to Governor state BL - gov_lib.setGovRobot(gov_robot, 'BL') + gov_lib.setGovRobot(start_bs.gov_robot, 'BL') yield from fmx_flux_reference(slit1GapDefault = slit1GapDefault, transSet = transSet) # Transition to Governor state SA - gov_lib.setGovRobot(gov_robot, 'SA') + gov_lib.setGovRobot(start_bs.gov_robot, 'SA') log_fmx_beamline_reference() diff --git a/start_bs.py b/start_bs.py index 142724b9..eb83de43 100755 --- a/start_bs.py +++ b/start_bs.py @@ -11,74 +11,69 @@ EpicsSignalBase.set_defaults(timeout=10, connection_timeout=10) # new style import redis from redis_json_dict import RedisJSONDict +from tiled.client import from_uri +from bluesky_tiled_plugins import TiledWriter +from bluesky_tiled_plugins.writing.tiled_writer import RunNormalizer +from bluesky_tiled_plugins.writing.consolidators import CONSOLIDATOR_REGISTRY, HDF5Consolidator +import matplotlib.pyplot as plt +from bluesky.run_engine import RunEngine +from bluesky.log import config_bluesky_logging +from bluesky.callbacks import * +from mxbluesky.devices import BeamlineDevices # setup RedisJsonDict uri = f"info.{os.environ['BEAMLINE_ID']}.nsls2.bnl.gov" # Provide an endstation prefix, if needed, with a trailing "-" -new_md = RedisJSONDict(redis.Redis(uri),prefix="lsdc-") - -#12/19 - author unknown. DAMA can help -""" -# Subscribe metadatastore to documents. -# If this is removed, data is not saved to metadatastore. -import metadatastore.commands -from bluesky.global_state import gs -gs.RE.subscribe_lossless('all', metadatastore.commands.insert) -from bluesky.callbacks.broker import post_run -# At the end of every run, verify that files were saved and -# print a confirmation message. -from bluesky.callbacks.broker import verify_files_saved -gs.RE.subscribe('stop', post_run(verify_files_saved)) -""" -# Import matplotlib and put it in interactive mode. -import matplotlib.pyplot as plt +new_md = RedisJSONDict(redis.Redis(uri, protocol=2),prefix="lsdc-") + plt.ion() -# Register bluesky IPython magics. -#from bluesky.magics import BlueskyMagics -#get_ipython().register_magics(BlueskyMagics) +class EigerMXConsolidator(HDF5Consolidator): -import bluesky.plans as bp + supported_mimetypes = {"application/x-hdf5;type=eiger"} -from bluesky.run_engine import RunEngine -RE = RunEngine() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.assets[0].parameter = "master" + + def validate(self, fix_errors=False) -> list[str]: + raise NotImplementedError("Validation of Eiger MX resources should be handled in the adapter, not the consolidator.") + + +CONSOLIDATOR_REGISTRY.update( + { + "application/x-hdf5;type=eiger": EigerMXConsolidator, + }, +) + +RE = RunEngine(context_managers=[]) beamline = os.environ["BEAMLINE_ID"] +tiled_key = os.environ[f"TILED_BLUESKY_WRITING_API_KEY_{beamline.upper()}"] +tiled_client = from_uri("https://tiled.nsls2.bnl.gov", api_key=tiled_key)[f"{beamline}/migration"] + +tw = TiledWriter( + tiled_client, + normalizer=RunNormalizer, + spec_to_mimetype={ + "AD_EIGER_MX": "application/x-hdf5;type=eiger", + "AD_EIGER_MX_OMEGA": "application/x-hdf5", + "AD_EIGER_MX_RASTER": "application/x-hdf5;type=eiger", + } +) + from databroker import Broker db = Broker.named(beamline) RE.md = new_md RE.subscribe(db.insert) +RE.subscribe(tw) -from bluesky.log import config_bluesky_logging config_bluesky_logging() -#from bluesky.utils import ts_msg_hook -#RE.msg_hook = ts_msg_hook -# from bluesky.callbacks.best_effort import BestEffortCallback -# bec = BestEffortCallback() -# RE.subscribe(bec) - -# convenience imports -# from ophyd.commands import * -from bluesky.callbacks import * -# from bluesky.spec_api import * -# from bluesky.global_state import gs, abort, stop, resume -# from databroker import (DataBroker as db, get_events, get_images, -# get_table, get_fields, restream, process) -# RE = gs.RE # convenience alias -#rest is hugo abort = RE.abort resume = RE.resume stop = RE.stop -# the following lines should not be needed as these should be persisted -#RE.md['group'] = beamline -#RE.md['beamline_id'] = beamline.upper() - -# loop = asyncio.get_event_loop() -# loop.set_debug(False) - - from ophyd import (SingleTrigger, ProsilicaDetector, ImagePlugin, StatsPlugin, ROIPlugin) @@ -116,7 +111,7 @@ class SampleXYZ(Device): if (beamline=="amx"): from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, - TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm) + TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm, SmartMagnet) from mxbluesky.devices.auto_recovery import PYZHomer from mxbluesky.devices.cryostream import CryoStream from mxbluesky.plans.auto_recovery import home_pins_plan @@ -124,6 +119,8 @@ class SampleXYZ(Device): from mxtools.flyer import MXFlyer from mxtools.raster_flyer import MXRasterFlyer from embl_robot import EMBLRobot + + beamline_devices = BeamlineDevices.from_beamline("amx", comm_prefix="XF:17IDB-ES:AMX{Comm}") mercury = ABBIXMercury('XF:17IDB-ES:AMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', @@ -154,15 +151,18 @@ class SampleXYZ(Device): top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon", name="govmon") gonio_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}goniomon", name="goniomon") - pyz_homer = PYZHomer("", name="pyz_homer") + pyz_homer = PYZHomer.from_beamline("amx", name="pyz_homer") dewar = Dewar("XF:17IDB-ES:AMX", name="dewar") home_pins = home_pins_plan(gov_mon_signal, gonio_mon_signal, pyz_homer, gonio) robot_arm = RobotArm("XF:17IDB-ES:AMX", name="robot_arm") - cs700 = CryoStream("XF:17ID1:CS700:", name="cs700", atol=0.1) + cs1000 = CryoStream("XF:17IDB-ES:AMX{CS:1}", name="cs1000", atol=0.1) + smart_magnet = SmartMagnet("XF:17IDB-ES:AMX", name="smart_magnet") + force_torque_sensor = EpicsSignal("XF:17IDB-ES:AMX{FTS:1}Read-Cmd.SCAN") + loop_detector_pv = EpicsSignal("XF:17IDB-ES:AMX{ML:LoopDet}Pred:TXT") elif beamline == "fmx": from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, - TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm) + TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm, SmartMagnet) from mxtools.vector_program import VectorProgram from mxbluesky.devices.auto_recovery import PYZHomer from mxbluesky.devices.cryostream import CryoStream @@ -173,6 +173,8 @@ class SampleXYZ(Device): from embl_robot import EMBLRobot import setenergy_lsdc + beamline_devices = BeamlineDevices.from_beamline("fmx", comm_prefix="XF:17IDC-ES:FMX{Comm}") + mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] @@ -201,52 +203,13 @@ class SampleXYZ(Device): top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") gov_mon_signal = EpicsSignal("XF:17ID:FMX{Karen}govmon", name="govmon") gonio_mon_signal = EpicsSignal("XF:17ID:FMX{Karen}goniomon", name="goniomon") - pyz_homer = PYZHomer("", name="pyz_homer") + pyz_homer = PYZHomer.from_beamline("fmx", name="pyz_homer") dewar = Dewar("XF:17IDC-ES:FMX", name="dewar") home_pins = home_pins_plan(gov_mon_signal, gonio_mon_signal, pyz_homer, gonio) robot_arm = RobotArm("XF:17IDC-ES:FMX", name="robot_arm") - cs700 = CryoStream("XF:17ID2:CS700:", name="cs700", atol=0.1) - -elif beamline=="nyx": - from mxbluesky.devices.md2 import LightDevice, BeamstopDevice, MD2SimpleHVDevice, MD2Device, ShutterDevice - two_click_low = mount_pos = loop_detector = top_aligner_fast = top_aligner_slow = work_pos = None - mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') - mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', - 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] - vdcm = VerticalDCM('XF:17IDA-OP:FMX{Mono:DCM', name='vdcm') - md2 = MD2Device("XF:19IDC-ES{MD2}:", name="md2") - gonio = md2 - shutter = ShutterDevice('XF:19IDC-ES{MD2}:', name='shutter') - beamstop = BeamstopDevice('XF:19IDC-ES{MD2}:', name='beamstop') - front_light = LightDevice('XF:19IDC-ES{MD2}:Front', name='front_light') - back_light = LightDevice('XF:19IDC-ES{MD2}:Back', name='back_light') - aperature = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Aperature', name='aperature') - scintillator = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Scintillator', name='scintillator') - capillary = MD2SimpleHVDevice('XF:19IDC-ES{MD2}:Capillary', name='capillary') - zebra = Zebra('XF:19IDC-ES{Zeb:1}:', name='zebra') - from nyxtools.vector import VectorProgram - vector = VectorProgram("XF:19IDC-ES{Gon:1-Vec}", name="vector") - from mxtools.eiger import EigerSingleTriggerV26 - detector = EigerSingleTriggerV26("XF:19ID-ES:NYX{Det:Eig9M}", name="detector", beamline=beamline) - #from nyxtools.flyer_eiger2 import NYXEiger2Flyer - from mxbluesky.md2_flyers import MD2StandardFlyer, MD2VectorFlyer, MD2RasterFlyer - flyer = MD2StandardFlyer(md2, detector) - vector_flyer = MD2VectorFlyer(md2, detector) - raster_flyer = MD2RasterFlyer(md2, detector) - - from nyxtools.isara_robot import IsaraRobotDevice - from denso_robot import OphydRobot - ophyd_robot = IsaraRobotDevice("XF19IDC-ES{Rbt:1}", name="robot") - robot = OphydRobot(ophyd_robot) # OphydRobot is the robot_lib API-compatible object - govs = _make_governors("XF:19IDC-ES", name="govs") - gov_robot = govs.gov.Robot - - det_move_done = EpicsSignalRO("XF:19IDC-ES{Det:1-Ax:Z}Mtr.DMOV", name="det_move_done") - #back_light = EpicsSignal(read_pv="XF:19IDD-CT{DIODE-Box_D1:4}InCh00:Data-RB",write_pv="XF:19IDD-CT{DIODE-Box_D1:4}OutCh00:Data-SP",name="back_light") - back_light_low_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:LowLimit-RB",name="back_light_low_limit") - back_light_high_limit = EpicsSignalRO("XF:19IDD-CT{DIODE-Box_D1:4}CfgCh00:HighLimit-RB",name="back_light_high_limit") - back_light_range = (back_light_low_limit.get(), back_light_high_limit.get()) - samplexyz = SampleXYZ("XF:19IDC-ES{Gon:1-Ax", name="samplexyz") + cs1000 = CryoStream("XF:17IDC-ES:FMX{CS:1}", name="cs1000", atol=0.1) + smart_magnet = SmartMagnet("XF:17IDC-ES:FMX", name="smart_magnet") + force_torque_sensor = EpicsSignal("XF:17IDC-ES:FMX{FTS:1}Read-Cmd.SCAN") else: raise Exception(f"Invalid beamline name provided: {beamline}") diff --git a/threads.py b/threads.py index 12b49bfa..7dc116dd 100644 --- a/threads.py +++ b/threads.py @@ -1,66 +1,137 @@ -from qtpy.QtCore import QThread, Signal, QPoint, Qt, QObject, QRunnable +from qtpy.QtCore import QThread, Signal, QObject, QRunnable from qtpy import QtGui -from PIL import Image, ImageQt import os -import sys -import urllib -from io import BytesIO import logging +import requests from config_params import SERVER_CHECK_DELAY import raddoseLib from pathlib import Path import cv2 import time -import numpy as np -import requests +from summarytable.summarytable import DataDirectory, BaseName, FileObserver logger = logging.getLogger() +# Sample-cam single-image (requests) fetch tuning. +CAM_CONNECT_TIMEOUT = 1.0 # seconds: TCP connect timeout +CAM_READ_TIMEOUT = 2.0 # seconds: between-bytes read-inactivity timeout +CAM_TOTAL_DEADLINE = 2.0 # seconds: hard wall-clock cap on the whole fetch +CAM_SLOW_WARN_MS = 1000.0 # ms: log a SLOW anomaly above this +CAM_ANOMALY_LOG_MIN_INTERVAL = 1.0 # seconds: rate-limit repeated anomaly logs + class VideoThread(QThread): frame_ready = Signal(object) + + def _log_anomaly(self, kind, url, elapsed_ms, err=None): + # Rate-limited so a sustained stall does not flood the log, but each + # anomaly window is still self-marked in the log for post-hoc grep. + now = time.monotonic() + if now - self._last_anomaly_log < CAM_ANOMALY_LOG_MIN_INTERVAL: + return + self._last_anomaly_log = now + if err is None: + logger.warning("%s url=%s elapsed_ms=%.0f", kind, url, elapsed_ms) + else: + logger.warning( + "%s url=%s elapsed_ms=%.0f err=%s", kind, url, elapsed_ms, err + ) + + def _fetch_snapshot(self, url, t0): + # Stream the body and enforce a hard wall-clock deadline so the worker + # loop can never block indefinitely on a trickling / stalled IOC. + resp = self._http_session.get( + url, + timeout=(CAM_CONNECT_TIMEOUT, CAM_READ_TIMEOUT), + stream=True, + ) + try: + resp.raise_for_status() + chunks = [] + for chunk in resp.iter_content(chunk_size=8192): + if chunk: + chunks.append(chunk) + if time.monotonic() - t0 > CAM_TOTAL_DEADLINE: + raise TimeoutError( + f"total deadline {CAM_TOTAL_DEADLINE:.1f}s exceeded" + ) + return b"".join(chunks) + finally: + resp.close() + + def _make_capture(self, url): + cap = cv2.VideoCapture(url, cv2.CAP_FFMPEG) + cap.set(cv2.CAP_PROP_OPEN_TIMEOUT_MSEC, 2000) + cap.set(cv2.CAP_PROP_READ_TIMEOUT_MSEC, 2000) + cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + return cap + def camera_refresh(self): - pixmap_orig = QtGui.QPixmap(320, 180) if self.url: + url = self.url + t0 = time.monotonic() try: - file = BytesIO(urllib.request.urlopen(self.url, timeout=self.delay/1000).read()) - img = Image.open(file) - qimage = ImageQt.ImageQt(img) - pixmap_orig = QtGui.QPixmap.fromImage(qimage) + content = self._fetch_snapshot(url, t0) + elapsed_ms = (time.monotonic() - t0) * 1000 + qimage = QtGui.QImage() + qimage.loadFromData(content) + if self.width and self.height: + qimage = qimage.scaled(self.width, self.height) self.showing_error = False + self.frame_ready.emit(qimage) + if elapsed_ms > CAM_SLOW_WARN_MS: + self._log_anomaly("SAMPLE_CAM_SLOW", url, elapsed_ms) except Exception as e: + elapsed_ms = (time.monotonic() - t0) * 1000 + # Always log anomalies (rate-limited) and keep emitting so the + # feed recovers on its own once the IOC/stream resumes. + self._log_anomaly("SAMPLE_CAM_TIMEOUT", url, elapsed_ms, err=e) if not self.showing_error: - painter = QtGui.QPainter(pixmap_orig) - painter.setPen(QtGui.QPen(Qt.white)) - painter.drawText( QPoint(10, 10), "No image obtained from: " ) - painter.drawText( QPoint(10, 30), f"{self.url}") - painter.end() - self.frame_ready.emit(pixmap_orig) + logger.warning("CAM_FETCH_FAILED url=%s err=%s", self.url, e) + # self.frame_ready.emit(None) self.showing_error = True + self.msleep(int(max(0, self.delay - elapsed_ms))) + return if self.video_capture: - if self.new_mjpg_url != self.old_mjpg_url: - self.video_capture.open(self.new_mjpg_url) + if self.new_mjpg_url != self.old_mjpg_url and self.new_mjpg_url is not None: + try: + self.video_capture.release() + finally: + self.video_capture = None + self.video_capture = self._make_capture(self.new_mjpg_url) self.old_mjpg_url = self.new_mjpg_url retval,self.currentFrame = self.video_capture.read() - if self.currentFrame is None: - #logger.debug('no frame read from stream URL - ensure the URL does not end with newline and that the filename is correct') + if self.currentFrame is None or not retval: + if not self.showing_error: + logger.info("CAM_READ_FAILED url=%s retval=%s", + self.new_mjpg_url, retval) + self.showing_error = True + self.msleep(100) + return + # on a successful read, reset the flag: + self.showing_error = False + + now = time.monotonic() * 1000 + if now <= self.next_emit: return + self.next_emit = now + self.delay height,width=self.currentFrame.shape[:2] qimage= QtGui.QImage(self.currentFrame,width,height,3*width,QtGui.QImage.Format_RGB888) + qimage = qimage.copy() qimage = qimage.rgbSwapped() - pixmap_orig = QtGui.QPixmap.fromImage(qimage) if self.width and self.height: - pixmap_orig = pixmap_orig.scaled(self.width, self.height) + qimage = qimage.scaledToHeight(self.height) if not self.showing_error: - self.frame_ready.emit(pixmap_orig) + self.frame_ready.emit(qimage) def __init__(self, *args, delay=1000, url='', mjpg_url=None, width=None, height=None,**kwargs): + self._retry_after = 0.0 self.delay = delay self.width = width self.height = height @@ -69,28 +140,45 @@ def __init__(self, *args, delay=1000, url='', mjpg_url=None, width=None, height= self.old_mjpg_url = None self.new_mjpg_url = None self.video_capture = None - if self.mjpg_url and self.mjpg_url.lower().endswith(".mjpg"): - self.video_capture = cv2.VideoCapture(self.mjpg_url) + if self.mjpg_url: + self.video_capture = self._make_capture(self.mjpg_url) self.old_mjpg_url = self.mjpg_url self.new_mjpg_url = self.mjpg_url self.mjpg_url = None self.showing_error = False + self._http_session = requests.Session() + self._last_anomaly_log = 0.0 self.is_running = True + self.next_emit = time.monotonic() * 1000 QThread.__init__(self, *args, **kwargs) - def updateCam(self, url): - if url.lower().endswith(".mjpg"): + def updateSnapshotUrl(self, url, delay=None): + if delay is not None: + self.delay = delay + self.new_mjpg_url = None + self.old_mjpg_url = None + self.url = url + + def updateCam(self, url, delay=None): + if delay is not None: + self.delay = delay + self.url = '' + if self.video_capture is None: + self.video_capture = self._make_capture(url) + self.old_mjpg_url = url + self.new_mjpg_url = url + else: self.new_mjpg_url = url def run(self): while self.is_running: self.camera_refresh() - self.msleep(self.delay) def stop(self): self.is_running = False self.wait() + self._http_session.close() class RaddoseThread(QThread): @@ -101,7 +189,10 @@ def __init__(self, *args, avg_dwd = 10, #Default of 10MGy energy = 12.66, flux = -1.0, wedge = 180.0, - verbose = False, **kwargs): + verbose = False, + dm_user = 1.0, + beamsize_type = "S", + **kwargs): self.avg_dwd = avg_dwd self.beamsizeV = beamsizeV self.beamsizeH = beamsizeH @@ -110,10 +201,16 @@ def __init__(self, *args, avg_dwd = 10, #Default of 10MGy self.flux = flux self.wedge = wedge self.verbose = verbose + self.dm_user = dm_user + self.beamsize_type = beamsize_type QThread.__init__(self, *args, **kwargs) def run(self): - lifetime_value = raddoseLib.fmx_expTime(self.avg_dwd, self.beamsizeV, self.beamsizeH, self.vectorL, self.energy, self.flux, self.wedge, self.verbose) + lifetime_value = raddoseLib.fmx_expTime( + self.avg_dwd, self.beamsizeV, self.beamsizeH, + self.vectorL, self.energy, self.flux, self.wedge, + self.verbose, dm_user=self.dm_user, beamsize_type=self.beamsize_type + ) self.lifetime.emit(lifetime_value) @@ -121,12 +218,13 @@ class ServerCheckThread(QThread): visit_dir_changed = Signal() def __init__(self, *args, delay=SERVER_CHECK_DELAY, **kwargs): self.delay = delay + self.is_running = True QThread.__init__(self, *args, **kwargs) def run(self): import db_lib beamline = os.environ["BEAMLINE_ID"] - while True: + while self.is_running: if Path(db_lib.getBeamlineConfigParam(beamline, "visitDirectory")).resolve() != Path.cwd(): message = "The server visit directory has changed, stopping!" logger.error(message) @@ -135,6 +233,9 @@ def run(self): break self.msleep(self.delay) + def stop(self): + self.is_running = False + self.wait() class SignalObject(QObject): finished = Signal(object) @@ -158,4 +259,17 @@ def run(self): # Execute the provided function with its arguments. result = self.run_function(*self.args, **self.kwargs) # Emit the finished signal with the result. - self.signal.finished.emit(result) \ No newline at end of file + self.signal.finished.emit(result) + + +def run_summary_monitor(dir: str, basename:str , period=10, stop_evt=None): + bn = BaseName(basename) + d = DataDirectory(dir, bn) + d.attach(FileObserver(bn)) + + while True: + if stop_evt and stop_evt.is_set(): + break + d.check_directory() + time.sleep(period) + diff --git a/utils/bluesky.py b/utils/bluesky.py new file mode 100644 index 00000000..b0e895fb --- /dev/null +++ b/utils/bluesky.py @@ -0,0 +1,66 @@ +import os +from pydantic import ValidationError +import requests +import logging +from db_lib import getRequestByID, getSampleByID +from config_params import CURRENT_CYCLE +from utils.models import Request, Sample + +logger = logging.getLogger(__name__) + +def get_sample_metadata(sample_id): + sample_md = Sample.model_validate(getSampleByID(sample_id)) + return sample_md.model_dump(mode="json") + +def get_bluesky_metadata(request_dict: "dict|None" = None, request_id: "str|None" = None) -> "dict[str, str]": + if request_id and not request_dict: + request_dict = getRequestByID(request_id) + if request_dict is None and request_id is None: + return {} + bs_md = {} + try: + request: Request = Request.model_validate(request_dict) + proposal_id = request.proposal_id # request["proposalID"] + bs_md["collection_metadata"] = request.request_def.model_dump(mode="json") # request["request_obj"] + except ValidationError as e: + logger.exception("Could not validate request") + proposal_id = request_dict.get("proposal_id") + bs_md["collection_metadata"] = request_dict.get("request_obj") + + beamline = os.environ["BEAMLINE_ID"] + bs_md["cycle"] = CURRENT_CYCLE + + + # Get sample metadata: + try: + sample_id = bs_md["collection_metadata"].get("sample") + if sample_id: + bs_md["sample_metadata"] = get_sample_metadata(sample_id) + except Exception as e: + logger.warning("Error getting sample info from API: %s", "Not populating sample info", exc_info=True) + + + try: + r = requests.get(f"{os.environ['NSLS2_API_URL']}/v1/proposal/{proposal_id}") + r.raise_for_status() + proposal_data = r.json()['proposal'] + pi_name = "" + for user in proposal_data["users"]: + if user.get("is_pi"): + pi_name = ( + f"{user.get('first_name', '')} {user.get('last_name', '')}".strip()) + proposal_id = proposal_data.get("proposal_id") + bs_md["proposal"] = { + "proposal_id": proposal_id, + "title": proposal_data.get("title"), + "type": proposal_data.get("type"), + "pi_name": pi_name, + } + bs_md["data_session"] = f'pass-{proposal_id}' if proposal_id is not None else f"{beamline}_beamline" + except Exception as e: + bs_md["data_session"] = f"{beamline}_beamline" + logger.warning("Error getting proposal info from API: %s", "Not populating proposal info", exc_info=True) + + bs_md["tiled_access_tags"] = [bs_md["data_session"]] + return bs_md + diff --git a/utils/command_execution.py b/utils/command_execution.py new file mode 100644 index 00000000..ea046004 --- /dev/null +++ b/utils/command_execution.py @@ -0,0 +1,19 @@ +from collections.abc import Callable, Mapping +from typing import TypeVar + + +Function = TypeVar("Function", bound=Callable[..., object]) +_PARALLEL_SAFE_ATTRIBUTE = "_lsdc_safe_to_run_in_parallel" + + +def safe_to_run_in_parallel(func: Function) -> Function: + setattr(func, _PARALLEL_SAFE_ATTRIBUTE, True) + return func + + +def is_safe_to_run_in_parallel( + command_name: str | None, + functions: Mapping[str, Callable[..., object]], +) -> bool: + func = functions.get(command_name) if command_name is not None else None + return bool(func is not None and getattr(func, _PARALLEL_SAFE_ATTRIBUTE, False)) diff --git a/utils/custom_pv.py b/utils/custom_pv.py index a7330a5b..f4f12bfd 100644 --- a/utils/custom_pv.py +++ b/utils/custom_pv.py @@ -1,14 +1,245 @@ +import logging +import time +from threading import Lock +from typing import Any, Callable, Optional, Type + from epics import PV +from qtpy import QtCore + + +_NOTHING = object() +logger = logging.getLogger() + + +class SignalQtBridge(QtCore.QObject): + """ + Bridges an EPICS PV or ophyd Signal callback to a Qt slot. + + Eliminates the repetitive boilerplate of declaring a unique Signal subclass + attribute, writing a one-liner ``pv.add_callback`` wrapper that emits it, + and then connecting the signal to the real slot in ``initCallbacks``. + + A single ``Signal(object)`` is used for the thread hop; the *slot* receives + whatever the *transform* returns (or *value* / *char_value* by default). + + Parameters + ---------- + signal_or_name : object | str + Existing EPICS PV object, ophyd Signal-like object, or a PV name + string (creates an EPICS PV with ``auto_monitor=True``). + slot : callable + Qt slot or any callable invoked on the main thread when the PV + changes. It receives exactly one argument – the emitted value. + For no-arg or multi-arg slots wrap with a lambda, e.g. + ``lambda _: my_slot()`` or ``lambda t: my_slot(*t)``. + use_char : bool, default False + Emit ``char_value`` (EPICS string representation) instead of the raw + ``value``. Ignored when *transform* is provided. + transform : callable, optional + ``transform(value, char_value, **kw) -> Any`` called inside the EPICS + callback thread. Its return value is emitted. Overrides *use_char*. + predicate : callable, optional + ``predicate(value, char_value, **kw) -> bool`` evaluated before + emitting. The signal is suppressed when this returns ``False``. + custom_pv_class : type[PV], optional + PV subclass to instantiate when *signal_or_name* is a string. + label : str, default "" + Optional label used in periodic rate logs. When empty, no per-bridge + rate logging is performed. + rate_log_interval_s : float, default 5.0 + Logging interval for incoming/outgoing callback rates when *label* is + provided. + coalesce_interval_ms : int, default 200 + Callback coalescing period in milliseconds. At each period the latest + value (if any) is emitted to the slot. + **callback_kwargs + Extra keyword arguments forwarded verbatim to ``pv.add_callback()``. + They are accessible inside *transform* / *predicate* via ``**kw``. + + Examples + -------- + Simple scalar (float): + + >>> bridge = SignalQtBridge( + ... "MY:ENERGY:RBV", self.processEnergyChange, + ... transform=lambda v, cv, **kw: float(v), + ... ) + + String PV: + + >>> bridge = SignalQtBridge( + ... my_existing_pv, self.printServerMessage, use_char=True + ... ) + + Conditional emission: + + >>> bridge = SignalQtBridge( + ... flag_pv, self.handleResult, + ... use_char=True, + ... predicate=lambda v, cv, **kw: cv != "0", + ... ) + + Multi-argument slot (emit a tuple, unpack in lambda): + + >>> bridge = SignalQtBridge( + ... samp_pv, + ... lambda t: self.processSampMove(*t), + ... transform=lambda v, cv, _mid="x", **kw: (int(v), _mid), + ... ) + """ + + signal = QtCore.Signal(object) + def __init__( + self, + signal_or_name, + slot: Callable, + *, + use_char: bool = False, + transform: Optional[Callable] = None, + predicate: Optional[Callable] = None, + custom_pv_class: Optional[Type[PV]] = None, + label: str = "", + rate_log_interval_s: float = 5.0, + coalesce_interval_ms: int = 200, + **callback_kwargs, + ): + super().__init__() + self.use_char = use_char + self.transform = transform + self.predicate = predicate + self._subscription_token: Any = None + self._epics_callback_index: Any = None + self._rate_label = label + self._rate_logging_enabled = bool(label) + self._rate_log_interval_s = rate_log_interval_s + self._rate_window_start = time.monotonic() + self._incoming_count = 0 + self._outgoing_count = 0 + self._rate_lock = Lock() -class MountedPinPV(PV): + self.signal.connect(slot) + + if isinstance(signal_or_name, str): + pv_class = custom_pv_class if custom_pv_class is not None else PV + if not issubclass(pv_class, PV): + raise TypeError("custom_pv_class must be PV or a subclass of PV") + self.source = pv_class(signal_or_name, auto_monitor=True) + else: + self.source = signal_or_name + + if hasattr(self.source, "add_callback"): + self._epics_callback_index = self.source.add_callback( + self._on_epics_changed, + **callback_kwargs, + ) + elif hasattr(self.source, "subscribe"): + event_type = callback_kwargs.pop("event_type", "value") + run = callback_kwargs.pop("run", False) + if callback_kwargs: + unknown = ", ".join(sorted(callback_kwargs)) + raise TypeError(f"Unsupported ophyd subscribe kwargs: {unknown}") + self._subscription_token = self.source.subscribe( + self._on_ophyd_changed, + event_type=event_type, + run=run, + ) + else: + raise TypeError( + "signal_or_name must be EPICS PV name, EPICS PV object, or ophyd Signal-like object" + ) + + self._pending_value: Any = _NOTHING + self._coalesce_timer = QtCore.QTimer(self) + self._coalesce_timer.setInterval(coalesce_interval_ms) + self._coalesce_timer.timeout.connect(self._flush_coalesced) + self._coalesce_timer.start() + + # ------------------------------------------------------------------ + # EPICS callback (runs in a CA background thread) + # ------------------------------------------------------------------ + + def _emit_if_allowed(self, value=None, char_value=None, **kw): + if self.predicate is not None and not self.predicate(value, char_value, **kw): + return + + if self.transform is not None: + result = self.transform(value, char_value, **kw) + elif self.use_char: + result = char_value + else: + result = value + + if self._rate_logging_enabled: + with self._rate_lock: + self._incoming_count += 1 + + self._pending_value = result + + def _flush_coalesced(self): + """Main-thread QTimer slot. Emits latest pending value if one exists.""" + if self._pending_value is not _NOTHING: + val = self._pending_value + self._pending_value = _NOTHING + if self._rate_logging_enabled: + with self._rate_lock: + self._outgoing_count += 1 + # Signal.emit() is thread-safe in Qt; this queued cross-thread call + # delivers the value to the slot on the main GUI thread. + self.signal.emit(val) + + if self._rate_logging_enabled: + now = time.monotonic() + elapsed = now - self._rate_window_start + if elapsed >= self._rate_log_interval_s: + with self._rate_lock: + incoming_count = self._incoming_count + outgoing_count = self._outgoing_count + self._incoming_count = 0 + self._outgoing_count = 0 + self._rate_window_start = now + + incoming_hz = incoming_count / elapsed if elapsed > 0 else 0.0 + outgoing_hz = outgoing_count / elapsed if elapsed > 0 else 0.0 + reduction_pct = 0.0 + if incoming_count > 0: + reduction_pct = 100.0 * (1.0 - (outgoing_count / incoming_count)) + + logger.info( + "BRIDGE_RATE label=%s incoming_hz=%.2f outgoing_hz=%.2f " + "reduction_pct=%.1f incoming_count=%d outgoing_count=%d window_s=%.2f", + self._rate_label, + incoming_hz, + outgoing_hz, + reduction_pct, + incoming_count, + outgoing_count, + elapsed, + ) + + def _on_epics_changed(self, value=None, char_value=None, **kw): + self._emit_if_allowed(value=value, char_value=char_value, **kw) + + def _on_ophyd_changed(self, value=None, old_value=None, obj=None, **kw): + self._emit_if_allowed(value=value, char_value=str(value), old_value=old_value, obj=obj, **kw) + + # ------------------------------------------------------------------ + # Convenience pass-throughs so callers can still do bridge.get() / + # bridge.put() without reaching into bridge.pv directly. + # ------------------------------------------------------------------ def get(self, *args, **kwargs): - value = str(super().get(*args, **kwargs)) - return value.split(",")[0] - - def get_pin_state(self): - value = str(super().get()).split(",") - if len(value) == 2: - return value[1] - return None + return self.source.get(*args, **kwargs) + + def put(self, *args, **kwargs): + self.source.put(*args, **kwargs) + + def close(self): + if self._coalesce_timer.isActive(): + self._coalesce_timer.stop() + if self._subscription_token is not None and hasattr(self.source, "unsubscribe"): + self.source.unsubscribe(self._subscription_token) + self._subscription_token = None + if self._epics_callback_index is not None and hasattr(self.source, "remove_callback"): + self.source.remove_callback(self._epics_callback_index) + self._epics_callback_index = None diff --git a/utils/models.py b/utils/models.py new file mode 100644 index 00000000..13ecd69b --- /dev/null +++ b/utils/models.py @@ -0,0 +1,326 @@ +from pydantic import BaseModel, field_validator, Field, model_validator +from uuid import UUID +from datetime import datetime +from typing import Optional, Literal, Union, Dict, Any, List, Tuple, NewType +from enum import Enum +from pathlib import Path + +PuckName = NewType("PuckName", str) +SampleName = NewType("SampleName", str) + + +class RequestType(str, Enum): + standard = "standard" + raster = "raster" + vector = "vector" + multicol = "multiCol" + +class BaseRequestDefinition(BaseModel): + sample: UUID + sweep_start: float + sweep_end: float + osc_range: Optional[float] = None + img_width: float + exposure_time: float + protocol: RequestType + detector_distance: float = Field(..., alias="detDist", validate_default=True) + parent_request_id: UUID | Literal[-1] = Field(..., alias="parentReqID") + base_path: Path = Field(..., alias="basePath", validate_default=True) + file_prefix: str + directory: Path + file_number_start: int + energy: float + wavelength: float + resolution: float + slit_height: float + slit_width: float + attenuation: float + visit_name: str + detector: str + beamline: str + pos_x: float + pos_y: float + pos_z: float + pos_type: str + grid_step: float = Field(..., alias="gridStep") + run_num: int = Field(..., alias="runNum") + xbeam: Optional[float] = None + ybeam: Optional[float] = None + model_config = {"frozen": False, "extra": "allow"} + + @model_validator(mode="before") + def ignore_specific_fields(cls, values): + # Removing dataPath because it is always stuck in /legacy/2022-3 which is of no use + fields_to_ignore = {"dataPath"} + if isinstance(values, dict): + return {k: v for k, v in values.items() if k not in fields_to_ignore} + return values + + # Validator for fields of type `Path` + @field_validator("base_path", "directory") + def convert_to_path(cls, value): + return Path(value) if value else value + + +class StandardRequestDefinition(BaseRequestDefinition): + protocol: Literal[RequestType.standard] = RequestType.standard + centering_option: Literal["AutoRaster", "Interactive"] = Field( + ..., alias="centeringOption" + ) + fast_dp: bool = Field(..., alias="fastDP") + fast_ep: bool = Field(..., alias="fastEP") + dimple: bool + xia2: bool + model_config = {"frozen": False} + + +class VectorRequestDefinition(BaseRequestDefinition): + protocol: Literal[RequestType.vector] = RequestType.vector + centering_option: Literal["AutoRaster", "Interactive"] = Field( + ..., alias="centeringOption" + ) + fast_dp: bool = Field(..., alias="fastDP") + fast_ep: bool = Field(..., alias="fastEP") + dimple: bool + xia2: bool + model_config = {"frozen": False} + + +class Point2D(BaseModel): + x: float + y: float + model_config = {"frozen": False} + + +class Point3D(BaseModel): + x: float + y: float + z: float + model_config = {"frozen": False} + + +class VectorDefinition(BaseModel): + start: Point3D = Field(..., alias="vecStart") + end: Point3D = Field(..., alias="vecEnd") + length: float = Field(..., alias="trans_total") + frames_per_point: int = Field(..., alias="fpp") + model_config = {"frozen": False} + + +class RasterRowDefinition(BaseModel): + start: Point2D + end: Point2D + num_steps: int = Field(..., alias="numsteps") + model_config = {"frozen": False} + + +class RasterDefinition(BaseModel): + beam_width: float = Field(..., alias="beamWidth") + beam_height: float = Field(..., alias="beamHeight") + status: int + x: float + y: float + z: float + omega: float + step_size: float = Field(..., alias="stepsize") + row_defs: list[RasterRowDefinition] = Field(..., alias="rowDefs") + num_cells: int = Field(..., alias="numCells") + raster_type: str = Field(..., alias="rasterType") + model_config = {"frozen": False} + + +class MaxRasterPosition(BaseModel): + file: Optional[str] + coords: list[float | None] + index: Optional[int] + omega: Optional[float] + model_config = {"frozen": False} + + +class RasterRequestDefinition(BaseRequestDefinition): + protocol: Literal[RequestType.raster] = RequestType.raster + raster_def: RasterDefinition = Field(..., alias="rasterDef") + max_raster: Optional[MaxRasterPosition] = None + model_config = {"frozen": False} + +class MultiColRequestDefinition(BaseRequestDefinition): + protocol: Literal[RequestType.multicol] = RequestType.multicol + raster_def: RasterDefinition = Field(..., alias="rasterDef") + max_raster: Optional[MaxRasterPosition] = None + model_config = {"frozen": False} + +RequestDefinition = Union[ + StandardRequestDefinition, RasterRequestDefinition, VectorRequestDefinition, MultiColRequestDefinition +] + + +class StandardResult(BaseModel): + time: Optional[float] = None + result_time: Optional[datetime] = None + diffraction_images: Optional[str] = None + fast_dp_row: Optional[List] = None + auto_proc_row: Optional[List] = None + model_config = {"frozen": False} + + @model_validator(mode="before") + def convert_epoch_to_datetime(cls, values: Any) -> Any: + # Access other fields using info.data + if values.get("result_time") is None and values.get("time") is not None: + values["result_time"] = datetime.fromtimestamp(values["time"]) + return values + +class VectorResult(StandardResult): + pass + +class RasterCellData(BaseModel): + image: Tuple[Path, int] + spot_count: float + spot_count_no_ice: float + d_min: float + d_min_method_1: Optional[float] = None + d_min_method_2: Optional[float] = None + total_intensity: float + cell_map_key: str = Field(..., alias="cellMapKey") + model_config = {"frozen": False} + + @field_validator("image") + def validate_and_convert_path(cls, value): + # Convert the first element (path string) to a Path object + path_str, some_int = value + value = (Path(path_str), some_int) + return value + + +class RasterCellCollection(BaseModel): + type: str + cells: List[RasterCellData] = Field(..., alias="resultObj") + model_config = {"frozen": False} + + +class RasterResultData(BaseModel): + sample_id: UUID + parent_request_id: UUID | Literal[-1] = Field(..., alias="parentReqID") + cell_map: Dict[str, Point3D] = Field(..., alias="rasterCellMap") + cell_data_collection: RasterCellCollection = Field(..., alias="rasterCellResults") + model_config = {"frozen": False} + + +class RasterResult(BaseModel): + uid: UUID + time: float + result_time: Optional[datetime] = None + provenance: Dict[str, Any] + result_type: str + owner: str + sample: UUID + request: UUID + data: RasterResultData = Field(..., alias="result_obj") + plot_image: Optional[str] = None + jpeg_image: Optional[str] = None + top_frames: Optional[str] = None + hist_mean_neighbor_dist: Optional[str] = None + hist_resolutions: Optional[str] = None + hist_spot_count: Optional[str] = None + + model_config = {"frozen": False} + + @model_validator(mode="before") + def convert_epoch_to_datetime(cls, values: Any) -> Any: + # Access other fields using info.data + if values.get("result_time") is None and values.get("time") is not None: + values["result_time"] = datetime.fromtimestamp(values["time"]) + return values + + +Result = Union[StandardResult, RasterResult, VectorResult] +# Result = RasterResult + + +class Request(BaseModel): + uid: UUID + sample: UUID + time: float + request_time: Optional[datetime] = None + state: Optional[Literal["active", "inactive"]] = None + seq_num: int + priority: int + request_type: RequestType + request_def: RequestDefinition = Field(..., alias="request_obj") + owner: str + proposal_id: str = Field(..., alias="proposalID") + result: Optional[Result] = None + + model_config = {"frozen": False} + + @field_validator("proposal_id", mode="before") + def proposal_id_to_str(cls, v): + if isinstance(v, (str, int)): + return str(v) + raise TypeError("proposal_id must be a string or integer") + + @model_validator(mode="before") + def convert_epoch_to_datetime(cls, values: dict[str, Any]) -> dict[str, Any]: + if not isinstance(values, dict): + return values + # Access the fields from the values dictionary + try: + if values.get("request_time") is None and values.get("time") is not None: + values["request_time"] = datetime.fromtimestamp(values["time"]) + except Exception as e: + print(f"Error converting epoch to datetime: {e} {type(cls)}") + return values + + +class Sample(BaseModel): + uid: UUID + name: SampleName + time: float + create_time: Optional[datetime] = None + container: UUID | Literal["NULL"] + position: int | None = None + owner: str + kind: str + proposal_id: Optional[str | int] = Field(..., alias="proposalID") + model: str + sequence: Optional[str | int] = None + request_count: int + model_config = {"frozen": False} + + @model_validator(mode="before") + def convert_epoch_to_datetime(cls, values: dict[str, Any]) -> dict[str, Any]: + if not isinstance(values, dict): + return values + # Access the fields from the values dictionary + try: + if values.get("create_time") is None and values.get("time") is not None: + values["create_time"] = datetime.fromtimestamp(values["time"]) + except Exception as e: + print(f"Error converting epoch to datetime: {e} {type(cls)}") + return values + + +class AutomatedCollection(BaseModel): + sample: Sample + rasters: Dict[UUID, Dict[UUID, Request]] + standard: Dict[UUID, Request] + model_config = {"frozen": False} + + +class ManualCollection(BaseModel): + sample: Sample + rasters: Dict[UUID, Request] + standards: Dict[UUID, Request] + vectors: Dict[UUID, Request] + model_config = {"frozen": False} + + +CollectionType = AutomatedCollection | ManualCollection + + +class CollectionData(BaseModel): + sample_collections: dict[SampleName, CollectionType] + sample_data: dict[SampleName, Sample] + puck_data: Dict[PuckName, List[SampleName]] + model_config = {"frozen": False} + beamline: Optional[str] = None + proposal: Optional[str] = None