deapi 5.3b6__tar.gz → 5.3.1__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 (124) hide show
  1. {deapi-5.3b6 → deapi-5.3.1}/.github/workflows/build.yaml +2 -2
  2. deapi-5.3.1/.github/workflows/prepare_release.yml +192 -0
  3. {deapi-5.3b6 → deapi-5.3.1}/.pre-commit-config.yaml +1 -1
  4. deapi-5.3.1/CHANGES.rst +92 -0
  5. {deapi-5.3b6 → deapi-5.3.1}/PKG-INFO +6 -5
  6. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/__init__.py +1 -2
  7. deapi-5.3.1/deapi/buffer_protocols/pb_6_33_4.py +47 -0
  8. {deapi-5.3b6 → deapi-5.3.1}/deapi/client.py +83 -51
  9. {deapi-5.3b6 → deapi-5.3.1}/deapi/data_types.py +2 -2
  10. {deapi-5.3b6 → deapi-5.3.1}/deapi/simulated_server/fake_server.py +20 -2
  11. {deapi-5.3b6 → deapi-5.3.1}/deapi/simulated_server/initialize_server.py +33 -1
  12. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/conftest.py +16 -2
  13. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_client.py +17 -7
  14. deapi-5.3.1/deapi/tests/test_fake_server/test_partial_sends.py +92 -0
  15. deapi-5.3.1/deapi/tests/test_fake_server/test_twin_server.py +96 -0
  16. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_scanning/test_virtual_image_buffers.py +8 -6
  17. {deapi-5.3b6 → deapi-5.3.1}/deapi/version.py +3 -2
  18. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/PKG-INFO +6 -5
  19. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/SOURCES.txt +10 -1
  20. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/requires.txt +6 -1
  21. deapi-5.3.1/doc/_static/digital_twin_holes.png +0 -0
  22. {deapi-5.3b6 → deapi-5.3.1}/doc/help/pyDEServer.rst +24 -1
  23. deapi-5.3.1/examples/digital_twin/README.rst +6 -0
  24. deapi-5.3.1/examples/digital_twin/imaging_holes_with_the_digital_twin_sgskip.py +147 -0
  25. {deapi-5.3b6 → deapi-5.3.1}/pyproject.toml +55 -5
  26. deapi-5.3.1/upcoming_changes/.gitkeep +0 -0
  27. deapi-5.3.1/upcoming_changes/README.rst +92 -0
  28. deapi-5.3b6/CHANGES.rst +0 -48
  29. {deapi-5.3b6 → deapi-5.3.1}/.github/workflows/documentation.yaml +0 -0
  30. {deapi-5.3b6 → deapi-5.3.1}/.github/workflows/publish.yaml +0 -0
  31. {deapi-5.3b6 → deapi-5.3.1}/.github/workflows/test-publish.yaml +0 -0
  32. {deapi-5.3b6 → deapi-5.3.1}/LICENSE +0 -0
  33. {deapi-5.3b6 → deapi-5.3.1}/README.md +0 -0
  34. {deapi-5.3b6 → deapi-5.3.1}/deapi/Guide to use mamba.pdf +0 -0
  35. {deapi-5.3b6 → deapi-5.3.1}/deapi/__init__.py +0 -0
  36. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/pb_2_3_0.py +0 -0
  37. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/pb_3_11_4.py +0 -0
  38. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/pb_3_19_3.py +0 -0
  39. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/pb_3_23_3.py +0 -0
  40. {deapi-5.3b6 → deapi-5.3.1}/deapi/buffer_protocols/pb_3_6_1.py +0 -0
  41. {deapi-5.3b6 → deapi-5.3.1}/deapi/conf.py +0 -0
  42. {deapi-5.3b6 → deapi-5.3.1}/deapi/fake_data/__init__.py +0 -0
  43. {deapi-5.3b6 → deapi-5.3.1}/deapi/fake_data/base_fake_data.py +0 -0
  44. {deapi-5.3b6 → deapi-5.3.1}/deapi/fake_data/grains.py +0 -0
  45. {deapi-5.3b6 → deapi-5.3.1}/deapi/index.rst +0 -0
  46. {deapi-5.3b6 → deapi-5.3.1}/deapi/prop_dump.json +0 -0
  47. {deapi-5.3b6 → deapi-5.3.1}/deapi/python_3_instruction.txt +0 -0
  48. {deapi-5.3b6 → deapi-5.3.1}/deapi/release_notes.txt +0 -0
  49. {deapi-5.3b6 → deapi-5.3.1}/deapi/service/de_service.py +0 -0
  50. {deapi-5.3b6 → deapi-5.3.1}/deapi/simulated_server/__init__.py +0 -0
  51. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/__init__.py +0 -0
  52. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/01_fps.py +0 -0
  53. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/02_hwRoisize.py +0 -0
  54. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/03_hwBinning.py +0 -0
  55. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/04_swBinning.py +0 -0
  56. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/05_swhwBinning.py +0 -0
  57. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/06_patternPixel.py +0 -0
  58. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/07_reference.py +0 -0
  59. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/08_virtmask.py +0 -0
  60. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/09_scanRoi.py +0 -0
  61. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/10_imageStatistics.py +0 -0
  62. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/__init__.py +0 -0
  63. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/func.py +0 -0
  64. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/propertyName.py +0 -0
  65. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/original_tests/test_legacy.py +0 -0
  66. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/speed_tests/__init__.py +0 -0
  67. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/speed_tests/test_internal_file_saving.py +0 -0
  68. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/speed_tests/test_movie_buffer_transfer.py +0 -0
  69. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_binning_rois/__init__.py +0 -0
  70. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_binning_rois/test_rois.py +0 -0
  71. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_examples.py +0 -0
  72. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_fake_server/__init__.py +0 -0
  73. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_fake_server/test_server.py +0 -0
  74. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_file_saving/__init__.py +0 -0
  75. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_file_saving/test_file_loading_libertem.py +0 -0
  76. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_file_saving/test_file_loading_rsciio.py +0 -0
  77. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_file_saving/test_h5ebsd.py +0 -0
  78. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_file_saving/test_scan_pattern_saving.py +0 -0
  79. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_insitu/__init__.py +0 -0
  80. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_insitu/test_start_stop.py +0 -0
  81. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_scanning/__init__.py +0 -0
  82. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_scanning/test_continual_scanning.py +0 -0
  83. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_utils/__init__.py +0 -0
  84. {deapi-5.3b6 → deapi-5.3.1}/deapi/tests/test_utils/test_utils.py +0 -0
  85. {deapi-5.3b6 → deapi-5.3.1}/deapi/utils.py +0 -0
  86. {deapi-5.3b6 → deapi-5.3.1}/deapi/wrappers.py +0 -0
  87. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/dependency_links.txt +0 -0
  88. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/entry_points.txt +0 -0
  89. {deapi-5.3b6 → deapi-5.3.1}/deapi.egg-info/top_level.txt +0 -0
  90. {deapi-5.3b6 → deapi-5.3.1}/doc/Makefile +0 -0
  91. {deapi-5.3b6 → deapi-5.3.1}/doc/_static/ScanPatterns.png +0 -0
  92. {deapi-5.3b6 → deapi-5.3.1}/doc/_static/de_api_icon.svg +0 -0
  93. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/autosummary/base.rst +0 -0
  94. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/custom-attribute-template.rst +0 -0
  95. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/custom-class-template.rst +0 -0
  96. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/custom-function-template.rst +0 -0
  97. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/custom-module-template.rst +0 -0
  98. {deapi-5.3b6 → deapi-5.3.1}/doc/_templates/custrom-method-template.rst +0 -0
  99. {deapi-5.3b6 → deapi-5.3.1}/doc/changes.rst +0 -0
  100. {deapi-5.3b6 → deapi-5.3.1}/doc/conf.py +0 -0
  101. {deapi-5.3b6 → deapi-5.3.1}/doc/help/dev_guide.rst +0 -0
  102. {deapi-5.3b6 → deapi-5.3.1}/doc/help/help.rst +0 -0
  103. {deapi-5.3b6 → deapi-5.3.1}/doc/help/index.rst +0 -0
  104. {deapi-5.3b6 → deapi-5.3.1}/doc/index.rst +0 -0
  105. {deapi-5.3b6 → deapi-5.3.1}/doc/intro.rst +0 -0
  106. {deapi-5.3b6 → deapi-5.3.1}/doc/make.bat +0 -0
  107. {deapi-5.3b6 → deapi-5.3.1}/doc/reference/index.rst +0 -0
  108. {deapi-5.3b6 → deapi-5.3.1}/doc/reference/scan_design.rst +0 -0
  109. {deapi-5.3b6 → deapi-5.3.1}/examples/README.rst +0 -0
  110. {deapi-5.3b6 → deapi-5.3.1}/examples/live_imaging/README.rst +0 -0
  111. {deapi-5.3b6 → deapi-5.3.1}/examples/live_imaging/bright_spot_intensity.py +0 -0
  112. {deapi-5.3b6 → deapi-5.3.1}/examples/live_imaging/taking_an_image_every_minute.py +0 -0
  113. {deapi-5.3b6 → deapi-5.3.1}/examples/live_imaging/viewing_the_sensor.py +0 -0
  114. {deapi-5.3b6 → deapi-5.3.1}/examples/live_imaging/viewing_the_sensor_tem.py +0 -0
  115. {deapi-5.3b6 → deapi-5.3.1}/examples/references/README.rst +0 -0
  116. {deapi-5.3b6 → deapi-5.3.1}/examples/setting_parameters/README.rst +0 -0
  117. {deapi-5.3b6 → deapi-5.3.1}/examples/setting_parameters/setting_up_stem.py +0 -0
  118. {deapi-5.3b6 → deapi-5.3.1}/examples/virtual_imaging/README.rst +0 -0
  119. {deapi-5.3b6 → deapi-5.3.1}/examples/virtual_imaging/setting_virtual_masks.py +0 -0
  120. {deapi-5.3b6 → deapi-5.3.1}/examples/virtual_imaging/vdf_vbf.py +0 -0
  121. {deapi-5.3b6 → deapi-5.3.1}/examples/visualization/README.rst +0 -0
  122. {deapi-5.3b6 → deapi-5.3.1}/examples/visualization/creating_a_simple_dashboard.py +0 -0
  123. {deapi-5.3b6 → deapi-5.3.1}/examples/visualization/using_get_result.py +0 -0
  124. {deapi-5.3b6 → deapi-5.3.1}/setup.cfg +0 -0
