Skip to content

Commit 2ca2b1f

Browse files
Update of noises (#405)
* update of background noise * update of background noises unittest * update noises background * unit testing now iwth BackendTestBase, for both numpy and torch. * Added docs and torch implementation for Poisson * modified get function for Poisson * docs polishing * Implemented Gaussian and Complex Gaussian * Docs polishing * Torch tests for Gaussian & Complex Gaussian * syntax * Implemented Feedback from Mirja * docs * update background * Implemented second round feedback from Mirja * update noises * Update test_noises.py * imports * Update test_noises.py * u * u * u * xp.asarray * rest of the functions * removed Image wrapper for Poisson * u * complex * u * Update test_noises.py * Update test_noises.py * whitespace * added checks for array types * update test noises --------- Co-authored-by: Alex <95913221+Pwhsky@users.noreply.github.com>
1 parent 9dbd195 commit 2ca2b1f

File tree

2 files changed

+309
-57
lines changed

2 files changed

+309
-57
lines changed

deeptrack/noises.py

Lines changed: 252 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"""
2-
Features for introducing noise to images.
1+
"""Features for introducing noise to images.
32
43
This module provides classes to add various types of noise to images,
54
including constant offsets, Gaussian noise, and Poisson-distributed noise.
@@ -63,21 +62,59 @@ class Noise(Feature):
6362
"""Base abstract noise class."""
6463

6564

66-
#TODO ***MG*** revise Background - torch, typing, docstring, unit test
6765
class Background(Noise):
68-
"""Adds a constant value to an image
66+
"""Add a constant value to an image.
6967
7068
Parameters
7169
----------
72-
offset : float
73-
The value to add to the image
70+
offset: float
71+
The value to add to the image.
72+
**kwargs: Any
73+
Additional keyword arguments passed to the parent `Noise` class.
74+
75+
Methods
76+
-------
77+
get(
78+
image: np.ndarray, torch.Tensor, or Image,
79+
offset: float,
80+
**kwargs,
81+
) -> np.ndarray, torch.Tensor, or Image
82+
Adds the constant offset to the input image.
83+
84+
Examples
85+
--------
86+
>>> import deeptrack as dt
87+
88+
Create an input image with zeros:
89+
>>> import numpy as np
90+
>>>
91+
>>> input_image = np.zeros((2,2))
92+
93+
Define the Background noise feature with offset 0.5:
94+
>>> noise = dt.Background(offset=0.5)
95+
96+
Apply the noise to the input image and print the resulting image:
97+
>>> output_image = noise.resolve(input_image)
98+
>>> print(output_image)
99+
[[0.5 0.5]
100+
[0.5 0.5]]
101+
74102
"""
75103

76104
def __init__(
77105
self: Background,
78106
offset: PropertyLike[float],
79107
**kwargs: Any,
80108
):
109+
"""Initialize the Background noise feature.
110+
111+
Parameters
112+
----------
113+
offset: PropertyLike[float]
114+
The constant value to be added to the image.
115+
**kwargs: Any
116+
Additional arguments passed to the parent `Noise` class.
117+
"""
81118
super().__init__(offset=offset, **kwargs)
82119

83120
def get(
@@ -86,23 +123,76 @@ def get(
86123
offset: float,
87124
**kwargs: Any,
88125
) -> NDArray[Any] | torch.Tensor | Image:
126+
"""Add the given offset to the image.
89127
90-
return image + offset
128+
Parameters
129+
----------
130+
image: np.ndarray, torch.Tensor, or Image
131+
The input image.
132+
offset: float
133+
The value to add to the image.
91134
135+
Returns
136+
-------
137+
np.ndarray, torch.Tensor, or Image
138+
The image with offset added.
139+
"""
140+
141+
return image + offset
92142

93143
Offset = Background
94144

95145

96-
#TODO ***JH*** revise Gaussian - torch, typing, docstring, unit test
97146
class Gaussian(Noise):
98-
"""Adds IID Gaussian noise to an image.
147+
"""Add IID Gaussian noise to an image.
148+
149+
Gaussian noise is sampled from a Gaussian distribution and added pixel-wise
150+
to the input image.
99151
100152
Parameters
101153
----------
102-
mu : float
154+
mu: float
103155
The mean of the Gaussian distribution.
104-
sigma : float
156+
sigma: float
105157
The standard deviation of the Gaussian distribution.
158+
159+
Notes
160+
-----
161+
If the backend is NumPy, the calculations use NumPy-compatible functions,
162+
and the output will be a np.array. If the backend is PyTorch, the
163+
calculations use PyTorch-compatible functions, and the output will be a
164+
torch.Tensor.
165+
166+
Methods
167+
-------
168+
get(
169+
image: np.ndarray, torch.Tensor, or Image,
170+
snr: float,
171+
background: float,
172+
max_val: float, optional,
173+
**kwargs,
174+
) -> np.ndarray, torch.Tensor, or Image
175+
Returns an image with Gaussian noise added.
176+
177+
Examples
178+
--------
179+
Add Gaussian noise to an image.
180+
>>> import deeptrack as dt
181+
182+
Create an input image with constant values:
183+
>>> import numpy as np
184+
>>>
185+
>>> input_image = np.ones((2,2)) * 3
186+
187+
Define the Gaussian noise feature with mean 1 and standard deviation 0.1:
188+
>>> noise = dt.Gaussian(mu=1, sigma=0.1)
189+
190+
Apply the noise to the input image and print the resulting image:
191+
>>> output_image = noise.resolve(input_image)
192+
>>> print(output_image)
193+
[[4.01965863 4.20688642]
194+
[4.02184982 3.87875873]]
195+
106196
"""
107197

108198
def __init__(
@@ -111,7 +201,6 @@ def __init__(
111201
sigma: PropertyLike[float] = 1,
112202
**kwargs: Any,
113203
):
114-
115204
super().__init__(mu=mu, sigma=sigma, **kwargs)
116205

117206
def get(
@@ -122,21 +211,69 @@ def get(
122211
**kwargs: Any,
123212
) -> NDArray[Any] | torch.Tensor | Image:
124213

125-
noisy_image = mu + image + np.random.randn(*image.shape) * sigma
214+
# For a Numpy backend.
215+
if self.get_backend() == "numpy":
216+
noisy_image = mu + image + np.random.randn(*image.shape) * sigma
217+
218+
# For a Torch backend.
219+
elif self.get_backend() == "torch":
220+
noisy_image = mu + image + torch.randn(*image.shape) * sigma
126221

127222
return noisy_image
128223

129224

130-
#TODO ***JH*** revise ComplexGaussian - torch, typing, docstring, unit test
131225
class ComplexGaussian(Noise):
132-
"""Adds complex-valued IID Gaussian noise to an image.
226+
"""Add complex-valued IID Gaussian noise to an image.
227+
228+
Complex Gaussian noise is sampled by combining two independent Gaussian
229+
distributions for real and imaginary values and is then added pixel-wise
230+
to the input image.
133231
134232
Parameters
135233
----------
136-
mu : float
234+
mu: float
137235
The mean of the Gaussian distribution.
138-
sigma : float
236+
sigma: float
139237
The standard deviation of the Gaussian distribution.
238+
239+
Notes
240+
-----
241+
If the backend is NumPy, the calculations use NumPy-compatible functions,
242+
and the output will be a np.array. If the backend is PyTorch, the
243+
calculations use PyTorch-compatible functions, and the output will be a
244+
torch.Tensor.
245+
246+
Methods
247+
-------
248+
get(
249+
image: np.ndarray, torch.Tensor, or Image,
250+
snr: float,
251+
background: float,
252+
max_val: float, optional,
253+
**kwargs,
254+
) -> np.ndarray, torch.Tensor, or Image
255+
Returns an image with complex Gaussian noise added.
256+
257+
Examples
258+
--------
259+
Add complex Gaussian noise to an image.
260+
261+
>>> import deeptrack as dt
262+
263+
Create an input image with constant values:
264+
>>> import numpy as np
265+
>>>
266+
>>> input_image = np.ones((2,2)) * 3
267+
268+
Define the Gaussian noise feature with mean 1 and standard deviation 0.1:
269+
>>> noise = dt.ComplexGaussian(mu=1, sigma=0.1)
270+
271+
Apply the noise to the input image and print the resulting image:
272+
>>> output_image = noise.resolve(input_image)
273+
>>> print(output_image)
274+
[[3.79975648-0.06967551j 4.09943404+0.06499738j]
275+
[3.99886747-0.23549974j 4.15725117-0.07847024j]]
276+
140277
"""
141278

142279
def __init__(
@@ -145,7 +282,6 @@ def __init__(
145282
sigma: PropertyLike[float] = 1,
146283
**kwargs: Any,
147284
):
148-
149285
super().__init__(mu=mu, sigma=sigma, **kwargs)
150286

151287
def get(
@@ -156,28 +292,78 @@ def get(
156292
**kwargs: Any,
157293
) -> NDArray[Any] | torch.Tensor | Image:
158294

159-
real_noise = np.random.randn(*image.shape)
160-
imag_noise = np.random.randn(*image.shape) * 1j
161-
noisy_image = mu + image + (real_noise + imag_noise) * sigma
162-
295+
# For a Numpy backend.
296+
if self.get_backend() == "numpy":
297+
real_noise = np.random.randn(*image.shape)
298+
imag_noise = np.random.randn(*image.shape) * 1j
299+
noisy_image = mu + image + (real_noise + imag_noise) * sigma
300+
301+
# For a Torch backend.
302+
elif self.get_backend() == "torch":
303+
real_noise = torch.randn(*image.shape)
304+
imag_noise = torch.randn(*image.shape) * 1j
305+
noisy_image = mu + image + (real_noise + imag_noise) * sigma
306+
163307
return noisy_image
164308

165309

166-
#TODO ***AL*** revise Poisson - torch, typing, docstring, unit test
167310
class Poisson(Noise):
168-
"""Adds Poisson-distributed noise to an image.
311+
"""Add Poisson-distributed noise to an image.
312+
313+
Poisson noise is sampled and added pixel-wise depending on the
314+
intensity of the pixel in the original image to achieve a desired
315+
signal-to-noise ratio `snr`.
169316
170317
Parameters
171318
----------
172-
snr : float
319+
snr: float
173320
Signal-to-noise ratio of the final image. The signal is determined
174321
by the peak value of the image.
175-
background : float
322+
background: float
176323
Value to be be used as the background. This is used to calculate the
177324
signal of the image.
178-
max_val : float, optional
325+
max_val: float, optional
179326
Maximum allowable value to prevent overflow in noise computation.
180327
Default is 1e8.
328+
329+
Notes
330+
-----
331+
If the backend is NumPy, the calculations use NumPy-compatible functions,
332+
and the output will be a np.array. If the backend is PyTorch, the
333+
calculations use PyTorch-compatible functions, and the output will be a
334+
torch.Tensor.
335+
336+
Methods
337+
-------
338+
get(
339+
image: np.ndarray, torch.Tensor, or Image,
340+
snr: float,
341+
background: float,
342+
max_val: float, optional,
343+
**kwargs,
344+
) -> np.ndarray, torch.Tensor, or Image
345+
Returns an image with Poisson noise added.
346+
347+
Examples
348+
--------
349+
Add Poisson noise to an image.
350+
351+
>>> import deeptrack as dt
352+
353+
Create an input image with ones:
354+
>>> import numpy as np
355+
>>>
356+
>>> input_image = np.ones((2,2))
357+
358+
Define the Poisson noise feature with a low SNR:
359+
>>> noise = dt.Poisson(snr=1)
360+
361+
Apply the noise to the input image and print the resulting image:
362+
>>> output_image = noise.resolve(input_image)
363+
>>> print(output_image)
364+
[[2. 1.]
365+
[0. 4.]]
366+
181367
"""
182368

183369
def __init__(
@@ -188,7 +374,6 @@ def __init__(
188374
max_val: PropertyLike[float] = 1e8,
189375
**kwargs,
190376
):
191-
192377
super().__init__(
193378
*args,
194379
snr=snr,
@@ -206,18 +391,43 @@ def get(
206391
**kwargs: Any,
207392
) -> NDArray[Any] | torch.Tensor | Image:
208393

209-
image[image < 0] = 0
210-
image_max = np.max(image)
211-
peak = np.abs(image_max - background)
212-
213-
rescale = snr ** 2 / peak ** 2
214-
rescale = np.clip(rescale, 1e-10, max_val / np.abs(image_max))
215-
try:
216-
noisy_image = Image(np.random.poisson(image * rescale) / rescale)
217-
noisy_image.merge_properties_from(image) # TODO Should only be done if input is Image!
218-
return noisy_image
219-
except ValueError:
220-
raise ValueError(
221-
"NumPy poisson function errored due to too large value. "
222-
"Set max_val in dt.Poisson to a lower value to fix."
394+
# For a numpy backend.
395+
if self.get_backend() == "numpy":
396+
image[image < 0] = 0
397+
image_max = np.max(image)
398+
peak = np.abs(image_max - background)
399+
400+
rescale = snr ** 2 / peak ** 2
401+
rescale = np.clip(
402+
rescale, 1e-10, max_val / np.abs(image_max)
403+
)
404+
try:
405+
noisy_image = Image(
406+
np.random.poisson(image * rescale) / rescale
407+
)
408+
noisy_image.merge_properties_from(image)
409+
return noisy_image
410+
except ValueError:
411+
raise ValueError(
412+
"NumPy poisson function errored due to too large value. "
413+
"Set max_val in dt.Poisson to a lower value to fix."
414+
)
415+
416+
# For a Torch backend.
417+
elif self.get_backend() == "torch":
418+
image = torch.clamp(image, min=0)
419+
image_max = torch.max(image)
420+
peak = torch.abs(image_max - background)
421+
422+
rescale = snr ** 2 / peak ** 2
423+
rescale = torch.clamp(
424+
rescale, min=1e-10, max=max_val / torch.abs(image_max)
223425
)
426+
try:
427+
noisy_image = torch.poisson(image * rescale) / rescale
428+
return noisy_image
429+
except ValueError:
430+
raise ValueError(
431+
"Torch Poisson function errored due to too large value. "
432+
"Set max_val in dt.Poisson to a lower value to fix."
433+
)

0 commit comments

Comments
 (0)