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.
Files changed (109) hide show
  1. {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/build.yaml +2 -2
  2. {deapi-5.3b4 → deapi-5.3b5}/PKG-INFO +1 -1
  3. {deapi-5.3b4 → deapi-5.3b5}/deapi/client.py +104 -42
  4. {deapi-5.3b4 → deapi-5.3b5}/deapi/data_types.py +5 -0
  5. {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/fake_server.py +15 -6
  6. {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/initialize_server.py +39 -13
  7. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/conftest.py +32 -1
  8. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/10_imageStatistics.py +21 -6
  9. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/test_legacy.py +2 -1
  10. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_client.py +13 -27
  11. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_fake_server/test_server.py +2 -1
  12. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_scan_pattern_saving.py +6 -6
  13. {deapi-5.3b4 → deapi-5.3b5}/deapi/version.py +0 -1
  14. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/PKG-INFO +1 -1
  15. {deapi-5.3b4 → deapi-5.3b5}/pyproject.toml +1 -1
  16. {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/documentation.yaml +0 -0
  17. {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/publish.yaml +0 -0
  18. {deapi-5.3b4 → deapi-5.3b5}/.github/workflows/test-publish.yaml +0 -0
  19. {deapi-5.3b4 → deapi-5.3b5}/.pre-commit-config.yaml +0 -0
  20. {deapi-5.3b4 → deapi-5.3b5}/CHANGES.rst +0 -0
  21. {deapi-5.3b4 → deapi-5.3b5}/LICENSE +0 -0
  22. {deapi-5.3b4 → deapi-5.3b5}/README.md +0 -0
  23. {deapi-5.3b4 → deapi-5.3b5}/deapi/Guide to use mamba.pdf +0 -0
  24. {deapi-5.3b4 → deapi-5.3b5}/deapi/__init__.py +0 -0
  25. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/__init__.py +0 -0
  26. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_2_3_0.py +0 -0
  27. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_11_4.py +0 -0
  28. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_19_3.py +0 -0
  29. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_23_3.py +0 -0
  30. {deapi-5.3b4 → deapi-5.3b5}/deapi/buffer_protocols/pb_3_6_1.py +0 -0
  31. {deapi-5.3b4 → deapi-5.3b5}/deapi/conf.py +0 -0
  32. {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/__init__.py +0 -0
  33. {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/base_fake_data.py +0 -0
  34. {deapi-5.3b4 → deapi-5.3b5}/deapi/fake_data/grains.py +0 -0
  35. {deapi-5.3b4 → deapi-5.3b5}/deapi/index.rst +0 -0
  36. {deapi-5.3b4 → deapi-5.3b5}/deapi/prop_dump.json +0 -0
  37. {deapi-5.3b4 → deapi-5.3b5}/deapi/python_3_instruction.txt +0 -0
  38. {deapi-5.3b4 → deapi-5.3b5}/deapi/release_notes.txt +0 -0
  39. {deapi-5.3b4 → deapi-5.3b5}/deapi/service/de_service.py +0 -0
  40. {deapi-5.3b4 → deapi-5.3b5}/deapi/simulated_server/__init__.py +0 -0
  41. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/__init__.py +0 -0
  42. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/01_fps.py +0 -0
  43. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/02_hwRoisize.py +0 -0
  44. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/03_hwBinning.py +0 -0
  45. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/04_swBinning.py +0 -0
  46. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/05_swhwBinning.py +0 -0
  47. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/06_patternPixel.py +0 -0
  48. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/07_reference.py +0 -0
  49. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/08_virtmask.py +0 -0
  50. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/09_scanRoi.py +0 -0
  51. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/__init__.py +0 -0
  52. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/func.py +0 -0
  53. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/original_tests/propertyName.py +0 -0
  54. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/__init__.py +0 -0
  55. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/test_internal_file_saving.py +0 -0
  56. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/speed_tests/test_movie_buffer_transfer.py +0 -0
  57. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_binning_rois/__init__.py +0 -0
  58. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_binning_rois/test_rois.py +0 -0
  59. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_examples.py +0 -0
  60. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_fake_server/__init__.py +0 -0
  61. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/__init__.py +0 -0
  62. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_file_loading_libertem.py +0 -0
  63. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_file_loading_rsciio.py +0 -0
  64. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_file_saving/test_h5ebsd.py +0 -0
  65. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_insitu/__init__.py +0 -0
  66. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_insitu/test_start_stop.py +0 -0
  67. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_utils/__init__.py +0 -0
  68. {deapi-5.3b4 → deapi-5.3b5}/deapi/tests/test_utils/test_utils.py +0 -0
  69. {deapi-5.3b4 → deapi-5.3b5}/deapi/utils.py +0 -0
  70. {deapi-5.3b4 → deapi-5.3b5}/deapi/wrappers.py +0 -0
  71. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/SOURCES.txt +0 -0
  72. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/dependency_links.txt +0 -0
  73. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/entry_points.txt +0 -0
  74. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/requires.txt +0 -0
  75. {deapi-5.3b4 → deapi-5.3b5}/deapi.egg-info/top_level.txt +0 -0
  76. {deapi-5.3b4 → deapi-5.3b5}/doc/Makefile +0 -0
  77. {deapi-5.3b4 → deapi-5.3b5}/doc/_static/de_api_icon.svg +0 -0
  78. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/autosummary/base.rst +0 -0
  79. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-attribute-template.rst +0 -0
  80. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-class-template.rst +0 -0
  81. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-function-template.rst +0 -0
  82. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custom-module-template.rst +0 -0
  83. {deapi-5.3b4 → deapi-5.3b5}/doc/_templates/custrom-method-template.rst +0 -0
  84. {deapi-5.3b4 → deapi-5.3b5}/doc/changes.rst +0 -0
  85. {deapi-5.3b4 → deapi-5.3b5}/doc/conf.py +0 -0
  86. {deapi-5.3b4 → deapi-5.3b5}/doc/help/dev_guide.rst +0 -0
  87. {deapi-5.3b4 → deapi-5.3b5}/doc/help/help.rst +0 -0
  88. {deapi-5.3b4 → deapi-5.3b5}/doc/help/index.rst +0 -0
  89. {deapi-5.3b4 → deapi-5.3b5}/doc/help/pyDEServer.rst +0 -0
  90. {deapi-5.3b4 → deapi-5.3b5}/doc/index.rst +0 -0
  91. {deapi-5.3b4 → deapi-5.3b5}/doc/intro.rst +0 -0
  92. {deapi-5.3b4 → deapi-5.3b5}/doc/make.bat +0 -0
  93. {deapi-5.3b4 → deapi-5.3b5}/doc/reference/index.rst +0 -0
  94. {deapi-5.3b4 → deapi-5.3b5}/examples/README.rst +0 -0
  95. {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/README.rst +0 -0
  96. {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/bright_spot_intensity.py +0 -0
  97. {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/taking_an_image_every_minute.py +0 -0
  98. {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/viewing_the_sensor.py +0 -0
  99. {deapi-5.3b4 → deapi-5.3b5}/examples/live_imaging/viewing_the_sensor_tem.py +0 -0
  100. {deapi-5.3b4 → deapi-5.3b5}/examples/references/README.rst +0 -0
  101. {deapi-5.3b4 → deapi-5.3b5}/examples/setting_parameters/README.rst +0 -0
  102. {deapi-5.3b4 → deapi-5.3b5}/examples/setting_parameters/setting_up_stem.py +0 -0
  103. {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/README.rst +0 -0
  104. {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/setting_virtual_masks.py +0 -0
  105. {deapi-5.3b4 → deapi-5.3b5}/examples/virtual_imaging/vdf_vbf.py +0 -0
  106. {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/README.rst +0 -0
  107. {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/creating_a_simple_dashboard.py +0 -0
  108. {deapi-5.3b4 → deapi-5.3b5}/examples/visualization/using_get_result.py +0 -0
  109. {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, macos-latest]
20
- python-version: ["3.11", "3.12"]
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deapi
3
- Version: 5.3b4
3
+ Version: 5.3b5
4
4
  Summary: API for DE Server
5
5
  Author: Direct Electron
6
6
  Project-URL: Bug Reports, https://github.com/directelectron/deapi/issues
@@ -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("Hardware Binning X", 2 if bin_x >= 2 and use_hw else 1)
1004
- retval &= self.SetProperty("Hardware Binning Y", 2 if bin_y >= 2 and use_hw else 1)
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(f"reading movie buffer {totalBytes}", )
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 (ADU/pix)"] = (
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(f"The number of electrons per pixel per second (eppixps): {num_el:.2f}")
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
- np.ceil(target_electrons_per_pixel / (exposure_time * num_el))
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(None, 0, 999, semaphoreName)
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
- res = self.socket.send(packet)
2687
- # packet.PrintDebugString()
2688
- # log.debug("sent result = %d\n", res)
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
- raise e("Error sending %s\n", command)
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
- return self.__ReceiveResponseForCommand(command)
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
- buffer += sock.recv(packet_size)
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 e("Error receiving %d bytes: %s", bytes, e)
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, str):
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
- buffer += self.socket.recv(total_bytes)
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 self.socket.timeout:
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
- try:
16
- args = parser.parse_args()
17
- if args.port:
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.stderr.write("started .... \n\n")
28
- sys.stderr.flush()
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.recv(4)
66
+ totallen = _recv_exact(conn, 4)
42
67
  totallenRecv = struct.unpack("I", totallen)[0]
43
- message = conn.recv(totallenRecv)
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.send(packet)
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 = 10
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(stats.eppix, attributes.eppix, rel_tol=0, abs_tol=10**-numPrecision * frameCount)
167
- ret &= math.isclose(stats.eppixps, attributes.eppixps, rel_tol=0, abs_tol=10**-numPrecision * fps)
168
- ret &= math.isclose(stats.eps, attributes.eps, rel_tol=0, abs_tol=10**-numPrecision * fps * numPhysicalPixels)
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(statsBin1.eppix, statsBin2.eppix, numPrecision, "e-/pix")
216
- ret &= func.compare2FloatValue(statsBin1.eppixps, statsBin2.eppixps, numPrecision, "e-/pix/s")
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(statsBin1.epa2, statsBin2.epa2, numPrecision, "e-/a^2")
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 = deClient.virtual_masks[1][:]
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
- while client.acquiring:
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
- while client.acquiring:
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
- while client.acquiring:
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
- while client.acquiring:
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
- while client.acquiring:
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
- while client.acquiring:
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
- while client.acquiring:
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, requestMovieBuffer=True)
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
- while client.acquiring:
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
- # assert that the dark reference corrects the image to zero...
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
- while client.acquiring:
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
- while client.acquiring:
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
- fake_server["Scan - Virtual Detector 1 Calculation"] = "Susd"
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)
@@ -1,4 +1,3 @@
1
1
  version = "5.3.0"
2
2
  versionInfo = list(map(int, version.split(".")))
3
3
  commandVersion = (versionInfo[0] - 4) * 10 + versionInfo[1] + 2
4
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deapi
3
- Version: 5.3b4
3
+ Version: 5.3b5
4
4
  Summary: API for DE Server
5
5
  Author: Direct Electron
6
6
  Project-URL: Bug Reports, https://github.com/directelectron/deapi/issues
@@ -25,7 +25,7 @@ dependencies = [
25
25
  "sympy",
26
26
  ]
27
27
  description = "API for DE Server"
28
- version = "5.3b4"
28
+ version = "5.3b5"
29
29
  keywords = [
30
30
  "EELS",
31
31
  "STEM",
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