def run(self, options): exec_globals = {'np': np, 'scipy': scipy, 'ivm': self.ivm} # For general Numpy operations we will need a grid to put the # results back into. This is specified by the 'grid' option. # Note that all data is combined using their raw grids so these # must all match if the result is to work - that is the user's job! gridfrom = options.pop("grid", None) if gridfrom is None: grid = self.ivm.main.grid else: grid = self.ivm.data[gridfrom].grid for name, data in self.ivm.data.items(): exec_globals[name] = data.raw() for name in list(options.keys()): proc = options.pop(name) if name in ("exec", "_"): for code in proc: try: exec(code, exec_globals) except: raise QpException( "'%s' is not valid Python code (Reason: %s)" % (code, sys.exc_info()[1])) else: try: result = eval(proc, exec_globals) self.ivm.add(result, grid=grid, name=name) except: raise QpException( "'%s' did not return valid data (Reason: %s)" % (proc, sys.exc_info()[1]))
def _run_reg(worker_id, queue, method_name, mode, reg_data, ref_data, options): """ Generic registration function for asynchronous process """ try: set_local_file_path() method = get_reg_method(method_name) if method is None: raise QpException("Unknown registration method: %s (known: %s)" % (method_name, str(get_plugins("reg-methods")))) if not reg_data: raise QpException("No registration data") elif mode == "moco": return _reg_moco(worker_id, method, reg_data, ref_data, options, queue) elif reg_data[0].ndim == 3: return _reg_3d(worker_id, method, reg_data, ref_data, options, queue) else: return _reg_4d(worker_id, method, reg_data, ref_data, options, queue) except: traceback.print_exc() return worker_id, False, sys.exc_info()[1]
def run(self, options): self.debug("Run") data = self.get_data(options) trans_name = options.pop("transform") output_name = options.pop("output-name", data.name + "_reg") transform = self.ivm.data.get(trans_name, None) if transform is None or "QpReg" not in transform.metadata: transform = self.ivm.extras.get(trans_name, None) if transform is None or "QpReg" not in transform.metadata: raise QpException("Transform not found: %s" % trans_name) method = get_reg_method(transform.metadata["QpReg"]) if method is None: raise QpException("Registration method not found: %s" % transform.metadata["QpReg"]) # Fake queue - we ignore progress reports as this process is not asynchronous queue = multiprocessing.Queue() self.log("Applying transformation to data: %s" % data.name) registered, apply_log = method.apply_transform(data, transform, dict(options), queue) registered = _normalize_output(data, registered, "_reg") self.log(apply_log) self.ivm.add(registered, name=output_name, make_current=True)
def structure_maps(self): processes = get_plugins("processes", "FastProcess") if len(processes) != 1: raise QpException("Can't identify Fast process") struc = self.options.get("struc", None) if struc not in self._ivm.data: raise QpException("Structural image not loaded: %s" % struc) qpdata = self._ivm.data[struc] ivm = ImageVolumeManagement() ivm.add(qpdata) process = processes[0](ivm) fast_options = { "data": qpdata.name, "class": 3, "type": self.options["type"], "output-pve": True, "output-pveseg": False, } process.execute(fast_options) while process.status == Process.RUNNING: time.sleep(1) if process.status == Process.FAILED: raise process.exception # FIXME hack process._complete() return { "gm": ivm.data["%s_pve_1" % qpdata.name], "wm": ivm.data["%s_pve_2" % qpdata.name], "csf": ivm.data["%s_pve_0" % qpdata.name], }
def run(self, options): data = self.get_data(options) if data.ndim != 4: raise QpException("Data must be 4D for DCE PK modelling") roi = self.get_roi(options, data.grid) self.suffix = options.pop('output-suffix', '') if self.suffix != "" and self.suffix[0] != "_": self.suffix = "_" + self.suffix regressors = options.pop('regressors', None) regressor_trs = options.pop('regressor_trs', None) regressor_types = options.pop('regressor_types', None) if regressors is None or regressor_types is None or regressor_trs is None: raise QpException("Regressors, regressor type and regressor TR options must be given") if isinstance(regressors, str): regressors = regressors.split() new_regressors = [] for r in regressors: if not os.path.isabs(r): r = os.path.join(self.indir, r) new_regressors.append(r) regressors = ",".join(new_regressors) if isinstance(regressor_trs, str): try: regressor_trs = [float(v) for v in regressor_trs.split(",")] except ValueError: raise QpException("Regressor TRs should be comma separated list of numbers") elif isinstance(regressor_trs, (int, float)): regressor_trs = [regressor_trs] self.n_regressors = len(regressor_trs) tr = options.pop("tr", None) if tr is None: raise QpException("TR must be given") # Non-compulsary options baseline = options.pop("baseline", 60) data_start_time = options.pop("data-start-time", None) delay_min = options.pop("delay-min", 0) delay_max = options.pop("delay-max", 0) delay_step = options.pop("delay-step", 1) # Use smallest sub-array of the data which contains all unmasked voxels self.grid = data.grid self.bb_slices = roi.get_bounding_box() self.debug("Using bounding box: %s", self.bb_slices) data_bb = data.raw()[tuple(self.bb_slices)] mask_bb = roi.raw()[tuple(self.bb_slices)] #n_workers = data_bb.shape[0] n_workers = 1 args = [data_bb, mask_bb, regressors, regressor_types, regressor_trs, tr, baseline, data_start_time, delay_min, delay_max, delay_step] self.voxels_done = [0] * n_workers self.total_voxels = np.count_nonzero(roi.raw()) self.start_bg(args, n_workers=n_workers)
def reg_4d(cls, reg_data, ref_data, options, queue): """ 4D Registration The default implementation simply registers each volume of the data independently. However, implementations can supply their own more optimal implementation if appropriate :param reg_data: 4D QpData containing data to register. :param ref_data: 3D QpData containing reference data. :param options: Method options as dictionary :param queue: Queue object which method may put progress information on to. Progress should be given as a number between 0 and 1. :return Tuple of three items. First, A QpData containing registered data Second, if options contains ``output-transform : True``, sequence of transformations found, one for each volume in ``reg_data``. Each is either a QpData object containing a sequence of 3 warp images or an Extra object containing a transformation matrix If ``output-transform`` is not given or not supported, returns None instead. Third, log information from the registration as a string. """ if reg_data.ndim != 4: raise QpException("reg_4d expected 4D data") if options.get("output-space", "ref") == "ref": output_space = ref_data else: output_space = reg_data out_data = np.zeros(list(output_space.grid.shape) + [reg_data.nvols]) transforms = [] log = "Default 4D registration using multiple 3d registrations\n" for vol in range(reg_data.shape[-1]): log += "Registering volume %i of %i\n" % (vol+1, reg_data.shape[-1]) reg_vol = NumpyData(reg_data.volume(vol), grid=reg_data.grid, name="regvol") #self.debug("Vol %i of %i" % (vol+1, reg_data.shape[-1])) if vol == options.get("ignore-idx", -1): # Ignore this index (e.g. because it is the same as the ref volume) if options.get("output-space", "ref") != "reg": raise QpException("Can't ignore an index unless the output space is the registration data") out_data[..., vol] = reg_vol.raw() transforms.append(None) else: #self.debug("Calling reg_3d", cls, cls.reg_3d) # We did not remove output-space from the options so regdata should # come back in the appropriate space regdata, transform, vol_log = cls.reg_3d(reg_vol, ref_data, options, queue) out_data[..., vol] = regdata.raw() transforms.append(transform) log += vol_log queue.put(float(vol)/reg_data.shape[-1]) return NumpyData(out_data, grid=output_space.grid, name=reg_data.name), transforms, log
def run(self, options): data_model_name = options.pop("data-model", None) if data_model_name is None: raise QpException("Data model not specified") data_model = self._data_models.get(data_model_name, None) if data_model is None: raise QpException("Unknown data model: %s" % data_model_name) data_model = data_model(self.ivm) data_model_options = options.pop("data-model-options", {}) data_model.options = data_model_options self.log("Created data model: %s\n" % data_model_name) struc_model_name = options.pop("struc-model", None) struc_model_options = options.pop("struc-model-options", {}) if struc_model_name is None: raise QpException("Structure model not specified") struc_model = self._struc_models.get(struc_model_name, None) if struc_model is None: raise QpException("Unknown structure model: %s" % struc_model_name) struc_model = struc_model(self.ivm) struc_model.options = struc_model_options self.log("Created structure model: %s\n" % struc_model_name) param_values = options.pop("param-values", {}) output_param_maps = options.pop("output-param-maps", False) self.log("Getting simulated data\n") ret = struc_model.get_simulated_data( data_model, param_values, output_param_maps=output_param_maps) if output_param_maps: sim_data, param_maps = ret else: sim_data, param_maps = ret, {} output_clean_name = options.pop("output-clean", "") if output_clean_name: self.ivm.add(sim_data.raw().copy(), grid=sim_data.grid, name=output_clean_name, make_current=False) for param, qpdata in param_maps.items(): self.ivm.add(qpdata, name=param, make_current=False) output_name = options.pop("output", "sim_data") self.ivm.add(sim_data, name=output_name, make_current=True) output_roi = output_name + "_roi" if sim_data.ndim > 3: roi_data = sim_data.raw()[..., 0] else: roi_data = sim_data.raw() roi = NumpyData(np.array(roi_data > 0, dtype=np.int), grid=sim_data.grid, roi=True, name=output_roi) self.ivm.add(roi, make_current=False)
def run(self, options): data = self.get_data(options) roi = self.get_roi(options, data.grid) output_name = options.pop("output-name", "%s_pca" % data.name) norm_input = options.pop('norm-input', True) norm_type = options.pop('norm-type', "sigenh") norm_output = options.pop('norm-output', False) n_components = options.pop('n-components', 5) if data.ndim != 4: raise QpException("PCA reduction possible on 4D data only") elif data.nvols <= n_components: raise QpException( "Number of PCA components must be less than number of data volumes" ) pca = PcaFeatReduce(n_components=n_components, norm_input=norm_input, norm_type=norm_type, norm_modes=norm_output) feature_images = pca.get_training_features(data.raw(), roi.raw(), feature_volume=True) for comp_idx in range(n_components): name = "%s%i" % (output_name, comp_idx) self.ivm.add(feature_images[:, :, :, comp_idx], grid=data.grid, name=name, make_current=(comp_idx == 0)) #if options.pop("reduced-data", True): # reduced = pca.pca.inverse_transform(feature_images) # self.ivm.add(reduced, grid=data.grid, name=name + "_reduced") cumulative = 0 var_rows = [] for idx, variance in enumerate(pca.explained_variance()): cumulative += variance var_rows.append([idx, variance, cumulative]) extra = MatrixExtra( output_name + "_variance", var_rows, col_headers=["PCA mode", "Explained variance", "Cumulative"]) self.ivm.add_extra(extra.name, extra) modes = np.zeros((len(pca.mean()), len(pca.modes()) + 1)) cols = [] for idx, mode in enumerate(pca.modes()): modes[:, idx] = mode cols.append("Mode %i" % idx) modes[:, -1] = pca.mean() cols.append("Mean") extra = MatrixExtra(output_name + "_modes", modes, col_headers=cols) self.ivm.add_extra(extra.name, extra)
def roi(self, is_roi): if is_roi: if self.nvols != 1: raise QpException("This data set cannot be an ROI - it is 4D") else: rawdata = self.raw() if not np.all(np.equal(np.mod(rawdata, 1), 0)): raise QpException( "This data set cannot be an ROI - it does not contain integers" ) self._meta["roi"] = is_roi
def run(self, options): """ Generate test data from Fabber model """ kwargs = { "patchsize": int(math.floor(options.pop("num-voxels", 1000)**(1. / 3) + 0.5)), "nt": options.pop("num-vols", 10), "noise": options.pop("noise", 0), "param_rois": options.pop("save-rois", False), } param_test_values = options.pop("param-test-values", None) output_name = options.pop("output-name", "fabber_test_data") grid_data_name = options.pop("grid", None) if not param_test_values: raise QpException("No test values given for model parameters") api = FabberProcess.api(options.pop("model-group", None)) from fabber import generate_test_data test_data = generate_test_data(api, options, param_test_values, **kwargs) data = test_data["data"] self.debug("Data shape: %s", data.shape) if grid_data_name is None: grid = DataGrid(data.shape[:3], np.identity(4)) else: grid_data = self.ivm.data.get(grid_data_name, None) if grid_data is None: raise QpException("Data not found for output grid: %s" % grid_data_name) grid = grid_data.grid self.ivm.add(data, name=output_name, grid=grid, make_current=True) clean_data = test_data.get("clean", None) if clean_data is not None: self.ivm.add(clean_data, name="%s_clean" % output_name, grid=grid, make_current=False) for param, param_roi in test_data.get("param-rois", {}).items(): self.ivm.add(param_roi, name="%s_roi_%s" % (output_name, param), grid=grid)
def get_data(self, options, multi=False): """ Standard method to get the data object the process is to operate on If no 'data' option is specified, go with main data if it exists If 'multi' then allow data to be a list of items :param options: Dictionary of options - ``data`` will be consumed if present :return: QpData instance """ data_name = options.pop("data", None) if data_name is None: if self.ivm.main is None: raise QpException("No data loaded") data = self.ivm.main elif multi and isinstance(data_name, list): # Allow specifying a list of data volumes which are concatenated if not data_name: raise QpException("Empty list given for data") for name in data_name: if name not in self.ivm.data: raise QpException("Data not found: %s" % name) multi_data = [self.ivm.data[name] for name in data_name] nvols = sum([d.nvols for d in multi_data]) self.debug("Multivol: nvols=%i", nvols) grid = None num_vols = 0 for data_item in multi_data: if grid is None: grid = data_item.grid data = np.zeros(list(grid.shape) + [ nvols, ]) data_item = data_item.resample(grid) if data_item.nvols == 1: rawdata = np.expand_dims(data_item.raw(), 3) else: rawdata = data_item.raw() data[..., num_vols:num_vols + data_item.nvols] = rawdata num_vols += data_item.nvols data = NumpyData(data, grid=grid, name="multi_data") else: if data_name in self.ivm.data: data = self.ivm.data[data_name] else: raise QpException("Data not found: %s" % data_name) return data
def run(self, options): data = self.get_data(options) if data.ndim != 4: raise QpException("Data must be 4D for DCE PK modelling") roi = self.get_roi(options, data.grid) self.suffix = options.pop('suffix', '') if self.suffix != "": self.suffix = "_" + self.suffix t1_name = options.pop("t1", "T10") if t1_name not in self.ivm.data: raise QpException("Could not find T1 map: %s" % t1_name) t1 = self.ivm.data[t1_name].resample(data.grid) R1 = options.pop('r1') R2 = options.pop('r2') DelT = options.pop('dt') InjT = options.pop('tinj') TR = options.pop('tr') TE = options.pop('te') FA = options.pop('fa') self.thresh = options.pop('ve-thresh') Dose = options.pop('dose', 0) model_choice = options.pop('model') # Baseline defaults to time points prior to injection baseline_tpts = int(1 + InjT / DelT) self.log("First %i time points used for baseline normalisation\n" % baseline_tpts) baseline = np.mean(data.raw()[:, :, :, :baseline_tpts], axis=-1) self.grid = data.grid self.nvols = data.nvols self.roi = roi.raw() data_vec = data.raw()[self.roi > 0] t1_vec = t1.raw()[self.roi > 0] self.baseline = baseline[self.roi > 0] # Normalisation of the image - convert to signal enhancement data_vec = data_vec / (np.tile(np.expand_dims(self.baseline, axis=-1), (1, data.nvols)) + 0.001) - 1 args = [ data_vec, t1_vec, R1, R2, DelT, InjT, TR, TE, FA, Dose, model_choice ] self.start_bg(args)
def resamp(self, qpdata): """ Resample a map according to resampling options """ resamp_options = dict(self.options.get("resampling", {})) if resamp_options: resamp_processes = get_plugins("processes", "ResampleProcess") if len(resamp_processes) != 1: raise QpException("Can't identify Resampling process") ivm = ImageVolumeManagement() ivm.add(qpdata) process = resamp_processes[0](ivm) resamp_options.update({ "data": qpdata.name, "output-name": "output_res" }) process.execute(resamp_options) while process.status == Process.RUNNING: time.sleep(1) if process.status == Process.FAILED: raise process.exception # FIXME hack process._complete() return ivm.data["output_res"] else: return qpdata
def get_projected_test_features(self, data, roi=None, smooth_timeseries=None, feature_volume=False): """ Return features for each voxel from previously trained PCA modes :param data: 4D data set :param roi: Optional 3D ROI :param feature_volume: determines whether the features are returned as a list or an image :return: If ``feature_volume``, 4D array with the same 3d dimensions as data and 4th dimension=number of PCA components. Otherwise, 2D array whose first dimension is unmasked voxels and 2nd dimension is the PCA components """ data_inmask, roi = self._mask(data, roi, smooth_timeseries) #Projecting the data using training set PCA if data_inmask.shape[1] != self.pca.mean_.shape[0]: raise QpException( "Input data length does not match previous training data") reduced_data = self.pca.transform(data_inmask) # Scaling features if self.norm_modes: self.debug("Normalising PCA modes") reduced_data = norm.normalise(reduced_data, "indiv") if not feature_volume: return reduced_data return _flat_data_to_image(reduced_data, roi)
def get_rundata(self): rundata = {} rundata["model-group"] = "t1" rundata["save-mean"] = "" rundata["save-model-fit"] = "" rundata["noise"] = "white" rundata["max-iterations"] = "20" rundata["model"] = "vfa" rundata["tr"] = self.tr.spin.value() / 1000 multivol = self.multivol_choice.combo.currentIndex() == 0 if multivol and self.multivol_combo.currentText() in self.ivm.data: rundata["data"] = self.multivol_combo.currentText() fas = self.multivol_fas.values() nvols = self.ivm.data[self.multivol_combo.currentText()].nvols if nvols != len(fas): raise QpException( "Number of flip angles must match the number of volumes in the selected data (%i)" % nvols) for idx, fa in enumerate(fas): rundata["fa%i" % (idx + 1)] = fa else: rundata["data"] = [] for r in range(self.singlevol_table.rowCount()): rundata["data"].append(self.singlevol_table.item(r, 0).text()) rundata["fa%i" % (r + 1)] = float( self.singlevol_table.item(r, 1).text()) return rundata
def run(self, options): data = self.get_data(options) output_name = options.pop("output-name", "%s_noisy" % data.name) if "std" in options: std = float(options.pop("std")) elif "percent" in options: percent = float(options.pop("percent")) std = np.mean(data.raw()) * float(percent) / 100 elif "snr" in options: snr = float(options.pop("snr")) roi = self.get_roi(options, grid=data.grid) signal = np.mean(data.raw()[roi.raw() > 0]) std = signal / snr else: raise QpException( "AddNoiseProcess: Must specify either std, percent or snr") noise = np.random.normal(loc=0, scale=std, size=list(data.grid.shape) + [ data.nvols, ]) if data.nvols == 1: noise = np.squeeze(noise, -1) noisy_data = data.raw() + noise self.ivm.add(noisy_data, grid=data.grid, name=output_name, make_current=True)
def run_threshold(self): # Check if an image and roi exists or otherwise throw an error if self.ivm.vol is None: raise QpException("No data loaded") if self.ivm.current_roi is None: raise QpException("No ROI loaded") slice = int(self.val_s1.text()) thresh = float(self.val_t1.text()) img = self.ivm.vol[:, :, :, slice1] img = img * (self.ivm.current_roi > 0) img[img1 < thresh] = 0 self.ivm.add_data(img, name='thresh', make_current=True)
def get_roi(self, options, grid=None, use_current=False): """ Standard method to get the ROI the process is to operate on If no 'roi' option is specified, go with currently selected ROI, if it exists. :param options: Dictionary of options - ``roi`` will be consumed if present :param grid: If specified, return ROI on this grid :param use_current: If True, return current ROI if no ROI specified :return: QpData instance. If no ROI can be found and ``grid`` is specified, will return an ROI where all voxels are unmasked. """ roi_name = options.pop("roi", None) if roi_name is None or roi_name.strip() == "": if use_current and self.ivm.current_roi is not None: roidata = self.ivm.current_roi elif grid is not None: roidata = NumpyData(np.ones(grid.shape[:3]), grid=grid, name="dummy_roi", roi=True) else: return None else: if roi_name in self.ivm.rois: roidata = self.ivm.rois[roi_name] else: raise QpException("ROI not found: %s" % roi_name) if grid is not None: roidata = roidata.resample(grid) return roidata
def setChoices(self, choices, return_values=None): """ Set the list of options to be chosen from :param choices: Sequence of strings :param return_values: Optional matching sequence of strings to be returned as the ``value`` for each choice """ if return_values is None: return_values = list(choices) if len(return_values) != len(choices): raise QpException("Number of return values must match number of choices") self.return_values = return_values self.choice_map = dict(zip([str(choice) for choice in choices], return_values)) try: self.blockSignals(True) self.clear() for choice in choices: self.addItem(str(choice)) self.setCurrentIndex(0) finally: self.blockSignals(False) self._changed()
def _smooth_pv(self, qpdata, sigma): """ Do Gaussian smoothing on a partial volume map Typically when the map is a discrete ROI and we want to smoothly blend it into the other tissue PV maps :param qpdata: PV map :param sigma: Gaussian kernel std.dev in mm :return: Smoothed PV map as Numpy array """ smooth_processes = get_plugins("processes", "SmoothingProcess") if len(smooth_processes) != 1: raise QpException("Can't identify smoothing process") ivm = ImageVolumeManagement() ivm.add(qpdata) process = smooth_processes[0](ivm) smooth_options = { "data": qpdata.name, "sigma": sigma, "output-name": "output_smooth", } process.execute(smooth_options) while process.status == Process.RUNNING: time.sleep(1) if process.status == Process.FAILED: raise process.exception # FIXME hack process._complete() return ivm.data["output_smooth"].raw()
def run(self, options): data = self.get_data(options) roi = self.get_roi(options, data.grid) n_clusters = options.pop('n-clusters', 5) invert_roi = options.pop('invert-roi', False) output_name = options.pop('output-name', data.name + '_clusters') kmeans_data, mask = data.mask(roi, invert=invert_roi, output_flat=True, output_mask=True) start1 = time.time() if data.nvols > 1: # Do PCA reduction norm_data = options.pop('norm-data', True) norm_type = options.pop('norm-type', "sigenh") n_pca = options.pop('n-pca', 5) reduction = options.pop('reduction', 'pca') if reduction == "pca": self.log("Using PCA dimensionality reduction") pca = PCA(n_components=n_pca, norm_input=True, norm_type=norm_type, norm_modes=norm_data) kmeans_data = pca.get_training_features(kmeans_data) else: raise QpException("Unknown reduction method: %s" % reduction) else: kmeans_data = kmeans_data[:, np.newaxis] kmeans = cl.KMeans(init='k-means++', n_clusters=n_clusters, n_init=10, n_jobs=1) kmeans.fit(kmeans_data) self.log("Elapsed time: %s" % (time.time() - start1)) label_image = np.zeros(data.grid.shape, dtype=np.int) label_image[mask] = kmeans.labels_ + 1 self.ivm.add(NumpyData(label_image, grid=data.grid, name=output_name, roi=True), make_current=True)
def _new_roi(self): dialog = QtGui.QDialog(self) dialog.setWindowTitle("New ROI") vbox = QtGui.QVBoxLayout() dialog.setLayout(vbox) optbox = OptionBox() optbox.add("ROI name", TextOption(), key="name") optbox.add("Data space from", DataOption(self.ivm), key="grid") vbox.addWidget(optbox) buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) vbox.addWidget(buttons) ok = dialog.exec_() if ok: roiname = optbox.option("name").value gridfrom = optbox.option("grid").value if not roiname or not gridfrom: raise QpException("Must provide a ROI name and a dataset to base it on") grid = self.ivm.data[gridfrom].grid roidata = np.zeros(grid.shape, dtype=np.int) self.ivm.add(NumpyData(roidata, grid=grid, roi=True, name=roiname), make_current=True) # Throw away old history. FIXME is this right, should we keep existing data and history? # Also should we cache old history in case we go back to this ROI? self._history = [] self._undo_btn.setEnabled(False) self.options.option("roi").value = roiname
def set_data_name(self, name): """ Set the name of the data item being displayed """ if name not in self.ivm.data: raise QpException("Data not found: %s" % name) else: idx = self.data_combo.findText(name) self.data_combo.setCurrentIndex(idx)
def _add_activation_mask(self, struc, qpdata, strucs): """ Add an activation mask This is a binary mask which splits a parent structure (e.g. GM) into two separate structures, inside and outside the mask, which can have different parameter properties :param struc: Structure dictionary. Must define the parent structure. :param qpdata: Activation mask map defined on same grid as existing data :param strucs: Dictionary of structure name to PV map for existing structures. Will be updated to include split parent structure. """ # Activation mask - replace parent structure parent_struc = struc.get("parent_struc", None) if parent_struc is None: raise QpException( "Parent structure not defined for activation mask: %s" % struc["name"]) elif parent_struc not in strucs: raise QpException( "Parent structure '%s' not found in structures list for activation mask: %s" % (parent_struc, struc["name"])) activation_mask = np.copy(qpdata.raw()) if "region" in struc: # If a specific region is given, isolate it activation_mask[activation_mask != struc["region"]] = 0 if qpdata.roi: # If mask is an ROI, make it take values 0 and 1 activation_mask[activation_mask <= 0] = 1 activation_mask[activation_mask > 0] = 1 # Activation structure takes over parent structure within the mask. # We do this by multiplication so the activation mask can in principle # be probabilistic activation_mask = activation_mask.astype(np.float32) parent_qpdata = strucs[parent_struc] parent_data = np.copy(parent_qpdata.raw()) activation_data = np.copy(parent_qpdata.raw()) activation_data *= activation_mask parent_data *= (1 - activation_mask) strucs[parent_struc] = NumpyData(parent_data, grid=parent_qpdata.grid, name=parent_qpdata.name) strucs[struc["name"]] = NumpyData(activation_data, grid=parent_qpdata.grid, name=struc["name"])
def _load(self): if self._desc is not None: res = self._imgs.itemData(self._imgs.currentIndex()) atlas = self._registry.loadAtlas(self._desc.atlasID, loadSummary=False, resolution=res) is_roi = self._desc.atlasType == "label" new_name = self._load_options.option("name").value add_name = self._load_options.option("data").value add = self._load_options.option("add").value == "add" load_all = self._load_options.option("regions").value == "all" vol = None if not load_all: indexes = self._label_table.selectionModel().selectedRows() vol = int(self._label_model.item(indexes[0].row(), 0).text()) new_data = fslimage_to_qpdata(atlas, vol=vol, name=new_name, roi=is_roi) if add and add_name in self.ivm.data: # User wants to add the region to an existing data set if load_all: raise QpException( "Cannot add data to existing data set when loading all regions" ) orig_data = self.ivm.data[add_name] if not orig_data.grid.matches(new_data.grid): raise QpException( "Can't add data to existing data set - grids do not match" ) if is_roi and not orig_data.roi: raise QpException( "Can't add data to existing data set - it is not an ROI" ) new_data = NumpyData(orig_data.raw() + new_data.raw(), grid=new_data.grid, name=add_name, roi=is_roi) self.ivm.add(new_data, make_current=True)
def moco(cls, moco_data, ref, options, queue): """ Motion correction The default implementation uses the ``reg_4d`` function to perform motion correction as registration to a common reference, however this function can have a custom implementation specific to motion correction if required. :param moco_data: A single 4D QpData containing data to motion correct. :param ref: Either 3D QpData containing reference data, or integer giving the volume index of ``moco_data`` to use :param options: Method options as dictionary :param queue: Queue object which method may put progress information on to. Progress should be given as a number between 0 and 1. :return Tuple of three items. First, motion corrected QpData in the same space as ``moco_data`` Second, if options contains ``output-transform : True``, sequence of transformations found, one for each volume in ``reg_data``. Each is either a QpData object containing a sequence of 3 warp images or an Extra object containing a transformation matrix If ``output-transform`` is not given or not supported, returns None instead. Third, log information from the registration as a string. """ if moco_data.ndim != 4: raise QpException("Cannot motion correct 3D data") log = "Default MOCO implementation using multiple 3d registrations\n" if isinstance(ref, int): if ref >= moco_data.nvols: raise QpException( "Reference volume index of %i, but data has only %i volumes" % (ref, moco_data.nvols)) ref = moco_data.volume(ref) options["output-space"] = "reg" out_data, transforms, moco_log = cls.reg_4d(moco_data, ref, options, queue) log += moco_log return out_data, transforms, log
def _show_prior_options(self): dlg = PriorsDialog(self, ivm=self.ivm, rundata=self._fabber_options) try: api = FabberProcess.api() options = self._fix_data_params(api) params = api.get_model_params(options) except Exception as exc: raise QpException("Unable to get list of model parameters\n\n%s\n\nModel options must be set before parameters can be listed" % str(exc)) dlg.set_params(params) dlg.fit_width() dlg.exec_()
def _get_cmdline(self, options): cmd = options.pop("cmd", None) if cmd is None: raise QpException("No command provided") cmdline = options.pop("cmdline", None) argdict = options.pop("argdict", {}) if cmdline is None and not argdict: raise QpException("No command arguments provided") for arg, value in argdict.items(): if value != "": cmdline += " --%s=%s" % (arg, value) else: cmdline += " --%s" % arg cmd = self._find(cmd) cmdline = cmd + " " + cmdline LOG.debug(cmdline) return cmdline
def run(self, options): """ Run the process """ from oxasl import Workspace, calib from fsl.data.image import Image data = self.get_data(options) img = Image(data.raw(), name=data.name) roi = self.get_roi(options, data.grid) options["mask"] = Image(roi.raw(), name=roi.name) calib_name = options.pop("calib-data") if calib_name not in self.ivm.data: raise QpException("Calibration data not found: %s" % calib_name) else: calib_img = Image(self.ivm.data[calib_name].resample( data.grid).raw(), name=calib_name) ref_roi_name = options.pop("ref-roi", None) if ref_roi_name is not None: if ref_roi_name not in self.ivm.rois: raise QpException("Reference ROI not found: %s" % calib_name) else: options["ref_mask"] = Image( self.ivm.rois[ref_roi_name].resample(data.grid).raw(), name=ref_roi_name) options["calib_method"] = options.pop("method", None) output_name = options.pop("output-name", data.name + "_calib") logbuf = six.StringIO() wsp = Workspace(log=logbuf, **options) wsp.calib = calib_img ## FIXME variance mode calibrated = calib.calibrate(wsp, img) self.log(logbuf.getvalue()) self.ivm.add(name=output_name, data=calibrated.data, grid=data.grid, make_current=True)
def load(fname): """ Load a data file :return: QpData instance """ if os.path.isdir(fname): return DicomFolder(fname) elif fname.endswith(".nii") or fname.endswith(".nii.gz"): return NiftiData(fname) else: raise QpException("%s: Unrecognized file type" % fname)