1- """
2- Features for introducing noise to images.
1+ """Features for introducing noise to images.
32
43This module provides classes to add various types of noise to images,
54including 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
6765class 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
93143Offset = Background
94144
95145
96- #TODO ***JH*** revise Gaussian - torch, typing, docstring, unit test
97146class 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
131225class 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
167310class 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