@@ -20,8 +20,8 @@ jobs:
20
20
  python-version: ["3.12", "3.13"]
21
21
  include:
22
22
  - os: ubuntu-latest
23
- python-version: "3.11"
24
- DEPENDENCIES: protobuf==5.26.0
23
+ python-version: "3.10"
24
+ DEPENDENCIES: protobuf==6.33.5
25
25
  LABEL: -oldest
26
26
  steps:
27
27
  - uses: actions/checkout@v4
@@ -0,0 +1,192 @@
1
+ name: Prepare Release
2
+
3
+ # Run manually from the Actions tab.
4
+ # Opens a PR that bumps the version in pyproject.toml and builds CHANGES.rst from
5
+ # the towncrier fragments in upcoming_changes/. After merging it, create the GitHub
6
+ # Release by hand (tag v<version>); publishing the release triggers publish.yaml,
7
+ # which uploads to PyPI.
8
+ on:
9
+ workflow_dispatch:
10
+ inputs:
11
+ bump:
12
+ description: "Version component to bump"
13
+ required: true
14
+ type: choice
15
+ options:
16
+ - pre-release # increments the bN counter on the current base version (5.3b6 -> 5.3.0b7)
17
+ - finalize # drop the bN suffix: release the current beta's base as stable (5.3b6 -> 5.3.0)
18
+ - minor
19
+ - bugfix
20
+ - major
21
+ beta:
22
+ description: "Mark as beta pre-release (adds bN suffix). Ignored for 'pre-release' (always beta) and 'finalize' (always stable)."
23
+ required: false
24
+ type: boolean
25
+ default: false
26
+
27
+ permissions:
28
+ contents: write # push branch
29
+ pull-requests: write # open PR
30
+
31
+ jobs:
32
+ prepare:
33
+ name: Prepare release PR
34
+ runs-on: ubuntu-latest
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v4
39
+ with:
40
+ fetch-depth: 0
41
+
42
+ - name: Set up uv
43
+ uses: astral-sh/setup-uv@v5
44
+ with:
45
+ python-version: "3.13"
46
+ enable-cache: true
47
+
48
+ # ── Compute the new version ──────────────────────────────────────────
49
+ - name: Compute new version
50
+ id: version
51
+ env:
52
+ BUMP: ${{ inputs.bump }}
53
+ IS_BETA: ${{ inputs.beta }}
54
+ run: |
55
+ CURRENT=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
56
+ export CURRENT_VERSION="$CURRENT"
57
+
58
+ NEW_VERSION=$(python3 - <<'PYEOF'
59
+ import re, os
60
+
61
+ current = os.environ["CURRENT_VERSION"]
62
+ bump = os.environ["BUMP"]
63
+ is_beta = os.environ["IS_BETA"].lower() == "true"
64
+
65
+ # deapi's versions have had no patch component (5.3b6); read it as 0 and
66
+ # always write major.minor.patch from here on (5.3b6 == 5.3.0b6 in PEP 440).
67
+ m = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?(?:b(\d+))?$", current)
68
+ if not m:
69
+ raise SystemExit(f"cannot parse the current version {current!r}")
70
+ major = int(m.group(1))
71
+ minor = int(m.group(2))
72
+ patch = int(m.group(3) or 0)
73
+ beta_n = int(m.group(4)) if m.group(4) else None
74
+ on_beta = beta_n is not None
75
+
76
+ if bump == "finalize":
77
+ # Release the current beta's base as stable: just drop the bN
78
+ # suffix, keep major.minor.patch. e.g. 5.3.0b7 -> 5.3.0.
79
+ if not on_beta:
80
+ raise SystemExit(
81
+ f"'finalize' requires a beta base version, but current "
82
+ f"version {current!r} has no bN suffix. Use minor / bugfix "
83
+ f"/ major to start a new release instead."
84
+ )
85
+ is_beta = False
86
+ elif bump == "pre-release":
87
+ # Keep the same base; just walk the beta counter forward.
88
+ is_beta = True
89
+ beta_n = (beta_n or 0) + 1
90
+ elif on_beta:
91
+ # We are on a beta of the NEXT release (e.g. 5.3.0b7). The base
92
+ # major.minor.patch is that upcoming version, so minor/bugfix/major
93
+ # would skip it entirely. The common intent from a beta is
94
+ # 'finalize', so steer the user there rather than guess.
95
+ raise SystemExit(
96
+ f"Current version {current!r} is a beta of the upcoming "
97
+ f"{major}.{minor}.{patch} release. To ship it, use bump="
98
+ f"'finalize' (-> {major}.{minor}.{patch}). A '{bump}' bump from "
99
+ f"a beta would skip {major}.{minor}.{patch} entirely; "
100
+ f"that is almost never intended."
101
+ )
102
+ elif bump == "major":
103
+ major, minor, patch = major + 1, 0, 0
104
+ elif bump == "minor":
105
+ minor, patch = minor + 1, 0
106
+ elif bump == "bugfix":
107
+ patch += 1
108
+
109
+ if is_beta:
110
+ if bump != "pre-release":
111
+ beta_n = 1 # fresh beta series for the new base
112
+ print(f"{major}.{minor}.{patch}b{beta_n}", end="")
113
+ else:
114
+ print(f"{major}.{minor}.{patch}", end="")
115
+ PYEOF
116
+ )
117
+
118
+ # Derive is_beta from the COMPUTED version (ends in bN?), not the raw input.
119
+ if [[ "$NEW_VERSION" =~ b[0-9]+$ ]]; then IS_BETA_OUT=true; else IS_BETA_OUT=false; fi
120
+
121
+ echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
122
+ echo "tag=v$NEW_VERSION" >> "$GITHUB_OUTPUT"
123
+ echo "branch=release/v$NEW_VERSION" >> "$GITHUB_OUTPUT"
124
+ echo "is_beta=$IS_BETA_OUT" >> "$GITHUB_OUTPUT"
125
+ echo "Bumping (${{ inputs.bump }}): $CURRENT → $NEW_VERSION"
126
+
127
+ # ── Bump version strings ─────────────────────────────────────────────
128
+ # Only the package version: deapi/version.py holds the client's protocol
129
+ # version (sent to DE-Server), which does not follow package releases.
130
+ - name: Bump version in pyproject.toml
131
+ run: |
132
+ sed -i 's/^version = ".*"/version = "${{ steps.version.outputs.new_version }}"/' pyproject.toml
133
+
134
+ # ── Build changelog ──────────────────────────────────────────────────
135
+ - name: Build changelog with towncrier
136
+ run: |
137
+ FRAGMENT_COUNT=$(find upcoming_changes -maxdepth 1 -name "*.rst" \
138
+ ! -name "README.rst" | wc -l)
139
+ if [ "$FRAGMENT_COUNT" -eq 0 ]; then
140
+ echo "⚠ No news fragments found — skipping towncrier (CHANGES.rst unchanged)."
141
+ else
142
+ uvx towncrier build --yes --version "${{ steps.version.outputs.new_version }}"
143
+ fi
144
+
145
+ # ── Commit and push ──────────────────────────────────────────────────
146
+ - name: Configure git
147
+ run: |
148
+ git config user.name "github-actions[bot]"
149
+ git config user.email "github-actions[bot]@users.noreply.github.com"
150
+
151
+ - name: Commit release changes
152
+ run: |
153
+ git checkout -b "${{ steps.version.outputs.branch }}"
154
+
155
+ # Stage the version bump, updated changelog, and consumed fragments.
156
+ git add pyproject.toml CHANGES.rst
157
+ git add -A upcoming_changes/ # stages deleted fragment files
158
+
159
+ git commit -m "chore: prepare release ${{ steps.version.outputs.tag }}"
160
+ git push origin "${{ steps.version.outputs.branch }}"
161
+
162
+ # ── Open pull request ────────────────────────────────────────────────
163
+ - name: Open pull request
164
+ env:
165
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
166
+ TAG: ${{ steps.version.outputs.tag }}
167
+ BRANCH: ${{ steps.version.outputs.branch }}
168
+ IS_BETA: ${{ steps.version.outputs.is_beta }}
169
+ run: |
170
+ if [ "$IS_BETA" = "true" ]; then PRE="tick **Set as a pre-release**"; else PRE="leave **Set as a pre-release** unticked"; fi
171
+ gh pr create \
172
+ --title "Release ${TAG}" \
173
+ --base main \
174
+ --head "${BRANCH}" \
175
+ --body "## Release ${TAG}
176
+
177
+ > Auto-generated by the **Prepare Release** workflow.
178
+
179
+ ### What changed
180
+ - Version bumped to \`${TAG#v}\` in \`pyproject.toml\`
181
+ - \`CHANGES.rst\` updated from the towncrier fragments in \`upcoming_changes/\`
182
+
183
+ ### Review checklist
184
+ - [ ] \`CHANGES.rst\` reads well — edit the text directly in this PR if needed
185
+ - [ ] The version in \`pyproject.toml\` is correct
186
+ - [ ] CI passes
187
+
188
+ ### After merging
189
+ Create the GitHub Release by hand: **Releases → Draft a new release**, tag
190
+ \`${TAG}\` on \`main\`, paste this version's section of \`CHANGES.rst\` as the
191
+ notes, ${PRE}, and publish. Publishing it runs \`publish.yaml\`, which uploads
192
+ to PyPI."
@@ -1,7 +1,7 @@
1
1
  repos:
2
2
  - repo: https://github.com/psf/black-pre-commit-mirror
3
3
  # Version can be updated by running "pre-commit autoupdate"
4
- rev: 25.9.0
4
+ rev: 26.5.1
5
5
  hooks:
6
6
  - id: black
7
7
  ci:
@@ -0,0 +1,92 @@
1
+ .. _changelog:
2
+
3
+ Changelog
4
+ *********
5
+
6
+ This document describes the changes in the DEAPI library.
7
+
8
+ Changes are filed as fragment files in ``upcoming_changes/`` and assembled into this
9
+ file by `towncrier <https://towncrier.readthedocs.io/>`_ when a release is prepared
10
+ (see ``upcoming_changes/README.rst``).
11
+
12
+ .. towncrier release notes start
13
+
14
+ 5.3.1 (2026-09-25)
15
+ ==================
16
+
17
+ Maintenance
18
+ -----------
19
+
20
+ - Bumped the google protobuf version to 6.33 and dropped support for Python < 3.10. Closes #3691 (`#64 <https://github.com/directelectron/deapi/pull/64>`_)
21
+
22
+
23
+ 5.3.0 (2026-09-24)
24
+ ==================
25
+
26
+ New Features
27
+ ------------
28
+
29
+ - ``pydeserver --twin`` serves frames rendered by the `de-twin <https://github.com/directelectron/de-twin>`_ digital twin instead of the built-in fake data: a simulated microscope, specimen and detector, with a DE-TEM-Channel (``--soap-port``) so stage and optics changes show up in the images. Install it with ``pip install "deapi[twin]"``. (`#59 <https://github.com/directelectron/deapi/pull/59>`_)
30
+
31
+
32
+ Bug Fixes
33
+ ---------
34
+
35
+ - Fixed virtual masks and XY scan arrays sometimes arriving incomplete, which hung the connection (seen on macOS): the client now sends every byte of them. (`#60 <https://github.com/directelectron/deapi/pull/60>`_)
36
+
37
+
38
+ Documentation
39
+ -------------
40
+
41
+ - Added an example that moves the stage with ``de_microscope`` and images holes in a grid, run against the digital twin. (`#59 <https://github.com/directelectron/deapi/pull/59>`_)
42
+
43
+
44
+ Maintenance
45
+ -----------
46
+
47
+ - The simulated server closes a connection after an error, so the client gets an error instead of waiting; the test suite only stops deapi's own simulated server on port 13240, never a real DE-Server. (`#60 <https://github.com/directelectron/deapi/pull/60>`_)
48
+ - Releases are prepared by the **Prepare Release** workflow, and the changelog is assembled from towncrier fragments in ``upcoming_changes/``, as in the other Direct Electron Python packages. (`#61 <https://github.com/directelectron/deapi/pull/61>`_)
49
+
50
+
51
+ 5.3.beta6
52
+ =========
53
+ - Fixed invalid property errors and bugs in set_binning
54
+ - Updated get_property_specifications
55
+ - Do not automatically set attributes.windowWidth and attributes.windowHeight
56
+ - Return pattern index for get_result and get_virtual_image_buffer
57
+
58
+ 5.3.beta5
59
+ =========
60
+ - Modify gain acquisition
61
+
62
+ 5.3.beta4
63
+ =========
64
+ - Add GetEvent functionality to client.py
65
+ - Fixed bug in set_binning
66
+
67
+ 5.3.beta3
68
+ =========
69
+ - Fix the problem that the set_binning function could not set HW binning to 1
70
+
71
+ 5.3.beta2
72
+ =========
73
+ - Add access register
74
+
75
+ 5.3.beta1
76
+ =========
77
+ - Create a Result Class to handle Result + Histogram
78
+
79
+ 5.3.beta0
80
+ =========
81
+ - Add support for binning in x/ y dimensions when returning a result (#19)
82
+
83
+ 5.2.2
84
+ ==========
85
+ - Initial release
86
+ - Renamed `Client` functions from CamelCase to snake_case (Legacy functions are still available)
87
+ - Added buffer_protocol module for handling different buffer protocol files
88
+ - Added a data_types module for handling different data types
89
+ - Added a python based DEServer for testing purposes
90
+ - Update the Testing to allow for a real DEServer to be used for testing (#7)
91
+ - Add support for `@pytest.mark.server` decorator for tests that require a full DEServer to be running (#7)
92
+ - Add a commandline interface for the pydeserver. (#8) Running `pydeserver --port 13241` will start the server on port 13241
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deapi
3
- Version: 5.3b6
3
+ Version: 5.3.1
4
4
  Summary: API for DE Server
5
5
  Author: Direct Electron
6
6
  Project-URL: Bug Reports, https://github.com/directelectron/deapi/issues
@@ -12,16 +12,15 @@ Keywords: EELS,STEM,TEM,hyperspy,microscopy,Cryo EM,4D STEM
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
15
  Classifier: Programming Language :: Python :: 3.10
18
16
  Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Scientific/Engineering
21
- Requires-Python: >=3.8
20
+ Requires-Python: >=3.10
22
21
  License-File: LICENSE
23
22
  Requires-Dist: numpy>=1.20.0
24
- Requires-Dist: protobuf>=3.20.0
23
+ Requires-Dist: protobuf<7,>=6.33.5
25
24
  Requires-Dist: pillow
26
25
  Requires-Dist: matplotlib
27
26
  Requires-Dist: scipy
@@ -42,6 +41,8 @@ Requires-Dist: hyperspy; extra == "tests"
42
41
  Requires-Dist: psutil; extra == "tests"
43
42
  Provides-Extra: service
44
43
  Requires-Dist: pyqt6; extra == "service"
44
+ Provides-Extra: twin
45
+ Requires-Dist: de-twin>=0.1.1; python_version >= "3.10" and extra == "twin"
45
46
  Provides-Extra: doc
46
47
  Requires-Dist: sphinx; extra == "doc"
47
48
  Requires-Dist: pydata_sphinx_theme; extra == "doc"
@@ -17,8 +17,7 @@ else:
17
17
 
18
18
  long = int # python 3 no longer has int
19
19
  if int(pyVersion[1]) >= 10:
20
- # import pb_3_19_3 as pb
21
- import deapi.buffer_protocols.pb_3_23_3 as pb
20
+ import deapi.buffer_protocols.pb_6_33_4 as pb
22
21
  elif int(pyVersion[1]) >= 8:
23
22
  import deapi.buffer_protocols.pb_3_11_4 as pb
24
23
  else:
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: DE.proto
5
+ # Protobuf Python Version: 6.33.4
6
+ """Generated protocol buffer code."""
7
+
8
+ from google.protobuf import descriptor as _descriptor
9
+ from google.protobuf import descriptor_pool as _descriptor_pool
10
+ from google.protobuf import runtime_version as _runtime_version
11
+ from google.protobuf import symbol_database as _symbol_database
12
+ from google.protobuf.internal import builder as _builder
13
+
14
+ _runtime_version.ValidateProtobufRuntimeVersion(
15
+ _runtime_version.Domain.PUBLIC, 6, 33, 4, "", "DE.proto"
16
+ )
17
+ # @@protoc_insertion_point(imports)
18
+
19
+ _sym_db = _symbol_database.Default()
20
+
21
+
22
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
23
+ b'\n\x08\x44\x45.proto\x12\x0b\x44\x45Messaging"\xbd\x02\n\x08\x44\x45Packet\x12(\n\x04type\x18\x01 \x01(\x0e\x32\x1a.DEMessaging.DEPacket.Type\x12\x19\n\x11\x63\x61mera_identifier\x18\x02 \x01(\r\x12+\n\x07\x63ommand\x18\x03 \x03(\x0b\x32\x1a.DEMessaging.SingleCommand\x12\x33\n\x0b\x61\x63knowledge\x18\x04 \x03(\x0b\x32\x1e.DEMessaging.SingleAcknowledge\x12,\n\x0b\x64\x61ta_header\x18\x05 \x01(\x0b\x32\x17.DEMessaging.DataHeader\x12\x13\n\x0b\x63\x61mera_name\x18\x06 \x01(\t"G\n\x04Type\x12\n\n\x06P_NULL\x10\x00\x12\r\n\tP_COMMAND\x10\x01\x12\x11\n\rP_ACKNOWLEDGE\x10\x02\x12\x11\n\rP_DATA_HEADER\x10\x03"g\n\rSingleCommand\x12\x12\n\ncommand_id\x18\x01 \x01(\r\x12\x14\n\x0c\x63ommand_name\x18\x02 \x01(\t\x12,\n\tparameter\x18\x03 \x03(\x0b\x32\x19.DEMessaging.AnyParameter"\xf0\x01\n\x0c\x41nyParameter\x12,\n\x04type\x18\x01 \x01(\x0e\x32\x1e.DEMessaging.AnyParameter.Type\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05p_int\x18\x03 \x01(\x11\x12\x0e\n\x06p_bool\x18\x04 \x01(\x08\x12\x10\n\x08p_string\x18\x05 \x01(\t\x12\x0f\n\x07p_float\x18\x06 \x01(\x02\x12\x0f\n\x07p_bytes\x18\x07 \x01(\x0c"Q\n\x04Type\x12\n\n\x06P_NULL\x10\x00\x12\t\n\x05P_INT\x10\x01\x12\n\n\x06P_BOOL\x10\x02\x12\x0c\n\x08P_STRING\x10\x03\x12\x0b\n\x07P_FLOAT\x10\x04\x12\x0b\n\x07P_BYTES\x10\x05"]\n\x0b\x41\x63knowledge\x12\x19\n\x11\x63\x61mera_identifier\x18\x01 \x01(\r\x12\x33\n\x0b\x61\x63knowledge\x18\x02 \x03(\x0b\x32\x1e.DEMessaging.SingleAcknowledge"\x91\x01\n\x11SingleAcknowledge\x12\r\n\x05\x65rror\x18\x01 \x01(\x08\x12\x12\n\ncommand_id\x18\x02 \x01(\r\x12\x14\n\x0c\x63ommand_name\x18\x03 \x01(\t\x12\x15\n\rerror_message\x18\x04 \x01(\t\x12,\n\tparameter\x18\x05 \x03(\x0b\x32\x19.DEMessaging.AnyParameter"2\n\nDataHeader\x12\x10\n\x08\x62ytesize\x18\x01 \x01(\x04\x12\x12\n\nidentifier\x18\x02 \x01(\tb\x06proto3'
24
+ )
25
+
26
+ _globals = globals()
27
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
28
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "DE_pb2", _globals)
29
+ if not _descriptor._USE_C_DESCRIPTORS:
30
+ DESCRIPTOR._loaded_options = None
31
+ _globals["_DEPACKET"]._serialized_start = 26
32
+ _globals["_DEPACKET"]._serialized_end = 343
33
+ _globals["_DEPACKET_TYPE"]._serialized_start = 272
34
+ _globals["_DEPACKET_TYPE"]._serialized_end = 343
35
+ _globals["_SINGLECOMMAND"]._serialized_start = 345
36
+ _globals["_SINGLECOMMAND"]._serialized_end = 448
37
+ _globals["_ANYPARAMETER"]._serialized_start = 451
38
+ _globals["_ANYPARAMETER"]._serialized_end = 691
39
+ _globals["_ANYPARAMETER_TYPE"]._serialized_start = 610
40
+ _globals["_ANYPARAMETER_TYPE"]._serialized_end = 691
41
+ _globals["_ACKNOWLEDGE"]._serialized_start = 693
42
+ _globals["_ACKNOWLEDGE"]._serialized_end = 786
43
+ _globals["_SINGLEACKNOWLEDGE"]._serialized_start = 789
44
+ _globals["_SINGLEACKNOWLEDGE"]._serialized_end = 934
45
+ _globals["_DATAHEADER"]._serialized_start = 936
46
+ _globals["_DATAHEADER"]._serialized_end = 986
47
+ # @@protoc_insertion_point(module_scope)
@@ -151,7 +151,7 @@ class Client:
151
151
  PropertyCollection(client=self, name=collection, properties=props),
152
152
  )
153
153
 
154
- def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=False):
154
+ def connect(self, host: str = "127.0.0.1", port: int = 13240, read_only=None):
155
155
  """Connect to DE-Server
156
156
 
157
157
  Parameters
@@ -160,20 +160,12 @@ class Client:
160
160
  The host to connect to, by default "127.0.0.1" for local connection
161
161
  port : int, optional
162
162
  The port to connect to, by default 13240
163
- read_only : bool, optional
163
+ read_only : bool, optional (deprecated for command version >= 17)
164
164
  If True, the client will be in read-only mode, by default False
165
+ For command version >= 17, the read_only parameter is ignored and
166
+ the client will query the server to determine whether it should be
167
+ read-only or read-write.
165
168
  """
