Skip to content

Commit d20fd6a

Browse files
authored
Merge pull request #49 from WEHI-ResearchComputing/41-allow-16-bit-mrc-input-for-denoising
41 allow 16 bit mrc input for denoising
2 parents 6bef6bf + 1f74ab9 commit d20fd6a

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

partinet/process_utils/guided_denoiser.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def transform(image: np.ndarray) -> np.ndarray:
2020
"""
2121
i_min = image.min()
2222
i_max = image.max()
23+
if i_max == i_min:
24+
# avoid division by zero; return a zero array when input is constant
25+
return np.zeros_like(image, dtype=np.uint8)
2326
image = ((image - i_min) / (i_max - i_min)) * 255
2427
return image.astype(np.uint8)
2528

@@ -28,12 +31,20 @@ def standard_scaler(image: np.ndarray) -> np.ndarray:
2831
"""
2932
Apply Gaussian blur and standardize the image to have zero mean and unit variance.
3033
34+
The input is cast to ``float32`` before any OpenCV operations to avoid the
35+
``CV_16F`` kernel-type error that occurs when processing 16‑bit micrographs
36+
(see issue #41). After blurring and normalization we transform the result to
37+
eight‑bit for downstream filters.
38+
3139
Args:
3240
image (np.ndarray): Input image array.
3341
3442
Returns:
3543
np.ndarray: Scaled and transformed image.
3644
"""
45+
# convert to a supported floating point type for OpenCV kernels
46+
image = image.astype(np.float32)
47+
3748
kernel_size = 9
3849
image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
3950
mu = np.mean(image)
@@ -150,6 +161,12 @@ def denoise(image_path: str) -> np.ndarray:
150161
"""
151162
kernel = gaussian_kernel(kernel_size=9)
152163
image = mrcfile.read(image_path)
164+
165+
# some MRCs are stored as 16‑bit integers; ensure we work in float32 so that
166+
# subsequent OpenCV calls (GaussianBlur, etc.) don't raise the ktype error
167+
# described in https://github.com/WEHI-ResearchComputing/PartiNet/issues/41
168+
image = image.astype(np.float32)
169+
153170
image = image.T
154171
image = np.rot90(image)
155172
normalized_image = standard_scaler(np.array(image))

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies = [
4040
"tensorboard",
4141
"scikit-learn",
4242
"mrcfile",
43+
"pytest",
4344
]
4445

4546
[project.scripts]

tests/test_guided_denoiser.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import tempfile
3+
4+
import numpy as np
5+
import mrcfile
6+
import pytest
7+
8+
from partinet.process_utils.guided_denoiser import denoise, transform, standard_scaler
9+
10+
11+
def _write_temp_mrc(array: np.ndarray, dtype: np.dtype) -> str:
12+
"""Write ``array`` to a temporary .mrc file and return its path."""
13+
fd, path = tempfile.mkstemp(suffix=".mrc")
14+
os.close(fd)
15+
with mrcfile.new(path, overwrite=True) as mrc:
16+
mrc.set_data(array.astype(dtype))
17+
return path
18+
19+
20+
@ pytest.mark.parametrize("dtype", [np.uint16, np.int16, np.float32])
21+
def test_denoise_handles_various_dtypes(dtype):
22+
"""The ``denoise`` pipeline should accept 16-bit and 32-bit inputs without
23+
throwing OpenCV kernel-type errors (issue #41).
24+
"""
25+
data = (np.random.rand(32, 32) * 255).astype(dtype)
26+
path = _write_temp_mrc(data, dtype)
27+
28+
try:
29+
out = denoise(path)
30+
assert isinstance(out, np.ndarray)
31+
# our pipeline always returns 8-bit data
32+
assert out.dtype == np.uint8
33+
assert out.shape == data.shape
34+
finally:
35+
os.unlink(path)
36+
37+
38+
def test_transform_stable_when_constant():
39+
"""``transform`` should not divide by zero if image has no contrast."""
40+
arr = np.full((4, 4), 100, dtype=np.float32)
41+
out = transform(arr)
42+
assert out.dtype == np.uint8
43+
assert np.all(out == 0)
44+
45+
46+
def test_standard_scaler_normalises():
47+
arr = np.arange(25, dtype=np.float32).reshape(5, 5)
48+
scaled = standard_scaler(arr)
49+
assert scaled.dtype == np.uint8
50+
# scaled values should not all be equal
51+
assert scaled.std() > 0

0 commit comments

Comments
 (0)