def place_predict(files): data_path = "/biac4/wandell/data/klchan13/100307/Diffusion/data" # Get file object data_file = nib.load(os.path.join(data_path, "data.nii.gz")) wm_data_file = nib.load(os.path.join(data_path,"wm_mask_registered.nii.gz")) # Get data and indices data = data_file.get_data() wm_data = wm_data_file.get_data() wm_idx = np.where(wm_data==1) # b values bvals = np.loadtxt(os.path.join(data_path, "bvals")) bval_list, b_inds, unique_b, rounded_bvals = snr.separate_bvals(bvals/1000) all_b_idx = np.squeeze(np.where(rounded_bvals != 0)) all_predict_brain = ozu.nans((wm_data_file.shape + bvals.shape)) bvals_predict_brain = ozu.nans((wm_data_file.shape + bvals.shape)) # Keep track of files in case there are any missing files i_track = np.ones(1830) for f_idx in np.arange(len(files)): this_file = files[f_idx] predict_data = nib.load(this_file).get_data() if this_file[0:11] == "all_predict": i = int(this_file.split(".")[0][11:]) print "Placing all_predict %4.2f of 1830"%(i+1) low = i*70 high = np.min([(i+1) * 70, int(np.sum(wm_data))]) all_predict_brain[wm_idx[0][low:high], wm_idx[1][low:high], wm_idx[2][low:high]] = predict_data elif this_file[0:13] == "bvals_predict": i = int(this_file.split(".")[0][13:]) print "Placing bvals_predict %4.2f of 1830"%(i+1) low = i*70 high = np.min([(i+1) * 70, int(np.sum(wm_data))]) bvals_predict_brain[wm_idx[0][low:high], wm_idx[1][low:high], wm_idx[2][low:high]] = predict_data i_track[i] = 0 actual = data[wm_idx, :][:, all_b_idx] missing_files = np.where(i_track) rmse_b = np.sqrt(np.mean((actual - all_predict_brain[wm_idx])**2,-1)) rmse_mb = p.sqrt(np.mean((actual - bvals_predict_brain[wm_idx])**2,-1)) # Save the rmse and predict data aff = data_file.get_affine() nib.Nifti1Image(all_predict_brain, aff).to_filename("all_predict_brain.nii.gz") nib.Nifti1Image(bvals_predict_brain, aff).to_filename("bvals_predict_brain.nii.gz") rmse_aff = np.eye(4) nib.Nifti1Image(rmse_b_flat, rmse_aff).to_filename("rmse_b_flat.nii.gz") nib.Nifti1Image(rmse_mb_flat, rmse_aff).to_filename("rmse_mb_flat.nii.gz") return missing_files, all_predict_brain, bvals_predict_brain, rmse_b_flat, rmse_mb_flat
def pdd_reliability(model1, model2): """ Compute the angle between the first PDD in two models. Parameters ---------- model1, model2: two objects from a class inherited from BaseModel. Must implement an auto_attr class method 'principal_diffusion_direction', which returns arrays of 3-vectors representing a direction in three space which is the principal diffusion direction in each voxel. Some models will have more than one principal diffusion direction in each voxel. In that case, the first direction in each voxel will be used to represent that voxel. """ vol_shape = model1.shape[:3] pdd1 = model1.principal_diffusion_direction[model1.mask] pdd2 = model2.principal_diffusion_direction[model2.mask] # Some models create not only the first PDD, but subsequent directions as # well, so If we have a lot of PDD, we take only the first one: if len(pdd1.shape) == 3: pdd1 = pdd1[:, 0] if len(pdd2.shape) == 3: pdd2 = pdd2[:, 0] out_flat = np.empty(pdd1.shape[0]) for vox in xrange(pdd1.shape[0]): this_ang = np.rad2deg(ozu.vector_angle(pdd1[vox], pdd2[vox])) out_flat[vox] = np.min([this_ang, 180-this_ang]) out = ozu.nans(vol_shape) out[model1.mask] = out_flat return out
def fit_angle(self): """ The angle between the tensors that were fitted """ out_flat = np.empty(self._flat_signal.shape[0]) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox][0]): idx = [i for i in self.rot_idx[int(flat_params[vox][0])]] # Sort them according to their weight and take the two # weightiest ones: w = flat_params[vox, 1:1 + self.n_canonicals] idx = np.array(idx)[np.argsort(w)] ang = np.rad2deg( ozu.vector_angle(self.rot_vecs.T[idx[-1]], self.rot_vecs.T[idx[-2]])) ang = np.min([ang, 180 - ang]) out_flat[vox] = ang else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape[:3]) out[self.mask] = out_flat return out
def fit_angle(self): """ The angle between the tensors that were fitted """ out_flat = np.empty(self._flat_signal.shape[0]) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox][0]): idx = [i for i in self.rot_idx[int(flat_params[vox][0])]] # Sort them according to their weight and take the two # weightiest ones: w = flat_params[vox,1:1+self.n_canonicals] idx = np.array(idx)[np.argsort(w)] ang = np.rad2deg(ozu.vector_angle( self.rot_vecs.T[idx[-1]], self.rot_vecs.T[idx[-2]])) ang = np.min([ang, 180-ang]) out_flat[vox] = ang else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape[:3]) out[self.mask] = out_flat return out
def model_diffusion(self, vertices=None, mode='ADC'): """ Calculate the ADC/diffusion distance on a novel set of vertices Parameters ---------- vertices : an n by 3 array """ # If none are provided, use the measurement points: if vertices is None: self.bvecs[:, self.b_idx] # Start by generating the values of the rotations we use in these # coordinates on the sphere rotations = self._calc_rotations(vertices, mode=mode) if self.verbose: print("Predicting signal from CanonicalTensorModel") out_flat = np.empty((self._flat_signal.shape[0], vertices.shape[-1])) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox, 1]): this_dist = flat_params[vox,1] * rotations[flat_params[vox,0]] out_flat[vox]= this_dist else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape[:3] + (vertices.shape[-1], )) out[self.mask] = out_flat return out
def fit(self): """ Predict the signal from the fit of the CanonicalTensorModel """ if self.verbose: print("Predicting signal from CanonicalTensorModel") out_flat = np.empty(self._flat_signal.shape) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox, 1]): if self.mode == 'log': this_relative = np.exp(flat_params[vox,1] * self.rotations[flat_params[vox,0]] + self.regressors[0][0] * flat_params[vox,2]) else: this_relative = (flat_params[vox,1] * self.rotations[flat_params[vox,0]] + self.regressors[0][0] * flat_params[vox,2]) if self.mode == 'signal_attenuation': this_relative = 1 - this_relative out_flat[vox]= this_relative * self._flat_S0[vox] else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape) out[self.mask] = out_flat return out
def crossing_index(self): """ Calculate an index of crossing in each voxel. This index is an analogue of FA, in that it is a normalized standard deviation between the values of the magnitudes of the peaks, which is then normalized by the standard deviation of the case in which there is only 1 peak with the value '1'. """ # Flatten and sort (small => large) flat_peaks = self.odf_peaks[self.mask] cross_flat = np.empty(flat_peaks.shape[0]) for vox in xrange(cross_flat.shape[0]): # Normalize all the peaks by the 2-norm of the vector: peaks_norm = flat_peaks[vox] / np.sqrt(np.dot(flat_peaks[vox], flat_peaks[vox])) non_zero_idx = np.where(peaks_norm > 0)[0] # Deal with some corner cases - if there is no peak, we define this # to be 0: if len(non_zero_idx) == 0: cross_flat[vox] = 0 # If there's only one peak, we define it to be 1: elif len(non_zero_idx) == 1: cross_flat[vox] = 1 # Otherwise, we need to do some math: else: std_peaks = np.std(peaks_norm[non_zero_idx]) std_norm = np.std(np.hstack([1, np.zeros(len(non_zero_idx) - 1)])) cross_flat[vox] = std_peaks / std_norm cross = ozu.nans(self.data.shape[:3]) cross[self.mask] = cross_flat return cross
def _correlator(self, correlator, r_idx=0, square=True): """ Helper function that uses a callable "func" to apply between two 1-d arrays. These 1-d arrays can have different outputs and the one we always want is the one which is r_idx into the output tuple """ val = np.empty(self._flat_signal.shape[0]) for ii in xrange(len(val)): if r_idx>=0: val[ii] = correlator(self._flat_signal[ii], self._flat_fit[ii])[r_idx] else: val[ii] = correlator(self._flat_signal[ii], self._flat_fit[ii]) if square: if has_numexpr: r_squared = numexpr.evaluate('val**2') else: r_squared = val**2 else: r_squared = val # Re-package it into a volume: out = ozu.nans(self.shape[:3]) out[self.mask] = r_squared out[out<-1]=-1.0 out[out>1]=1.0 return out
def odf(self): """ The orientation distribution function estimated from the SparseKernel model """ _verts = self.odf_verts[0] # These are the vertices on which we estimate # the ODF if self.verbose: prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] out_flat = np.zeros((self._flat_signal.shape[0], _verts.shape[0])) flat_params = self.model_params[self.mask] # We are going to use cached computations in the fit object: _fit_obj = None # Initially we don't have a cached fit object for vox in xrange(out_flat.shape[0]): this_fit = self.kernel_model.SparseKernelFit( flat_params[vox][1:], flat_params[vox][0], model=self._km) out_flat[vox] = this_fit.odf(cache=_fit_obj, vertices=_verts) _fit_obj = this_fit # From now on, we will use this cached object _verts = None # And we need to ignore the vertices, so that the # cached fit object can use the cached computation. if self.verbose: prog_bar.animate(vox, f_name=f_name) out = ozu.nans(self.signal.shape[:3] + (out_flat.shape[-1],)) out[self.mask] = out_flat return out
def fit(self): """ This is the signal estimated from the odf. """ if self.verbose: print("Predicting signal from SphericalHarmonicsModel") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] # Reshape the odf to be one voxel per row: flat_odf = self.odf[self.mask] pred_sig = np.empty(flat_odf.shape) for vox in xrange(pred_sig.shape[0]): # Predict based on the convolution: this_pred_sig = self.response_function.convolve_odf( flat_odf[vox], self._flat_S0[vox]) # We might have a scaling and an offset in addition, so let's fit # those in each voxel based on the signal: a, b = np.polyfit(this_pred_sig, self._flat_signal[vox], 1) pred_sig[vox] = a * this_pred_sig + b if self.verbose: prog_bar.animate(vox, f_name=f_name) # Pack it back into a volume shaped thing: out = ozu.nans(self.signal.shape) out[self.mask] = pred_sig return out
def crossing_index(self): """ Calculate an index of crossing in each voxel. This index is an analogue of FA, in that it is a normalized standard deviation between the values of the magnitudes of the peaks, which is then normalized by the standard deviation of the case in which there is only 1 peak with the value '1'. """ # Flatten and sort (small => large) flat_peaks = self.odf_peaks[self.mask] cross_flat = np.empty(flat_peaks.shape[0]) for vox in xrange(cross_flat.shape[0]): # Normalize all the peaks by the 2-norm of the vector: peaks_norm = flat_peaks[vox] / np.sqrt( np.dot(flat_peaks[vox], flat_peaks[vox])) non_zero_idx = np.where(peaks_norm > 0)[0] # Deal with some corner cases - if there is no peak, we define this # to be 0: if len(non_zero_idx) == 0: cross_flat[vox] = 0 # If there's only one peak, we define it to be 1: elif len(non_zero_idx) == 1: cross_flat[vox] = 1 # Otherwise, we need to do some math: else: std_peaks = (np.std(peaks_norm[non_zero_idx])) std_norm = np.std( np.hstack([1, np.zeros(len(non_zero_idx) - 1)])) cross_flat[vox] = std_peaks / std_norm cross = ozu.nans(self.data.shape[:3]) cross[self.mask] = cross_flat return cross
def model_params(self): """ The diffusion tensor parameters estimated from the data, using dipy. If this calculation has already occurred, just load the data from a nifti file, which has shape x by y by z by 12, where the last dimension is the model params: evecs (9) + evals (3) """ out = ozu.nans((self.data.shape[:3] + (12,))) flat_params = np.empty((self._flat_S0.shape[0], 12)) # The file already exists: if os.path.isfile(self.params_file): if self.verbose: print("Loading TensorModel params from: %s" %self.params_file) out[self.mask] = ni.load(self.params_file).get_data()[self.mask] else: if self.verbose: print("Fitting TensorModel params using dipy") tensor_model = dti.TensorModel(self.gtab, fit_method=self.fit_method) for vox, vox_data in enumerate(self.data[self.mask]): flat_params[vox] = tensor_model.fit(vox_data).model_params out[self.mask] = flat_params # Save the params for future use: params_ni = ni.Nifti1Image(out, self.affine) # If we asked it to be temporary, no need to save anywhere: if self.params_file != 'temp': params_ni.to_filename(self.params_file) # And return the params for current use: return out
def principal_diffusion_direction(self): """ Gives you not only the principal, but also the 2nd, 3rd, etc """ out_flat = ozu.nans(self._flat_signal.shape + (3,)) # flat_peaks = self.odf_peaks[self.mask] flat_peaks = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): coeff_idx = np.where(flat_peaks[vox]>0)[0] for i, idx in enumerate(coeff_idx): out_flat[vox, i] = self.bvecs[:,self.b_idx].T[idx] out = ozu.nans(self.signal.shape + (3,)) out[self.mask] = out_flat return out
def predict(self, sphere, bvals=None): """ Predict the values of the signal on a novel sphere (not neccesarily measured points) in every voxel Parameters ---------- sphere : 3 x n array """ if self.verbose: print("Predicting signal from TensorModel") if bvals is None: # Assume there's only one b value and you know where to find it: bvals = self.bvals[self.b_idx][0] * np.ones(sphere.shape[-1]) else: # If you gave them as input, you need to scale them: bvals = bvals/float(self.scaling_factor) pred_adc_flat = self.predict_adc(sphere)[self.mask] predict_flat = np.empty(pred_adc_flat.shape) out = ozu.nans(self.signal.shape[:3] + (sphere.shape[-1], )) for ii in xrange(len(predict_flat)): predict_flat[ii] = ozt.stejskal_tanner(self._flat_S0[ii], bvals, pred_adc_flat[ii]) out[self.mask] = predict_flat return out
def fit(self): """ Predict the signal from the fit of the CanonicalTensorModel """ if self.verbose: print("Predicting signal from CanonicalTensorModel") out_flat = np.empty(self._flat_signal.shape) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox, 1]): if self.mode == 'log': this_relative = np.exp( flat_params[vox, 1] * self.rotations[flat_params[vox, 0]] + self.regressors[0][0] * flat_params[vox, 2]) else: this_relative = ( flat_params[vox, 1] * self.rotations[flat_params[vox, 0]] + self.regressors[0][0] * flat_params[vox, 2]) if self.mode == 'signal_attenuation': this_relative = 1 - this_relative out_flat[vox] = this_relative * self._flat_S0[vox] else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape) out[self.mask] = out_flat return out
def fit(self): """ Predict the signal based on the kernel model fit """ if self.verbose: print("Predicting signal from SparseKernelModel") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] out_flat = np.zeros(self._flat_signal.shape) flat_params = self.model_params[self.mask] # We will use a cached fit object generated in the first iteration _fit_obj = None # And the vertices are the b vectors: _verts = self.bvecs[:, self.b_idx].T for vox in xrange(out_flat.shape[0]): this_fit = self.kernel_model.SparseKernelFit(flat_params[vox][1:], flat_params[vox][0], model=self._km) this_relative = this_fit.predict(cache=_fit_obj, vertices=_verts) _fit_obj = this_fit # From now on, we will use this cached object _verts = None # And set the verts input to None, so that it is # ignored in future iterations out_flat[vox] = this_relative * self._flat_S0[vox] if self.verbose: prog_bar.animate(vox, f_name=f_name) out = ozu.nans(self.signal.shape) out[self.mask] = out_flat return out
def fit_angle(self): """ The angle between the two primary peaks in the ODF """ out_flat = np.zeros(self._flat_signal.shape[0]) flat_odf = self.odf[self.mask] for vox in xrange(out_flat.shape[0]): if np.any(np.isnan(flat_odf[vox])): out_flat[vox] = np.nan else: p, i = recspeed.local_maxima(flat_odf[vox], self.odf_verts[1]) mask = p > 0.5 * np.max(p) p = p[mask] i = i[mask] if len(p) < 2: out_flat[vox] = np.nan else: out_flat[vox] = np.rad2deg(ozu.vector_angle( self.odf_verts[0][i[0]], self.odf_verts[0][i[1]])) out = ozu.nans(self.signal.shape[:3]) out[self.mask] = out_flat return out
def model_diffusion(self, vertices=None, mode='ADC'): """ Calculate the ADC/diffusion distance on a novel set of vertices Parameters ---------- vertices : an n by 3 array """ # If none are provided, use the measurement points: if vertices is None: self.bvecs[:, self.b_idx] # Start by generating the values of the rotations we use in these # coordinates on the sphere rotations = self._calc_rotations(vertices, mode=mode) if self.verbose: print("Predicting signal from CanonicalTensorModel") out_flat = np.empty((self._flat_signal.shape[0], vertices.shape[-1])) flat_params = self.model_params[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_params[vox, 1]): this_dist = flat_params[vox, 1] * rotations[flat_params[vox, 0]] out_flat[vox] = this_dist else: out_flat[vox] = np.nan out = ozu.nans(self.signal.shape[:3] + (vertices.shape[-1], )) out[self.mask] = out_flat return out
def fit(self): """ Predict the data from the fit of the SparseDeconvolutionModel """ if self.verbose: msg = "Predicting signal from SparseDeconvolutionModel" msg += " with %s"%self.solver print(msg) iso_regressor, tensor_regressor, fit_to = self.regressors out_flat = np.empty(self._flat_signal.shape) for vox in xrange(self._n_vox): this_params = self._flat_params[vox] this_params[np.isnan(this_params)] = 0.0 if self.mode == 'log': this_relative=np.exp(np.dot(self.design_matrix, this_params)+ np.mean(fit_to.T[vox])) else: this_relative = (np.dot(self.design_matrix, this_params) + np.mean(fit_to.T[vox])) if (self.mode == 'relative_signal' or self.mode=='normalize' or self.mode=='log'): this_pred_sig = this_relative * self._flat_S0[vox] elif self.mode == 'signal_attenuation': this_pred_sig = (1 - this_relative) * self._flat_S0[vox] # Fit scale and offset: #a,b = np.polyfit(this_pred_sig, self._flat_signal[vox], 1) # out_flat[vox] = a*this_pred_sig + b out_flat[vox] = this_pred_sig out = ozu.nans(self.signal.shape) out[self.mask] = out_flat return out
def fit(self): """ This is the signal estimated from the odf. """ if self.verbose: print("Predicting signal from SphericalHarmonicsModel") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split(".")[-1] f_name = this_class + "." + inspect.stack()[0][3] # Reshape the odf to be one voxel per row: flat_odf = self.odf[self.mask] pred_sig = np.empty(flat_odf.shape) for vox in xrange(pred_sig.shape[0]): # Predict based on the convolution: this_pred_sig = self.response_function.convolve_odf(flat_odf[vox], self._flat_S0[vox]) # We might have a scaling and an offset in addition, so let's fit # those in each voxel based on the signal: a, b = np.polyfit(this_pred_sig, self._flat_signal[vox], 1) pred_sig[vox] = a * this_pred_sig + b if self.verbose: prog_bar.animate(vox, f_name=f_name) # Pack it back into a volume shaped thing: out = ozu.nans(self.signal.shape) out[self.mask] = pred_sig return out
def fit_angle(self): """ The angle between the two primary peaks in the ODF """ out_flat = np.zeros(self._flat_signal.shape[0]) flat_odf = self.odf[self.mask] for vox in xrange(out_flat.shape[0]): if np.any(np.isnan(flat_odf[vox])): out_flat[vox] = np.nan else: p, i = recspeed.local_maxima(flat_odf[vox], self.odf_verts[1]) mask = p > 0.5 * np.max(p) p = p[mask] i = i[mask] if len(p) < 2: out_flat[vox] = np.nan else: out_flat[vox] = np.rad2deg( ozu.vector_angle(self.odf_verts[0][i[0]], self.odf_verts[0][i[1]])) out = ozu.nans(self.signal.shape[:3]) out[self.mask] = out_flat return out
def relative_mae(model1, model2): """ Given two model objects, compare the model fits to signal-to-signal reliability in the mean absolute error sense """ # Assume that the last dimension is the signal dimension, so the dimension # across which the mae will be calculated: out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model2.mask] signal_mae = ozu.mae(sig1, sig2) fit1_mae = ozu.mae(fit1, sig2) fit2_mae = ozu.mae(fit2, sig1) # Average in each element: fit_mae = (fit1_mae + fit2_mae) / 2. rel_mae = fit_mae / signal_mae out[model1.mask] = rel_mae return out
def predict(self, sphere, bvals=None): """ Predict the values of the signal on a novel sphere (not neccesarily measured points) in every voxel Parameters ---------- sphere : 3 x n array """ if self.verbose: print("Predicting signal from TensorModel") if bvals is None: # Assume there's only one b value and you know where to find it: bvals = self.bvals[self.b_idx][0] * np.ones(sphere.shape[-1]) else: # If you gave them as input, you need to scale them: bvals = bvals / float(self.scaling_factor) pred_adc_flat = self.predict_adc(sphere)[self.mask] predict_flat = np.empty(pred_adc_flat.shape) out = ozu.nans(self.signal.shape[:3] + (sphere.shape[-1], )) for ii in xrange(len(predict_flat)): predict_flat[ii] = ozt.stejskal_tanner(self._flat_S0[ii], bvals, pred_adc_flat[ii]) out[self.mask] = predict_flat return out
def pdd_reliability(model1, model2): """ Compute the angle between the first PDD in two models. Parameters ---------- model1, model2: two objects from a class inherited from BaseModel. Must implement an auto_attr class method 'principal_diffusion_direction', which returns arrays of 3-vectors representing a direction in three space which is the principal diffusion direction in each voxel. Some models will have more than one principal diffusion direction in each voxel. In that case, the first direction in each voxel will be used to represent that voxel. """ vol_shape = model1.shape[:3] pdd1 = model1.principal_diffusion_direction[model1.mask] pdd2 = model2.principal_diffusion_direction[model2.mask] # Some models create not only the first PDD, but subsequent directions as # well, so If we have a lot of PDD, we take only the first one: if len(pdd1.shape) == 3: pdd1 = pdd1[:, 0] if len(pdd2.shape) == 3: pdd2 = pdd2[:, 0] out_flat = np.empty(pdd1.shape[0]) for vox in xrange(pdd1.shape[0]): this_ang = np.rad2deg(ozu.vector_angle(pdd1[vox], pdd2[vox])) out_flat[vox] = np.min([this_ang, 180 - this_ang]) out = ozu.nans(vol_shape) out[model1.mask] = out_flat return out
def odf(self): """ The orientation distribution function estimated from the SparseKernel model """ _verts = self.odf_verts[ 0] # These are the vertices on which we estimate # the ODF if self.verbose: prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] out_flat = np.zeros((self._flat_signal.shape[0], _verts.shape[0])) flat_params = self.model_params[self.mask] # We are going to use cached computations in the fit object: _fit_obj = None # Initially we don't have a cached fit object for vox in xrange(out_flat.shape[0]): this_fit = self.kernel_model.SparseKernelFit(flat_params[vox][1:], flat_params[vox][0], model=self._km) out_flat[vox] = this_fit.odf(cache=_fit_obj, vertices=_verts) _fit_obj = this_fit # From now on, we will use this cached object _verts = None # And we need to ignore the vertices, so that the # cached fit object can use the cached computation. if self.verbose: prog_bar.animate(vox, f_name=f_name) out = ozu.nans(self.signal.shape[:3] + (out_flat.shape[-1], )) out[self.mask] = out_flat return out
def overfitting_index(model1, model2): """ How badly is the model overfitting? This can be assessed by comparing the RMSE of the model compared to the fit data (or learning set), relative to the RMSE of the model on another data set (or testing set) """ sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model2.mask] rmse_train1 = ozu.rmse(fit1, sig1) rmse_train2 = ozu.rmse(fit2, sig2) rmse_test1 = ozu.rmse(fit1, sig2) rmse_test2 = ozu.rmse(fit2, sig1) fit_rmse = (rmse_train1 + rmse_train2) / 2. predict_rmse = (rmse_test1 + rmse_test2) / 2. # The measure is a contrast index of the error on the training data vs. the # error on the testing data: overfit = (fit_rmse - predict_rmse) / (fit_rmse + predict_rmse) out = ozu.nans(model1.shape[:-1]) out[model1.mask] = overfit return out
def model_params(self): """ The diffusion tensor parameters estimated from the data, using dipy. If this calculation has already occurred, just load the data from a nifti file, which has shape x by y by z by 12, where the last dimension is the model params: evecs (9) + evals (3) """ out = ozu.nans((self.data.shape[:3] + (12, ))) flat_params = np.empty((self._flat_S0.shape[0], 12)) # The file already exists: if os.path.isfile(self.params_file): if self.verbose: print("Loading TensorModel params from: %s" % self.params_file) out[self.mask] = ni.load(self.params_file).get_data()[self.mask] else: if self.verbose: print("Fitting TensorModel params using dipy") tensor_model = dti.TensorModel(self.gtab, fit_method=self.fit_method) for vox, vox_data in enumerate(self.data[self.mask]): flat_params[vox] = tensor_model.fit(vox_data).model_params out[self.mask] = flat_params # Save the params for future use: params_ni = ni.Nifti1Image(out, self.affine) # If we asked it to be temporary, no need to save anywhere: if self.params_file != 'temp': params_ni.to_filename(self.params_file) # And return the params for current use: return out
def _correlator(self, correlator, r_idx=0, square=True): """ Helper function that uses a callable "func" to apply between two 1-d arrays. These 1-d arrays can have different outputs and the one we always want is the one which is r_idx into the output tuple """ val = np.empty(self._flat_signal.shape[0]) for ii in xrange(len(val)): if r_idx >= 0: val[ii] = correlator(self._flat_signal[ii], self._flat_fit[ii])[r_idx] else: val[ii] = correlator(self._flat_signal[ii], self._flat_fit[ii]) if square: if has_numexpr: r_squared = numexpr.evaluate('val**2') else: r_squared = val**2 else: r_squared = val # Re-package it into a volume: out = ozu.nans(self.shape[:3]) out[self.mask] = r_squared out[out < -1] = -1.0 out[out > 1] = 1.0 return out
def cross_predict(model1, model2): """ Given two model objects, fit to the measurements conducted in one, and then predict the measurements in the other model's rotational coordinate frame (b vectors). Calculate relative RMSE on that prediction, relative to the noise in the measurement due to the rotation. Average across both directions of this operation. """ out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] # Cross predict, using the parameters from one model to predict the # measurements in the other model's b vectors: predict1 = model1.predict(model2.bvecs[:, model2.b_idx])[model1.mask] predict2 = model2.predict(model1.bvecs[:, model1.b_idx])[model2.mask] signal_rmse = ozu.rmse(sig1, sig2) predict1_rmse = ozu.rmse(predict1, sig2) predict2_rmse = ozu.rmse(predict2, sig1) # Average in each element: predict_rmse = (predict1_rmse + predict2_rmse) / 2. rel_rmse = predict_rmse / signal_rmse out[model1.mask] = rel_rmse return out
def relative_mae(model1, model2): """ Given two model objects, compare the model fits to signal-to-signal reliability in the mean absolute error sense """ # Assume that the last dimension is the signal dimension, so the dimension # across which the mae will be calculated: out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model2.mask] signal_mae = ozu.mae(sig1, sig2) fit1_mae = ozu.mae(fit1, sig2) fit2_mae = ozu.mae(fit2, sig1) # Average in each element: fit_mae = (fit1_mae + fit2_mae) / 2. rel_mae = fit_mae/signal_mae out[model1.mask] = rel_mae return out
def cross_predict(model1, model2): """ Given two model objects, fit to the measurements conducted in one, and then predict the measurements in the other model's rotational coordinate frame (b vectors). Calculate relative RMSE on that prediction, relative to the noise in the measurement due to the rotation. Average across both directions of this operation. """ out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] # Cross predict, using the parameters from one model to predict the # measurements in the other model's b vectors: predict1 = model1.predict(model2.bvecs[:, model2.b_idx])[model1.mask] predict2 = model2.predict(model1.bvecs[:, model1.b_idx])[model2.mask] signal_rmse = ozu.rmse(sig1, sig2) predict1_rmse = ozu.rmse(predict1, sig2) predict2_rmse = ozu.rmse(predict2, sig1) # Average in each element: predict_rmse = (predict1_rmse + predict2_rmse) / 2. rel_rmse = predict_rmse/signal_rmse out[model1.mask] = rel_rmse return out
def overfitting_index(model1, model2): """ How badly is the model overfitting? This can be assessed by comparing the RMSE of the model compared to the fit data (or learning set), relative to the RMSE of the model on another data set (or testing set) """ sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model2.mask] rmse_train1 = ozu.rmse(fit1, sig1) rmse_train2 = ozu.rmse(fit2, sig2) rmse_test1 = ozu.rmse(fit1, sig2) rmse_test2 = ozu.rmse(fit2, sig1) fit_rmse = (rmse_train1 + rmse_train2) / 2. predict_rmse = (rmse_test1 + rmse_test2) /2. # The measure is a contrast index of the error on the training data vs. the # error on the testing data: overfit = (fit_rmse - predict_rmse) / (fit_rmse + predict_rmse) out = ozu.nans(model1.shape[:-1]) out[model1.mask] = overfit return out
def signal_reliability(self, DWI2, correlator=stats.pearsonr, r_idx=0, square=True): """ Calculate the r-squared of the correlator function provided, in each voxel (across directions, not including b0) between this class instance and another class instance, provided as input. Parameters ---------- DWI2: Another DWI class instance, with data that should look the same, if there wasn't any noise in the measurement correlator: callable. This is a function that calculates a measure of correlation (e.g. stats.pearsonr, or stats.linregress) r_idx: int, points to the location of r within the tuple returned by the correlator callable if r_idx is negative, that means that the return value is not a tuple and should be treated as the value itself. square: bool, If square is True, that means that the value returned from the correlator should be squared before returning it, otherwise, the value itself is returned. """ val = np.empty(self._flat_signal.shape[0]) for ii in xrange(len(val)): if r_idx>=0: val[ii] = correlator(self._flat_signal[ii], DWI2._flat_signal[ii])[r_idx] else: val[ii] = correlator(self._flat_signal[ii], DWI2._flat_signal[ii]) if square: if has_numexpr: r_squared = numexpr.evaluate('val**2') else: r_squared = val**2 else: r_squared = val # Re-package it into a volume: out = ozu.nans(self.shape[:3]) out[self.mask] = r_squared out[out<-1]=-1.0 out[out>1]=1.0 return out
def signal_reliability(self, DWI2, correlator=stats.pearsonr, r_idx=0, square=True): """ Calculate the r-squared of the correlator function provided, in each voxel (across directions, not including b0) between this class instance and another class instance, provided as input. Parameters ---------- DWI2: Another DWI class instance, with data that should look the same, if there wasn't any noise in the measurement correlator: callable. This is a function that calculates a measure of correlation (e.g. stats.pearsonr, or stats.linregress) r_idx: int, points to the location of r within the tuple returned by the correlator callable if r_idx is negative, that means that the return value is not a tuple and should be treated as the value itself. square: bool, If square is True, that means that the value returned from the correlator should be squared before returning it, otherwise, the value itself is returned. """ val = np.empty(self._flat_signal.shape[0]) for ii in xrange(len(val)): if r_idx >= 0: val[ii] = correlator(self._flat_signal[ii], DWI2._flat_signal[ii])[r_idx] else: val[ii] = correlator(self._flat_signal[ii], DWI2._flat_signal[ii]) if square: if has_numexpr: r_squared = numexpr.evaluate('val**2') else: r_squared = val**2 else: r_squared = val # Re-package it into a volume: out = ozu.nans(self.shape[:3]) out[self.mask] = r_squared out[out < -1] = -1.0 out[out > 1] = 1.0 return out
def model_adc(self): """ """ fit_rel_sig = self.fit[self.mask]/self._flat_S0.reshape(self._n_vox,1) log_rel_sig = np.log(fit_rel_sig) out_flat = log_rel_sig/(-self.bvals[self.b_idx][0]) out = ozu.nans(self.signal.shape) out[self.mask] = out_flat return out
def RMSE(self): """ We need to overload this to make the shapes to broadcast into make sense. XXX Need to consider whether it makes sense to take out our overloaded signal and relative_signal above, so we might not need this either... """ out = ozu.nans(self.signal.shape[:3]) flat_fit = self.fit[self.mask][:,:self.fit.shape[-1]-1] flat_rmse = ozu.rmse(self._flat_signal, flat_fit) out[self.mask] = flat_rmse return out
def tensors(self): out = ozu.nans(self.evecs.shape) evals = self.evals[self.mask] evecs = self.evecs[self.mask] D_flat = np.empty(evecs.shape) for ii in xrange(len(D_flat)): Q = evecs[ii] L = evals[ii] D_flat[ii] = np.dot(Q * L, Q.T) out[self.mask] = D_flat return out
def RMSE(self): """ We need to overload this to make the shapes to broadcast into make sense. XXX Need to consider whether it makes sense to take out our overloaded signal and relative_signal above, so we might not need this either... """ out = ozu.nans(self.signal.shape[:3]) flat_fit = self.fit[self.mask][:, :self.fit.shape[-1] - 1] flat_rmse = ozu.rmse(self._flat_signal, flat_fit) out[self.mask] = flat_rmse return out
def tensors(self): out = ozu.nans(self.evecs.shape) evals = self.evals[self.mask] evecs = self.evecs[self.mask] D_flat = np.empty(evecs.shape) for ii in xrange(len(D_flat)): Q = evecs[ii] L = evals[ii] D_flat[ii] = np.dot(Q*L, Q.T) out[self.mask] = D_flat return out
def predict_all(self): """ Calculate the predicted signal for all the possible OLS solutions """ # Get the bvec weights (we don't know how many...) and the # isotropic weights (which are always last): b_w = self.ols[:, :-1, :].copy().squeeze() i_w = self.ols[:, -1, :].copy().squeeze() # nan out the places where weights are negative: #b_w[b_w<0] = np.nan #i_w[i_w<0] = np.nan # A predicted signal for each voxel, for each rot_idx, for each # direction: flat_out = np.empty((self._flat_signal.shape[0], len(self.rot_idx), self._flat_signal.shape[-1])) if self.verbose: print("Predicting all signals for MultiCanonicalTensorModel:") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] for vox in xrange(flat_out.shape[0]): for idx, rot_idx in enumerate(self.rot_idx): # The constant regressor gets added in first: this_relative = i_w[idx, vox] * self.regressors[0][0] # And we add the different canonicals on top of that: this_relative += ( np.dot( b_w[idx, :, vox], # The tensor regressors are different in cases where we # are fitting to relative/attenuation signal, so grab that # from the regressors attr: np.array([self.regressors[1][x] for x in rot_idx]))) if self.mode == 'relative_signal' or self.mode == 'normalize': flat_out[vox, idx] = this_relative * self._flat_S0[vox] elif self.mode == 'signal_attenuation': flat_out[vox, idx] = (1 - this_relative) * self._flat_S0[vox] if self.verbose: prog_bar.animate(vox, f_name=f_name) out = ozu.nans(self.signal.shape[:3] + (len(self.rot_idx), ) + (self.signal.shape[-1], )) out[self.mask] = flat_out return out
def fit(self): if self.verbose: print("Predicting signal from TensorModel") adc_flat = self.model_adc[self.mask] fit_flat = np.empty(adc_flat.shape) out = ozu.nans(self.signal.shape) for ii in xrange(len(fit_flat)): fit_flat[ii] = ozt.stejskal_tanner(self._flat_S0[ii], self.bvals[self.b_idx], adc_flat[ii]) out[self.mask] = fit_flat return out
def predict_all(self): """ Calculate the predicted signal for all the possible OLS solutions """ # Get the bvec weights (we don't know how many...) and the # isotropic weights (which are always last): b_w = self.ols[:,:-1,:].copy().squeeze() i_w = self.ols[:,-1,:].copy().squeeze() # nan out the places where weights are negative: #b_w[b_w<0] = np.nan #i_w[i_w<0] = np.nan # A predicted signal for each voxel, for each rot_idx, for each # direction: flat_out = np.empty((self._flat_signal.shape[0], len(self.rot_idx), self._flat_signal.shape[-1])) if self.verbose: print("Predicting all signals for MultiCanonicalTensorModel:") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] for vox in xrange(flat_out.shape[0]): for idx, rot_idx in enumerate(self.rot_idx): # The constant regressor gets added in first: this_relative = i_w[idx,vox] * self.regressors[0][0] # And we add the different canonicals on top of that: this_relative += (np.dot(b_w[idx,:,vox], # The tensor regressors are different in cases where we # are fitting to relative/attenuation signal, so grab that # from the regressors attr: np.array([self.regressors[1][x] for x in rot_idx]))) if self.mode == 'relative_signal' or self.mode=='normalize': flat_out[vox, idx] = this_relative * self._flat_S0[vox] elif self.mode == 'signal_attenuation': flat_out[vox, idx] = (1-this_relative)*self._flat_S0[vox] if self.verbose: prog_bar.animate(vox, f_name=f_name) out = ozu.nans(self.signal.shape[:3] + (len(self.rot_idx),) + (self.signal.shape[-1],)) out[self.mask] = flat_out return out
def model_params(self): """ Use sklearn to fit the parameters: """ # The file already exists: if os.path.isfile(self.params_file) and not self.force_recompute: if self.verbose: print("Loading params from file: %s"%self.params_file) # Get the cached values and be done with it: return ni.load(self.params_file).get_data() else: if self.verbose: print("Fitting SparseDeconvolutionModel:") prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] iso_regressor, tensor_regressor, fit_to = self.regressors if self._n_vox==1: # We have to be a bit (too) clever here, so that the indexing # below works out: fit_to = np.array([fit_to]).T # One weight for each rotation params = np.empty((self._n_vox, self.rotations.shape[0])) for vox in xrange(self._n_vox): # Call out to the core fitting routine: params[vox] = self._fit_it(fit_to.T[vox], self.design_matrix) if self.verbose: prog_bar.animate(vox, f_name=f_name) out_params = ozu.nans((self.signal.shape[:3] + (self.design_matrix.shape[-1],))) out_params[self.mask] = params # Save the params to a file: params_ni = ni.Nifti1Image(out_params, self.affine) if self.params_file != 'temp': if self.verbose: print("Saving params to file: %s"%self.params_file) params_ni.to_filename(self.params_file) # And return the params for current use: return out_params
def odf_peak_angles(self): """ Calculate the angle between the two largest peaks in the odf peak distribution """ out_flat = ozu.nans(self._flat_signal.shape[0]) flat_odf_peaks = self.odf_peaks[self.mask] for vox in xrange(out_flat.shape[0]): if ~np.isnan(flat_odf_peaks[vox][0]): idx1 = np.argsort(flat_odf_peaks[vox])[-1] idx2 = np.argsort(flat_odf_peaks[vox])[-2] if idx1 != idx2: ang = np.rad2deg(ozu.vector_angle( self.bvecs[:,self.b_idx].T[idx1], self.bvecs[:,self.b_idx].T[idx2])) ang = np.min([ang, 180-ang]) out_flat[vox] = ang out = ozu.nans(self.signal.shape[:3]) out[self.mask] = out_flat return out
def model_diffusion_distance(self): """ The diffusion distance implied by the model parameters """ tensors_flat = self.tensors[self.mask] dist_flat = np.empty(self._flat_signal.shape) for vox in xrange(len(dist_flat)): dist_flat[vox]=ozt.diffusion_distance(self.bvecs[:, self.b_idx], tensors_flat[vox]) out = ozu.nans(self.signal.shape) out[self.mask] = dist_flat return out
def model_diffusion_distance(self): """ The diffusion distance implied by the model parameters """ tensors_flat = self.tensors[self.mask] dist_flat = np.empty(self._flat_signal.shape) for vox in xrange(len(dist_flat)): dist_flat[vox] = ozt.diffusion_distance(self.bvecs[:, self.b_idx], tensors_flat[vox]) out = ozu.nans(self.signal.shape) out[self.mask] = dist_flat return out
def model_params(self): """ Find the model parameters using least-squares optimization. """ if self.model_form == 'constrained': n_params = 3 elif self.model_form=='flexible' or self.model_form == 'ball_and_stick': n_params = 4 else: e_s = "%s is not a recognized model form"% self.model_form raise ValueError(e_s) params = np.empty((self.fit_signal.shape[0],n_params)) if self.verbose: print('Fitting CanonicalTensorModelOpt:') prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] # Initialize the starting conditions for the first voxel if self.model_form == 'constrained': this_params = 0, 0, np.mean(self.fit_signal[0]) elif (self.model_form=='flexible' or self.model_form=='ball_and_stick'): this_params = (0, 0, np.mean(self.fit_signal[0]), np.mean(self.fit_signal[0])) for vox in xrange(self.fit_signal.shape[0]): # From the second voxel and onwards, we use the end point of the # last voxel as the starting point for this voxel: start_params = this_params # Do the least-squares fitting (setting tolerance to a rather # lenient value?): this_params, status = opt.leastsq(self._err_func, start_params, args=(self.fit_signal[vox]), ftol=10e-5 ) params[vox] = this_params if self.verbose: prog_bar.animate(vox, f_name=f_name) out_params = ozu.nans(self.signal.shape[:3] + (n_params,)) out_params[self.mask] = np.array(params).squeeze() return out_params
def residuals(self): """ The prediction-subtracted residual in each voxel """ out = ozu.nans(self.signal.shape) sig = self._flat_signal fit = self._flat_fit if has_numexpr: out[self.mask] = numexpr.evaluate('sig - fit') else: out[self.mask] = sig - fit return out
def adc(self): """ This is the empirically defined ADC: .. math:: ADC = -log \frac{S}{b S0} """ out = ozu.nans(self.signal.shape) out[self.mask] = ((-1 / self.bvals[self.b_idx][0]) * np.log(self._flat_relative_signal)) return out
def adc(self): """ This is the empirically defined ADC: .. math:: ADC = -log \frac{S}{b S0} """ out = ozu.nans(self.signal.shape) out[self.mask] = ((-1/self.bvals[self.b_idx][0]) * np.log(self._flat_relative_signal)) return out
def model_params(self): """ Find the model parameters using least-squares optimization. """ if self.model_form == 'constrained': n_params = 3 elif self.model_form == 'flexible' or self.model_form == 'ball_and_stick': n_params = 4 else: e_s = "%s is not a recognized model form" % self.model_form raise ValueError(e_s) params = np.empty((self.fit_signal.shape[0], n_params)) if self.verbose: print('Fitting CanonicalTensorModelOpt:') prog_bar = ozu.ProgressBar(self._flat_signal.shape[0]) this_class = str(self.__class__).split("'")[-2].split('.')[-1] f_name = this_class + '.' + inspect.stack()[0][3] # Initialize the starting conditions for the first voxel if self.model_form == 'constrained': this_params = 0, 0, np.mean(self.fit_signal[0]) elif (self.model_form == 'flexible' or self.model_form == 'ball_and_stick'): this_params = (0, 0, np.mean(self.fit_signal[0]), np.mean(self.fit_signal[0])) for vox in xrange(self.fit_signal.shape[0]): # From the second voxel and onwards, we use the end point of the # last voxel as the starting point for this voxel: start_params = this_params # Do the least-squares fitting (setting tolerance to a rather # lenient value?): this_params, status = opt.leastsq(self._err_func, start_params, args=(self.fit_signal[vox]), ftol=10e-5) params[vox] = this_params if self.verbose: prog_bar.animate(vox, f_name=f_name) out_params = ozu.nans(self.signal.shape[:3] + (n_params, )) out_params[self.mask] = np.array(params).squeeze() return out_params
def rmse(model1, model2): """ Calculate the voxel-wise RMSE between one model signal and the other model signal. """ out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] out_flat = np.empty(sig1.shape[0]) for vox in xrange(sig1.shape[0]): out_flat[vox] = ozu.rmse(sig1[vox], sig2[vox]) out[model1.mask] = out_flat return out
def RMSE(self): """ The square-root of the mean of the squared residuals """ # Preallocate the output: out = ozu.nans(self.data.shape[:3]) res = self.residuals[self.mask] if has_numexpr: out[self.mask] = np.sqrt(np.mean(numexpr.evaluate('res ** 2'), -1)) else: out[self.mask] = np.sqrt(np.mean(np.power(res, 2), -1)) return out
def predict_adc(self, sphere): """ The ADC predicted on a sphere (containing points other than the bvecs) """ out = ozu.nans(self.signal.shape[:3] + (sphere.shape[-1], )) tensors_flat = self.tensors[self.mask].reshape((-1, 3, 3)) pred_adc_flat = np.empty((np.sum(self.mask), sphere.shape[-1])) for ii in xrange(len(pred_adc_flat)): pred_adc_flat[ii] = ozt.apparent_diffusion_coef( sphere, tensors_flat[ii]) out[self.mask] = pred_adc_flat return out
def principal_diffusion_direction(self): """ The direction in which the best fit canonical tensor is pointing (in x,y,z coordinates) """ flat_params = self.model_params[self.mask] out_flat = np.empty(flat_params.shape) for vox in xrange(flat_params.shape[0]): if not np.isnan(flat_params[vox, 0]): out_flat[vox] = self.rot_vecs.T[flat_params[vox, 0]] else: out_flat[vox] = [np.nan, np.nan, np.nan] out = ozu.nans(self.model_params.shape) out[self.mask] = out_flat return out
def relative_rmse(model1, model2): """ Given two model objects, compare the model fits to signal-to-signal reliability in the root mean square error sense. Parameters ---------- model1, model2: two objects from a class inherited from BaseModel Returns ------- relative_RMSE: A measure of goodness of fit, relative to measurement reliability. The measure is larger than 1 when the model is worse than signal-to-signal reliability and smaller than 1 when the model is better. Notes ----- More specificially, we calculate the rmse from the fit in model1 to the signal in model2 and then vice-versa. We average between the two results. Then, we calculate the rmse from the signal in model1 to the signal in model2. We normalize the average model-to-signal rmse to the signal-to-signal rmse as a measure of goodness of fit of the model. """ # Assume that the last dimension is the signal dimension, so the dimension # across which the rmse will be calculated: out = ozu.nans(model1.shape[:-1]) sig1 = model1.signal[model1.mask] sig2 = model2.signal[model2.mask] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model2.mask] signal_rmse = ozu.rmse(sig1, sig2) fit1_rmse = ozu.rmse(fit1, sig2) fit2_rmse = ozu.rmse(fit2, sig1) # Average in each element: fit_rmse = (fit1_rmse + fit2_rmse) / 2. rel_rmse = fit_rmse / signal_rmse out[model1.mask] = rel_rmse return out
def fit_reliability(model1, model2): """ Compute the vector angle between the model-predicted signal in each voxel as a measure of model reliability. """ vol_shape = model1.shape[:3] fit1 = model1.fit[model1.mask] fit2 = model2.fit[model1.mask] out_flat = np.empty(fit1.shape[0]) for vox in xrange(out_flat.shape[0]): out_flat[vox] = np.corrcoef(fit1[vox], fit2[vox])[0, 1] out = ozu.nans(vol_shape) out[model1.mask] = out_flat return out