166
- self.read_only = read_only
167
- if not read_only and (host == "localhost" or host == "127.0.0.1"):
168
- tcp_no_delay = 0 # on loopback interface, nodelay causes delay
169
-
170
- if self.usingMmf:
171
- self.mmf = mmap.mmap(0, MMF_DATA_BUFFER_SIZE, "ImageFileMappingObject")
172
- self.mmf[0] = True
173
- else:
174
- self.usingMmf = False # Disabled MMF if connected remotely
175
- tcp_no_delay = 1
176
-
177
169
  if logLevel == logging.DEBUG:
178
170
  log.debug("Connecting to server: %s", host)
179
171
 
@@ -183,7 +175,6 @@ class Client:
183
175
  self.socket.connect(
184
176
  (host, port)
185
177
  ) # Connect to server reading port for sending data
186
- self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, tcp_no_delay)
187
178
  self.socket.setblocking(False)
188
179
  self.socket.settimeout(2)
189
180
 
@@ -200,15 +191,15 @@ class Client:
200
191
  self.port = port
201
192
  log.info("Connected to server: %s, port: %d", host, port)
202
193
 
203
- if cVersion >= 12:
204
- self.set_client_read_only(read_only)
205
-
206
194
  server_version = self.GetProperty("Server Software Version")
