deapi 5.3b4__tar.gz → 5.3b5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/build.yaml +2 -2
- {deapi-5.3b4 → deapi-5.3b5}/PKG-INFO +1 -1
- {deapi-5.3b4 → deapi-5.3b5}/deapi/client.py +104 -42
- {deapi-5.3b4 → deapi-5.3b5}/deapi/data_types.py +5 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/fake_server.py +15 -6
- {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/initialize_server.py +39 -13
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/conftest.py +32 -1
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/10_imageStatistics.py +21 -6
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/test_legacy.py +2 -1
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_client.py +13 -27
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_fake_server/test_server.py +2 -1
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_scan_pattern_saving.py +6 -6
- {deapi-5.3b4 → deapi-5.3b5}/deapi/version.py +0 -1
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/PKG-INFO +1 -1
- {deapi-5.3b4 → deapi-5.3b5}/pyproject.toml +1 -1
- {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/documentation.yaml +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/publish.yaml +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/test-publish.yaml +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/.pre-commit-config.yaml +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/CHANGES.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/LICENSE +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/README.md +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/Guide to use mamba.pdf +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_2_3_0.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_11_4.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_19_3.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_23_3.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_6_1.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/conf.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/base_fake_data.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/grains.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/index.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/prop_dump.json +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/python_3_instruction.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/release_notes.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/service/de_service.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/01_fps.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/02_hwRoisize.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/03_hwBinning.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/04_swBinning.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/05_swhwBinning.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/06_patternPixel.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/07_reference.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/08_virtmask.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/09_scanRoi.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/func.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/propertyName.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/test_internal_file_saving.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/test_movie_buffer_transfer.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_binning_rois/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_binning_rois/test_rois.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_examples.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_fake_server/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_file_loading_libertem.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_file_loading_rsciio.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_h5ebsd.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_insitu/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_insitu/test_start_stop.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_utils/__init__.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_utils/test_utils.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/utils.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi/wrappers.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/SOURCES.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/dependency_links.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/entry_points.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/requires.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/top_level.txt +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/Makefile +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_static/de_api_icon.svg +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/autosummary/base.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-attribute-template.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-class-template.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-function-template.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-module-template.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custrom-method-template.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/changes.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/conf.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/help/dev_guide.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/help/help.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/help/index.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/help/pyDEServer.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/index.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/intro.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/make.bat +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/doc/reference/index.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/bright_spot_intensity.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/taking_an_image_every_minute.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/viewing_the_sensor.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/viewing_the_sensor_tem.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/references/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/setting_parameters/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/setting_parameters/setting_up_stem.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/setting_virtual_masks.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/vdf_vbf.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/README.rst +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/creating_a_simple_dashboard.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/using_get_result.py +0 -0
- {deapi-5.3b4 → deapi-5.3b5}/setup.cfg +0 -0
|
@@ -16,8 +16,8 @@ jobs:
|
|
|
16
16
|
strategy:
|
|
17
17
|
fail-fast: false
|
|
18
18
|
matrix:
|
|
19
|
-
os: [ubuntu-latest, windows-latest
|
|
20
|
-
python-version: ["3.
|
|
19
|
+
os: [ubuntu-latest, windows-latest]
|
|
20
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
21
21
|
include:
|
|
22
22
|
- os: ubuntu-latest
|
|
23
23
|
python-version: 3.11
|
|
@@ -15,8 +15,6 @@ import mmap
|
|
|
15
15
|
from datetime import datetime
|
|
16
16
|
from time import sleep
|
|
17
17
|
import re
|
|
18
|
-
import win32event
|
|
19
|
-
import win32api
|
|
20
18
|
import threading
|
|
21
19
|
from typing import List, Union, Tuple
|
|
22
20
|
|
|
@@ -47,6 +45,10 @@ from deapi.version import version, commandVersion
|
|
|
47
45
|
from deapi.version import commandVersion as cVersion
|
|
48
46
|
from deapi.wrappers import write_only, disable_scan, deprecated_argument
|
|
49
47
|
|
|
48
|
+
if sys.platform.startswith("win"):
|
|
49
|
+
import win32event
|
|
50
|
+
import win32api
|
|
51
|
+
|
|
50
52
|
|
|
51
53
|
## the commandInfo contains [VERSION_MAJOR.VERSION_MINOR.VERSION_PATCH.VERSION_REVISION]
|
|
52
54
|
|
|
@@ -55,6 +57,7 @@ logLevel = logging.INFO
|
|
|
55
57
|
logging.basicConfig(format="%(asctime)s DE %(levelname)-8s %(message)s", level=logLevel)
|
|
56
58
|
log = logging.getLogger("DECameraClientLib")
|
|
57
59
|
|
|
60
|
+
|
|
58
61
|
def print_info():
|
|
59
62
|
log.info(f"DEAPI Version: {version} (Command Version: {commandVersion})")
|
|
60
63
|
log.info("Python : " + sys.version.split("(")[0])
|
|
@@ -62,6 +65,7 @@ def print_info():
|
|
|
62
65
|
log.info("CommandVer: " + str(commandVersion))
|
|
63
66
|
log.info("logLevel : " + str(logging.getLevelName(logLevel)))
|
|
64
67
|
|
|
68
|
+
|
|
65
69
|
class Client:
|
|
66
70
|
"""A class for connecting to the DE-Server
|
|
67
71
|
|
|
@@ -183,7 +187,7 @@ class Client:
|
|
|
183
187
|
response = self._sendCommand(command)
|
|
184
188
|
if response != False:
|
|
185
189
|
self.camera = self.__getParameters(response.acknowledge[0])[0]
|
|
186
|
-
|
|
190
|
+
|
|
187
191
|
if logLevel == logging.DEBUG:
|
|
188
192
|
log.debug("Camera: %s", self.camera)
|
|
189
193
|
|
|
@@ -282,7 +286,7 @@ class Client:
|
|
|
282
286
|
_,
|
|
283
287
|
) = self.get_result(mask_name, DataType.DE8u, attributes=a)
|
|
284
288
|
return res
|
|
285
|
-
|
|
289
|
+
|
|
286
290
|
def get_current_camera(self) -> str:
|
|
287
291
|
"""
|
|
288
292
|
Get the current camera on the server.
|
|
@@ -291,7 +295,7 @@ class Client:
|
|
|
291
295
|
return "No current camera"
|
|
292
296
|
else:
|
|
293
297
|
return self.camera
|
|
294
|
-
|
|
298
|
+
|
|
295
299
|
@write_only
|
|
296
300
|
def set_current_camera(self, camera_name: str = None):
|
|
297
301
|
"""
|
|
@@ -340,7 +344,7 @@ class Client:
|
|
|
340
344
|
if search is not None:
|
|
341
345
|
available_registers = [p for p in available_registers if search in p]
|
|
342
346
|
return available_registers
|
|
343
|
-
|
|
347
|
+
|
|
344
348
|
@deprecated_argument(
|
|
345
349
|
name="propertyName", since="5.2.0", alternative="property_name"
|
|
346
350
|
)
|
|
@@ -508,7 +512,7 @@ class Client:
|
|
|
508
512
|
)
|
|
509
513
|
|
|
510
514
|
return ret
|
|
511
|
-
|
|
515
|
+
|
|
512
516
|
def get_register(self, register_name: str):
|
|
513
517
|
"""
|
|
514
518
|
Get the value of a register of the camera on DE-Server
|
|
@@ -639,7 +643,6 @@ class Client:
|
|
|
639
643
|
)
|
|
640
644
|
|
|
641
645
|
return ret
|
|
642
|
-
|
|
643
646
|
|
|
644
647
|
@write_only
|
|
645
648
|
def set_register(self, name: str, value):
|
|
@@ -674,7 +677,6 @@ class Client:
|
|
|
674
677
|
|
|
675
678
|
return ret
|
|
676
679
|
|
|
677
|
-
|
|
678
680
|
@write_only
|
|
679
681
|
def set_engineering_mode(self, enable, password):
|
|
680
682
|
"""
|
|
@@ -1000,8 +1002,12 @@ class Client:
|
|
|
1000
1002
|
if commandVersion >= 13:
|
|
1001
1003
|
retval = self.SetProperty("Server Normalize Properties", "Off")
|
|
1002
1004
|
|
|
1003
|
-
retval &= self.SetProperty(
|
|
1004
|
-
|
|
1005
|
+
retval &= self.SetProperty(
|
|
1006
|
+
"Hardware Binning X", 2 if bin_x >= 2 and use_hw else 1
|
|
1007
|
+
)
|
|
1008
|
+
retval &= self.SetProperty(
|
|
1009
|
+
"Hardware Binning Y", 2 if bin_y >= 2 and use_hw else 1
|
|
1010
|
+
)
|
|
1005
1011
|
|
|
1006
1012
|
prop_hw_bin_x = self.GetProperty("Hardware Binning X")
|
|
1007
1013
|
prop_hw_bin_y = self.GetProperty("Hardware Binning Y")
|
|
@@ -1577,7 +1583,6 @@ class Client:
|
|
|
1577
1583
|
lapsed = (self.GetTime() - step_time) * 1000
|
|
1578
1584
|
log.debug(" Command Time: %.1f ms", lapsed)
|
|
1579
1585
|
step_time = self.GetTime()
|
|
1580
|
-
ack = response.acknowledge[0]
|
|
1581
1586
|
|
|
1582
1587
|
if response:
|
|
1583
1588
|
values = self.__getParameters(response.acknowledge[0])
|
|
@@ -1894,7 +1899,9 @@ class Client:
|
|
|
1894
1899
|
f"expected: {totalBytes}, received: {movieBufferSize}"
|
|
1895
1900
|
)
|
|
1896
1901
|
else:
|
|
1897
|
-
log.info(
|
|
1902
|
+
log.info(
|
|
1903
|
+
f"reading movie buffer {totalBytes}",
|
|
1904
|
+
)
|
|
1898
1905
|
movieBuffer = self._recvFromSocket(self.socket, totalBytes)
|
|
1899
1906
|
log.info("Done reading movie buffer")
|
|
1900
1907
|
else:
|
|
@@ -2345,7 +2352,7 @@ class Client:
|
|
|
2345
2352
|
|
|
2346
2353
|
if counting:
|
|
2347
2354
|
self["Image Processing - Mode"] = "Counting"
|
|
2348
|
-
self["Reference - Counting Gain Target (
|
|
2355
|
+
self["Reference - Counting Gain Target (e-/pix)"] = (
|
|
2349
2356
|
2000
|
|
2350
2357
|
if target_electrons_per_pixel is None
|
|
2351
2358
|
else target_electrons_per_pixel
|
|
@@ -2367,30 +2374,41 @@ class Client:
|
|
|
2367
2374
|
while self.acquiring:
|
|
2368
2375
|
time.sleep(2)
|
|
2369
2376
|
|
|
2377
|
+
img, dtype, attr, _ = self.get_result(
|
|
2378
|
+
FrameType.SUMINTERMEDIATE, PixelFormat.UINT16
|
|
2379
|
+
)
|
|
2380
|
+
|
|
2370
2381
|
if counting:
|
|
2371
2382
|
exposure_time = self["Reference - Counting Gain Exposure Time (seconds)"]
|
|
2372
2383
|
total_acquisitions = self["Reference - Counting Gain Acquisitions"]
|
|
2384
|
+
num_el = np.max([attr.eppixpf * frame_rate, attr.eppixps])
|
|
2385
|
+
log.info(
|
|
2386
|
+
f"The number of electrons per pixel per second (eppixps): {num_el:.2f}"
|
|
2387
|
+
)
|
|
2373
2388
|
else:
|
|
2374
2389
|
exposure_time = self["Reference - Integrating Gain Exposure Time (seconds)"]
|
|
2375
2390
|
total_acquisitions = self["Reference - Integrating Gain Acquisitions"]
|
|
2391
|
+
num_el = attr.imageMean * frame_rate
|
|
2392
|
+
log.info(f"The number of ADUs per pixel per second : {num_el:.2f}")
|
|
2376
2393
|
|
|
2377
|
-
img, dtype, attr, _ = self.get_result(FrameType.SUMTOTAL, PixelFormat.FLOAT32)
|
|
2378
2394
|
self.SetProperty("Exposure Mode", prevExposureMode)
|
|
2379
|
-
|
|
2380
2395
|
self.SetProperty("Exposure Time (seconds)", prevExposureTime)
|
|
2381
2396
|
|
|
2382
2397
|
num_el = np.max([attr.eppixpf * frame_rate, attr.eppixps])
|
|
2383
|
-
log.info(
|
|
2398
|
+
log.info(
|
|
2399
|
+
f"The number of electrons per pixel per second (eppixps): {num_el:.2f}"
|
|
2400
|
+
)
|
|
2384
2401
|
|
|
2385
2402
|
if attr.saturation > 0.0001: # Nothing should be saturated in a gain image.
|
|
2386
2403
|
raise ValueError(
|
|
2387
2404
|
"The trial gain reference image has pixels that are close to saturation. "
|
|
2388
2405
|
"Please reduce the beam intensity or exposure time."
|
|
2389
2406
|
)
|
|
2407
|
+
|
|
2390
2408
|
# recalculating to check...
|
|
2391
|
-
total_acquisitions = int(
|
|
2392
|
-
|
|
2393
|
-
)
|
|
2409
|
+
# total_acquisitions = int(
|
|
2410
|
+
# np.ceil(target_electrons_per_pixel / (exposure_time * num_el))
|
|
2411
|
+
# )
|
|
2394
2412
|
if total_acquisitions == 1:
|
|
2395
2413
|
total_acquisitions = 2
|
|
2396
2414
|
|
|
@@ -2404,6 +2422,7 @@ class Client:
|
|
|
2404
2422
|
target_electrons_per_pixel: float = None,
|
|
2405
2423
|
timeout: int = 600,
|
|
2406
2424
|
counting: bool = False,
|
|
2425
|
+
num_acq: int = 0,
|
|
2407
2426
|
):
|
|
2408
2427
|
"""Take a gain reference.
|
|
2409
2428
|
|
|
@@ -2430,6 +2449,8 @@ class Client:
|
|
|
2430
2449
|
If True, the gain reference will be taken in counting mode, by default False.
|
|
2431
2450
|
This is useful for cameras that support counting mode and can be used to take gain references
|
|
2432
2451
|
with a lower noise level.
|
|
2452
|
+
num_acq: int, optional
|
|
2453
|
+
Force the number of acquisiton to this number, otherwise it will be automatically calculated.
|
|
2433
2454
|
"""
|
|
2434
2455
|
if target_electrons_per_pixel is None and not counting:
|
|
2435
2456
|
target_electrons_per_pixel = 16000
|
|
@@ -2440,6 +2461,9 @@ class Client:
|
|
|
2440
2461
|
frame_rate, target_electrons_per_pixel, counting
|
|
2441
2462
|
)
|
|
2442
2463
|
|
|
2464
|
+
if num_acq != 0:
|
|
2465
|
+
num_acquisitions = num_acq
|
|
2466
|
+
|
|
2443
2467
|
log.info(
|
|
2444
2468
|
f"Gain reference: {exposure_time:.2f} seconds, "
|
|
2445
2469
|
f"total acquisitions: {num_acquisitions}, "
|
|
@@ -2476,19 +2500,33 @@ class Client:
|
|
|
2476
2500
|
return time.clock()
|
|
2477
2501
|
else:
|
|
2478
2502
|
return time.perf_counter()
|
|
2479
|
-
|
|
2503
|
+
|
|
2504
|
+
def ensure_get_event_supported(self):
|
|
2505
|
+
"""Ensure get_event related functions are only used on Windows platforms.
|
|
2506
|
+
|
|
2507
|
+
Raises
|
|
2508
|
+
------
|
|
2509
|
+
NotImplementedError
|
|
2510
|
+
If called on a non-Windows platform.
|
|
2511
|
+
"""
|
|
2512
|
+
if not sys.platform.startswith("win"):
|
|
2513
|
+
raise NotImplementedError(
|
|
2514
|
+
"get_event functionality is only available on Windows platforms."
|
|
2515
|
+
)
|
|
2516
|
+
|
|
2480
2517
|
def enable_get_event(self):
|
|
2481
2518
|
"""
|
|
2482
2519
|
Enable event retrieval from the server.
|
|
2483
|
-
|
|
2520
|
+
|
|
2484
2521
|
Creates a Windows semaphore to handle event notifications and enables
|
|
2485
2522
|
the getEventEnabled flag. This must be called before get_event() can be used.
|
|
2486
|
-
|
|
2523
|
+
|
|
2487
2524
|
Returns
|
|
2488
2525
|
-------
|
|
2489
2526
|
bool
|
|
2490
2527
|
True if event retrieval was successfully enabled, False otherwise.
|
|
2491
2528
|
"""
|
|
2529
|
+
self.ensure_get_event_supported()
|
|
2492
2530
|
with self.eventMutex:
|
|
2493
2531
|
command = self._addSingleCommand(self.ENABLE_GET_EVENT, None, None)
|
|
2494
2532
|
response = self._sendCommand(command)
|
|
@@ -2496,15 +2534,17 @@ class Client:
|
|
|
2496
2534
|
if response != False:
|
|
2497
2535
|
semaphoreName = self.__getParameters(response.acknowledge[0])[0]
|
|
2498
2536
|
if semaphoreName is not None:
|
|
2499
|
-
self.sdkEventSemaphore = win32event.CreateSemaphore(
|
|
2537
|
+
self.sdkEventSemaphore = win32event.CreateSemaphore(
|
|
2538
|
+
None, 0, 999, semaphoreName
|
|
2539
|
+
)
|
|
2500
2540
|
else:
|
|
2501
2541
|
return False
|
|
2502
|
-
|
|
2542
|
+
|
|
2503
2543
|
self.getEventEnabled = True
|
|
2504
2544
|
return True
|
|
2505
2545
|
else:
|
|
2506
2546
|
return False
|
|
2507
|
-
|
|
2547
|
+
|
|
2508
2548
|
def get_event(self):
|
|
2509
2549
|
"""
|
|
2510
2550
|
Retrieve the next event from the server.
|
|
@@ -2533,6 +2573,7 @@ class Client:
|
|
|
2533
2573
|
If they are empty strings, then the limits did not change.
|
|
2534
2574
|
If event retrieval is disabled or the client is disconnected, an empty list is returned.
|
|
2535
2575
|
"""
|
|
2576
|
+
self.ensure_get_event_supported()
|
|
2536
2577
|
if self.sdkEventSemaphore is not None:
|
|
2537
2578
|
win32event.WaitForSingleObject(self.sdkEventSemaphore, win32event.INFINITE)
|
|
2538
2579
|
else:
|
|
@@ -2541,7 +2582,7 @@ class Client:
|
|
|
2541
2582
|
with self.eventMutex:
|
|
2542
2583
|
if not self.connected or not self.getEventEnabled:
|
|
2543
2584
|
return []
|
|
2544
|
-
|
|
2585
|
+
|
|
2545
2586
|
command = self._addSingleCommand(self.GET_EVENT, None, None)
|
|
2546
2587
|
response = self._sendCommand(command)
|
|
2547
2588
|
|
|
@@ -2556,20 +2597,21 @@ class Client:
|
|
|
2556
2597
|
eventSpec.append(values[4])
|
|
2557
2598
|
|
|
2558
2599
|
return eventSpec
|
|
2559
|
-
|
|
2600
|
+
|
|
2560
2601
|
def disable_get_event(self):
|
|
2561
2602
|
"""
|
|
2562
2603
|
Disable event retrieval from the server.
|
|
2563
|
-
|
|
2604
|
+
|
|
2564
2605
|
Releases and closes the Windows semaphore used for event notification,
|
|
2565
2606
|
and disables the getEventEnabled flag. After calling this, get_event()
|
|
2566
2607
|
will no longer function.
|
|
2567
|
-
|
|
2608
|
+
|
|
2568
2609
|
Returns
|
|
2569
2610
|
-------
|
|
2570
2611
|
bool
|
|
2571
2612
|
True if event retrieval was successfully disabled, False otherwise.
|
|
2572
2613
|
"""
|
|
2614
|
+
self.ensure_get_event_supported()
|
|
2573
2615
|
with self.eventMutex:
|
|
2574
2616
|
command = self._addSingleCommand(self.DISABLE_GET_EVENT, None, None)
|
|
2575
2617
|
response = self._sendCommand(command)
|
|
@@ -2583,7 +2625,7 @@ class Client:
|
|
|
2583
2625
|
return True
|
|
2584
2626
|
else:
|
|
2585
2627
|
return False
|
|
2586
|
-
|
|
2628
|
+
|
|
2587
2629
|
def is_get_event_enabled(self):
|
|
2588
2630
|
"""
|
|
2589
2631
|
Check if event retrieval from the server is currently enabled.
|
|
@@ -2593,6 +2635,7 @@ class Client:
|
|
|
2593
2635
|
bool
|
|
2594
2636
|
True if event retrieval is enabled, False otherwise.
|
|
2595
2637
|
"""
|
|
2638
|
+
self.ensure_get_event_supported()
|
|
2596
2639
|
with self.eventMutex:
|
|
2597
2640
|
return self.getEventEnabled
|
|
2598
2641
|
|
|
@@ -2675,7 +2718,7 @@ class Client:
|
|
|
2675
2718
|
|
|
2676
2719
|
if command is None:
|
|
2677
2720
|
return False
|
|
2678
|
-
|
|
2721
|
+
|
|
2679
2722
|
if len(command.camera_name) == 0:
|
|
2680
2723
|
command.camera_name = (
|
|
2681
2724
|
self.camera
|
|
@@ -2683,18 +2726,28 @@ class Client:
|
|
|
2683
2726
|
|
|
2684
2727
|
try:
|
|
2685
2728
|
packet = struct.pack("I", command.ByteSize()) + command.SerializeToString()
|
|
2686
|
-
|
|
2687
|
-
#
|
|
2688
|
-
#
|
|
2729
|
+
# sendall() loops internally until every byte is delivered (or raises).
|
|
2730
|
+
# send() can return a short count on a non-blocking/timeout socket —
|
|
2731
|
+
# that leaves _recv_exact on the server waiting for the rest of the
|
|
2732
|
+
# message while this side waits for the reply: a deadlock.
|
|
2733
|
+
self.socket.sendall(packet)
|
|
2689
2734
|
except socket.error as e:
|
|
2690
|
-
|
|
2735
|
+
log.error("Error sending command: %s", e)
|
|
2736
|
+
return False
|
|
2691
2737
|
|
|
2692
2738
|
if logLevel == logging.DEBUG:
|
|
2693
2739
|
lapsed = (self.GetTime() - step_time) * 1000
|
|
2694
2740
|
log.debug(" Send Time: %.1f ms", lapsed)
|
|
2695
2741
|
step_time = self.GetTime()
|
|
2696
2742
|
|
|
2697
|
-
|
|
2743
|
+
try:
|
|
2744
|
+
return self.__ReceiveResponseForCommand(command)
|
|
2745
|
+
except ConnectionResetError as e:
|
|
2746
|
+
# Server closed the connection mid-reply (e.g. it crashed or dropped
|
|
2747
|
+
# the client). Return False so callers' existing `if response != False`
|
|
2748
|
+
# guards work correctly, rather than propagating an unexpected exception.
|
|
2749
|
+
log.error("Connection reset while waiting for response: %s", e)
|
|
2750
|
+
return False
|
|
2698
2751
|
|
|
2699
2752
|
def __ReceiveResponseForCommand(self, command):
|
|
2700
2753
|
step_time = self.GetTime()
|
|
@@ -2779,7 +2832,17 @@ class Client:
|
|
|
2779
2832
|
packet_size = upper_lim
|
|
2780
2833
|
loopTime = self.GetTime()
|
|
2781
2834
|
try:
|
|
2782
|
-
|
|
2835
|
+
chunk = sock.recv(packet_size)
|
|
2836
|
+
if not chunk:
|
|
2837
|
+
# recv() returns b'' when the remote end has closed the
|
|
2838
|
+
# connection. Without this check the loop would spin
|
|
2839
|
+
# forever because b'' never raises an exception and never
|
|
2840
|
+
# advances total_len — the primary hang on macOS / Py 3.11+.
|
|
2841
|
+
raise ConnectionResetError(
|
|
2842
|
+
f"Server closed the connection after {total_len} "
|
|
2843
|
+
f"of {bytes} expected bytes"
|
|
2844
|
+
)
|
|
2845
|
+
buffer += chunk
|
|
2783
2846
|
|
|
2784
2847
|
except socket.timeout:
|
|
2785
2848
|
log.debug(
|
|
@@ -2793,8 +2856,7 @@ class Client:
|
|
|
2793
2856
|
else:
|
|
2794
2857
|
pass # continue further
|
|
2795
2858
|
except socket.error as e:
|
|
2796
|
-
raise
|
|
2797
|
-
break
|
|
2859
|
+
raise ConnectionResetError(f"Error receiving {bytes} bytes: {e}") from e
|
|
2798
2860
|
total_len = len(buffer)
|
|
2799
2861
|
|
|
2800
2862
|
totalTimeMs = (self.GetTime() - startTime) * 1000
|
|
@@ -2882,7 +2944,7 @@ class Client:
|
|
|
2882
2944
|
GetProperty = get_property
|
|
2883
2945
|
SetProperty = set_property
|
|
2884
2946
|
SetPropertyAndGetChangedProperties = set_property_and_get_changed_properties
|
|
2885
|
-
GetRegister = get_register
|
|
2947
|
+
GetRegister = get_register
|
|
2886
2948
|
SetRegister = set_register
|
|
2887
2949
|
ListRegisters = list_registers
|
|
2888
2950
|
setEngMode = set_engineering_mode
|
|
@@ -2967,7 +3029,7 @@ class Client:
|
|
|
2967
3029
|
DISABLE_GET_EVENT = 37
|
|
2968
3030
|
GET_REGISTER = 38
|
|
2969
3031
|
SET_REGISTER = 39
|
|
2970
|
-
LIST_REGISTERS = 40
|
|
3032
|
+
LIST_REGISTERS = 40
|
|
2971
3033
|
|
|
2972
3034
|
|
|
2973
3035
|
MMF_DATA_HEADER_SIZE = 24
|
|
@@ -698,6 +698,11 @@ class VirtualMask:
|
|
|
698
698
|
|
|
699
699
|
def __getitem__(self, item):
|
|
700
700
|
full_img = self.client.get_virtual_mask(self.index)
|
|
701
|
+
if full_img is None:
|
|
702
|
+
raise RuntimeError(
|
|
703
|
+
f"get_virtual_mask({self.index}) returned None — "
|
|
704
|
+
"the server may have dropped the connection or encountered an error."
|
|
705
|
+
)
|
|
701
706
|
return full_img[item]
|
|
702
707
|
|
|
703
708
|
def __setitem__(self, key, value):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import socket
|
|
1
2
|
import time
|
|
2
3
|
import warnings
|
|
3
4
|
|
|
@@ -19,15 +20,15 @@ def add_parameter(ack, value):
|
|
|
19
20
|
Add a parameter to a protobuffer
|
|
20
21
|
"""
|
|
21
22
|
param = ack.parameter.add()
|
|
22
|
-
if isinstance(value,
|
|
23
|
+
if isinstance(value, bool): # must be before int — bool is a subclass of int
|
|
24
|
+
param.type = pb.AnyParameter.P_BOOL
|
|
25
|
+
param.p_bool = value
|
|
26
|
+
elif isinstance(value, str):
|
|
23
27
|
param.type = pb.AnyParameter.P_STRING
|
|
24
28
|
param.p_string = value
|
|
25
29
|
elif isinstance(value, int):
|
|
26
30
|
param.type = pb.AnyParameter.P_INT
|
|
27
31
|
param.p_int = value
|
|
28
|
-
elif isinstance(value, bool):
|
|
29
|
-
param.type = pb.AnyParameter.P_BOOL
|
|
30
|
-
param.p_bool = value
|
|
31
32
|
elif isinstance(value, float):
|
|
32
33
|
param.type = pb.AnyParameter.P_FLOAT
|
|
33
34
|
param.p_float = value
|
|
@@ -346,9 +347,14 @@ class FakeServer:
|
|
|
346
347
|
if total_len < total_bytes:
|
|
347
348
|
while total_len < total_bytes:
|
|
348
349
|
try:
|
|
349
|
-
|
|
350
|
+
chunk = self.socket.recv(total_bytes - total_len)
|
|
351
|
+
if not chunk:
|
|
352
|
+
raise ConnectionResetError(
|
|
353
|
+
"Connection closed while reading virtual mask"
|
|
354
|
+
)
|
|
355
|
+
buffer += chunk
|
|
350
356
|
total_len = len(buffer)
|
|
351
|
-
except
|
|
357
|
+
except socket.timeout:
|
|
352
358
|
raise ValueError("Socket timed out")
|
|
353
359
|
buffer = buffer
|
|
354
360
|
mask = np.frombuffer(buffer, dtype=np.int8).reshape((w, h))
|
|
@@ -714,6 +720,9 @@ class FakeServer:
|
|
|
714
720
|
if histo_min == 0 and histo_max == 0:
|
|
715
721
|
histo_min = np.min(image)
|
|
716
722
|
histo_max = np.max(image)
|
|
723
|
+
# np.histogram requires max > min; pad when image is uniform
|
|
724
|
+
if histo_min == histo_max:
|
|
725
|
+
histo_max = histo_min + 1
|
|
717
726
|
image_hist, bins = np.histogram(
|
|
718
727
|
image.flatten(), bins=histo_bins, range=(histo_min, histo_max)
|
|
719
728
|
)
|
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import traceback
|
|
2
3
|
|
|
3
4
|
from deapi.simulated_server.fake_server import FakeServer
|
|
4
5
|
import socket
|
|
5
6
|
import struct
|
|
6
7
|
from deapi.buffer_protocols import pb
|
|
7
|
-
import sys
|
|
8
8
|
import argparse
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def _recv_exact(conn, n):
|
|
12
|
+
"""Read exactly *n* bytes from *conn*, looping over partial reads.
|
|
13
|
+
|
|
14
|
+
Returns the complete byte string, or raises ``ConnectionResetError`` if
|
|
15
|
+
the peer closes the connection before all bytes arrive. This is necessary
|
|
16
|
+
because TCP is a stream protocol: a single ``recv(n)`` call may legally
|
|
17
|
+
return anywhere from 1 to n bytes, and on macOS the loopback interface
|
|
18
|
+
fragments packets far more aggressively than Linux does.
|
|
19
|
+
"""
|
|
20
|
+
buf = b""
|
|
21
|
+
while len(buf) < n:
|
|
22
|
+
chunk = conn.recv(n - len(buf))
|
|
23
|
+
if not chunk:
|
|
24
|
+
raise ConnectionResetError(
|
|
25
|
+
f"Connection closed after {len(buf)} of {n} expected bytes"
|
|
26
|
+
)
|
|
27
|
+
buf += chunk
|
|
28
|
+
return buf
|
|
29
|
+
|
|
30
|
+
|
|
11
31
|
# Defining main function
|
|
12
32
|
def main(port=13240):
|
|
13
33
|
parser = argparse.ArgumentParser()
|
|
14
34
|
parser.add_argument("--port", type=int, help="Port to listen on")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
port = args.port
|
|
19
|
-
except:
|
|
20
|
-
pass
|
|
35
|
+
args, _ = parser.parse_known_args()
|
|
36
|
+
if args.port:
|
|
37
|
+
port = args.port
|
|
21
38
|
|
|
22
39
|
HOST = "127.0.0.1" # Standard loopback interface address (localhost)
|
|
23
40
|
PORT = port # Port to listen on (non-privileged ports are > 1023)
|
|
24
41
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
|
|
42
|
+
# Allow the port to be reused immediately after the process exits
|
|
43
|
+
# (avoids "Address already in use" / TIME_WAIT failures between test runs)
|
|
44
|
+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
25
45
|
server_socket.bind((HOST, PORT))
|
|
26
46
|
server_socket.listen()
|
|
27
|
-
sys.
|
|
28
|
-
sys.
|
|
47
|
+
sys.stdout.write("started .... \n\n")
|
|
48
|
+
sys.stdout.flush()
|
|
29
49
|
sys.stderr.write(
|
|
30
50
|
"Waiting for a Connection to: \n"
|
|
31
51
|
f" Host: {HOST}\n"
|
|
@@ -34,13 +54,18 @@ def main(port=13240):
|
|
|
34
54
|
sys.stderr.flush()
|
|
35
55
|
while True:
|
|
36
56
|
conn, addr = server_socket.accept() # What waits for a connection
|
|
57
|
+
# Guard against a stalled client leaving _recv_exact blocked forever.
|
|
58
|
+
# 120 s is generous enough to survive debugger pauses and slow CI
|
|
59
|
+
# runners, while still breaking the partial-send deadlock if sendall()
|
|
60
|
+
# somehow delivers a short write.
|
|
61
|
+
conn.settimeout(120)
|
|
37
62
|
server = FakeServer(socket=conn)
|
|
38
63
|
connected = True
|
|
39
64
|
while connected:
|
|
40
65
|
try:
|
|
41
|
-
totallen = conn
|
|
66
|
+
totallen = _recv_exact(conn, 4)
|
|
42
67
|
totallenRecv = struct.unpack("I", totallen)[0]
|
|
43
|
-
message = conn
|
|
68
|
+
message = _recv_exact(conn, totallenRecv)
|
|
44
69
|
message_packet = pb.DEPacket()
|
|
45
70
|
message_packet.ParseFromString(message)
|
|
46
71
|
response = server._respond_to_command(message_packet)
|
|
@@ -50,10 +75,11 @@ def main(port=13240):
|
|
|
50
75
|
packet = (
|
|
51
76
|
struct.pack("I", r.ByteSize()) + r.SerializeToString()
|
|
52
77
|
)
|
|
53
|
-
conn.
|
|
78
|
+
conn.sendall(packet)
|
|
54
79
|
else:
|
|
55
80
|
conn.sendall(r)
|
|
56
|
-
except:
|
|
81
|
+
except Exception:
|
|
82
|
+
traceback.print_exc(file=sys.stderr)
|
|
57
83
|
connected = False
|
|
58
84
|
|
|
59
85
|
|
|
@@ -22,6 +22,37 @@ def close_port(port):
|
|
|
22
22
|
process.terminate()
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def wait_for_idle(client, timeout: float = 30, interval: float = 0.1):
|
|
26
|
+
"""Poll ``client.acquiring`` until it is False or *timeout* seconds elapse.
|
|
27
|
+
|
|
28
|
+
Replaces bare ``while client.acquiring: time.sleep(N)`` loops in tests so
|
|
29
|
+
that a stalled socket or an unexpectedly long acquisition does not cause the
|
|
30
|
+
test suite to hang indefinitely.
|
|
31
|
+
|
|
32
|
+
The default *timeout* of 30 s is sized for fake-server tests where all
|
|
33
|
+
acquisitions complete in well under a second (FPS=1000, small scan grids).
|
|
34
|
+
Pass a larger value for tests that run against real hardware with long
|
|
35
|
+
exposures, e.g. ``wait_for_idle(client, timeout=300)``.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
client:
|
|
40
|
+
A connected :class:`deapi.Client` instance.
|
|
41
|
+
timeout:
|
|
42
|
+
Maximum number of seconds to wait before failing the test.
|
|
43
|
+
interval:
|
|
44
|
+
Polling interval in seconds.
|
|
45
|
+
"""
|
|
46
|
+
deadline = time.monotonic() + timeout
|
|
47
|
+
while client.acquiring:
|
|
48
|
+
if time.monotonic() > deadline:
|
|
49
|
+
pytest.fail(
|
|
50
|
+
f"Camera still acquiring after {timeout:.0f} s — "
|
|
51
|
+
"possible socket deadlock or FakeServer state error"
|
|
52
|
+
)
|
|
53
|
+
time.sleep(interval)
|
|
54
|
+
|
|
55
|
+
|
|
25
56
|
# Modifying pytest run options
|
|
26
57
|
def pytest_addoption(parser):
|
|
27
58
|
parser.addoption(
|
|
@@ -111,7 +142,7 @@ def server(xprocess, request):
|
|
|
111
142
|
curdir = pathlib.Path(__file__).parent.parent
|
|
112
143
|
|
|
113
144
|
class Starter(ProcessStarter):
|
|
114
|
-
timeout =
|
|
145
|
+
timeout = 60
|
|
115
146
|
pattern = "started"
|
|
116
147
|
args = [
|
|
117
148
|
sys.executable,
|
|
@@ -163,9 +163,18 @@ def statisticsValueCheck(imageProcessingMode, correctionMode):
|
|
|
163
163
|
print(stats.eppix)
|
|
164
164
|
print(stats.epa2)
|
|
165
165
|
# precision issue, we will compare the float number with a tolerance value. The tolerance value is set to 10**-numPrecision * frameCount for e-/pix, 10**-numPrecision * fps for e-/pix/s and 10**-numPrecision * fps * numPhysicalPixels for e-/s.
|
|
166
|
-
ret &= math.isclose(
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
ret &= math.isclose(
|
|
167
|
+
stats.eppix, attributes.eppix, rel_tol=0, abs_tol=10**-numPrecision * frameCount
|
|
168
|
+
)
|
|
169
|
+
ret &= math.isclose(
|
|
170
|
+
stats.eppixps, attributes.eppixps, rel_tol=0, abs_tol=10**-numPrecision * fps
|
|
171
|
+
)
|
|
172
|
+
ret &= math.isclose(
|
|
173
|
+
stats.eps,
|
|
174
|
+
attributes.eps,
|
|
175
|
+
rel_tol=0,
|
|
176
|
+
abs_tol=10**-numPrecision * fps * numPhysicalPixels,
|
|
177
|
+
)
|
|
169
178
|
|
|
170
179
|
ret &= func.compare2FloatValue(stats.epa2, attributes.epa2, numPrecision, "e-/a^2")
|
|
171
180
|
return ret
|
|
@@ -212,10 +221,16 @@ def compareBin1Bin2(imageProcessingMode, correctionMode, swBinningFactor):
|
|
|
212
221
|
swBinY,
|
|
213
222
|
)
|
|
214
223
|
|
|
215
|
-
ret &= func.compare2FloatValue(
|
|
216
|
-
|
|
224
|
+
ret &= func.compare2FloatValue(
|
|
225
|
+
statsBin1.eppix, statsBin2.eppix, numPrecision, "e-/pix"
|
|
226
|
+
)
|
|
227
|
+
ret &= func.compare2FloatValue(
|
|
228
|
+
statsBin1.eppixps, statsBin2.eppixps, numPrecision, "e-/pix/s"
|
|
229
|
+
)
|
|
217
230
|
ret &= func.compare2FloatValue(statsBin1.eps, statsBin2.eps, numPrecision, "e-/s")
|
|
218
|
-
ret &= func.compare2FloatValue(
|
|
231
|
+
ret &= func.compare2FloatValue(
|
|
232
|
+
statsBin1.epa2, statsBin2.epa2, numPrecision, "e-/a^2"
|
|
233
|
+
)
|
|
219
234
|
return ret
|
|
220
235
|
|
|
221
236
|
|
|
@@ -125,5 +125,6 @@ class TestVirtualMasks08:
|
|
|
125
125
|
|
|
126
126
|
deClient.SetVirtualMask(maskID, 1024, 1024, mask)
|
|
127
127
|
# Generate and check the first image
|
|
128
|
-
mask
|
|
128
|
+
# virtual_masks uses 0-based indexing where index 0 maps to mask ID 1
|
|
129
|
+
mask = deClient.virtual_masks[0][:]
|
|
129
130
|
assert mask.shape == (1024, 1024)
|
|
@@ -10,6 +10,7 @@ from deapi.data_types import (
|
|
|
10
10
|
MovieBufferStatus,
|
|
11
11
|
ContrastStretchType,
|
|
12
12
|
)
|
|
13
|
+
from deapi.tests.conftest import wait_for_idle
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class TestClient:
|
|
@@ -57,8 +58,7 @@ class TestClient:
|
|
|
57
58
|
client.scan(size_x=10, size_y=10, enable="On")
|
|
58
59
|
client.start_acquisition(1)
|
|
59
60
|
assert client.acquiring
|
|
60
|
-
|
|
61
|
-
time.sleep(1)
|
|
61
|
+
wait_for_idle(client)
|
|
62
62
|
assert not client.acquiring
|
|
63
63
|
|
|
64
64
|
def test_start_acquisition_scan_disabled(self, client):
|
|
@@ -66,8 +66,7 @@ class TestClient:
|
|
|
66
66
|
client.scan(enable="Off")
|
|
67
67
|
client.start_acquisition(10)
|
|
68
68
|
assert client.acquiring
|
|
69
|
-
|
|
70
|
-
time.sleep(1)
|
|
69
|
+
wait_for_idle(client)
|
|
71
70
|
assert not client.acquiring
|
|
72
71
|
|
|
73
72
|
def test_get_result(self, client):
|
|
@@ -80,8 +79,7 @@ class TestClient:
|
|
|
80
79
|
assert client["Hardware ROI Offset X"] == 0
|
|
81
80
|
assert client["Hardware ROI Offset Y"] == 0
|
|
82
81
|
client.start_acquisition(1)
|
|
83
|
-
|
|
84
|
-
time.sleep(1)
|
|
82
|
+
wait_for_idle(client)
|
|
85
83
|
result = client.get_result()
|
|
86
84
|
assert isinstance(result, tuple)
|
|
87
85
|
assert len(result) == 4
|
|
@@ -93,8 +91,7 @@ class TestClient:
|
|
|
93
91
|
client["Frames Per Second"] = 1000
|
|
94
92
|
client.scan(size_x=10, size_y=10, enable="On")
|
|
95
93
|
client.start_acquisition(1)
|
|
96
|
-
|
|
97
|
-
time.sleep(1)
|
|
94
|
+
wait_for_idle(client)
|
|
98
95
|
result = client.get_result("singleframe_integrated")
|
|
99
96
|
assert isinstance(result[3], Histogram)
|
|
100
97
|
result[3].plot()
|
|
@@ -107,8 +104,7 @@ class TestClient:
|
|
|
107
104
|
assert isinstance(result, tuple)
|
|
108
105
|
assert len(result) == 4
|
|
109
106
|
assert result[0].shape == (1024, 1024)
|
|
110
|
-
|
|
111
|
-
time.sleep(1)
|
|
107
|
+
wait_for_idle(client)
|
|
112
108
|
|
|
113
109
|
def test_binning_linked_parameters(self, client):
|
|
114
110
|
|
|
@@ -123,8 +119,7 @@ class TestClient:
|
|
|
123
119
|
client["Hardware Binning X"] = binx
|
|
124
120
|
assert client["Hardware Binning X"] == binx
|
|
125
121
|
client.start_acquisition(1)
|
|
126
|
-
|
|
127
|
-
time.sleep(1)
|
|
122
|
+
wait_for_idle(client)
|
|
128
123
|
result = client.get_result("singleframe_integrated")
|
|
129
124
|
assert result[0].shape[1] == 1024 // binx
|
|
130
125
|
|
|
@@ -173,8 +168,7 @@ class TestClient:
|
|
|
173
168
|
assert client["Scan - Virtual Detector 3 Calculation"] == "Difference"
|
|
174
169
|
np.testing.assert_allclose(client.virtual_masks[2][::2], 2)
|
|
175
170
|
client.start_acquisition(1)
|
|
176
|
-
|
|
177
|
-
time.sleep(1)
|
|
171
|
+
wait_for_idle(client)
|
|
178
172
|
result = client.get_result("virtual_image3")
|
|
179
173
|
assert result is not None
|
|
180
174
|
assert result[0].shape == (10, 8)
|
|
@@ -247,7 +241,7 @@ class TestClient:
|
|
|
247
241
|
def test_stream_data(self, client):
|
|
248
242
|
client["Frames Per Second"] = 5
|
|
249
243
|
client.scan(size_x=10, size_y=10, enable="On")
|
|
250
|
-
client.start_acquisition(1,
|
|
244
|
+
client.start_acquisition(1, request_movie_buffer=True)
|
|
251
245
|
numberFrames = 0
|
|
252
246
|
index = 0
|
|
253
247
|
status = MovieBufferStatus.OK
|
|
@@ -309,8 +303,7 @@ class TestClient:
|
|
|
309
303
|
assert is_set
|
|
310
304
|
assert client["Scan - Points"] == np.sum(mask) * 2
|
|
311
305
|
client.start_acquisition(1)
|
|
312
|
-
|
|
313
|
-
time.sleep(1)
|
|
306
|
+
wait_for_idle(client)
|
|
314
307
|
result = client.get_result("virtual_image1")
|
|
315
308
|
assert result[0].shape == (12, 12)
|
|
316
309
|
client["Scan - Type"] = "Raster" # clean up
|
|
@@ -352,28 +345,21 @@ class TestClient:
|
|
|
352
345
|
client.take_dark_reference(frame_rate=10)
|
|
353
346
|
client["Image Processing - Flatfield Correction"] = "Dark"
|
|
354
347
|
client.start_acquisition(1)
|
|
355
|
-
|
|
356
|
-
while client.acquiring:
|
|
357
|
-
time.sleep(1)
|
|
348
|
+
wait_for_idle(client)
|
|
358
349
|
image = client.get_result()[0]
|
|
359
350
|
np.testing.assert_array_equal(image, 0)
|
|
360
351
|
|
|
361
|
-
# Now flip the dark reference
|
|
362
352
|
client["Image Processing - Flip Horizontally"] = "On"
|
|
363
353
|
client["Exposure Time (seconds)"] = 1
|
|
364
354
|
client.start_acquisition(1)
|
|
365
|
-
|
|
366
|
-
time.sleep(1)
|
|
355
|
+
wait_for_idle(client)
|
|
367
356
|
image = client.get_result()[0]
|
|
368
357
|
np.testing.assert_array_equal(image, 0)
|
|
369
358
|
client["Image Processing - Flip Horizontally"] = "Off"
|
|
370
359
|
|
|
371
|
-
# test bin by a factor of 2
|
|
372
|
-
|
|
373
360
|
client["Binning X"] = 2
|
|
374
361
|
client["Binning Y"] = 2
|
|
375
362
|
client.start_acquisition(1)
|
|
376
|
-
|
|
377
|
-
time.sleep(1)
|
|
363
|
+
wait_for_idle(client)
|
|
378
364
|
image = client.get_result()[0]
|
|
379
365
|
np.testing.assert_array_equal(image, 0)
|
|
@@ -28,7 +28,8 @@ class TestFakeServer:
|
|
|
28
28
|
|
|
29
29
|
def test_set_virtual_image_calculation(self, fake_server):
|
|
30
30
|
assert fake_server["Scan - Virtual Detector 1 Calculation"] == "Sum"
|
|
31
|
-
|
|
31
|
+
with pytest.warns(UserWarning, match="not in options"):
|
|
32
|
+
fake_server["Scan - Virtual Detector 1 Calculation"] = "Susd"
|
|
32
33
|
assert fake_server["Scan - Virtual Detector 1 Calculation"] == "Sum"
|
|
33
34
|
|
|
34
35
|
def test_server_software_version(self, fake_server):
|
|
@@ -22,9 +22,9 @@ class TestSavingScans:
|
|
|
22
22
|
|
|
23
23
|
i = 16
|
|
24
24
|
num_pos = i * i
|
|
25
|
-
if not os.path.exists("D:\Temp"):
|
|
26
|
-
os.mkdir("D:\Temp")
|
|
27
|
-
temp_dir = "D:\Temp"
|
|
25
|
+
if not os.path.exists(r"D:\Temp"):
|
|
26
|
+
os.mkdir(r"D:\Temp")
|
|
27
|
+
temp_dir = r"D:\Temp"
|
|
28
28
|
|
|
29
29
|
if scan_type == "Serpentine":
|
|
30
30
|
frame_num_order = np.arange(num_pos)
|
|
@@ -91,9 +91,9 @@ class TestSavingVirtual:
|
|
|
91
91
|
def test_save_scans(self, client, scan_type, buffer):
|
|
92
92
|
i = 8
|
|
93
93
|
num_pos = i * i
|
|
94
|
-
if not os.path.exists("D:\Temp"):
|
|
95
|
-
os.mkdir("D:\Temp")
|
|
96
|
-
temp_dir = "D:\Temp"
|
|
94
|
+
if not os.path.exists(r"D:\Temp"):
|
|
95
|
+
os.mkdir(r"D:\Temp")
|
|
96
|
+
temp_dir = r"D:\Temp"
|
|
97
97
|
|
|
98
98
|
if scan_type == "Serpentine":
|
|
99
99
|
frame_num_order = np.arange(num_pos)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|