@@ -147,6 +147,44 @@ def multilinear_signal(self, D, F, S0, bvalues, offset=0):
147147 return signal
148148
149149 def simulate_training_data (self , bvalues , SNR = (5 ,100 ), n = 1000000 , Drange = (0.0005 ,0.0034 ), frange = (0 ,1 ), Dprange = (0.005 ,0.1 ), rician_noise = False ):
150+ """
151+ Simulates IVIM (Intravoxel Incoherent Motion) training data with optional Rician noise.
152+
153+ Parameters:
154+ ----------
155+ bvalues : array-like
156+ A list or array of b-values used in the diffusion signal simulation.
157+ SNR : float, or tuple of two floats, optional
158+ Signal-to-noise ratio. If a tuple (min, max) is provided, SNRs are sampled
159+ logarithmically between those bounds. If set to 0, no noise is added. Default is (5, 100).
160+ n : int, optional
161+ Number of simulated voxels/signals to generate. Default is 1,000,000.
162+ Drange : tuple of two floats, optional
163+ Range (min, max) for the diffusion coefficient D (in mm²/s). Default is (0.0005, 0.0034).
164+ frange : tuple of two floats, optional
165+ Range (min, max) for the perfusion fraction f. Default is (0, 1).
166+ Dprange : tuple of two floats, optional
167+ Range (min, max) for the pseudo-diffusion coefficient Dp (in mm²/s). Default is (0.005, 0.1).
168+ rician_noise : bool, optional
169+ If True, Rician noise is added to the simulated signal. Default is False.
170+
171+ Returns:
172+ -------
173+ data_sim : ndarray of shape (n, len(bvalues))
174+ Simulated IVIM signal data normalized by S0.
175+ D : ndarray of shape (n, 1)
176+ Ground truth diffusion coefficient values used in simulation.
177+ f : ndarray of shape (n, 1)
178+ Ground truth perfusion fraction values used in simulation.
179+ Dp : ndarray of shape (n, 1)
180+ Ground truth pseudo-diffusion coefficient values used in simulation.
181+
182+ Notes:
183+ -----
184+ - The function uses self.ivim_signal() to generate signals.
185+ - Noise is applied after generating noise-free IVIM signals, using either Gaussian or Rician noise.
186+ - Simulated signals are normalized by the mean S0 (b = 0) signal.
187+ """
150188 test = self ._rng .uniform (0 , 1 , (n , 1 ))
151189 D = Drange [0 ] + (test * (Drange [1 ] - Drange [0 ]))
152190 test = self ._rng .uniform (0 , 1 , (n , 1 ))
@@ -168,6 +206,20 @@ def simulate_training_data(self, bvalues, SNR = (5,100), n = 1000000, Drange = (
168206 for aa in range (len (D )):
169207 data_sim [aa , :] = self .ivim_signal (D [aa ][0 ], Dp [aa ][0 ], f [aa ][0 ], 1 , bvalues , snr = SNR [aa ], rician_noise = rician_noise )
170208 # if SNR is set to zero, don't add noise
209+ if addnoise :
210+ # initialise noise arrays
211+ noise_imag = np .zeros ([n , len (bvalues )])
212+ noise_real = np .zeros ([n , len (bvalues )])
213+ # fill arrays
214+ for i in range (0 , n - 1 ):
215+ noise_real [i ,] = self ._rng .normal (0 , 1 / SNR [i ],(1 ,len (bvalues )))
216+ noise_imag [i ,] = self ._rng .normal (0 , 1 / SNR [i ], (1 , len (bvalues )))
217+ if rician_noise :
218+ # add Rician noise as the square root of squared gaussian distributed real signal + noise and imaginary noise
219+ data_sim = np .sqrt (np .power (data_sim + noise_real , 2 ) + np .power (noise_imag , 2 ))
220+ else :
221+ # or add Gaussian noise
222+ data_sim = data_sim + noise_real
171223 S0_noisy = np .mean (data_sim [:, bvalues == 0 ], axis = 1 )
172224 data_sim = data_sim / S0_noisy [:, None ]
173225 return data_sim , D , f , Dp
0 commit comments