207
195
  server_version = re.findall(r"\d+", server_version)
208
196
 
209
197
  version = [int(part) for part in server_version[:4]]
210
198
  temp = version[2] + version[1] * 1000 + version[0] * 1000000
211
- if temp >= 2008000 and version[3] >= 12073:
199
+ if temp >= 2008003 and version[3] >= 12436:
200
+ ## version 2.8.3 build 12436+
201
+ self.commandVersion = 17
202
+ elif temp >= 2008000 and version[3] >= 12073:
212
203
  ## version 2.8.0 build 12073+ — virtual image buffer support (SDK 5.3.0)
213
204
  self.commandVersion = 16
214
205
  elif (temp >= 2007005 and version[3] < 11274) or temp >= 2008000:
@@ -234,6 +225,25 @@ class Client:
234
225
  self.commandVersion = 3
235
226
  else:
236
227
  self.commandVersion = commandVersion
228
+
229
+ if self.commandVersion >= 12 and self.commandVersion < 17:
230
+ if read_only is None:
231
+ read_only = False
232
+ self.set_client_read_only(read_only)
233
+ elif self.commandVersion >= 17:
234
+ if read_only is not None:
235
+ log.warning(
236
+ "The `read_only` argument to `connect()` is ignored for servers "
237
+ "with command version >= 17 (DE-MC version >= 2.8.3.12436). "
238
+ "The client now determines read-only status automatically."
239
+ )
240
+ self.get_client_read_only()
241
+
242
+ self.SelectImageTransferMode()
243
+ self.socket.setsockopt(
244
+ socket.IPPROTO_TCP, socket.TCP_NODELAY, self.tcp_no_delay
245
+ )
246
+
237
247
  log.info(f"Command Version: {self.commandVersion}")
