sem2surface 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sem2surface
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: 3D surface reconstruction from multi-detector SEM/BSE images
5
5
  Author: Vladislav A. Yastrebov
6
6
  License-Expression: BSD-3-Clause
@@ -39,6 +39,9 @@ Dynamic: license-file
39
39
 
40
40
  # sem2surface
41
41
 
42
+ [![PyPI version](https://img.shields.io/pypi/v/sem2surface.svg)](https://pypi.org/project/sem2surface/)
43
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/sem2surface.svg)](https://github.com/vyastreb/sem2surface/blob/master/LICENSE)
44
+
42
45
  `sem2surface` reconstructs a three-dimensional surface from three to five
43
46
  multi-detector SEM/BSE images. It extracts two normalized principal images,
44
47
  identifies their orientation with a Radon transform, and integrates the resulting
@@ -54,78 +57,49 @@ calibration procedure.
54
57
 
55
58
  ## Installation
56
59
 
57
- Python 3.10 or newer is required. An isolated virtual environment is strongly
58
- recommended so the application does not conflict with system Python packages.
59
-
60
- ### Linux and macOS
61
-
62
- Create an environment and install a released version:
60
+ Python 3.10 or newer is required. Install `sem2surface` from PyPI with:
63
61
 
64
62
  ```bash
65
- python3 -m venv .venv
66
- source .venv/bin/activate
67
- python -m pip install --upgrade pip
68
- python -m pip install sem2surface
69
- sem2surface-gui
63
+ pip install sem2surface
70
64
  ```
71
65
 
72
- Until a release is uploaded to PyPI, run the final installation command from
73
- this source checkout instead:
66
+ If several Python installations are present, use the interpreter explicitly:
74
67
 
75
68
  ```bash
76
- python -m pip install .
69
+ python3 -m pip install sem2surface # Linux or macOS
77
70
  ```
78
71
 
79
- On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
80
- users can install it with their system package manager as `python3-tk` before
81
- creating the environment.
82
-
83
72
  ### Windows
84
73
 
85
74
  Install Python 3.10 or newer from
86
75
  [python.org](https://www.python.org/downloads/windows/). Keep the standard
87
- `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell in
88
- the folder where you want the environment and run:
76
+ `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell or
77
+ Command Prompt and run:
89
78
 
90
79
  ```powershell
91
- py -3 -m venv .venv
92
- .\.venv\Scripts\Activate.ps1
93
- python -m pip install --upgrade pip
94
- python -m pip install sem2surface
80
+ py -m pip install sem2surface
95
81
  sem2surface-gui
96
82
  ```
97
83
 
98
- If PowerShell blocks the activation script, allow it only for the current
99
- PowerShell process and retry activation:
84
+ If Windows cannot find the installed launcher, start the GUI through Python:
100
85
 
101
86
  ```powershell
102
- Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
103
- .\.venv\Scripts\Activate.ps1
104
- ```
105
-
106
- In Command Prompt, activate the same environment with:
107
-
108
- ```bat
109
- .venv\Scripts\activate.bat
87
+ py -m sem2surface_gui
110
88
  ```
111
89
 
112
- Activation is optional. The GUI can always be launched directly:
113
-
114
- ```powershell
115
- .\.venv\Scripts\sem2surface-gui.exe
116
- ```
90
+ On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
91
+ users can install `python3-tk` with their system package manager.
117
92
 
118
- Before the PyPI release, install either from a source checkout with
119
- `python -m pip install .` or from the provided wheel:
93
+ A virtual environment is optional. It is useful when the system Python is
94
+ externally managed, installation permissions are restricted, or other packages
95
+ have conflicting dependencies:
120
96
 
121
- ```powershell
122
- python -m pip install "C:\path\to\sem2surface-0.2.0-py3-none-any.whl"
97
+ ```bash
98
+ python3 -m venv .venv
99
+ source .venv/bin/activate # Windows PowerShell: .\.venv\Scripts\Activate.ps1
100
+ python -m pip install sem2surface
123
101
  ```
124
102
 
125
- Use `deactivate` to leave the environment on any platform. Do not copy a
126
- virtual environment between computers or move it after creation; create a new
127
- one and reinstall the package instead.
128
-
129
103
  VTK export is optional because VTK is a large dependency:
130
104
 
131
105
  ```bash
@@ -212,6 +186,12 @@ decomposition, Radon search, and oriented gradients.
212
186
  When timestamps are disabled, an existing output with the same name is replaced.
213
187
  Choose a dedicated output folder or enable timestamps when results must be kept.
214
188
 
189
+ For TIFF acquisitions, `sem2surface` automatically removes a microscope
190
+ annotation footer when it detects a nearly uniform bright separator followed by
191
+ a statistically distinct lower band. The detector works with both 8-bit Zeiss
192
+ and 16-bit FEI exports, leaves footer-free images unchanged, and records every
193
+ detected crop in the processing log.
194
+
215
195
  ## Reference examples and scaling
216
196
 
217
197
  The [examples directory](https://github.com/vyastreb/sem2surface/tree/master/examples)
@@ -232,6 +212,15 @@ python examples/Surface_1/test_without_gui.py
232
212
  <!-- PyPI cannot resolve repository-relative images. Keep this absolute URL. -->
233
213
  ![Reconstruction of the indented surface](https://raw.githubusercontent.com/vyastreb/sem2surface/master/img/indent_superposition.jpg)
234
214
 
215
+ ## Changes in 0.2.2
216
+
217
+ - Replaced the historical hard-coded footer threshold with bit-depth-independent
218
+ SEM annotation-band detection.
219
+ - Added safeguards against cropping isolated bright lines in specimen data.
220
+ - Added per-image logging of detected footers and removed row counts.
221
+ - Added regression coverage for 8-bit Zeiss, 16-bit FEI, footer-free, and
222
+ isolated-line images.
223
+
235
224
  ## Development
236
225
 
237
226
  Create an isolated environment and install the editable project with its test
@@ -266,7 +255,7 @@ For a first trial, create a separate account and API token on
266
255
  version:
267
256
 
268
257
  ```bash
269
- python -m twine upload --repository testpypi dist/sem2surface-0.2.0*
258
+ python -m twine upload --repository testpypi dist/sem2surface-0.2.2*
270
259
  ```
271
260
 
272
261
  When prompted, use `__token__` as the username and the complete TestPyPI token,
@@ -274,7 +263,7 @@ including its `pypi-` prefix, as the password. Test the uploaded wheel in a new
274
263
  environment without resolving dependencies from TestPyPI:
275
264
 
276
265
  ```bash
277
- python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.0
266
+ python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.2
278
267
  sem2surface --version
279
268
  ```
280
269
 
@@ -282,7 +271,7 @@ For the real release, create a PyPI account and API token at
282
271
  [pypi.org](https://pypi.org/), then run:
283
272
 
284
273
  ```bash
285
- python -m twine upload dist/sem2surface-0.2.0*
274
+ python -m twine upload dist/sem2surface-0.2.2*
286
275
  ```
287
276
 
288
277
  PyPI and TestPyPI use separate accounts and tokens. Never commit a token or put
@@ -1,5 +1,8 @@
1
1
  # sem2surface
2
2
 
3
+ [![PyPI version](https://img.shields.io/pypi/v/sem2surface.svg)](https://pypi.org/project/sem2surface/)
4
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/sem2surface.svg)](https://github.com/vyastreb/sem2surface/blob/master/LICENSE)
5
+
3
6
  `sem2surface` reconstructs a three-dimensional surface from three to five
4
7
  multi-detector SEM/BSE images. It extracts two normalized principal images,
5
8
  identifies their orientation with a Radon transform, and integrates the resulting
@@ -15,78 +18,49 @@ calibration procedure.
15
18
 
16
19
  ## Installation
17
20
 
18
- Python 3.10 or newer is required. An isolated virtual environment is strongly
19
- recommended so the application does not conflict with system Python packages.
20
-
21
- ### Linux and macOS
22
-
23
- Create an environment and install a released version:
21
+ Python 3.10 or newer is required. Install `sem2surface` from PyPI with:
24
22
 
25
23
  ```bash
26
- python3 -m venv .venv
27
- source .venv/bin/activate
28
- python -m pip install --upgrade pip
29
- python -m pip install sem2surface
30
- sem2surface-gui
24
+ pip install sem2surface
31
25
  ```
32
26
 
33
- Until a release is uploaded to PyPI, run the final installation command from
34
- this source checkout instead:
27
+ If several Python installations are present, use the interpreter explicitly:
35
28
 
36
29
  ```bash
37
- python -m pip install .
30
+ python3 -m pip install sem2surface # Linux or macOS
38
31
  ```
39
32
 
40
- On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
41
- users can install it with their system package manager as `python3-tk` before
42
- creating the environment.
43
-
44
33
  ### Windows
45
34
 
46
35
  Install Python 3.10 or newer from
47
36
  [python.org](https://www.python.org/downloads/windows/). Keep the standard
48
- `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell in
49
- the folder where you want the environment and run:
37
+ `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell or
38
+ Command Prompt and run:
50
39
 
51
40
  ```powershell
52
- py -3 -m venv .venv
53
- .\.venv\Scripts\Activate.ps1
54
- python -m pip install --upgrade pip
55
- python -m pip install sem2surface
41
+ py -m pip install sem2surface
56
42
  sem2surface-gui
57
43
  ```
58
44
 
59
- If PowerShell blocks the activation script, allow it only for the current
60
- PowerShell process and retry activation:
45
+ If Windows cannot find the installed launcher, start the GUI through Python:
61
46
 
62
47
  ```powershell
63
- Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
64
- .\.venv\Scripts\Activate.ps1
65
- ```
66
-
67
- In Command Prompt, activate the same environment with:
68
-
69
- ```bat
70
- .venv\Scripts\activate.bat
48
+ py -m sem2surface_gui
71
49
  ```
72
50
 
73
- Activation is optional. The GUI can always be launched directly:
74
-
75
- ```powershell
76
- .\.venv\Scripts\sem2surface-gui.exe
77
- ```
51
+ On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
52
+ users can install `python3-tk` with their system package manager.
78
53
 
79
- Before the PyPI release, install either from a source checkout with
80
- `python -m pip install .` or from the provided wheel:
54
+ A virtual environment is optional. It is useful when the system Python is
55
+ externally managed, installation permissions are restricted, or other packages
56
+ have conflicting dependencies:
81
57
 
82
- ```powershell
83
- python -m pip install "C:\path\to\sem2surface-0.2.0-py3-none-any.whl"
58
+ ```bash
59
+ python3 -m venv .venv
60
+ source .venv/bin/activate # Windows PowerShell: .\.venv\Scripts\Activate.ps1
61
+ python -m pip install sem2surface
84
62
  ```
85
63
 
86
- Use `deactivate` to leave the environment on any platform. Do not copy a
87
- virtual environment between computers or move it after creation; create a new
88
- one and reinstall the package instead.
89
-
90
64
  VTK export is optional because VTK is a large dependency:
91
65
 
92
66
  ```bash
@@ -173,6 +147,12 @@ decomposition, Radon search, and oriented gradients.
173
147
  When timestamps are disabled, an existing output with the same name is replaced.
174
148
  Choose a dedicated output folder or enable timestamps when results must be kept.
175
149
 
150
+ For TIFF acquisitions, `sem2surface` automatically removes a microscope
151
+ annotation footer when it detects a nearly uniform bright separator followed by
152
+ a statistically distinct lower band. The detector works with both 8-bit Zeiss
153
+ and 16-bit FEI exports, leaves footer-free images unchanged, and records every
154
+ detected crop in the processing log.
155
+
176
156
  ## Reference examples and scaling
177
157
 
178
158
  The [examples directory](https://github.com/vyastreb/sem2surface/tree/master/examples)
@@ -193,6 +173,15 @@ python examples/Surface_1/test_without_gui.py
193
173
  <!-- PyPI cannot resolve repository-relative images. Keep this absolute URL. -->
194
174
  ![Reconstruction of the indented surface](https://raw.githubusercontent.com/vyastreb/sem2surface/master/img/indent_superposition.jpg)
195
175
 
176
+ ## Changes in 0.2.2
177
+
178
+ - Replaced the historical hard-coded footer threshold with bit-depth-independent
179
+ SEM annotation-band detection.
180
+ - Added safeguards against cropping isolated bright lines in specimen data.
181
+ - Added per-image logging of detected footers and removed row counts.
182
+ - Added regression coverage for 8-bit Zeiss, 16-bit FEI, footer-free, and
183
+ isolated-line images.
184
+
196
185
  ## Development
197
186
 
198
187
  Create an isolated environment and install the editable project with its test
@@ -227,7 +216,7 @@ For a first trial, create a separate account and API token on
227
216
  version:
228
217
 
229
218
  ```bash
230
- python -m twine upload --repository testpypi dist/sem2surface-0.2.0*
219
+ python -m twine upload --repository testpypi dist/sem2surface-0.2.2*
231
220
  ```
232
221
 
233
222
  When prompted, use `__token__` as the username and the complete TestPyPI token,
@@ -235,7 +224,7 @@ including its `pypi-` prefix, as the password. Test the uploaded wheel in a new
235
224
  environment without resolving dependencies from TestPyPI:
236
225
 
237
226
  ```bash
238
- python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.0
227
+ python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.2
239
228
  sem2surface --version
240
229
  ```
241
230
 
@@ -243,7 +232,7 @@ For the real release, create a PyPI account and API token at
243
232
  [pypi.org](https://pypi.org/), then run:
244
233
 
245
234
  ```bash
246
- python -m twine upload dist/sem2surface-0.2.0*
235
+ python -m twine upload dist/sem2surface-0.2.2*
247
236
  ```
248
237
 
249
238
  PyPI and TestPyPI use separate accounts and tokens. Never commit a token or put
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sem2surface"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "3D surface reconstruction from multi-detector SEM/BSE images"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sem2surface
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: 3D surface reconstruction from multi-detector SEM/BSE images
5
5
  Author: Vladislav A. Yastrebov
6
6
  License-Expression: BSD-3-Clause
@@ -39,6 +39,9 @@ Dynamic: license-file
39
39
 
40
40
  # sem2surface
41
41
 
42
+ [![PyPI version](https://img.shields.io/pypi/v/sem2surface.svg)](https://pypi.org/project/sem2surface/)
43
+ [![License: BSD 3-Clause](https://img.shields.io/pypi/l/sem2surface.svg)](https://github.com/vyastreb/sem2surface/blob/master/LICENSE)
44
+
42
45
  `sem2surface` reconstructs a three-dimensional surface from three to five
43
46
  multi-detector SEM/BSE images. It extracts two normalized principal images,
44
47
  identifies their orientation with a Radon transform, and integrates the resulting
@@ -54,78 +57,49 @@ calibration procedure.
54
57
 
55
58
  ## Installation
56
59
 
57
- Python 3.10 or newer is required. An isolated virtual environment is strongly
58
- recommended so the application does not conflict with system Python packages.
59
-
60
- ### Linux and macOS
61
-
62
- Create an environment and install a released version:
60
+ Python 3.10 or newer is required. Install `sem2surface` from PyPI with:
63
61
 
64
62
  ```bash
65
- python3 -m venv .venv
66
- source .venv/bin/activate
67
- python -m pip install --upgrade pip
68
- python -m pip install sem2surface
69
- sem2surface-gui
63
+ pip install sem2surface
70
64
  ```
71
65
 
72
- Until a release is uploaded to PyPI, run the final installation command from
73
- this source checkout instead:
66
+ If several Python installations are present, use the interpreter explicitly:
74
67
 
75
68
  ```bash
76
- python -m pip install .
69
+ python3 -m pip install sem2surface # Linux or macOS
77
70
  ```
78
71
 
79
- On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
80
- users can install it with their system package manager as `python3-tk` before
81
- creating the environment.
82
-
83
72
  ### Windows
84
73
 
85
74
  Install Python 3.10 or newer from
86
75
  [python.org](https://www.python.org/downloads/windows/). Keep the standard
87
- `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell in
88
- the folder where you want the environment and run:
76
+ `pip`, Tcl/Tk, and Python Launcher components enabled. Then open PowerShell or
77
+ Command Prompt and run:
89
78
 
90
79
  ```powershell
91
- py -3 -m venv .venv
92
- .\.venv\Scripts\Activate.ps1
93
- python -m pip install --upgrade pip
94
- python -m pip install sem2surface
80
+ py -m pip install sem2surface
95
81
  sem2surface-gui
96
82
  ```
97
83
 
98
- If PowerShell blocks the activation script, allow it only for the current
99
- PowerShell process and retry activation:
84
+ If Windows cannot find the installed launcher, start the GUI through Python:
100
85
 
101
86
  ```powershell
102
- Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
103
- .\.venv\Scripts\Activate.ps1
104
- ```
105
-
106
- In Command Prompt, activate the same environment with:
107
-
108
- ```bat
109
- .venv\Scripts\activate.bat
87
+ py -m sem2surface_gui
110
88
  ```
111
89
 
112
- Activation is optional. The GUI can always be launched directly:
113
-
114
- ```powershell
115
- .\.venv\Scripts\sem2surface-gui.exe
116
- ```
90
+ On Linux, Tkinter may be packaged separately. For example, Ubuntu and Debian
91
+ users can install `python3-tk` with their system package manager.
117
92
 
118
- Before the PyPI release, install either from a source checkout with
119
- `python -m pip install .` or from the provided wheel:
93
+ A virtual environment is optional. It is useful when the system Python is
94
+ externally managed, installation permissions are restricted, or other packages
95
+ have conflicting dependencies:
120
96
 
121
- ```powershell
122
- python -m pip install "C:\path\to\sem2surface-0.2.0-py3-none-any.whl"
97
+ ```bash
98
+ python3 -m venv .venv
99
+ source .venv/bin/activate # Windows PowerShell: .\.venv\Scripts\Activate.ps1
100
+ python -m pip install sem2surface
123
101
  ```
124
102
 
125
- Use `deactivate` to leave the environment on any platform. Do not copy a
126
- virtual environment between computers or move it after creation; create a new
127
- one and reinstall the package instead.
128
-
129
103
  VTK export is optional because VTK is a large dependency:
130
104
 
131
105
  ```bash
@@ -212,6 +186,12 @@ decomposition, Radon search, and oriented gradients.
212
186
  When timestamps are disabled, an existing output with the same name is replaced.
213
187
  Choose a dedicated output folder or enable timestamps when results must be kept.
214
188
 
189
+ For TIFF acquisitions, `sem2surface` automatically removes a microscope
190
+ annotation footer when it detects a nearly uniform bright separator followed by
191
+ a statistically distinct lower band. The detector works with both 8-bit Zeiss
192
+ and 16-bit FEI exports, leaves footer-free images unchanged, and records every
193
+ detected crop in the processing log.
194
+
215
195
  ## Reference examples and scaling
216
196
 
217
197
  The [examples directory](https://github.com/vyastreb/sem2surface/tree/master/examples)
@@ -232,6 +212,15 @@ python examples/Surface_1/test_without_gui.py
232
212
  <!-- PyPI cannot resolve repository-relative images. Keep this absolute URL. -->
233
213
  ![Reconstruction of the indented surface](https://raw.githubusercontent.com/vyastreb/sem2surface/master/img/indent_superposition.jpg)
234
214
 
215
+ ## Changes in 0.2.2
216
+
217
+ - Replaced the historical hard-coded footer threshold with bit-depth-independent
218
+ SEM annotation-band detection.
219
+ - Added safeguards against cropping isolated bright lines in specimen data.
220
+ - Added per-image logging of detected footers and removed row counts.
221
+ - Added regression coverage for 8-bit Zeiss, 16-bit FEI, footer-free, and
222
+ isolated-line images.
223
+
235
224
  ## Development
236
225
 
237
226
  Create an isolated environment and install the editable project with its test
@@ -266,7 +255,7 @@ For a first trial, create a separate account and API token on
266
255
  version:
267
256
 
268
257
  ```bash
269
- python -m twine upload --repository testpypi dist/sem2surface-0.2.0*
258
+ python -m twine upload --repository testpypi dist/sem2surface-0.2.2*
270
259
  ```
271
260
 
272
261
  When prompted, use `__token__` as the username and the complete TestPyPI token,
@@ -274,7 +263,7 @@ including its `pypi-` prefix, as the password. Test the uploaded wheel in a new
274
263
  environment without resolving dependencies from TestPyPI:
275
264
 
276
265
  ```bash
277
- python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.0
266
+ python -m pip install --index-url https://test.pypi.org/simple/ --no-deps sem2surface==0.2.2
278
267
  sem2surface --version
279
268
  ```
280
269
 
@@ -282,7 +271,7 @@ For the real release, create a PyPI account and API token at
282
271
  [pypi.org](https://pypi.org/), then run:
283
272
 
284
273
  ```bash
285
- python -m twine upload dist/sem2surface-0.2.0*
274
+ python -m twine upload dist/sem2surface-0.2.2*
286
275
  ```
287
276
 
288
277
  PyPI and TestPyPI use separate accounts and tokens. Never commit a token or put
@@ -23,7 +23,7 @@ from scipy.ndimage import gaussian_filter
23
23
  from skimage.transform import radon
24
24
 
25
25
 
26
- __version__ = "0.2.0"
26
+ __version__ = "0.2.2"
27
27
  DEFAULT_PIXEL_SIZE = 1e-6
28
28
  _PIXEL_WIDTH_TAGS = ("PixelWidth=", "Image Pixel Size =")
29
29
 
@@ -261,6 +261,49 @@ def _read_image(path: Path) -> np.ndarray:
261
261
  return np.asarray(convert_to_grayscale(array), dtype=np.float64)
262
262
 
263
263
 
264
+ def _find_sem_data_rows(image: np.ndarray) -> int:
265
+ """Return the number of rows above a SEM annotation footer.
266
+
267
+ FEI and Zeiss exports use different bit depths and footer layouts, but both
268
+ place a nearly uniform bright separator near the bottom of the image. A
269
+ separator is accepted only when the pixels below it have a substantially
270
+ different median intensity, which avoids treating an isolated bright line
271
+ in the specimen as a footer.
272
+ """
273
+ array = np.asarray(image, dtype=np.float64)
274
+ if array.ndim != 2:
275
+ raise ValueError("Footer detection requires a two-dimensional image")
276
+ rows, columns = array.shape
277
+ if rows < 16 or columns < 16:
278
+ return rows
279
+
280
+ finite = array[np.isfinite(array)]
281
+ if finite.size == 0:
282
+ return rows
283
+ low, high = np.percentile(finite, (0.1, 99.9))
284
+ intensity_span = float(high - low)
285
+ if not np.isfinite(intensity_span) or intensity_span <= 0:
286
+ return rows
287
+
288
+ # The 3% tolerance includes FEI separator values (64512) when a few
289
+ # saturated specimen pixels raise the 99.9th percentile to 65535.
290
+ bright_threshold = high - 0.03 * intensity_span
291
+ bright_fraction = np.mean(array >= bright_threshold, axis=1)
292
+ first_candidate = max(1, int(np.ceil(0.5 * rows)))
293
+ minimum_footer_rows = max(4, int(np.ceil(0.01 * rows)))
294
+ candidate_stop = rows - minimum_footer_rows + 1
295
+
296
+ for row in range(first_candidate, candidate_stop):
297
+ if bright_fraction[row] < 0.98 or bright_fraction[row - 1] >= 0.5:
298
+ continue
299
+ preceding_rows = min(64, row)
300
+ specimen_median = float(np.nanmedian(array[row - preceding_rows : row]))
301
+ footer_median = float(np.nanmedian(array[row:]))
302
+ if abs(footer_median - specimen_median) >= 0.10 * intensity_span:
303
+ return row
304
+ return rows
305
+
306
+
264
307
  def _plot_image_decomposition(
265
308
  imgs: np.ndarray,
266
309
  intensity: np.ndarray,
@@ -537,16 +580,16 @@ def _construct_surface(
537
580
  if len(widths) != 1:
538
581
  raise ValueError("All detector images must have the same width")
539
582
 
540
- # Preserve the footer marker used by the reference data while applying the
541
- # same crop safely to every detector image.
542
- crop_rows: list[int] = []
543
- for image in images:
544
- crop = image.shape[0]
545
- row_means = np.mean(image, axis=1)
546
- marker = np.flatnonzero(np.abs(row_means[1:] - 1437.0) < 2)
547
- if marker.size:
548
- crop = max(1, int(marker[0]))
549
- crop_rows.append(crop)
583
+ # Detect each footer independently, then apply the smallest common crop so
584
+ # all detector images retain identical dimensions.
585
+ crop_rows = [_find_sem_data_rows(image) for image in images]
586
+ for path, image, crop in zip(paths, images, crop_rows):
587
+ if crop < image.shape[0]:
588
+ log(
589
+ log_file,
590
+ f"SEM annotation footer detected in {path.name}: "
591
+ f"removed {image.shape[0] - crop} rows",
592
+ )
550
593
  cut_y = min(crop_rows)
551
594
  if cut_y < 2:
552
595
  raise ValueError("Automatic footer detection left too little image data")
@@ -3,15 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
- from importlib.metadata import PackageNotFoundError, version
7
6
  from pathlib import Path
8
7
  from typing import Sequence
9
8
 
10
-
11
- try:
12
- VERSION = version("sem2surface")
13
- except PackageNotFoundError: # Running directly from an unpacked source tree.
14
- VERSION = "0.2.0"
9
+ from sem2surface import __version__ as VERSION
15
10
 
16
11
 
17
12
  def build_parser() -> argparse.ArgumentParser:
@@ -8,7 +8,7 @@ def test_cli_exposes_version(capsys):
8
8
  with pytest.raises(SystemExit) as raised:
9
9
  parser.parse_args(["--version"])
10
10
  assert raised.value.code == 0
11
- assert "0.2.0" in capsys.readouterr().out
11
+ assert "0.2.2" in capsys.readouterr().out
12
12
 
13
13
 
14
14
  def test_cli_requires_three_images(capsys):
@@ -4,6 +4,7 @@ import numpy as np
4
4
  import pytest
5
5
 
6
6
  from sem2surface import (
7
+ _find_sem_data_rows,
7
8
  compute_image_gradients,
8
9
  construct_surface,
9
10
  convert_to_grayscale,
@@ -58,3 +59,40 @@ def test_caller_owned_log_is_not_closed():
58
59
  with pytest.raises(ValueError):
59
60
  construct_surface([], log_file=stream)
60
61
  assert not stream.closed
62
+
63
+
64
+ def test_footer_detection_handles_fei_16_bit_separator():
65
+ rng = np.random.default_rng(3)
66
+ image = rng.integers(28000, 48000, size=(80, 64), dtype=np.uint16)
67
+ image.flat[:12] = 65535 # Saturated specimen pixels above the separator.
68
+ image[64] = 64512
69
+ image[65:] = 1024
70
+ image[68:76:3, 5:59:7] = 64512
71
+ image[-1] = 64512
72
+
73
+ assert _find_sem_data_rows(image) == 64
74
+
75
+
76
+ def test_footer_detection_handles_zeiss_8_bit_band():
77
+ rng = np.random.default_rng(5)
78
+ image = rng.integers(90, 190, size=(120, 64), dtype=np.uint8)
79
+ image[101] = 0
80
+ image[102:] = 255
81
+ image[105:117:3, 4:60:6] = 0
82
+
83
+ assert _find_sem_data_rows(image) == 102
84
+
85
+
86
+ def test_footer_detection_leaves_footer_free_image_unchanged():
87
+ rng = np.random.default_rng(7)
88
+ image = rng.integers(40, 220, size=(120, 64), dtype=np.uint8)
89
+
90
+ assert _find_sem_data_rows(image) == image.shape[0]
91
+
92
+
93
+ def test_footer_detection_ignores_isolated_bright_specimen_line():
94
+ rng = np.random.default_rng(11)
95
+ image = rng.integers(80, 180, size=(120, 64), dtype=np.uint8)
96
+ image[75] = 255
97
+
98
+ assert _find_sem_data_rows(image) == image.shape[0]
File without changes
File without changes