From a82ca59aef8025b8d3b31fb5f6597b5b4bb26742 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Tue, 18 Mar 2025 09:17:38 -0500 Subject: [PATCH 1/4] Make objects smarter and fix contrast stretching. --- deapi/client.py | 105 +++++++-- deapi/data_types.py | 44 ++++ deapi/panel_apps/__init__.py | 0 deapi/panel_apps/panel_client.py | 9 - deapi/simulated_server/fake_server.py | 94 ++++---- deapi/tests/conftest.py | 15 ++ .../speed_tests/test_internal_file_saving.py | 103 +++++++++ deapi/tests/test_client.py | 13 +- deapi/tests/test_file_saving/test_h5ebsd.py | 200 ++++++++++++++++++ .../live_imaging/bright_spot_intensity.py | 60 ++++++ .../taking_an_image_every_minute.py | 52 +++++ 11 files changed, 619 insertions(+), 76 deletions(-) delete mode 100644 deapi/panel_apps/__init__.py delete mode 100644 deapi/panel_apps/panel_client.py create mode 100644 deapi/tests/test_file_saving/test_h5ebsd.py create mode 100644 examples/live_imaging/bright_spot_intensity.py create mode 100644 examples/live_imaging/taking_an_image_every_minute.py diff --git a/deapi/client.py b/deapi/client.py index 6e7f7a4..8fe3ab7 100644 --- a/deapi/client.py +++ b/deapi/client.py @@ -406,10 +406,10 @@ def get_property_specifications(self, propertyName): propertyName : str The name of the property to get the allowed values for """ - t0 = self.GetTime() + t0 = self.GetTime() values = False command = self.__addSingleCommand(self.GET_PROPERTY_SPECIFICATIONS, propertyName) - response = self.__sendCommand(command) + response = self.__sendCommand(command) if response == False: return None @@ -431,7 +431,7 @@ def get_property_specifications(self, propertyName): rangeString = "" for i in range(optionsLength): if propSpec.dataType == "Integer": - rangeString += str(int(propSpec.options[i])) + rangeString += str(int(propSpec.options[i])) else: rangeString += str(propSpec.options[i]) if i == 0: @@ -525,6 +525,10 @@ def set_property(self, name: str, value): value : any The value to set the property to """ + if self.readOnly: + log.error("Read-only client cannot set properties.") + return False + t0 = self.GetTime() ret = False @@ -561,6 +565,10 @@ def set_property_and_get_changed_properties(self, name, value, changedProperties changedProperties : list List of properties that have changed """ + if self.readOnly: + log.error("Read-only client cannot set properties.") + return False + t0 = self.GetTime() ret = False @@ -597,6 +605,10 @@ def set_engineering_mode(self, enable, password): password : str The password to enable engineering mode """ + if self.readOnly: + log.error("Read-only client cannot set engineering mode.") + return False + ret = False command = self._addSingleCommand(self.SET_ENG_MODE, None, [enable, password]) @@ -608,6 +620,9 @@ def set_engineering_mode(self, enable, password): @write_only def setEngModeAndGetChangedProperties(self, enable, password, changedProperties): + if self.readOnly: + log.error("Read-only client cannot set engineering mode.") + return False ret = False @@ -640,6 +655,10 @@ def set_hw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): sizeY : int The height of the ROI """ + if self.read_only: + log.error("Read-only client cannot set HW ROI.") + return False + t0 = self.GetTime() ret = False @@ -665,6 +684,9 @@ def set_hw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): @write_only def SetScanSize(self, sizeX, sizeY): + if self.read_only: + log.error("Read-only client cannot set scan size.") + return False t0 = self.GetTime() ret = False @@ -687,6 +709,10 @@ def SetScanSize(self, sizeX, sizeY): @write_only def SetScanSizeAndGetChangedProperties(self, sizeX, sizeY, changedProperties): + if self.read_only: + log.error("Read-only client cannot set scan size.") + return False + t0 = self.GetTime() ret = False @@ -713,6 +739,9 @@ def SetScanSizeAndGetChangedProperties(self, sizeX, sizeY, changedProperties): @write_only def SetScanROI(self, enable, offsetX, offsetY, sizeX, sizeY): + if self.read_only: + log.error("Read-only client cannot set scan ROI.") + return False t0 = self.GetTime() ret = False @@ -793,6 +822,10 @@ def set_hw_roi_and_get_changed_properties( changedProperties : list List of properties that have changed """ + if self.read_only: + log.error("Read-only client cannot set HW ROI.") + return False + t0 = self.GetTime() ret = False @@ -837,6 +870,10 @@ def set_sw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): sizeY : int The height of the ROI """ + if self.read_only: + log.error("Read-only client cannot set SW ROI.") + return False + t0 = self.GetTime() ret = False @@ -882,6 +919,10 @@ def set_sw_roi_and_get_changed_properties( changedProperties : list List of properties that have changed """ + if self.read_only: + log.error("Read-only client cannot set SW ROI.") + return False + t0 = self.GetTime() ret = False @@ -929,16 +970,16 @@ def set_adaptive_roi(self, offsetX, offsetY, sizeX, sizeY): log.error("Read-only client cannot set adaptive ROI.") return False - t0 = self.GetTime() + t0 = self.GetTime() ret = False - + command = self.__addSingleCommand(self.SET_ADAPTIVE_ROI, None, [offsetX, offsetY, sizeX, sizeY]) - response = self.__sendCommand(command) + response = self.__sendCommand(command) if response != False: ret = response.acknowledge[0].error != True self.refreshProperties = True - if logLevel == logging.DEBUG: + if logLevel == logging.DEBUG: log.debug("SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", offsetX, offsetY, sizeX, sizeY, (self.GetTime() - t0) * 1000) return ret @@ -966,11 +1007,11 @@ def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, s log.error("Read-only client cannot set adaptive ROI and get changed properties.") return False - t0 = self.GetTime() + t0 = self.GetTime() ret = False - + command = self.__addSingleCommand(self.SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES, None, [offsetX, offsetY, sizeX, sizeY]) - response = self.__sendCommand(command) + response = self.__sendCommand(command) if response != False: ret = response.acknowledge[0].error != True self.refreshProperties = True @@ -978,7 +1019,7 @@ def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, s if ret: ret = self.ParseChangedProperties(changedProperties, response) - if logLevel == logging.DEBUG: + if logLevel == logging.DEBUG: log.debug("SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", offsetX, offsetY, sizeX, sizeY, (self.GetTime() - t0) * 1000) return ret @@ -1016,6 +1057,10 @@ def start_acquisition( with all of the frames. """ + if self.read_only: + log.error("Read-only client cannot start acquisition.") + return False + start_time = self.GetTime() step_time = self.GetTime() @@ -1098,7 +1143,7 @@ def stop_acquisition(self): log.debug(" Stop Time: %.1f ms", lapsed) return b"Stopped" in respond - + @write_only def start_manual_movie_saving(self): """ @@ -1115,7 +1160,7 @@ def start_manual_movie_saving(self): log.debug(" Start saving during acquisition time: %.1f ms", lapsed) return b"ManualMovieStart" in respond - + @write_only def stop_manual_movie_saving(self): """ @@ -1153,6 +1198,10 @@ def set_xy_array(self, positions, width=None, height=None): The height of the scan array, by default None. If None, the max of the y positions will be used and the scan will cover the full height of the image. """ + if self.read_only: + log.error("Read-only client cannot set scan xy array.") + return False + if positions.dtype != np.int32: log.error("Positions must be integers... Casting to int") positions = positions.astype(np.int32) @@ -1349,16 +1398,16 @@ def get_result( i += 1 attributes.eppixpf = values[i] i += 1 - if self.commandVersion >= 12: - attributes.eppixIncident = values[i] + if commandVersion >= 12: + attributes.eppix_incident = values[i] i += 1 - attributes.epsIncident = values[i] + attributes.eps_incident = values[i] i += 1 - attributes.eppixpsIncident = values[i] + attributes.eppixps_incident = values[i] i += 1 - attributes.epa2Incident = values[i] + attributes.epa2_incident = values[i] i += 1 - attributes.eppixpfIncident = values[i] + attributes.eppixpf_incident = values[i] i += 1 attributes.redSatWarningValue = values[i] i += 1 @@ -1509,6 +1558,10 @@ def set_virtual_mask(self, id, w, h, mask): mask : np.ndarray The mask to set """ + if self.read_only: + log.error("Read-only client cannot set virtual mask.") + return False + if id < 1 or id > 4: log.error( " SetVirtualMask The virtual mask id must be selected between 1-4" @@ -1733,6 +1786,10 @@ def grab(self, frames=1, dataSetName="", fileName=None): fileName : str, optional Save the returned image as a file if provided, by default None """ + if self.read_only: + log.error("Read-only client cannot grab image.") + return False + imageW = self.GetProperty("Image Size X (pixels)") imageH = self.GetProperty("Image Size Y (pixels)") fps = self.GetProperty("Frames Per Second") @@ -1939,6 +1996,10 @@ def get_image(self, pixelFormat=PixelFormat.AUTO, fileName=None, textSize=0): textSize : int, optional The text size, by default 0 """ + if self.read_only: + log.error("Read-only client cannot start acquisition.") + return False + self.StartAcquisition(1) frameType = FrameType.SUMTOTAL @@ -1967,6 +2028,10 @@ def take_dark_reference(self, frameRate: float = 20): frameRate : float, optional The frame rate, by default 20 frames per second """ + if self.read_only: + log.error("Read-only client cannot start acquisition.") + return False + sys.stdout.write("Taking dark references: ") sys.stdout.flush() @@ -2370,7 +2435,7 @@ def ParseChangedProperties(self, changedProperties, response): SET_SCAN_XY_ARRAY = 32 SET_ADAPTIVE_ROI = 33 SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES = 34 - GET_PROPERTY_SPECIFICATIONS = 35 + GET_PROPERTY_SPECIFICATIONS = 35 MMF_DATA_HEADER_SIZE = 24 diff --git a/deapi/data_types.py b/deapi/data_types.py index 9348f92..90dd789 100644 --- a/deapi/data_types.py +++ b/deapi/data_types.py @@ -9,6 +9,8 @@ from enum import IntEnum import warnings +import numpy as np + class FrameType(Enum): """An Enum of the different frame types that can be returned by the DE API""" @@ -293,6 +295,37 @@ def __init__( self.bins = bins self.data = data + def __repr__(self): + return (f"Histogram(min={self.min}," + f" max={self.max}, " + f"upperMostLocalMaxima={self.upperMostLocalMaxima}," + f" bins={self.bins}," + f" data={self.data})") + + + def plot(self, ax=None): + """Plot the histogram using matplotlib + + Parameters + ---------- + ax : matplotlib.axes.Axes, optional + Axes object to plot the histogram on. If not provided, a new figure will be created. + + Returns + ------- + matplotlib.axes.Axes + Axes object containing the histogram plot + """ + import matplotlib.pyplot as plt + + if ax is None: + fig, ax = plt.subplots() + ax.plot(np.linspace(self.min, self.max, self.bins), self.data) + ax.set_title("Histogram") + ax.set_xlabel("Detector Units") + ax.set_ylabel("Frequency") + return ax + class MovieBufferInfo: """ @@ -394,6 +427,14 @@ def __init__( currentValue = None # current value readonly = False # Read-only property + def __repr__(self): + return (f"PropertySpec(dataType={self.dataType}," + f" valueType={self.valueType}, " + f"category={self.category}," + f" options={self.options}," + f" defaultValue={self.defaultValue}, " + f"currentValue={self.currentValue})") + class PropertyCollection: """Class to interact with collections of properties in the DE API @@ -529,6 +570,9 @@ class VirtualMask: of VirtualMask objects. """ + def __str__(self): + return f"Virtual Mask {self.index}" + def __init__(self, client, index): self.client = client self.index = index diff --git a/deapi/panel_apps/__init__.py b/deapi/panel_apps/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/deapi/panel_apps/panel_client.py b/deapi/panel_apps/panel_client.py deleted file mode 100644 index b446329..0000000 --- a/deapi/panel_apps/panel_client.py +++ /dev/null @@ -1,9 +0,0 @@ -import numpy as np -import panel as pn - - -class CustomDashBoard: - def __init__(self): - self.buttons = [] - self.plots = [] - self.layout diff --git a/deapi/simulated_server/fake_server.py b/deapi/simulated_server/fake_server.py index 89dbdf7..f6b99fe 100644 --- a/deapi/simulated_server/fake_server.py +++ b/deapi/simulated_server/fake_server.py @@ -565,44 +565,6 @@ def _fake_get_result(self, command): ) curr = self.current_navigation_index flat_index = int(np.ravel_multi_index(curr, self.fake_data.navigator.shape)) - - # map to right order... - response_mapping = [ - pixel_format, - windowWidth, - windowHeight, - "Test", - 0, - self.acquisition_status == "Acquiring", - flat_index, - 1, - 0, - 2**16, - 100, - 10, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - time.time(), - 0, - 0, - 0, - ] - for val in response_mapping: - ack1 = add_parameter(ack1, val) - ans = (acknowledge_return,) - # add the data header packet for how many bytes are in the data - pack = pb.DEPacket() - pack.type = pb.DEPacket.P_DATA_HEADER - if 2 < frame_type < 8: image = self.fake_data[self.current_navigation_index].astype( pixel_format_dict[pixel_format] @@ -614,22 +576,62 @@ def _fake_get_result(self, command): ) result = image.tobytes() elif 11 < frame_type < 17: # virtual image - mask = self.virtual_masks[frame_type - 12] - if mask.shape != (windowWidth, windowHeight): - mask = resize( - mask, (windowWidth, windowHeight), preserve_range=True + image = self.virtual_masks[frame_type - 12] + if image.shape != (windowWidth, windowHeight): + image = resize( + image, (windowWidth, windowHeight), preserve_range=True ).astype(np.int8) - result = mask.tobytes() + result = image.tobytes() elif 17 <= frame_type < 22: - mask = self.virtual_masks[frame_type - 17] + image = self.virtual_masks[frame_type - 17] calculation_type = self[ f"Scan - Virtual Detector {frame_type-17} Calculation" ] - result = self.fake_data.get_virtual_image(mask, method=calculation_type) - result = result.astype(pixel_format_dict[pixel_format]).tobytes() + image = self.fake_data.get_virtual_image(image, method=calculation_type) + image = image.astype(pixel_format_dict[pixel_format]) + result = image.tobytes() else: raise ValueError(f"Frame type {frame_type} not Supported in PythonDEServer") + # map to right order... + response_mapping = [ + pixel_format, # pix format + windowWidth, # window width + windowHeight, # window height + "Test", # name + 0, # acquisition index + self.acquisition_status == "Acquiring", # status + flat_index, # frame number + 1, # frame count + 0, # image min + 2**16, # image max + 100, # image mean + 10, # image std + 0, # eppix + 0, # eps + 0, # eppixps + 0, # epa2 + 0, # eppixpf + 0, # eppix_incident + 0, # eps_incident + 0, # epa2_incident + 0, # eppixpf_incident + 0, # saturation + time.time(), # current time + 0, # autoStretchMin + 0, #autoStretchMax + 0, # autoStretchGamma + 0, # histogram min + np.max(image), # histogram max + np.max(image), # histogram upper local max + ] + for val in response_mapping: + ack1 = add_parameter(ack1, val) + ans = (acknowledge_return,) + # add the data header packet for how many bytes are in the data + pack = pb.DEPacket() + pack.type = pb.DEPacket.P_DATA_HEADER + pack.data_header.bytesize = len(result) ans += (pack,) ans += (result,) diff --git a/deapi/tests/conftest.py b/deapi/tests/conftest.py index c6c877a..19db05d 100644 --- a/deapi/tests/conftest.py +++ b/deapi/tests/conftest.py @@ -29,6 +29,9 @@ def pytest_addoption(parser): default=False, help="Test the speed of certain operations", ) + parser.addoption( + "--engineering", action="store", default="", help="Run engineering mode" + ) def pytest_configure(config): @@ -50,6 +53,15 @@ def pytest_collection_modifyitems(config, items): if "server" in item.keywords: item.add_marker(skip_server) + if config.getoption("--engineering") and config.getoption("--engineering") != "": + # Do not skip engineering tests + return + else: # pragma: no cover + skip_engineering = pytest.mark.skip(reason="need --engineering option to run") + for item in items: + if "engineering" in item.keywords: + item.add_marker(skip_engineering) + if config.getoption("--speed"): # Do not skip speed tests return @@ -70,6 +82,9 @@ def client(xprocess, request): host=request.config.getoption("--host"), port=request.config.getoption("--port"), ) + + if request.config.getoption("--engineering"): + c.set_engineering_mode(enable =True, password =request.config.getoption("--engineering")) yield c time.sleep(4) c.disconnect() diff --git a/deapi/tests/speed_tests/test_internal_file_saving.py b/deapi/tests/speed_tests/test_internal_file_saving.py index 9b294c4..9ed98b0 100644 --- a/deapi/tests/speed_tests/test_internal_file_saving.py +++ b/deapi/tests/speed_tests/test_internal_file_saving.py @@ -52,3 +52,106 @@ def save(self, client, size=64, file_format="MRC"): time.sleep(0.1) print(client["Speed - Frame Write Time (us)"]) return client["Speed - Frame Write Time (us)"] + +class TestCompressionSpeed: + + @pytest.mark.engineering + @pytest.mark.server + def test_compression_speeds(self, client): + methods = ["lz4"] # zstd throws an error??? + levels = [ 5, 7, 9] + + compression_times = { + "lz4": {l: [] for l in levels}, + } + for method in methods: + for level in levels: + client["Compression - Mode"] = method + client["Compression - Level"] = level + client["Compression - Threads"] = 8 # Max is half the number of cores + client["Grabbing - Target Buffer Size (MB)"] = 32 + assert client["Compression - Mode"] == method + assert client["Compression - Level"] == level + client["Frames Per Second"] = 300 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 64 + client.scan["Size Y"] = 64 + client["Autosave Movie"] = "On" + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = "D:\Temp" + client.start_acquisition(1) + while client.acquiring: + time.sleep(0.1) + time.sleep(2) + processing_log = client["Autosave Movie Frames File Path"].split("0_movie")[0] + "processing.log" + with open(processing_log, "r") as f: + lines = f.readlines() + summary_index = 0 + for i, l in enumerate(lines): + if "Summary" in l: + summary_index = i + + summary_dict = {} + for l in lines[summary_index + 2:]: + try: + key, values = l.split("=") + key = key.strip() + summary_dict[key] = values.strip() + except ValueError: + pass + print(f"{method}-{level}: {summary_dict['Compression Speed(per core)']}") + compression_times[method][level] = summary_dict["Compression Speed(per core)"].split(" ")[0] + print(compression_times) + version = client["Server Software Version"] + fname = version + "_compression_speed.json" + with open(fname, "w") as outfile: + json.dump(compression_times, outfile) + +class TestCompressionSlow: + + @pytest.mark.engineering + @pytest.mark.server + def test_compression_speeds(self, client): + method = "zlib" + level =4 + + client["Compression - Mode"] = method + client["Compression - Level"] = level + client["Compression - Threads"] = 8 # Max is half the number of cores + client["Grabbing - Target Buffer Size (MB)"] = 32 + assert client["Compression - Mode"] == method + assert client["Compression - Level"] == level + client["Frames Per Second"] = 300 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 64 + client.scan["Size Y"] = 64 + client["Autosave Movie"] = "On" + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = "D:\Temp" + client.start_acquisition(1) + while client.acquiring: + time.sleep(0.1) + + time.sleep(2) + processing_log = client["Autosave Movie Frames File Path"].split("0_movie")[0] + "processing.log" + with open(processing_log, "r") as f: + lines = f.readlines() + summary_index = 0 + for i, l in enumerate(lines): + if "Summary" in l: + summary_index = i + + summary_dict = {} + for l in lines[summary_index + 2:]: + try: + key, values = l.split("=") + key = key.strip() + summary_dict[key] = values.strip() + except ValueError: + pass + print(summary_dict["Compression Speed(per core)"]) + + + + + diff --git a/deapi/tests/test_client.py b/deapi/tests/test_client.py index d3b96f8..468eefe 100644 --- a/deapi/tests/test_client.py +++ b/deapi/tests/test_client.py @@ -2,7 +2,7 @@ import numpy as np -from deapi import Client +from deapi import Client, Histogram import pytest from deapi.data_types import PropertySpec, VirtualMask, MovieBufferStatus, ContrastStretchType @@ -92,6 +92,17 @@ def test_get_result(self, client): assert result[0].shape == (1024, 1024) assert result[2].stretchType == ContrastStretchType.NONE + + def test_get_histogram(self, client): + client["Frames Per Second"] = 1000 + client.scan(size_x=10, size_y=10, enable="On") + client.start_acquisition(1) + while client.acquiring: + time.sleep(1) + result = client.get_result("singleframe_integrated") + assert isinstance(result[3], Histogram) + result[3].plot() + def test_get_result_no_scan(self, client): client["Frames Per Second"] = 1000 client.scan(enable="Off") diff --git a/deapi/tests/test_file_saving/test_h5ebsd.py b/deapi/tests/test_file_saving/test_h5ebsd.py new file mode 100644 index 0000000..df98473 --- /dev/null +++ b/deapi/tests/test_file_saving/test_h5ebsd.py @@ -0,0 +1,200 @@ +""" + +This module tests file saving for h5EBSD files + + +This should be run before any release to make sure that the file loaders downstream +work. +""" + +import os +import time + +import blosc2 +import numpy as np +import pytest +import hyperspy.api as hs +import glob +import h5py + + + +class TestSavingHyperSpy: + @pytest.fixture(autouse=True) + def clean_state(self, client): + # First set the hardware ROI to a known state + client["Hardware ROI Offset X"] = 0 + client["Hardware ROI Offset Y"] = 0 + client["Hardware Binning X"] = 1 + client["Hardware Binning Y"] = 1 + client["Hardware ROI Size X"] = 1024 + client["Hardware ROI Size Y"] = 1024 + # Set the software Binning to 1 + client["Binning X"] = 1 + client["Binning Y"] = 1 + + @pytest.mark.server + def test_initialize(self, client): + client["Frames Per Second"] = 100 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 8 + client.scan["Size Y"] = 8 + client["Autosave Movie"] = "On" + client["Autosave 4D File Format"] = "H5EBSD" + print(client.get_property_spec("Autosave 4D File Format")) + assert client["Autosave 4D File Format"] == "H5EBSD" + + @pytest.mark.server + def test_save_hspy_4DSTEM(self, client): + if not os.path.exists("D:\Temp"): + os.mkdir("D:\Temp") + if not os.path.exists("D:\Temp\HSPY"): + os.mkdir("D:\Temp\HSPY") + temp_dir = "D:\Temp\HSPY" + client["Frames Per Second"] = 100 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 8 + client.scan["Size Y"] = 8 + client["Autosave Movie"] = "On" + client["Autosave 4D File Format"] = "HSPY" + client["Autosave Directory"] = temp_dir + client + client.start_acquisition(1) + while client.acquiring: + time.sleep(0.1) + time.sleep(2) + assert os.path.exists(client["Autosave Movie Frames File Path"]) + print(client["Autosave Movie Frames File Path"]) + h5py.File(client["Autosave Movie Frames File Path"], "r") + + @pytest.mark.parametrize("compression", ["blosclz", "lz4", "zstd", "zlib"]) + @pytest.mark.server + @pytest.mark.engineering + def test_set_compression(self, client, compression): + client["Compression - Mode"] = compression + assert client["Compression - Mode"] == compression.lower() + if not os.path.exists("D:\Temp"): + os.mkdir("D:\Temp") + if not os.path.exists("D:\Temp\H5EBSD"): + os.mkdir("D:\Temp\H5EBSD") + temp_dir = "D:\Temp\H5EBSD" + client["Frames Per Second"] = 100 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 8 + client.scan["Size Y"] = 8 + + client[""] = 8 + client.scan["Size Y"] = 8 + client["Autosave Movie"] = "On" + client["Grabbing - Target Buffer Size (MB)"] = 16 + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = temp_dir + client.start_acquisition(1) + while client.acquiring: + time.sleep(0.1) + time.sleep(2) + assert os.path.exists(client["Autosave Movie Frames File Path"]) + s = client["Autosave Movie Frames File Path"] + f2 = h5py.File(s, "r") + dset = f2["Scan 1/EBSD/Data/patterns"] + d = dset[:] + assert d.shape == (64, 1024, 1024) + + + + @pytest.mark.parametrize("buffer", [8,16]) + @pytest.mark.server + def test_save_EBSD(self, client, buffer): + if not os.path.exists("D:\Temp"): + os.mkdir("D:\Temp") + if not os.path.exists("D:\Temp\H5EBSD"): + os.mkdir("D:\Temp\H5EBSD") + temp_dir = "D:\Temp\H5EBSD" + client["Frames Per Second"] = 200 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 8 + client.scan["Size Y"] = 8 + client["Autosave Movie"] = "On" + client["Grabbing - Target Buffer Size (MB)"] = buffer + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = temp_dir + client["Hardware ROI Offset X"] = 512-128 + client["Hardware ROI Offset Y"] = 512-128 + client["Hardware Binning X"] = 2 + client["Hardware Binning Y"] = 2 + client["Hardware ROI Size X"] = 256 + client["Hardware ROI Size Y"] = 256 + client.start_acquisition(1) + while client.acquiring: + time.sleep(0.1) + time.sleep(2) + assert os.path.exists(client["Autosave Movie Frames File Path"]) + + print(client["Autosave Movie Frames File Path"]) + f = h5py.File(client["Autosave Movie Frames File Path"], "r+") + dset = f["Pattern Data"]["Patterns"] + d = dset[:] + assert d.shape == (64, 128, 128) + time.sleep(4) + + @pytest.mark.server + def test_save_EBSD_large(self, client): + if not os.path.exists("D:\Temp"): + os.mkdir("D:\Temp") + if not os.path.exists("D:\Temp\H5EBSD"): + os.mkdir("D:\Temp\H5EBSD") + temp_dir = "D:\Temp\H5EBSD" + client.set_engineering_mode(enable=True, password="woohoo!") + client["Frames Per Second"] = 400 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 64 + client.scan["Size Y"] = 64 + client["Autosave Movie"] = "On" + client["Grabbing - Target Buffer Size (MB)"] = 32 + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = temp_dir + client["Compression - Mode"] = "BLOSCLZ" + + client.start_acquisition(1) + tic = time.time() + while client.acquiring: + time.sleep(0.1) + toc = time.time() + + print(f"Time to acquire: {toc-tic}") + total_frames = client.scan["Size X"]*client.scan["Size Y"] + estimated_time = total_frames/client["Frames Per Second"] + print(f"Estimated time to acquire: {estimated_time}") + time.sleep(2) + assert os.path.exists(client["Autosave Movie Frames File Path"]) + print(client["Autosave Movie Frames File Path"]) + f2 = h5py.File(client["Autosave Movie Frames File Path"], "r") + + + @pytest.mark.server + def test_save_EBSD_stop(self, client): + if not os.path.exists("D:\Temp"): + os.mkdir("D:\Temp") + if not os.path.exists("D:\Temp\H5EBSD"): + os.mkdir("D:\Temp\H5EBSD") + temp_dir = "D:\Temp\H5EBSD" + client["Frames Per Second"] = 200 + client["Scan - Enable"] = "On" + client.scan["Size X"] = 128 + client.scan["Size Y"] = 128 + client["Autosave Movie"] = "On" + client["Grabbing - Target Buffer Size (MB)"] = 16 + client["Autosave 4D File Format"] = "H5EBSD" + client["Autosave Directory"] = temp_dir + client["Compression - Mode"] = "BLOSCLZ" + + client.start_acquisition(1) + assert client.acquiring + time.sleep(15) + client.stop_acquisition() + assert os.path.exists(client["Autosave Movie Frames File Path"]) + print(client["Autosave Movie Frames File Path"]) + f2 = h5py.File(client["Autosave Movie Frames File Path"], "r") + + assert f2["Scan 1/EBSD/Data/patterns"].chunks == (8, 1024, 1024) + assert f2["Scan 1/EBSD/Data/patterns"].shape[0] < 128*128 diff --git a/examples/live_imaging/bright_spot_intensity.py b/examples/live_imaging/bright_spot_intensity.py new file mode 100644 index 0000000..c97ce5b --- /dev/null +++ b/examples/live_imaging/bright_spot_intensity.py @@ -0,0 +1,60 @@ +""" +Monitoring Bright Spot Intensity +================================ + +This example demonstrates how to monitor the intensity of the brightest pixel in the sensor data during acquisition. +""" +import numpy as np + +import deapi +import time + +client = deapi.Client() +client.connect() + +# %% +# Set the hardware ROI to 256x256 +# -------------------------------- +# In this case we just use the center 256 pixels of the 1024 pixel sensor. (This should be simplified in the future +# to have default values for the hardware ROI) + +client["Hardware ROI Size X"] = 256 +client["Hardware ROI Size Y"] = 256 +client["Hardware ROI Offset X"] = 384 +client["Hardware ROI Offset Y"] = 384 + + +# Set up a 4DSTEM acquisition +# --------------------------- +# We will set up a 4DSTEM acquisition with a 64x64 scan size and 100 frames per second. +# %% +client.scan(size_x=8, size_y=8, enable="On", points_per_camera_frame=10) + + +# %% +# Start the acquisition +# --------------------- +# We will start the acquisition + +client.start_acquisition(1) + + +# %% +# Monitor the intensity of the brightest pixel +# -------------------------------------------- +# We will monitor the intensity of the brightest pixel in the sensor data during acquisition. This will +# not get every frame, but will get the most recent frame every 0.1 seconds after integrating the number +# of `points_per_camera_frame`. The `histogram` is also returned which gives the data separated into +# 256 bins. + + +while client.acquiring: + image, pixelFormat, attributes, histogram = client.get_result("singleframe_integrated") + print(f"Max intensity: {image.max()}") + time.sleep(0.1) + +# %% +# Retract the Camera +# ------------------ + +client["Camera Position Control"] = "Retract" \ No newline at end of file diff --git a/examples/live_imaging/taking_an_image_every_minute.py b/examples/live_imaging/taking_an_image_every_minute.py new file mode 100644 index 0000000..b8a0842 --- /dev/null +++ b/examples/live_imaging/taking_an_image_every_minute.py @@ -0,0 +1,52 @@ +""" +Taking an Image every Minute +============================ + +This example shows how to take an image every minute. The results are saved to disk as: + +- Individual frames (Movies) +- The final summed image (Final Image) + +In this case we can set the "Autosave Movie Sum Count" to 10. This will sum 10 frames together +before saving the final image. +""" + + +import deapi +import matplotlib.pyplot as plt +import time +import numpy as np + +c = deapi.Client() +c.connect() + +# Set the autosave directory +loc_time = time.localtime() +c["Autosave Directory"] = f"D:\\Service\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" + +c["Autosave Movie"] = "On" # Save the individual frames +c["Autosave Final Image"] = "On" # Save the final summed image +c["Autosave Movie Sum Count"] =10 # The total number of frames summed for one call to `c.start_acquisition`. + +results = [] # store the results in a list + +for i in range(100): + c.start_acquisition(1) # Acquire one image (This is non-blocking and should run very fast) + time.sleep(60) # sleep for 60 seconds + + # this might take a half a second? + # You can also just skip this and load directly from the saved files. This gets only the summed image. + results.append(c.get_result()) + +# %% +# Load the final summed image +# ============================ +# This will load the final summed image from the first result in the list and then +# plots both the histogram and the image. + + +image, dtype, attributes, histogram = results[0] # get the first result + +plt.plot(np.linspace(histogram.min, histogram.max, histogram.bins),histogram.data) + +plt.imshow(image) \ No newline at end of file From eb15518be2b58709e376ed71f19dee6670e64aa7 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Wed, 2 Apr 2025 17:19:16 -0500 Subject: [PATCH 2/4] Clean up Read/write only --- deapi/client.py | 70 +++++-------------------------------------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/deapi/client.py b/deapi/client.py index 8fe3ab7..852972c 100644 --- a/deapi/client.py +++ b/deapi/client.py @@ -93,6 +93,7 @@ class Client: def __init__(self): self.commandVersion = commandVersion + self.read_only = False def set_log_level(self, level): log = logging.getLogger("DECameraClientLib") @@ -171,6 +172,7 @@ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False): port : int, optional The port to connect to, by default 13240 """ + self.read_only = read_only if not read_only and (host == "localhost" or host == "127.0.0.1"): tcpNoDelay = 0 # on loopback interface, nodelay causes delay @@ -525,9 +527,6 @@ def set_property(self, name: str, value): value : any The value to set the property to """ - if self.readOnly: - log.error("Read-only client cannot set properties.") - return False t0 = self.GetTime() ret = False @@ -565,9 +564,6 @@ def set_property_and_get_changed_properties(self, name, value, changedProperties changedProperties : list List of properties that have changed """ - if self.readOnly: - log.error("Read-only client cannot set properties.") - return False t0 = self.GetTime() ret = False @@ -605,10 +601,6 @@ def set_engineering_mode(self, enable, password): password : str The password to enable engineering mode """ - if self.readOnly: - log.error("Read-only client cannot set engineering mode.") - return False - ret = False command = self._addSingleCommand(self.SET_ENG_MODE, None, [enable, password]) @@ -620,9 +612,6 @@ def set_engineering_mode(self, enable, password): @write_only def setEngModeAndGetChangedProperties(self, enable, password, changedProperties): - if self.readOnly: - log.error("Read-only client cannot set engineering mode.") - return False ret = False @@ -655,9 +644,6 @@ def set_hw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): sizeY : int The height of the ROI """ - if self.read_only: - log.error("Read-only client cannot set HW ROI.") - return False t0 = self.GetTime() ret = False @@ -684,9 +670,7 @@ def set_hw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): @write_only def SetScanSize(self, sizeX, sizeY): - if self.read_only: - log.error("Read-only client cannot set scan size.") - return False + t0 = self.GetTime() ret = False @@ -709,10 +693,6 @@ def SetScanSize(self, sizeX, sizeY): @write_only def SetScanSizeAndGetChangedProperties(self, sizeX, sizeY, changedProperties): - if self.read_only: - log.error("Read-only client cannot set scan size.") - return False - t0 = self.GetTime() ret = False @@ -739,10 +719,6 @@ def SetScanSizeAndGetChangedProperties(self, sizeX, sizeY, changedProperties): @write_only def SetScanROI(self, enable, offsetX, offsetY, sizeX, sizeY): - if self.read_only: - log.error("Read-only client cannot set scan ROI.") - return False - t0 = self.GetTime() ret = False @@ -768,9 +744,6 @@ def SetScanROI(self, enable, offsetX, offsetY, sizeX, sizeY): @write_only def SetScanROIAndGetChangedProperties(self, enable, offsetX, offsetY, sizeX, sizeY, changedProperties): - if self.read_only: - log.error("Read-only client cannot set scan ROI.") - return False t0 = self.GetTime() ret = False @@ -822,9 +795,7 @@ def set_hw_roi_and_get_changed_properties( changedProperties : list List of properties that have changed """ - if self.read_only: - log.error("Read-only client cannot set HW ROI.") - return False + t0 = self.GetTime() ret = False @@ -870,9 +841,6 @@ def set_sw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): sizeY : int The height of the ROI """ - if self.read_only: - log.error("Read-only client cannot set SW ROI.") - return False t0 = self.GetTime() ret = False @@ -919,9 +887,6 @@ def set_sw_roi_and_get_changed_properties( changedProperties : list List of properties that have changed """ - if self.read_only: - log.error("Read-only client cannot set SW ROI.") - return False t0 = self.GetTime() ret = False @@ -951,6 +916,7 @@ def set_sw_roi_and_get_changed_properties( return ret + @write_only def set_adaptive_roi(self, offsetX, offsetY, sizeX, sizeY): """ Automatically choose the proper HW ROI and set SW ROI of the current camera on DE-Server. @@ -966,9 +932,6 @@ def set_adaptive_roi(self, offsetX, offsetY, sizeX, sizeY): sizeY : int The height of the ROI """ - if self.read_only: - log.error("Read-only client cannot set adaptive ROI.") - return False t0 = self.GetTime() ret = False @@ -984,6 +947,7 @@ def set_adaptive_roi(self, offsetX, offsetY, sizeX, sizeY): return ret + @write_only def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, sizeY, changedProperties, timeoutMsec = 5000): """ Automatically choose the proper HW ROI and set SW ROI of the current camera on DE-Server and get all of @@ -1003,9 +967,6 @@ def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, s changedProperties : list List of properties that have changed """ - if self.read_only: - log.error("Read-only client cannot set adaptive ROI and get changed properties.") - return False t0 = self.GetTime() ret = False @@ -1057,9 +1018,6 @@ def start_acquisition( with all of the frames. """ - if self.read_only: - log.error("Read-only client cannot start acquisition.") - return False start_time = self.GetTime() step_time = self.GetTime() @@ -1198,9 +1156,6 @@ def set_xy_array(self, positions, width=None, height=None): The height of the scan array, by default None. If None, the max of the y positions will be used and the scan will cover the full height of the image. """ - if self.read_only: - log.error("Read-only client cannot set scan xy array.") - return False if positions.dtype != np.int32: log.error("Positions must be integers... Casting to int") @@ -1558,9 +1513,6 @@ def set_virtual_mask(self, id, w, h, mask): mask : np.ndarray The mask to set """ - if self.read_only: - log.error("Read-only client cannot set virtual mask.") - return False if id < 1 or id > 4: log.error( @@ -1786,10 +1738,6 @@ def grab(self, frames=1, dataSetName="", fileName=None): fileName : str, optional Save the returned image as a file if provided, by default None """ - if self.read_only: - log.error("Read-only client cannot grab image.") - return False - imageW = self.GetProperty("Image Size X (pixels)") imageH = self.GetProperty("Image Size Y (pixels)") fps = self.GetProperty("Frames Per Second") @@ -1996,9 +1944,6 @@ def get_image(self, pixelFormat=PixelFormat.AUTO, fileName=None, textSize=0): textSize : int, optional The text size, by default 0 """ - if self.read_only: - log.error("Read-only client cannot start acquisition.") - return False self.StartAcquisition(1) frameType = FrameType.SUMTOTAL @@ -2028,9 +1973,6 @@ def take_dark_reference(self, frameRate: float = 20): frameRate : float, optional The frame rate, by default 20 frames per second """ - if self.read_only: - log.error("Read-only client cannot start acquisition.") - return False sys.stdout.write("Taking dark references: ") sys.stdout.flush() From 94deb4f69b4abb88a62a4800685e04a5fe3c1171 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Thu, 17 Apr 2025 13:29:51 -0500 Subject: [PATCH 3/4] Clean up tests for passing with/without server --- deapi/client.py | 14 +- deapi/simulated_server/fake_server.py | 18 ++- .../speed_tests/test_internal_file_saving.py | 2 + deapi/tests/test_client.py | 18 ++- .../test_file_loading_libertem.py | 15 +- .../test_file_loading_rsciio.py | 15 +- deapi/tests/test_file_saving/test_h5ebsd.py | 144 ------------------ .../test_scan_pattern_saving.py | 15 +- deapi/tests/test_insitu/__init__.py | 0 deapi/tests/test_insitu/test_start_stop.py | 50 ++++++ examples/insitu_imaging/start_stop.py | 45 +++++- 11 files changed, 154 insertions(+), 182 deletions(-) create mode 100644 deapi/tests/test_insitu/__init__.py create mode 100644 deapi/tests/test_insitu/test_start_stop.py diff --git a/deapi/client.py b/deapi/client.py index 852972c..ef0e90a 100644 --- a/deapi/client.py +++ b/deapi/client.py @@ -1403,19 +1403,19 @@ def get_result( log.debug("Hist %d: %d" % (j, values[i + j])) histogram.data[j] = values[i + j] - if pixelFormat == PixelFormat.FLOAT32: - imageDataType = numpy.float32 - elif pixelFormat == PixelFormat.UINT16: - imageDataType = numpy.uint16 - else: - imageDataType = numpy.uint8 - self.width = attributes.frameWidth self.height = attributes.frameHeight recvbyteSizeString = self._recvFromSocket( self.socket, 4 ) # get the first 4 bytes + + if pixelFormat == PixelFormat.FLOAT32: + imageDataType = numpy.float32 + elif pixelFormat == PixelFormat.UINT16: + imageDataType = numpy.uint16 + else: + imageDataType = numpy.uint8 if len(recvbyteSizeString) == 4: recvbyteSize = struct.unpack( "I", recvbyteSizeString diff --git a/deapi/simulated_server/fake_server.py b/deapi/simulated_server/fake_server.py index f6b99fe..c15f1d0 100644 --- a/deapi/simulated_server/fake_server.py +++ b/deapi/simulated_server/fake_server.py @@ -536,7 +536,6 @@ def _fake_get_result(self, command): acknowledge_return.type = pb.DEPacket.P_ACKNOWLEDGE ack1 = acknowledge_return.acknowledge.add() ack1.command_id = command.command[0].command_id - frame_type = command.command[0].parameter[0].p_int pixel_format = command.command[0].parameter[1].p_int center_x = command.command[0].parameter[2].p_int @@ -550,9 +549,10 @@ def _fake_get_result(self, command): stretch_max = command.command[0].parameter[10].p_float stretch_gama = command.command[0].parameter[11].p_float outlier = command.command[0].parameter[12].p_float - histo_min = command.command[0].parameter[13].p_float - histo_max = command.command[0].parameter[14].p_float - histo_bins = command.command[0].parameter[15].p_int + timeout = command.command[0].parameter[13].p_int + histo_min = command.command[0].parameter[14].p_float + histo_max = command.command[0].parameter[15].p_float + histo_bins = command.command[0].parameter[16].p_int pixel_format_dict = {1: np.int8, 5: np.int16, 13: np.float32} @@ -614,19 +614,23 @@ def _fake_get_result(self, command): 0, # eppixpf 0, # eppix_incident 0, # eps_incident + 0, # eppixps_incident 0, # epa2_incident 0, # eppixpf_incident + 0, # red sat warning + 0, # orange sat warning 0, # saturation time.time(), # current time 0, # autoStretchMin 0, #autoStretchMax 0, # autoStretchGamma 0, # histogram min - np.max(image), # histogram max - np.max(image), # histogram upper local max + float(np.min(image)), # histogram max + float(np.max(image)), # histogram upper local max ] for val in response_mapping: - ack1 = add_parameter(ack1, val) + ack1 = acknowledge_return.acknowledge.add() + add_parameter(ack1, val) ans = (acknowledge_return,) # add the data header packet for how many bytes are in the data pack = pb.DEPacket() diff --git a/deapi/tests/speed_tests/test_internal_file_saving.py b/deapi/tests/speed_tests/test_internal_file_saving.py index 9ed98b0..0811ab4 100644 --- a/deapi/tests/speed_tests/test_internal_file_saving.py +++ b/deapi/tests/speed_tests/test_internal_file_saving.py @@ -57,6 +57,7 @@ class TestCompressionSpeed: @pytest.mark.engineering @pytest.mark.server + @pytest.mark.skip(reason="Not implemented yet") def test_compression_speeds(self, client): methods = ["lz4"] # zstd throws an error??? levels = [ 5, 7, 9] @@ -111,6 +112,7 @@ class TestCompressionSlow: @pytest.mark.engineering @pytest.mark.server + @pytest.mark.skip(reason="Not implemented yet") def test_compression_speeds(self, client): method = "zlib" level =4 diff --git a/deapi/tests/test_client.py b/deapi/tests/test_client.py index 468eefe..6dfb04c 100644 --- a/deapi/tests/test_client.py +++ b/deapi/tests/test_client.py @@ -76,7 +76,7 @@ def test_start_acquisition_scan_disabled(self, client): def test_get_result(self, client): client["Frames Per Second"] = 1000 - client.scan(size_x=10, size_y=10, enable="On") + client.scan(size_x=3, size_y=3, enable="On") assert client["Hardware ROI Size X"] == 1024 assert client["Hardware ROI Size Y"] == 1024 assert client["Hardware Binning X"] == 1 @@ -92,7 +92,7 @@ def test_get_result(self, client): assert result[0].shape == (1024, 1024) assert result[2].stretchType == ContrastStretchType.NONE - + @pytest.mark.server def test_get_histogram(self, client): client["Frames Per Second"] = 1000 client.scan(size_x=10, size_y=10, enable="On") @@ -155,9 +155,14 @@ def test_resize_virtual_mask(self, client): assert client.virtual_masks[2][:].shape == (512, 512) def test_virtual_mask_calculation(self, client): - client.scan(size_x=8, size_y=10, enable="On") - assert client.scan_sizex == 8 - assert client.scan_sizey == 10 + client["Scan - Size X"] = 8 + client["Scan - Size Y"] = 10 + client["Scan - Type"] = "Raster" + client["Scan - Enable"] = "On" + assert client["Scan - Type"] == "Raster" + assert client["Scan - Enable"] == "On" + assert client["Scan - Size X"] == 8 + assert client["Scan - Size Y"] == 10 client.virtual_masks[2][:] = 2 client.virtual_masks[2].calculation = "Difference" client.virtual_masks[2][1::2] = 0 @@ -282,6 +287,7 @@ def test_stream_data(self, client): index += 1 if not success: break + time.sleep(4) @pytest.mark.server def test_set_xy_array(self, client): @@ -305,3 +311,5 @@ def test_set_xy_array(self, client): time.sleep(1) result = client.get_result("virtual_image1") assert result[0].shape == (12, 12) + client["Scan - Type"] = "Raster" #clean up + diff --git a/deapi/tests/test_file_saving/test_file_loading_libertem.py b/deapi/tests/test_file_saving/test_file_loading_libertem.py index dbc2047..ffd4438 100644 --- a/deapi/tests/test_file_saving/test_file_loading_libertem.py +++ b/deapi/tests/test_file_saving/test_file_loading_libertem.py @@ -22,12 +22,16 @@ def clean_state(self, client): client["Hardware Binning Y"] = 1 client["Hardware ROI Size X"] = 1024 client["Hardware ROI Size Y"] = 1024 + client["Scan - Type"] = "Raster" # Set the software Binning to 1 client["Binning X"] = 1 client["Binning Y"] = 1 + if client.acquiring: + client.stop_acquisition() + time.sleep(1) @pytest.mark.parametrize( - "file_format", ["DE5", "HSPY"] + "file_format", ["DE5",] ) # MRC file loading in LiberTEM is broken! @pytest.mark.server def test_save_4DSTEM(self, client, file_format): @@ -36,10 +40,10 @@ def test_save_4DSTEM(self, client, file_format): temp_dir = "D:\Temp" client["Frames Per Second"] = 100 client["Scan - Enable"] = "On" - client.scan["Size X"] = 16 - client.scan["Size Y"] = 8 - assert client.scan["Size X"] == 16 - assert client.scan["Size Y"] == 8 + client["Scan - Size X"] = 16 + client["Scan - Size Y"] = 8 + assert client["Scan - Size X"] == 16 + assert client["Scan - Size Y"] == 8 client["Autosave Movie"] = "On" client["Autosave 4D File Format"] = file_format client["Autosave Directory"] = temp_dir @@ -47,6 +51,7 @@ def test_save_4DSTEM(self, client, file_format): client.start_acquisition(1) while client.acquiring: time.sleep(0.1) + time.sleep(1) assert (file_format.lower() in client["Autosave Movie Frames File Path"]) movie = glob.glob(temp_dir + "/*movie." + file_format.lower())[0] dataset = lt.Context().load("auto", path=movie) diff --git a/deapi/tests/test_file_saving/test_file_loading_rsciio.py b/deapi/tests/test_file_saving/test_file_loading_rsciio.py index 1104b0b..9103b44 100644 --- a/deapi/tests/test_file_saving/test_file_loading_rsciio.py +++ b/deapi/tests/test_file_saving/test_file_loading_rsciio.py @@ -35,19 +35,22 @@ def test_save_4DSTEM(self, client, file_format): temp_dir = "D:\Temp" client["Frames Per Second"] = 100 client["Scan - Enable"] = "On" - client.scan["Size X"] = 8 - client.scan["Size Y"] = 8 + client["Scan - Size X"] = 12 + client["Scan - Size Y"] = 12 + assert client["Scan - Size X"] == 12 + assert client["Scan - Size Y"] == 12 client["Autosave Movie"] = "On" client["Autosave 4D File Format"] = file_format client["Autosave Directory"] = temp_dir - client client.start_acquisition(1) while client.acquiring: time.sleep(0.1) + time.sleep(1) + assert (file_format.lower() in client["Autosave Movie Frames File Path"]) s = hs.load(client["Autosave Movie Frames File Path"]) if file_format == "MRC": - assert s.data.shape == (64, 1024, 1024) + assert s.data.shape == (144, 1024, 1024) elif file_format == "DE5": - assert s.data.shape == (1024, 1024, 8, 8) + assert s.data.shape == (1024, 1024, 12, 12) else: - assert s.data.shape == (8, 8, 1024, 1024) + assert s.data.shape == (12, 12, 1024, 1024) diff --git a/deapi/tests/test_file_saving/test_h5ebsd.py b/deapi/tests/test_file_saving/test_h5ebsd.py index df98473..779b5ee 100644 --- a/deapi/tests/test_file_saving/test_h5ebsd.py +++ b/deapi/tests/test_file_saving/test_h5ebsd.py @@ -33,17 +33,6 @@ def clean_state(self, client): client["Binning X"] = 1 client["Binning Y"] = 1 - @pytest.mark.server - def test_initialize(self, client): - client["Frames Per Second"] = 100 - client["Scan - Enable"] = "On" - client.scan["Size X"] = 8 - client.scan["Size Y"] = 8 - client["Autosave Movie"] = "On" - client["Autosave 4D File Format"] = "H5EBSD" - print(client.get_property_spec("Autosave 4D File Format")) - assert client["Autosave 4D File Format"] == "H5EBSD" - @pytest.mark.server def test_save_hspy_4DSTEM(self, client): if not os.path.exists("D:\Temp"): @@ -58,7 +47,6 @@ def test_save_hspy_4DSTEM(self, client): client["Autosave Movie"] = "On" client["Autosave 4D File Format"] = "HSPY" client["Autosave Directory"] = temp_dir - client client.start_acquisition(1) while client.acquiring: time.sleep(0.1) @@ -66,135 +54,3 @@ def test_save_hspy_4DSTEM(self, client): assert os.path.exists(client["Autosave Movie Frames File Path"]) print(client["Autosave Movie Frames File Path"]) h5py.File(client["Autosave Movie Frames File Path"], "r") - - @pytest.mark.parametrize("compression", ["blosclz", "lz4", "zstd", "zlib"]) - @pytest.mark.server - @pytest.mark.engineering - def test_set_compression(self, client, compression): - client["Compression - Mode"] = compression - assert client["Compression - Mode"] == compression.lower() - if not os.path.exists("D:\Temp"): - os.mkdir("D:\Temp") - if not os.path.exists("D:\Temp\H5EBSD"): - os.mkdir("D:\Temp\H5EBSD") - temp_dir = "D:\Temp\H5EBSD" - client["Frames Per Second"] = 100 - client["Scan - Enable"] = "On" - client.scan["Size X"] = 8 - client.scan["Size Y"] = 8 - - client[""] = 8 - client.scan["Size Y"] = 8 - client["Autosave Movie"] = "On" - client["Grabbing - Target Buffer Size (MB)"] = 16 - client["Autosave 4D File Format"] = "H5EBSD" - client["Autosave Directory"] = temp_dir - client.start_acquisition(1) - while client.acquiring: - time.sleep(0.1) - time.sleep(2) - assert os.path.exists(client["Autosave Movie Frames File Path"]) - s = client["Autosave Movie Frames File Path"] - f2 = h5py.File(s, "r") - dset = f2["Scan 1/EBSD/Data/patterns"] - d = dset[:] - assert d.shape == (64, 1024, 1024) - - - - @pytest.mark.parametrize("buffer", [8,16]) - @pytest.mark.server - def test_save_EBSD(self, client, buffer): - if not os.path.exists("D:\Temp"): - os.mkdir("D:\Temp") - if not os.path.exists("D:\Temp\H5EBSD"): - os.mkdir("D:\Temp\H5EBSD") - temp_dir = "D:\Temp\H5EBSD" - client["Frames Per Second"] = 200 - client["Scan - Enable"] = "On" - client.scan["Size X"] = 8 - client.scan["Size Y"] = 8 - client["Autosave Movie"] = "On" - client["Grabbing - Target Buffer Size (MB)"] = buffer - client["Autosave 4D File Format"] = "H5EBSD" - client["Autosave Directory"] = temp_dir - client["Hardware ROI Offset X"] = 512-128 - client["Hardware ROI Offset Y"] = 512-128 - client["Hardware Binning X"] = 2 - client["Hardware Binning Y"] = 2 - client["Hardware ROI Size X"] = 256 - client["Hardware ROI Size Y"] = 256 - client.start_acquisition(1) - while client.acquiring: - time.sleep(0.1) - time.sleep(2) - assert os.path.exists(client["Autosave Movie Frames File Path"]) - - print(client["Autosave Movie Frames File Path"]) - f = h5py.File(client["Autosave Movie Frames File Path"], "r+") - dset = f["Pattern Data"]["Patterns"] - d = dset[:] - assert d.shape == (64, 128, 128) - time.sleep(4) - - @pytest.mark.server - def test_save_EBSD_large(self, client): - if not os.path.exists("D:\Temp"): - os.mkdir("D:\Temp") - if not os.path.exists("D:\Temp\H5EBSD"): - os.mkdir("D:\Temp\H5EBSD") - temp_dir = "D:\Temp\H5EBSD" - client.set_engineering_mode(enable=True, password="woohoo!") - client["Frames Per Second"] = 400 - client["Scan - Enable"] = "On" - client.scan["Size X"] = 64 - client.scan["Size Y"] = 64 - client["Autosave Movie"] = "On" - client["Grabbing - Target Buffer Size (MB)"] = 32 - client["Autosave 4D File Format"] = "H5EBSD" - client["Autosave Directory"] = temp_dir - client["Compression - Mode"] = "BLOSCLZ" - - client.start_acquisition(1) - tic = time.time() - while client.acquiring: - time.sleep(0.1) - toc = time.time() - - print(f"Time to acquire: {toc-tic}") - total_frames = client.scan["Size X"]*client.scan["Size Y"] - estimated_time = total_frames/client["Frames Per Second"] - print(f"Estimated time to acquire: {estimated_time}") - time.sleep(2) - assert os.path.exists(client["Autosave Movie Frames File Path"]) - print(client["Autosave Movie Frames File Path"]) - f2 = h5py.File(client["Autosave Movie Frames File Path"], "r") - - - @pytest.mark.server - def test_save_EBSD_stop(self, client): - if not os.path.exists("D:\Temp"): - os.mkdir("D:\Temp") - if not os.path.exists("D:\Temp\H5EBSD"): - os.mkdir("D:\Temp\H5EBSD") - temp_dir = "D:\Temp\H5EBSD" - client["Frames Per Second"] = 200 - client["Scan - Enable"] = "On" - client.scan["Size X"] = 128 - client.scan["Size Y"] = 128 - client["Autosave Movie"] = "On" - client["Grabbing - Target Buffer Size (MB)"] = 16 - client["Autosave 4D File Format"] = "H5EBSD" - client["Autosave Directory"] = temp_dir - client["Compression - Mode"] = "BLOSCLZ" - - client.start_acquisition(1) - assert client.acquiring - time.sleep(15) - client.stop_acquisition() - assert os.path.exists(client["Autosave Movie Frames File Path"]) - print(client["Autosave Movie Frames File Path"]) - f2 = h5py.File(client["Autosave Movie Frames File Path"], "r") - - assert f2["Scan 1/EBSD/Data/patterns"].chunks == (8, 1024, 1024) - assert f2["Scan 1/EBSD/Data/patterns"].shape[0] < 128*128 diff --git a/deapi/tests/test_file_saving/test_scan_pattern_saving.py b/deapi/tests/test_file_saving/test_scan_pattern_saving.py index c5d93f2..1b83e5f 100644 --- a/deapi/tests/test_file_saving/test_scan_pattern_saving.py +++ b/deapi/tests/test_file_saving/test_scan_pattern_saving.py @@ -16,7 +16,11 @@ class TestSavingScans: @pytest.mark.parametrize("file_format", ["HSPY", "MRC"]) @pytest.mark.server def test_save_scans(self, client, scan_type, buffer, file_format): - i = 8 + if client.acquiring: + client.stop_acquisition() + time.sleep(1) + + i = 16 num_pos = i * i if not os.path.exists("D:\Temp"): os.mkdir("D:\Temp") @@ -41,8 +45,11 @@ def test_save_scans(self, client, scan_type, buffer, file_format): client["Frames Per Second"] = 100 client["Scan - Enable"] = "On" - client.scan["Size X"] = i - client.scan["Size Y"] = i + client["Scan - Size X"] = i + client["Scan - Size Y"] = i + time.sleep(1) + assert client["Scan - Size X"] == i + assert client["Scan - Size Y"] == i client["Autosave Movie"] = "On" client["Autosave 4D File Format"] = file_format @@ -56,7 +63,7 @@ def test_save_scans(self, client, scan_type, buffer, file_format): while client.acquiring: time.sleep(0.1) - time.sleep(2) # Wait for the file to be written to disk etc. + time.sleep(5) # Wait for the file to be written to disk etc. diff --git a/deapi/tests/test_insitu/__init__.py b/deapi/tests/test_insitu/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deapi/tests/test_insitu/test_start_stop.py b/deapi/tests/test_insitu/test_start_stop.py new file mode 100644 index 0000000..72e0ae4 --- /dev/null +++ b/deapi/tests/test_insitu/test_start_stop.py @@ -0,0 +1,50 @@ +from codecs import ignore_errors + +import pytest +import time +import os +import glob +import shutil + +class TestInsitu: + @pytest.fixture(autouse=True) + def clean_state(self, client): + # First set the hardware ROI to a known state + client["Hardware ROI Offset X"] = 0 + client["Hardware ROI Offset Y"] = 0 + client["Hardware Binning X"] = 1 + client["Hardware Binning Y"] = 1 + client["Hardware ROI Size X"] = 1024 + client["Hardware ROI Size Y"] = 1024 + # Set the software Binning to 1 + client["Binning X"] = 1 + client["Binning Y"] = 1 + client["Autosave Movie"] = "On" + client["Autosave Movie File Format"] = "MRC" + try: + shutil.rmtree("D:\\Temp\\start_stop") + except FileNotFoundError: + pass + client["Autosave Directory"] = "D:\\Temp\\start_stop" + + @pytest.mark.server + @pytest.mark.skip(reason="Not implemented yet") + def test_start_stop(self, client): + client["Frames Per Second"] = 100 + client.start_acquisition(1000) + assert client.acquiring + time.sleep(1) + client.start_manual_movie_saving() + time.sleep(1) + client.stop_manual_movie_saving() + time.sleep(1) + client.stop_acquisition() + time.sleep(3) + assert not client.acquiring + # autosave directory not saved + #path = client["Autosave Movie Frames File Path"] + path = glob.glob("D:\\Temp\\start_stop\\*movie.mrc")[0] + assert os.path.exists(path) + size = os.path.getsize(path) + assert size < 2*1024*1024*150 # less than 150 frames + diff --git a/examples/insitu_imaging/start_stop.py b/examples/insitu_imaging/start_stop.py index fc0450f..66a9c41 100644 --- a/examples/insitu_imaging/start_stop.py +++ b/examples/insitu_imaging/start_stop.py @@ -1,10 +1,47 @@ """ Event Based Recording ===================== -This example demonstrates how to start and stop recording data from some event. +This example demonstrates how to start and stop recording data from some event. This is just +a simple example using the time.sleep function to wait for 10 seconds before starting the +recording but this could be any event. For example we could start recording when the temperature +starts to ramp or when there is some change in the sample from the previous image. +""" -In this case the fake "test" data is a nano-particle which we see some contrast -change -""" +import deapi +import time + +c = deapi.Client() +c.connect() + + +# Set the autosave directory +loc_time = time.localtime() +c["Autosave Directory"] = f"D:\\AutomatedInSitu\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" + +c["Autosave Movie"] = "On" # Save the individual frames +c["Autosave Final Image"] = "On" # Save the final summed image +c["Autosave Movie Sum Count"] =10 # The total number of frames summed for one call to `c.start_acquisition`. + +# Usually maximum FPS and then increase Autosave Movie Sum Count to get the desired frame rate +c["Frames Per Second"] = 100 + +c.start_acquisition(numberOfAcquisitions=1000) + +# %% +# Running Acquisition +# =================== +# This will run the acquisition for 1000 repeats at 100/10 FPS (10 FPS). In total this +# will take 100 seconds to complete. If for some reason we want to start recording midway +# through an acquisition, for example if we start applying a temperature ramp, or if the +# sample starts to change we can use the `start_manual_movie_saving` function to start recording. + + +time.sleep(10) # wait for 10 seconds +c.start_manual_movie_saving() # start recording +time.sleep(10) # wait for 10 seconds +c.stop_manual_movie_saving() # stop recording + +while c.acquiring: + time.sleep(1) # wait for acquisition to finish From 4c5140ab1ea5afc885c43884de30a118b0fe45d3 Mon Sep 17 00:00:00 2001 From: Carter Francis Date: Thu, 17 Apr 2025 14:18:31 -0500 Subject: [PATCH 4/4] Make sure Examples run with Server --- .github/workflows/documentation.yaml | 2 +- deapi/client.py | 74 ++++++++++++------- deapi/data_types.py | 34 +++++---- deapi/simulated_server/fake_server.py | 64 ++++++++-------- deapi/tests/conftest.py | 4 +- deapi/tests/original_tests/test_legacy.py | 1 - .../speed_tests/test_internal_file_saving.py | 39 ++++++---- deapi/tests/test_client.py | 16 ++-- .../test_file_loading_libertem.py | 7 +- .../test_file_loading_rsciio.py | 2 +- deapi/tests/test_file_saving/test_h5ebsd.py | 6 -- .../test_scan_pattern_saving.py | 14 ++-- deapi/tests/test_insitu/test_start_stop.py | 6 +- examples/insitu_imaging/start_stop.py | 28 ++++--- .../live_imaging/bright_spot_intensity.py | 15 +++- .../taking_an_image_every_minute.py | 39 ++++++---- examples/live_imaging/viewing_the_sensor.py | 12 ++- .../setting_parameters/setting_up_stem.py | 12 +-- .../virtual_imaging/setting_virtual_masks.py | 12 ++- examples/virtual_imaging/vdf_vbf.py | 10 ++- 20 files changed, 244 insertions(+), 153 deletions(-) diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index f185596..61b600a 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -23,7 +23,7 @@ jobs: - name: Sphinx build run: | - python deapi/simulated_server/initialize_server.py 13241 & + python deapi/simulated_server/initialize_server.py 13240 & sleep 5 && sphinx-build doc _build && kill %1 diff --git a/deapi/client.py b/deapi/client.py index ef0e90a..6dad1a6 100644 --- a/deapi/client.py +++ b/deapi/client.py @@ -348,7 +348,9 @@ def get_property_spec(self, propertyName: str): """ t0 = self.GetTime() values = False - command = self._addSingleCommand(self.GET_ALLOWABLE_VALUES_DEPRECATED, propertyName) + command = self._addSingleCommand( + self.GET_ALLOWABLE_VALUES_DEPRECATED, propertyName + ) response = self._sendCommand(command) if response == False: return None @@ -397,7 +399,6 @@ def get_property_spec(self, propertyName: str): return propSpec - def get_property_specifications(self, propertyName): """ Get a list of allowed values for a property of the current camera on DE-Server @@ -409,8 +410,10 @@ def get_property_specifications(self, propertyName): The name of the property to get the allowed values for """ t0 = self.GetTime() - values = False - command = self.__addSingleCommand(self.GET_PROPERTY_SPECIFICATIONS, propertyName) + values = False + command = self.__addSingleCommand( + self.GET_PROPERTY_SPECIFICATIONS, propertyName + ) response = self.__sendCommand(command) if response == False: return None @@ -418,15 +421,15 @@ def get_property_specifications(self, propertyName): values = self.__getParameters(response.acknowledge[0]) propSpec = PropertySpec() - propSpec.dataType = values[0] - propSpec.valueType = values[1] - propSpec.category = values[len(values)-4] - propSpec.options = list(values[2:len(values)-4]) - propSpec.defaultValue = str(values[len(values)-3]) - propSpec.currentValue = str(values[len(values)-2]) - propSpec.readOnly = bool(values[len(values)-1]) + propSpec.dataType = values[0] + propSpec.valueType = values[1] + propSpec.category = values[len(values) - 4] + propSpec.options = list(values[2 : len(values) - 4]) + propSpec.defaultValue = str(values[len(values) - 3]) + propSpec.currentValue = str(values[len(values) - 2]) + propSpec.readOnly = bool(values[len(values) - 1]) - optionsLength = len(propSpec.options) + optionsLength = len(propSpec.options) if propSpec.valueType == "Range": if optionsLength == 2: @@ -460,7 +463,6 @@ def get_property_specifications(self, propertyName): return propSpec - def get_property(self, propertyName: str): """ Get the value of a property of the current camera on DE-Server @@ -671,7 +673,6 @@ def set_hw_roi(self, offsetX: int, offsetY: int, sizeX: int, sizeY: int): @write_only def SetScanSize(self, sizeX, sizeY): - t0 = self.GetTime() ret = False @@ -743,7 +744,9 @@ def SetScanROI(self, enable, offsetX, offsetY, sizeX, sizeY): return ret @write_only - def SetScanROIAndGetChangedProperties(self, enable, offsetX, offsetY, sizeX, sizeY, changedProperties): + def SetScanROIAndGetChangedProperties( + self, enable, offsetX, offsetY, sizeX, sizeY, changedProperties + ): t0 = self.GetTime() ret = False @@ -796,7 +799,6 @@ def set_hw_roi_and_get_changed_properties( List of properties that have changed """ - t0 = self.GetTime() ret = False @@ -936,19 +938,30 @@ def set_adaptive_roi(self, offsetX, offsetY, sizeX, sizeY): t0 = self.GetTime() ret = False - command = self.__addSingleCommand(self.SET_ADAPTIVE_ROI, None, [offsetX, offsetY, sizeX, sizeY]) + command = self.__addSingleCommand( + self.SET_ADAPTIVE_ROI, None, [offsetX, offsetY, sizeX, sizeY] + ) response = self.__sendCommand(command) if response != False: ret = response.acknowledge[0].error != True self.refreshProperties = True if logLevel == logging.DEBUG: - log.debug("SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", offsetX, offsetY, sizeX, sizeY, (self.GetTime() - t0) * 1000) + log.debug( + "SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", + offsetX, + offsetY, + sizeX, + sizeY, + (self.GetTime() - t0) * 1000, + ) return ret @write_only - def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, sizeY, changedProperties, timeoutMsec = 5000): + def set_adaptive_roi_and_get_changed_properties( + self, offsetX, offsetY, sizeX, sizeY, changedProperties, timeoutMsec=5000 + ): """ Automatically choose the proper HW ROI and set SW ROI of the current camera on DE-Server and get all of the changed properties. This is useful for testing and determining how certain @@ -971,7 +984,11 @@ def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, s t0 = self.GetTime() ret = False - command = self.__addSingleCommand(self.SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES, None, [offsetX, offsetY, sizeX, sizeY]) + command = self.__addSingleCommand( + self.SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES, + None, + [offsetX, offsetY, sizeX, sizeY], + ) response = self.__sendCommand(command) if response != False: ret = response.acknowledge[0].error != True @@ -981,7 +998,14 @@ def set_adaptive_roi_and_get_changed_properties(self, offsetX, offsetY, sizeX, s ret = self.ParseChangedProperties(changedProperties, response) if logLevel == logging.DEBUG: - log.debug("SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", offsetX, offsetY, sizeX, sizeY, (self.GetTime() - t0) * 1000) + log.debug( + "SetAdaptiveROI: (%i,%i,%i,%i) , completed in %.1f ms", + offsetX, + offsetY, + sizeX, + sizeY, + (self.GetTime() - t0) * 1000, + ) return ret @@ -1108,7 +1132,7 @@ def start_manual_movie_saving(self): Start saving movie during acquisition. """ start_time = self.GetTime() - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP sock.sendto(b"PyClientManualMovieStart", (self.host, self.port)) respond = sock.recv(32) if logLevel == logging.INFO: @@ -1125,9 +1149,9 @@ def stop_manual_movie_saving(self): Stop saving movie during acquisition. """ start_time = self.GetTime() - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP sock.sendto(b"PyClientManualMovieStop", (self.host, self.port)) - respond = sock.recv(32); + respond = sock.recv(32) if logLevel == logging.INFO: log.info(f"{self.host} {self.port} {respond}") if logLevel <= logging.DEBUG: @@ -2303,7 +2327,7 @@ def ParseChangedProperties(self, changedProperties, response): SetCurrentCamera = set_current_camera ListProperties = list_properties GetPropertySpec = get_property_spec - #PropertyValidValues = property_valid_values + # PropertyValidValues = property_valid_values GetProperty = get_property SetProperty = set_property SetPropertyAndGetChangedProperties = set_property_and_get_changed_properties diff --git a/deapi/data_types.py b/deapi/data_types.py index 90dd789..91b8658 100644 --- a/deapi/data_types.py +++ b/deapi/data_types.py @@ -296,12 +296,13 @@ def __init__( self.data = data def __repr__(self): - return (f"Histogram(min={self.min}," - f" max={self.max}, " - f"upperMostLocalMaxima={self.upperMostLocalMaxima}," - f" bins={self.bins}," - f" data={self.data})") - + return ( + f"Histogram(min={self.min}," + f" max={self.max}, " + f"upperMostLocalMaxima={self.upperMostLocalMaxima}," + f" bins={self.bins}," + f" data={self.data})" + ) def plot(self, ax=None): """Plot the histogram using matplotlib @@ -407,7 +408,7 @@ def __init__( options: list = None, default_value=None, current_value=None, - read_only = None, + read_only=None, ): self.dataType = data_type self.valueType = value_type @@ -425,15 +426,18 @@ def __init__( options = None # List of options defaultValue = None # default value currentValue = None # current value - readonly = False # Read-only property + readonly = False # Read-only property def __repr__(self): - return (f"PropertySpec(dataType={self.dataType}," - f" valueType={self.valueType}, " - f"category={self.category}," - f" options={self.options}," - f" defaultValue={self.defaultValue}, " - f"currentValue={self.currentValue})") + return ( + f"PropertySpec(dataType={self.dataType}," + f" valueType={self.valueType}, " + f"category={self.category}," + f" options={self.options}," + f" defaultValue={self.defaultValue}, " + f"currentValue={self.currentValue})" + ) + class PropertyCollection: """Class to interact with collections of properties in the DE API @@ -575,7 +579,7 @@ def __str__(self): def __init__(self, client, index): self.client = client - self.index = index + self.index = index + 1 # Zero index is reserved. def __getitem__(self, item): full_img = self.client.get_virtual_mask(self.index) diff --git a/deapi/simulated_server/fake_server.py b/deapi/simulated_server/fake_server.py index c15f1d0..602db68 100644 --- a/deapi/simulated_server/fake_server.py +++ b/deapi/simulated_server/fake_server.py @@ -595,38 +595,38 @@ def _fake_get_result(self, command): raise ValueError(f"Frame type {frame_type} not Supported in PythonDEServer") # map to right order... response_mapping = [ - pixel_format, # pix format - windowWidth, # window width - windowHeight, # window height - "Test", # name - 0, # acquisition index - self.acquisition_status == "Acquiring", # status - flat_index, # frame number - 1, # frame count - 0, # image min - 2**16, # image max - 100, # image mean - 10, # image std - 0, # eppix - 0, # eps - 0, # eppixps - 0, # epa2 - 0, # eppixpf - 0, # eppix_incident - 0, # eps_incident - 0, # eppixps_incident - 0, # epa2_incident - 0, # eppixpf_incident - 0, # red sat warning - 0, # orange sat warning - 0, # saturation - time.time(), # current time - 0, # autoStretchMin - 0, #autoStretchMax - 0, # autoStretchGamma - 0, # histogram min - float(np.min(image)), # histogram max - float(np.max(image)), # histogram upper local max + pixel_format, # pix format + windowWidth, # window width + windowHeight, # window height + "Test", # name + 0, # acquisition index + self.acquisition_status == "Acquiring", # status + flat_index, # frame number + 1, # frame count + 0, # image min + 2**16, # image max + 100, # image mean + 10, # image std + 0, # eppix + 0, # eps + 0, # eppixps + 0, # epa2 + 0, # eppixpf + 0, # eppix_incident + 0, # eps_incident + 0, # eppixps_incident + 0, # epa2_incident + 0, # eppixpf_incident + 0, # red sat warning + 0, # orange sat warning + 0, # saturation + time.time(), # current time + 0, # autoStretchMin + 0, # autoStretchMax + 0, # autoStretchGamma + 0, # histogram min + float(np.min(image)), # histogram max + float(np.max(image)), # histogram upper local max ] for val in response_mapping: ack1 = acknowledge_return.acknowledge.add() diff --git a/deapi/tests/conftest.py b/deapi/tests/conftest.py index 19db05d..15e6971 100644 --- a/deapi/tests/conftest.py +++ b/deapi/tests/conftest.py @@ -84,7 +84,9 @@ def client(xprocess, request): ) if request.config.getoption("--engineering"): - c.set_engineering_mode(enable =True, password =request.config.getoption("--engineering")) + c.set_engineering_mode( + enable=True, password=request.config.getoption("--engineering") + ) yield c time.sleep(4) c.disconnect() diff --git a/deapi/tests/original_tests/test_legacy.py b/deapi/tests/original_tests/test_legacy.py index 58ca06c..69bdebc 100644 --- a/deapi/tests/original_tests/test_legacy.py +++ b/deapi/tests/original_tests/test_legacy.py @@ -10,7 +10,6 @@ class TestFPS01: maximum value and that the camera is able to acquire at that size. """ - @pytest.mark.server @pytest.fixture(autouse=True) def clean_state(self, client): # First set the hardware ROI to a known state diff --git a/deapi/tests/speed_tests/test_internal_file_saving.py b/deapi/tests/speed_tests/test_internal_file_saving.py index 0811ab4..0592c54 100644 --- a/deapi/tests/speed_tests/test_internal_file_saving.py +++ b/deapi/tests/speed_tests/test_internal_file_saving.py @@ -53,14 +53,15 @@ def save(self, client, size=64, file_format="MRC"): print(client["Speed - Frame Write Time (us)"]) return client["Speed - Frame Write Time (us)"] + class TestCompressionSpeed: @pytest.mark.engineering @pytest.mark.server @pytest.mark.skip(reason="Not implemented yet") def test_compression_speeds(self, client): - methods = ["lz4"] # zstd throws an error??? - levels = [ 5, 7, 9] + methods = ["lz4"] # zstd throws an error??? + levels = [5, 7, 9] compression_times = { "lz4": {l: [] for l in levels}, @@ -69,7 +70,7 @@ def test_compression_speeds(self, client): for level in levels: client["Compression - Mode"] = method client["Compression - Level"] = level - client["Compression - Threads"] = 8 # Max is half the number of cores + client["Compression - Threads"] = 8 # Max is half the number of cores client["Grabbing - Target Buffer Size (MB)"] = 32 assert client["Compression - Mode"] == method assert client["Compression - Level"] == level @@ -84,7 +85,10 @@ def test_compression_speeds(self, client): while client.acquiring: time.sleep(0.1) time.sleep(2) - processing_log = client["Autosave Movie Frames File Path"].split("0_movie")[0] + "processing.log" + processing_log = ( + client["Autosave Movie Frames File Path"].split("0_movie")[0] + + "processing.log" + ) with open(processing_log, "r") as f: lines = f.readlines() summary_index = 0 @@ -93,21 +97,26 @@ def test_compression_speeds(self, client): summary_index = i summary_dict = {} - for l in lines[summary_index + 2:]: + for l in lines[summary_index + 2 :]: try: key, values = l.split("=") key = key.strip() summary_dict[key] = values.strip() except ValueError: pass - print(f"{method}-{level}: {summary_dict['Compression Speed(per core)']}") - compression_times[method][level] = summary_dict["Compression Speed(per core)"].split(" ")[0] + print( + f"{method}-{level}: {summary_dict['Compression Speed(per core)']}" + ) + compression_times[method][level] = summary_dict[ + "Compression Speed(per core)" + ].split(" ")[0] print(compression_times) version = client["Server Software Version"] fname = version + "_compression_speed.json" with open(fname, "w") as outfile: json.dump(compression_times, outfile) + class TestCompressionSlow: @pytest.mark.engineering @@ -115,11 +124,11 @@ class TestCompressionSlow: @pytest.mark.skip(reason="Not implemented yet") def test_compression_speeds(self, client): method = "zlib" - level =4 + level = 4 client["Compression - Mode"] = method client["Compression - Level"] = level - client["Compression - Threads"] = 8 # Max is half the number of cores + client["Compression - Threads"] = 8 # Max is half the number of cores client["Grabbing - Target Buffer Size (MB)"] = 32 assert client["Compression - Mode"] == method assert client["Compression - Level"] == level @@ -135,7 +144,10 @@ def test_compression_speeds(self, client): time.sleep(0.1) time.sleep(2) - processing_log = client["Autosave Movie Frames File Path"].split("0_movie")[0] + "processing.log" + processing_log = ( + client["Autosave Movie Frames File Path"].split("0_movie")[0] + + "processing.log" + ) with open(processing_log, "r") as f: lines = f.readlines() summary_index = 0 @@ -144,7 +156,7 @@ def test_compression_speeds(self, client): summary_index = i summary_dict = {} - for l in lines[summary_index + 2:]: + for l in lines[summary_index + 2 :]: try: key, values = l.split("=") key = key.strip() @@ -152,8 +164,3 @@ def test_compression_speeds(self, client): except ValueError: pass print(summary_dict["Compression Speed(per core)"]) - - - - - diff --git a/deapi/tests/test_client.py b/deapi/tests/test_client.py index 6dfb04c..e6cf9e5 100644 --- a/deapi/tests/test_client.py +++ b/deapi/tests/test_client.py @@ -4,7 +4,12 @@ from deapi import Client, Histogram import pytest -from deapi.data_types import PropertySpec, VirtualMask, MovieBufferStatus, ContrastStretchType +from deapi.data_types import ( + PropertySpec, + VirtualMask, + MovieBufferStatus, + ContrastStretchType, +) class TestClient: @@ -100,7 +105,7 @@ def test_get_histogram(self, client): while client.acquiring: time.sleep(1) result = client.get_result("singleframe_integrated") - assert isinstance(result[3], Histogram) + assert isinstance(result[3], Histogram) result[3].plot() def test_get_result_no_scan(self, client): @@ -168,7 +173,7 @@ def test_virtual_mask_calculation(self, client): client.virtual_masks[2][1::2] = 0 client.virtual_masks[2][::2] = 2 assert client.virtual_masks[2].calculation == "Difference" - assert client["Scan - Virtual Detector 2 Calculation"] == "Difference" + assert client["Scan - Virtual Detector 3 Calculation"] == "Difference" np.testing.assert_allclose(client.virtual_masks[2][::2], 2) client.start_acquisition(1) while client.acquiring: @@ -297,7 +302,7 @@ def test_set_xy_array(self, client): mask[3:-3, 3:-3] = 0 pos = np.argwhere(mask) - is_set =client.set_xy_array(pos) + is_set = client.set_xy_array(pos) assert client["Scan - Type"] == "XY Array" assert is_set assert client["Scan - Points"] == np.sum(mask) @@ -311,5 +316,4 @@ def test_set_xy_array(self, client): time.sleep(1) result = client.get_result("virtual_image1") assert result[0].shape == (12, 12) - client["Scan - Type"] = "Raster" #clean up - + client["Scan - Type"] = "Raster" # clean up diff --git a/deapi/tests/test_file_saving/test_file_loading_libertem.py b/deapi/tests/test_file_saving/test_file_loading_libertem.py index ffd4438..8eb7dfa 100644 --- a/deapi/tests/test_file_saving/test_file_loading_libertem.py +++ b/deapi/tests/test_file_saving/test_file_loading_libertem.py @@ -31,7 +31,10 @@ def clean_state(self, client): time.sleep(1) @pytest.mark.parametrize( - "file_format", ["DE5",] + "file_format", + [ + "DE5", + ], ) # MRC file loading in LiberTEM is broken! @pytest.mark.server def test_save_4DSTEM(self, client, file_format): @@ -52,7 +55,7 @@ def test_save_4DSTEM(self, client, file_format): while client.acquiring: time.sleep(0.1) time.sleep(1) - assert (file_format.lower() in client["Autosave Movie Frames File Path"]) + assert file_format.lower() in client["Autosave Movie Frames File Path"] movie = glob.glob(temp_dir + "/*movie." + file_format.lower())[0] dataset = lt.Context().load("auto", path=movie) assert tuple(dataset.shape) == (8, 16, 1024, 1024) diff --git a/deapi/tests/test_file_saving/test_file_loading_rsciio.py b/deapi/tests/test_file_saving/test_file_loading_rsciio.py index 9103b44..22ceaaa 100644 --- a/deapi/tests/test_file_saving/test_file_loading_rsciio.py +++ b/deapi/tests/test_file_saving/test_file_loading_rsciio.py @@ -46,7 +46,7 @@ def test_save_4DSTEM(self, client, file_format): while client.acquiring: time.sleep(0.1) time.sleep(1) - assert (file_format.lower() in client["Autosave Movie Frames File Path"]) + assert file_format.lower() in client["Autosave Movie Frames File Path"] s = hs.load(client["Autosave Movie Frames File Path"]) if file_format == "MRC": assert s.data.shape == (144, 1024, 1024) diff --git a/deapi/tests/test_file_saving/test_h5ebsd.py b/deapi/tests/test_file_saving/test_h5ebsd.py index 779b5ee..917d9cf 100644 --- a/deapi/tests/test_file_saving/test_h5ebsd.py +++ b/deapi/tests/test_file_saving/test_h5ebsd.py @@ -9,16 +9,10 @@ import os import time - -import blosc2 -import numpy as np import pytest -import hyperspy.api as hs -import glob import h5py - class TestSavingHyperSpy: @pytest.fixture(autouse=True) def clean_state(self, client): diff --git a/deapi/tests/test_file_saving/test_scan_pattern_saving.py b/deapi/tests/test_file_saving/test_scan_pattern_saving.py index 1b83e5f..875656f 100644 --- a/deapi/tests/test_file_saving/test_scan_pattern_saving.py +++ b/deapi/tests/test_file_saving/test_scan_pattern_saving.py @@ -65,8 +65,6 @@ def test_save_scans(self, client, scan_type, buffer, file_format): time.sleep(5) # Wait for the file to be written to disk etc. - - if file_format == "HSPY": movie = hs.load(client["Autosave Movie Frames File Path"]) frame_order = movie.data[:, :, 0, 0] @@ -75,7 +73,11 @@ def test_save_scans(self, client, scan_type, buffer, file_format): print(client["Autosave Movie Frames File Path"]) fp = client["Autosave Movie Frames File Path"] movie = hs.load(client["Autosave Movie Frames File Path"]) - np.testing.assert_array_equal(movie.data.reshape(-1, 1024, 1024), np.arange(num_pos)[:, np.newaxis, np.newaxis] * np.ones((1, 1024, 1024))) + np.testing.assert_array_equal( + movie.data.reshape(-1, 1024, 1024), + np.arange(num_pos)[:, np.newaxis, np.newaxis] + * np.ones((1, 1024, 1024)), + ) class TestSavingVirtual: @@ -104,7 +106,7 @@ def test_save_scans(self, client, scan_type, buffer): ) frame_num_order = frame_num_order.reshape(-1) else: # Raster - frame_num_order =np.arange(num_pos) + frame_num_order = np.arange(num_pos) client["Frames Per Second"] = 100 client["Scan - Enable"] = "On" @@ -128,4 +130,6 @@ def test_save_scans(self, client, scan_type, buffer): print(client["Autosave Virtual Image 0 File Path"]) fp = client["Autosave Virtual Image 0 File Path"] movie = hs.load(client["Autosave Virtual Image 0 File Path"]) - np.testing.assert_array_equal(movie.data.reshape(-1), frame_num_order*(1024*1024)) + np.testing.assert_array_equal( + movie.data.reshape(-1), frame_num_order * (1024 * 1024) + ) diff --git a/deapi/tests/test_insitu/test_start_stop.py b/deapi/tests/test_insitu/test_start_stop.py index 72e0ae4..55f07b5 100644 --- a/deapi/tests/test_insitu/test_start_stop.py +++ b/deapi/tests/test_insitu/test_start_stop.py @@ -6,6 +6,7 @@ import glob import shutil + class TestInsitu: @pytest.fixture(autouse=True) def clean_state(self, client): @@ -42,9 +43,8 @@ def test_start_stop(self, client): time.sleep(3) assert not client.acquiring # autosave directory not saved - #path = client["Autosave Movie Frames File Path"] + # path = client["Autosave Movie Frames File Path"] path = glob.glob("D:\\Temp\\start_stop\\*movie.mrc")[0] assert os.path.exists(path) size = os.path.getsize(path) - assert size < 2*1024*1024*150 # less than 150 frames - + assert size < 2 * 1024 * 1024 * 150 # less than 150 frames diff --git a/examples/insitu_imaging/start_stop.py b/examples/insitu_imaging/start_stop.py index 66a9c41..f8ffa52 100644 --- a/examples/insitu_imaging/start_stop.py +++ b/examples/insitu_imaging/start_stop.py @@ -7,21 +7,28 @@ starts to ramp or when there is some change in the sample from the previous image. """ - import deapi import time +import sys + c = deapi.Client() +if not sys.platform.startswith("win"): + c.usingMmf = False # True if on same machine as DE Server and a Windows machine c.connect() # Set the autosave directory loc_time = time.localtime() -c["Autosave Directory"] = f"D:\\AutomatedInSitu\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" +c["Autosave Directory"] = ( + f"D:\\AutomatedInSitu\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" +) -c["Autosave Movie"] = "On" # Save the individual frames -c["Autosave Final Image"] = "On" # Save the final summed image -c["Autosave Movie Sum Count"] =10 # The total number of frames summed for one call to `c.start_acquisition`. +c["Autosave Movie"] = "On" # Save the individual frames +c["Autosave Final Image"] = "On" # Save the final summed image +c["Autosave Movie Sum Count"] = ( + 10 # The total number of frames summed for one call to `c.start_acquisition`. +) # Usually maximum FPS and then increase Autosave Movie Sum Count to get the desired frame rate c["Frames Per Second"] = 100 @@ -37,11 +44,12 @@ # sample starts to change we can use the `start_manual_movie_saving` function to start recording. -time.sleep(10) # wait for 10 seconds -c.start_manual_movie_saving() # start recording -time.sleep(10) # wait for 10 seconds -c.stop_manual_movie_saving() # stop recording +time.sleep(10) # wait for 10 seconds +c.start_manual_movie_saving() # start recording +time.sleep(10) # wait for 10 seconds +c.stop_manual_movie_saving() # stop recording while c.acquiring: - time.sleep(1) # wait for acquisition to finish + time.sleep(1) # wait for acquisition to finish +c.disconnect() # disconnect from the server diff --git a/examples/live_imaging/bright_spot_intensity.py b/examples/live_imaging/bright_spot_intensity.py index c97ce5b..ce366c3 100644 --- a/examples/live_imaging/bright_spot_intensity.py +++ b/examples/live_imaging/bright_spot_intensity.py @@ -4,12 +4,20 @@ This example demonstrates how to monitor the intensity of the brightest pixel in the sensor data during acquisition. """ + import numpy as np import deapi import time +import sys + client = deapi.Client() + +if not sys.platform.startswith("win"): + client.usingMmf = ( + False # True if on same machine as DE Server and a Windows machine + ) client.connect() # %% @@ -49,7 +57,9 @@ while client.acquiring: - image, pixelFormat, attributes, histogram = client.get_result("singleframe_integrated") + image, pixelFormat, attributes, histogram = client.get_result( + "singleframe_integrated" + ) print(f"Max intensity: {image.max()}") time.sleep(0.1) @@ -57,4 +67,5 @@ # Retract the Camera # ------------------ -client["Camera Position Control"] = "Retract" \ No newline at end of file +client["Camera Position Control"] = "Retract" +client.disconnect() diff --git a/examples/live_imaging/taking_an_image_every_minute.py b/examples/live_imaging/taking_an_image_every_minute.py index b8a0842..c7ba881 100644 --- a/examples/live_imaging/taking_an_image_every_minute.py +++ b/examples/live_imaging/taking_an_image_every_minute.py @@ -1,6 +1,6 @@ """ -Taking an Image every Minute -============================ +Taking an Image every 10 seconds +================================ This example shows how to take an image every minute. The results are saved to disk as: @@ -11,28 +11,37 @@ before saving the final image. """ - import deapi import matplotlib.pyplot as plt import time -import numpy as np +import sys c = deapi.Client() + +if not sys.platform.startswith("win"): + c.usingMmf = False # True if on same machine as DE Server and a Windows machine + c.connect() # Set the autosave directory loc_time = time.localtime() -c["Autosave Directory"] = f"D:\\Service\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" +c["Autosave Directory"] = ( + f"D:\\Service\\{loc_time.tm_year}-{loc_time.tm_mon}-{loc_time.tm_mday}" +) -c["Autosave Movie"] = "On" # Save the individual frames -c["Autosave Final Image"] = "On" # Save the final summed image -c["Autosave Movie Sum Count"] =10 # The total number of frames summed for one call to `c.start_acquisition`. +c["Autosave Movie"] = "On" # Save the individual frames +c["Autosave Final Image"] = "On" # Save the final summed image +c["Autosave Movie Sum Count"] = ( + 10 # The total number of frames summed for one call to `c.start_acquisition`. +) -results = [] # store the results in a list +results = [] # store the results in a list -for i in range(100): - c.start_acquisition(1) # Acquire one image (This is non-blocking and should run very fast) - time.sleep(60) # sleep for 60 seconds +for i in range(10): + c.start_acquisition( + 1 + ) # Acquire one image (This is non-blocking and should run very fast) + time.sleep(10) # sleep for 60 seconds # this might take a half a second? # You can also just skip this and load directly from the saved files. This gets only the summed image. @@ -45,8 +54,8 @@ # plots both the histogram and the image. -image, dtype, attributes, histogram = results[0] # get the first result +image, dtype, attributes, histogram = results[0] # get the first result -plt.plot(np.linspace(histogram.min, histogram.max, histogram.bins),histogram.data) +plt.imshow(image) -plt.imshow(image) \ No newline at end of file +c.disconnect() diff --git a/examples/live_imaging/viewing_the_sensor.py b/examples/live_imaging/viewing_the_sensor.py index c22883e..62e8242 100644 --- a/examples/live_imaging/viewing_the_sensor.py +++ b/examples/live_imaging/viewing_the_sensor.py @@ -19,15 +19,23 @@ from deapi import Client import matplotlib.pyplot as plt import numpy as np +import sys +import time client = Client() + +if not sys.platform.startswith("win"): + client.usingMmf = ( + False # True if on same machine as DE Server and a Windows machine + ) client.usingMmf = False -client.connect(port=13241) # connect to the running DE Server +client.connect(port=13240) # connect to the running DE Server client["Frames Per Second"] = 500 client.scan(size_x=64, size_y=64, enable="On") client.start_acquisition(1) +time.sleep(1) # wait for the acquisition to start fig, axs = plt.subplots(1, 2) data, _, _, _ = client.get_result("virtual_image0") @@ -55,3 +63,5 @@ # use blitting in matplotlib. (up to ~500 fps) live_im.autoscale() live_virt_im.autoscale() + +client.disconnect() diff --git a/examples/setting_parameters/setting_up_stem.py b/examples/setting_parameters/setting_up_stem.py index 1e6bd24..bdefa8e 100644 --- a/examples/setting_parameters/setting_up_stem.py +++ b/examples/setting_parameters/setting_up_stem.py @@ -12,12 +12,16 @@ """ from deapi import Client +import sys # %% # Connect to the DE server client = Client() -client.usingMmf = False -client.connect(port=13241) # connect to the running DE Server +if not sys.platform.startswith("win"): + client.usingMmf = ( + False # True if on same machine as DE Server and a Windows machine + ) +client.connect(port=13240) # connect to the running DE Server # %% # Set the hardware ROI to 256x256 @@ -37,6 +41,4 @@ # Set the number of frames per second to 1000 client["Frames Per Second"] = 1000 - -# %% -# Acquire a STEM image using a HAADF detector +client.disconnect() diff --git a/examples/virtual_imaging/setting_virtual_masks.py b/examples/virtual_imaging/setting_virtual_masks.py index cca54c3..6bfdc29 100644 --- a/examples/virtual_imaging/setting_virtual_masks.py +++ b/examples/virtual_imaging/setting_virtual_masks.py @@ -20,14 +20,16 @@ import matplotlib.pyplot as plt import time from skimage.draw import disk +import sys c = Client() -c.usingMmf = False -c.connect(port=13241) # connect to the running DE Server +if not sys.platform.startswith("win"): + c.usingMmf = False # True if on same machine as DE Server and a Windows machine +c.connect(port=13240) # connect to the running DE Server c.virtual_masks[0][:] = 1 # Set everything to 1 c.virtual_masks[0].plot() # plot the current v0 mask - +print("Virtual Mask 0: ", c.virtual_masks[0][:].shape) # %% # Changing the virtual mask # ------------------------- @@ -82,7 +84,7 @@ # start acquisition function c["Frames Per Second"] = 5000 # 5000 frames per second -c.scan(enable="On", size_x=128, size_y=128) +c.scan(enable="On", size_x=16, size_y=16) c.start_acquisition() while c.acquiring: # wait for acquisition to finish and then plot the results @@ -92,3 +94,5 @@ for a, virt in zip(axs, ["virtual_image0", "virtual_image1", "virtual_image2"]): data, _, _, _ = c.get_result(virt) a.imshow(data) + +c.disconnect() diff --git a/examples/virtual_imaging/vdf_vbf.py b/examples/virtual_imaging/vdf_vbf.py index fda7ba4..e9aa598 100644 --- a/examples/virtual_imaging/vdf_vbf.py +++ b/examples/virtual_imaging/vdf_vbf.py @@ -29,10 +29,14 @@ from skimage.segmentation import flood from skimage.morphology import dilation, disk import matplotlib.pyplot as plt +import sys client = Client() -client.usingMmf = False # True if on same machine as DE Server and a Windows machine -client.connect(port=13241) # connect to the running DE Server +if not sys.platform.startswith("win"): + client.usingMmf = ( + False # True if on same machine as DE Server and a Windows machine + ) +client.connect(port=13240) # connect to the running DE Server # %% # Get A Single Diffraction Pattern @@ -99,3 +103,5 @@ def auto_find_bf(img, threshold=0.5, sigma=10, dilation_rad=30): data, _, _, _ = client.get_result(virt) a.imshow(data) a.set_title(virt) + +client.disconnect()