238
248
  self._initialize_attributes()
239
249
  self.update_scan_size()
@@ -245,8 +255,17 @@ class Client:
245
255
 
246
256
  def set_client_read_only(self, read_only):
247
257
  self.read_only = read_only
248
- command = self._addSingleCommand(self.SET_CLIENT_READ_ONLY, None, [read_only])
258
+ command = self._addSingleCommand(
259
+ self.SET_CLIENT_READ_ONLY_DEPRECATED, None, [read_only]
260
+ )
261
+ response = self._sendCommand(command)
262
+ return response
263
+
264
+ def get_client_read_only(self):
265
+ command = self._addSingleCommand(self.GET_CLIENT_READ_ONLY, None)
249
266
  response = self._sendCommand(command)
267
+ if response != False:
268
+ self.read_only = self.__getParameters(response.acknowledge[0])[0]
250
269
  return response
251
270
 
252
271
  def update_scan_size(self):
@@ -435,9 +454,11 @@ class Client:
435
454
  The name of the property to get the specifications for
436
455
  """
437
456
  if self.commandVersion < 13:
438
- log.error("get_property_specifications is only supported for server version 2.7.5 and above.")
457
+ log.error(
458
+ "get_property_specifications is only supported for server version 2.7.5 and above."
459
+ )
439
460
  return None
440
-
461
+
441
462
  command = self._addSingleCommand(
442
463
  self.GET_PROPERTY_SPECIFICATIONS, property_name
443
464
  )
@@ -448,9 +469,11 @@ class Client:
448
469
  values = self.__getParameters(response.acknowledge[0])
449
470
 
450
471
  if not values or len(values) == 0:
451
- log.error(f"get_property_specifications({property_name}) failed, parameter size not matched.")
472
+ log.error(
473
+ f"get_property_specifications({property_name}) failed, parameter size not matched."
474
+ )
452
475
  return None
453
-
476
+
454
477
  prop_spec = PropertySpecifications()
455
478
 
456
479
  param_id = 0
@@ -463,9 +486,11 @@ class Client:
463
486
  elif data_type == "Integer":
464
487
  prop_spec.prop_type = PropertyType.Integer
465
488
  else:
466
- log.error(f"get_property_specifications({property_name}) failed, property type not matched.")
489
+ log.error(
490
+ f"get_property_specifications({property_name}) failed, property type not matched."
491
+ )
467
492
  return None
468
-
493
+
469
494
  prop_allowable_type = values[param_id]
470
495
  param_id += 1
471
496
 
@@ -477,7 +502,9 @@ class Client:
477
502
  prop_spec.max_value = values[param_id]
478
503
  param_id += 1
479
504
  else:
480
- log.error(f"get_property_specifications({property_name}) failed, cannot read the min/max value.")
505
+ log.error(
506
+ f"get_property_specifications({property_name}) failed, cannot read the min/max value."
507
+ )
481
508
  return None
482
509
  elif prop_allowable_type == "Set":
483
510
  prop_spec.prop_allowable_type = PropertyAllowableType.Set
@@ -485,7 +512,9 @@ class Client:
485
512
  elif prop_allowable_type == "AllowAll":
486
513
  prop_spec.prop_allowable_type = PropertyAllowableType.AllowAll
487
514
  else:
488
- log.error(f"get_property_specifications({property_name}) failed, unknown allowable type.")
515
+ log.error(
516
+ f"get_property_specifications({property_name}) failed, unknown allowable type."
517
+ )
489
518
  return None
490
519
 
491
520
  prop_spec.category = values[-4]
@@ -1498,7 +1527,7 @@ class Client:
1498
1527
  command = self._addSingleCommand(self.SET_SCAN_XY_ARRAY, None, vals_to_send)
1499
1528
  try:
1500
1529
  packet = struct.pack("I", command.ByteSize()) + command.SerializeToString()
1501
- self.socket.send(packet)
1530
+ self.socket.sendall(packet)
1502
1531
  ret = self.__ReceiveResponseForCommand(command) != False
1503
1532
  log.info(f"response {ret}")
1504
1533
  except socket.error:
@@ -1850,7 +1879,7 @@ class Client:
1850
1879
  command = self._addSingleCommand(self.SET_VIRTUAL_MASK, None, [id, w, h])
1851
1880
  ret = True
1852
1881
  packet = struct.pack("I", command.ByteSize()) + command.SerializeToString()
1853
- self.socket.send(packet)
1882
+ self.socket.sendall(packet)
1854
1883
 
1855
1884
  if ret:
1856
1885
  if mask.dtype != np.uint8:
@@ -2823,6 +2852,19 @@ class Client:
2823
2852
  if self.connected:
2824
2853
  self.disconnect()
2825
2854
 
2855
+ def SelectImageTransferMode(self):
2856
+ if not self.read_only and (
2857
+ self.host == "localhost" or self.host == "127.0.0.1"
2858
+ ):
2859
+ self.tcp_no_delay = 0 # on loopback interface, nodelay causes delay
2860
+
2861
+ if self.usingMmf:
2862
+ self.mmf = mmap.mmap(0, MMF_DATA_BUFFER_SIZE, "ImageFileMappingObject")
2863
+ self.mmf[0] = True
2864
+ else:
2865
+ self.usingMmf = False # Disabled MMF if connected remotely
2866
+ self.tcp_no_delay = 1
2867
+
2826
2868
  # get multiple parameters from a single acknowledge packet
2827
2869
  def __getParameters(self, single_acknowledge=None):
2828
2870
  output = []
@@ -3050,29 +3092,17 @@ class Client:
3050
3092
  return buffer
3051
3093
 
3052
3094
  def __sendToSocket(self, sock, buffer, bytes):
3095
+ """Send all of *buffer*, or raise.
3096
+
3097
+ ``socket.send`` may accept only part of a buffer (routinely on macOS's loopback once
3098
+ the socket has a timeout), so the return value must be honoured: dropping the rest
3099
+ left the server waiting for bytes that never came, and every later command on the
3100
+ connection hung. ``sendall`` loops until every byte is sent and raises
3101
+ ``socket.timeout`` if the server stops reading for *timeout* seconds.
3102
+ """
3053
3103
  timeout = self.exposureTime * 10 + 30
3054
- startTime = self.GetTime()
3055
3104
  self.socket.settimeout(timeout)
3056
-
3057
- retval = True
3058
- chunkSize = 4096
3059
- for i in range(0, len(buffer), chunkSize):
3060
- try:
3061
- sock.send(buffer[i : min(len(buffer), i + chunkSize)])
3062
- except socket.timeout:
3063
-
3064
- log.debug(f" __sendToSocket : timeout in trying to send {bytes} bytes")
3065
- if self.GetTime() - startTime > timeout:
3066
- log.error(" __recvFromSocket: max timeout %d seconds", timeout)
3067
- retval = False
3068
- break
3069
- else:
3070
- pass # continue further
3071
- except socket.error as e:
3072
- log.error(f"Error during send: {e}")
3073
- # Handle the error as needed, e.g., close the connection
3074
- retval = False
3075
- break
3105
+ sock.sendall(buffer)
3076
3106
  return buffer
3077
3107
 
3078
3108
  def __saveText(self, image, fileName, textSize):
@@ -3162,6 +3192,7 @@ class Client:
3162
3192
  height = 0
3163
3193
  mmf = 0
3164
3194
  usingMmf = True
3195
+ tcp_no_delay = 0
3165
3196
  debugImagesFolder = "D:\\DebugImages\\"
3166
3197
  connected = False
3167
3198
  camera = ""
@@ -3201,7 +3232,7 @@ class Client:
3201
3232
  SET_SCAN_ROI = 28
3202
3233
  SET_SCAN_SIZE_AND_GET_CHANGED_PROPERTIES = 29
3203
3234
  SET_SCAN_ROI__AND_GET_CHANGED_PROPERTIES = 30
3204
- SET_CLIENT_READ_ONLY = 31
3235
+ SET_CLIENT_READ_ONLY_DEPRECATED = 31
3205
3236
  SET_SCAN_XY_ARRAY = 32
3206
3237
  SET_ADAPTIVE_ROI = 33
3207
3238
  SET_ADAPTIVE_ROI_AND_GET_CHANGED_PROPERTIES = 34
@@ -3213,6 +3244,7 @@ class Client:
3213
3244
  LIST_REGISTERS = 40
3214
3245
  GET_VIRTUAL_IMAGE_INFO = 41
3215
3246
  GET_VIRTUAL_IMAGE = 42
3247
+ GET_CLIENT_READ_ONLY = 43
3216
3248
 
3217
3249
 
3218
3250
  MMF_DATA_HEADER_SIZE = 24