def _prepare_classif_for_VipHomotopic_Cortical(classif, filling_size): array_classif = np.asarray(classif) # Dilate the cortex by 1 voxel, otherwise the topologically spherical front # initialized by VipHomotopic (Cortical surface mode) is at the inside of # the border, which means that the final segmentation will be one voxel # off. The default -fclosing parameter prevents this from happening in # sulci by closing them, but it still happens at the top of gyri. Setting a # fake voxel size of 1 mm is a trick for expressing the dilation in terms # of voxels. # # The 1-voxel border is necessary for AimsMorpho{Dilation,Erosion}. aimsdata_classif = aims.Volume_S16(classif.getSize(), [1, 1, 1]) aimsdata_classif[:] = classif[:] saved_voxel_size = classif.header()["voxel_size"][:3] aimsdata_classif.setVoxelSize(1, 1, 1) dilated = aimsalgo.AimsMorphoDilation(aimsdata_classif, 1) # Restore the voxel size in case the header is shared with the aims.Volume # that aimsdata_classif was created from (classif). BUG: restoring the # value like this is not thread-safe! aimsdata_classif.setVoxelSize(saved_voxel_size) del aimsdata_classif array_dilated = dilated.np tmp_classif = aims.Volume(classif) array_tmp_classif = np.asarray(tmp_classif) array_tmp_classif[np.logical_and(array_tmp_classif == CSF_LABEL, array_dilated != 0)] = CORTEX_LABEL del dilated, array_dilated # This almost-white region will serve as a first boundary in VipHomotopic, # before the final dilation towards the white matter. This helps restore # the continuity of the white matter so that the homotopic criterion # chooses the right connections (i.e. do not create spurious strands or # planes through the cortex). white = aims.Volume_S16(classif.gteSize(), [1, 1, 1]) white[:] = classif[:] # Restore the header (in particular the voxel_size), which may not have # been copied in the constructor because a border is requested. white.header().update(classif.header()) array_white = white.np array_white[array_white != WHITE_LABEL] = CSF_LABEL dilated_white = aimsalgo.AimsMorphoDilation(white, filling_size) del white, array_white array_dilated_white = dilated_white.np STEP1_FRONT_BARRIER = 199 array_tmp_classif[array_dilated_white != 0] = STEP1_FRONT_BARRIER del dilated_white, array_dilated_white array_tmp_classif[array_classif == WHITE_LABEL] = WHITE_LABEL return tmp_classif
def execution(self, context): from soma import aims graph = aims.read(self.read.fullPath()) aims.GraphManip.buckets2Volume(graph) if self.extract_volume: vol = graph[self.extract_volume] else: atts = [ x for x in graph.keys() if isinstance(graph[x], aims.rc_ptr_Volume_S16) ] if len(atts) == 0: raise RuntimeError(_t_('the ROI graph contains no voxel data')) elif len(atts) > 1: raise RuntimeError( _t_('the ROI graph contains several volumes. ' 'Select the extract_volume parameter as one in ') + '( ' + ', '.join(atts) + ' )') vol = graph[atts[0]] # handle bounding box which may have cropped the data bmin = graph['boundingbox_min'] bmax = graph['boundingbox_max'] context.write('vol:', vol) context.write('bmin:', bmin, ', bmax:', bmax, ', size:', vol.getSize()) if bmin[:3] != [0, 0, 0] \ or bmax[:3] != [x+1 for x in vol.getSize()[:3]]: context.write('enlarging') # needs expanding in a bigger volume vol2 = aims.Volume_S16(bmax[0] + 1, bmax[1] + 1, bmax[2] + 1) vol2.fill(0) ar = vol2.np ar[bmin[0]:bmax[0] + 1, bmin[1]:bmax[1] + 1, bmin[2]:bmax[2] + 1, :] \ = vol.np if self.extract_contours == 'Yes': ar_copy = ar.copy() for label in [v['roi_label'] for v in graph.vertices()]: ind = list(zip(*np.where(ar_copy == label))) for i in ind: erase = True for neigh in neighbors(*i): if ar_copy[neigh] != label: erase = False if erase: ar[i] = 0 vol2.copyHeaderFrom(vol.header()) aims.write(vol2, self.write.fullPath()) else: # bounding box OK aims.write(vol.get(), self.write.fullPath()) registration.getTransformationManager().copyReferential( self.read, self.write) if self.removeSource: for f in self.read.fullPaths(): shelltools.rm(f)
def subject_labeling(gfile, dict_bck, translation, mask, vol_size, n_opal, distmap_list, bck_list, proba_list, label_list, patch_sizes): ''' Label a subject sulcal graph (.arg file) for a specific pattern search using the SNIPE method ''' print('Labeling %s' % gfile) distmap_list = [aims.Volume(d) for d in distmap_list] # Extract bucket fm = aims.FastMarching() if gfile not in dict_bck: graph = aims.read(gfile) side = gfile[gfile.rfind('/') + 1:gfile.rfind('/') + 2] data = extract_data(graph, flip=True if side == 'R' else False) sbck = data['bck2'] else: sbck = dict_bck[gfile] sbck = np.array(sbck) sbck += translation sbck = np.asarray(apply_imask(sbck, mask)) # Extract distmap fm = aims.FastMarching() vol = aims.Volume_S16(vol_size[0], vol_size[1], vol_size[2]) vol.fill(0) for p in sbck: vol[p[0], p[1], p[2]] = 1 sdistmap = fm.doit(vol, [0], [1]) adistmap = np.asarray(sdistmap) adistmap[adistmap > pow(10, 10)] = 0 # Compute classification grading_list = [] for ps in patch_sizes: print('** PATCH SIZE %i' % ps) opm = OptimizedPatchMatch(patch_size=[ps, ps, ps], segmentation=False, k=n_opal) list_dfann = opm.run(distmap=sdistmap, distmap_list=distmap_list, bck_list=bck_list, proba_list=proba_list) grading_list.append(grading(list_dfann, label_list)) grade = np.nanmean(np.mean(grading_list, axis=0)) ypred = 1 if grade > 0 else 0 return ypred, grade
def subject_labeling(self, gfile): print('Labeling %s' % gfile) # Extract bucket fm = aims.FastMarching() if gfile not in self.dict_bck: graph = aims.read(gfile) side = gfile[gfile.rfind('/') + 1:gfile.rfind('/') + 2] data = extract_data(graph, flip=True if side == 'R' else False) sbck = data['bck2'] else: sbck = self.dict_bck[gfile] sbck = np.array(sbck) sbck += self.translation sbck = np.asarray(apply_imask(sbck, self.amask)) # Extract distmap fm = aims.FastMarching() vol = aims.Volume_S16(self.vol_size[0], self.vol_size[1], self.vol_size[2]) vol.fill(0) for p in sbck: vol[p[0], p[1], p[2]] = 1 sdistmap = fm.doit(vol, [0], [1]) adistmap = np.asarray(sdistmap) adistmap[adistmap > pow(10, 10)] = 0 # Compute classification grading_list = [] for ps in self.patch_sizes: print('** PATCH SIZE %i' % ps) opm = OptimizedPatchMatch(patch_size=[ps, ps, ps], segmentation=False, k=self.n_opal) list_dfann = opm.run(distmap=sdistmap, distmap_list=self.distmap_list, bck_list=self.bck_list, proba_list=self.proba_list) grading_list.append(grading(list_dfann, self.label_list)) grade = np.nanmean(np.mean(grading_list, axis=0)) ypred = 1 if grade > 0 else 0 return ypred, grade
def learning(self, gfile_list): self.bck_list, self.label_list, self.distmap_list = [], [], [] # Extract buckets and labels from the graphs for gfile in gfile_list: if gfile not in self.dict_bck: graph = aims.read(gfile) side = gfile[gfile.rfind('/') + 1:gfile.rfind('/') + 2] data = extract_data(graph, flip=True if side == 'R' else False) label = 0 fn = [] for name in data['names']: if name.startswith(self.pattern): label = 1 fn.append( sum([ 1 for n in self.names_filter if name.startswith(n) ])) bck_filtered = np.asarray(data['bck2'])[np.asarray(fn) == 1] # save data self.dict_bck[gfile] = data['bck2'] self.dict_label[gfile] = label self.dict_bck_filtered[gfile] = bck_filtered self.bck_list.append(self.dict_bck[gfile]) self.label_list.append(self.dict_label[gfile]) # Compute volume size + mask bb = np.array([[100., -100.], [100., -100.], [100., -100.]]) for gfile in gfile_list: abck = np.asarray(self.dict_bck[gfile]) for i in range(3): bb[i, 0] = min(bb[i, 0], int(min(abck[:, i])) - 1) bb[i, 1] = max(bb[i, 1], int(max(abck[:, i])) + 1) self.translation = [-int(bb[i, 0]) + 11 for i in range(3)] self.vol_size = [int((bb[i, 1] - bb[i, 0])) + 22 for i in range(3)] # Compute distmap volumes + mask fm = aims.FastMarching() vol_filtered = aims.Volume_S16(self.vol_size[0], self.vol_size[1], self.vol_size[2]) for gfile in gfile_list: bck = np.asarray(self.dict_bck[gfile]) + self.translation bck_filtered = np.asarray(self.dict_bck_filtered[gfile]) if len(bck_filtered) != 0: bck_filtered += self.translation # compute distmap vol = aims.Volume_S16(self.vol_size[0], self.vol_size[1], self.vol_size[2]) vol.fill(0) for p in bck: vol[p[0], p[1], p[2]] = 1 distmap = fm.doit(vol, [0], [1]) adistmap = np.asarray(distmap) adistmap[adistmap > pow(10, 10)] = 0 self.distmap_list.append(distmap) # compute mask for p in bck_filtered: vol_filtered[p[0], p[1], p[2]] = 1 dilation = aimsalgo.MorphoGreyLevel_S16() mask = dilation.doDilation(vol_filtered, 5) self.amask = np.asarray(mask) # Compute proba_list y_train = self.label_list class_sample_count = np.array( [len(np.where(y_train == t)[0]) for t in np.unique(y_train)]) w = [ max(1., class_sample_count[1] / float(class_sample_count[0])), max(1., class_sample_count[0] / float(class_sample_count[1])) ] self.proba_list = [w[yi] for yi in y_train]
def fix_cortex_topology(input_classif, filling_size=2., fclosing=10.): """Fix the topology of a cortical segmentation. The topology of a hollow sphere is imposed onto a voxelwise segmentation of the cortex, which consists of the following labels: Label 0 (`CSF_LABEL`) Outside of the cortex, corresponding to the cerebrospinal fluid, defined in 26-connectivity. Label 100 (`CORTEX_LABEL`) The cortex itself, defined using 6-connectivity. Label 200 (`WHITE_LABEL`) Inside of the cortex, corresponds to the white matter, defined in 26-connectivity. Parameters ---------- classif: aims.Volume The input voxelwise classification. filling_size: float The size, in millimetres, of the largest holes in either cortical boundary that will be filled. This must be kept smaller than the smallest cortical thickness in the image (see `Method` below for a more precise description of this parameter). The default value is 2 mm, which is appropriate for a human brain. fclosing: float The radius, in millimetres, of the morphological closing which is used by VipHomotopic in Cortical surface mode to retrieve the brain's outer envelope. The default value, 10 mm, is appropriate for a human brain. Returns ------- The topology-corrected voxelwise classification is returned in an `aims.Volume_S16`. Raises ------ OSError This function throws ``OSError`` if ``VipHomotopic`` cannot be found or executed. soma.subprocess.CalledProcessError This exception can occur if ``VipHomotopic``, which is in charge of the homotopic morphological operations, terminates with an error. Environment ----------- This function needs the ``VipHomotopic`` command from the Morphologist image segmentation pipeline to reside in the ``PATH``. Note that the original ``VipHomotopic`` has hard-coded limits on the number of iterations for morphological operations, which may be exceeded when working on high-resolution (sub-millimetre) images. Input/output ------------ The command ``VipHomotopic``, which is used to perform the homotopic morphological operations, reports progress on stdout/stderr. Images are passed to ``VipHomotopic`` using files under a temporary directory allocated with `tempfile.mkdtemp`. Method ------ The topology correction is done in two main steps: 1. A topologically spherical bounding box of the brain is computed and dilated towards the inside until it reaches the white matter. This retrieves a topologically correct object which fits the grey--white boundary. 2. The previous object is eroded from the inside in the region where it overlaps with the cortex. This retrieves a topologically correct pial boundary. Each of these main steps is performed in two sub-steps: first the homotopic morpholological operation is performed until a boundary which is dilated by `filling_size`, then to the original boundary. This guides the front propagation, in order to prevent the formation of spurious strands. This method will change voxels from the cortex class to either the white matter or the CSF class, as needed to ensure the topology. Note that the output is not a deterministic function of the input, because the homotopic operations use a pseudo-random order for the front propagation. """ fclosing = float(fclosing) assert fclosing >= 0 filling_size = float(filling_size) assert filling_size >= 0 # VipHomotopic only works with 16-bit signed integer voxels. conv = aims.ShallowConverter(intype=input_classif, outtype="Volume_S16") classif = conv(input_classif) tmp_classif = _prepare_classif_for_VipHomotopic_Cortical( classif, filling_size) tmp_dir = None try: tmp_dir = tempfile.mkdtemp(prefix="highres-cortex.") aims.write(tmp_classif, os.path.join(tmp_dir, "tmp_classif.nii.gz")) del tmp_classif with open(os.path.join(tmp_dir, "fake.han"), "w") as f: f.write("sequence: unknown\n" "gray: mean: 120 sigma: 10\n" "white: mean: 433 sigma: 10\n") # VipHomotopic in Cortical surface mode retrieves a spherical # grey--white boundary by iteratively eroding the bounding box of the # cortex in a homotopic manner. It will proceed in two steps, first # stopping at STEP1_FRONT_BARRIER, and finally at WHITE_LABEL. subprocess.check_call([ "VipHomotopic", "-mode", "C", "-input", "tmp_classif.nii.gz", "-classif", "tmp_classif.nii.gz", "-hana", "fake.han", "-fclosing", repr(fclosing), "-output", "cortex.nii.gz" ], cwd=tmp_dir) aims.write(classif, os.path.join(tmp_dir, "classif.nii.gz")) # First boundary to guide VipHomotopic (prevent leaking through holes # in sulci). aimsdata_classif = aims.Volume_S16(classif.getSize, [1, 1, 1]) aimsdata_classif[:] = classif[:] # Restore the header (in particular the voxel_size), which may not have # been copied in the constructor because a border is requested. aimsdata_classif.header().update(classif.header()) eroded = aimsalgo.AimsMorphoErosion(aimsdata_classif, filling_size) del classif, aimsdata_classif aims.write(eroded, os.path.join(tmp_dir, "eroded.nii.gz")) del eroded # The spherical grey--white boundary is dilated in a homotopic manner # until the border of eroded_classif is reached. subprocess.check_call([ "VipHomotopic", "-mode", "H", "-input", "eroded.nii.gz", "-cortex", "cortex.nii.gz", "-fclosing", "0", "-output", "bigsulci.nii.gz" ], cwd=tmp_dir) subprocess.check_call([ "VipHomotopic", "-mode", "H", "-input", "classif.nii.gz", "-cortex", "bigsulci.nii.gz", "-fclosing", "0", "-output", "pial_surface.nii.gz" ], cwd=tmp_dir) cortex = aims.read(os.path.join(tmp_dir, "cortex.nii.gz")) pial_surface = aims.read(os.path.join(tmp_dir, "pial_surface.nii.gz")) finally: shutil.rmtree(tmp_dir, ignore_errors=True) array_cortex = np.asarray(cortex) array_pial_surface = np.asarray(pial_surface) array_cortex[array_cortex == 0] = 200 array_cortex[array_cortex == 255] = 100 array_cortex[array_pial_surface != 0] = 0 return cortex
def execution(self, context): fic = self.file_of_point a = aims.Volume_S16(self.dimX, self.dimY, self.dimZ) a.setVoxelSize(self.sizeX, self.sizeY, self.sizeZ) # mettre les valeurs : fic = open(self.file_of_point.fullPath(), 'r') tmp = 'x' b = 1 # en cas d'erreurs while tmp != '': tmp = fic.readline() tab = tmp.split() if len(tab) == 4: v = tab[0] v = int(v) x = tab[1] x = int(x) y = tab[2] y = int(y) z = tab[3] z = int(z) if (x <= self.dimX) and (y <= self.dimY) and (z <= self.dimZ): a.setValue(v, x, y, z) elif (x > self.dimX): context.write( 'dimX is strong - too long - perhaps "file_of_point" contains error' ) b = 0 break elif (y > self.dimY): context.write( 'dimY is strong - too long - perhaps "file_of_point" contains error' ) b = 0 break elif (z > self.dimZ): context.write( 'dimZ is strong - too long - perhaps "file_of_point" contains error' ) b = 0 break fic.close() # write image if (b == 1): context.write('write image') aims.write(a, self.image_output.fullPath()) if self.create_roigraph == 'yes': if self.graph_output is not None: command = [ 'AimsGraphConvert', '-i', self.image_output, '-o', self.graph_output, '--roi', '--bucket' ] context.system(*command) else: context.write( 'graph_output is mandatory because you are chose create_roigraph=yes !' ) else: context.write('correct error and try again')
def _run_process(self): # Compute voronoi mri = aims.read(self.t1mri) true_graph = aims.read(self.true_graph) vs = true_graph['voxel_size'] vvol = vs[0] * vs[1] * vs[2] true_bck, true_names, _ = extract_data(true_graph) nlist = list(set(true_names)) dnames = {k: v + 1 for k, v in zip(nlist, range(len(nlist)))} dnum = {v + 1: k for k, v in zip(nlist, range(len(nlist)))} fm = aims.FastMarching() vol = aims.Volume_S16(mri.getSizeX(), mri.getSizeY(), mri.getSizeZ()) vol.fill(0) for p, n in zip(true_bck, true_names): vol[p[0], p[1], p[2]] = dnames[n] fm.doit(vol, [0], list(dnames.values())) vor = fm.voronoiVol() # Compute error rates re = pd.DataFrame(index=[str(g) for g in self.labeled_graphs]) for gfile in self.labeled_graphs: graph = aims.read(gfile) bck, _, labels = extract_data(graph) y_pred = [ vor[int(round(p[0])), int(round(p[1])), int(round(p[2]))][0] for p in bck ] names = np.asarray([dnum[n] for n in y_pred]) for ss in self.sulci_side_list: names_ss = labels[names == ss] labels_ss = names[labels == ss] re.ix[gfile, 'TP_' + str(ss)] = float(len(names_ss[names_ss == ss])) * vvol re.ix[gfile, 'FP_' + str(ss)] = float(len(labels_ss[labels_ss != ss])) * vvol re.ix[gfile, 'FN_' + str(ss)] = float(len(names_ss[names_ss != ss])) * vvol re.ix[gfile, 's_' + str(ss)] = float(len(names_ss)) * vvol sum_s = sum( [re.ix[gfile, 's_' + str(ss)] for ss in self.sulci_side_list]) for ss in self.sulci_side_list: FP = re.ix[gfile, 'FP_' + str(ss)] FN = re.ix[gfile, 'FN_' + str(ss)] VP = re.ix[gfile, 'TP_' + str(ss)] s = re.ix[gfile, 's_' + str(ss)] if FP + FN + 2 * VP != 0: re.ix[gfile, 'ESI_' + str(ss)] = s / sum_s * (FP + FN) / (FP + FN + 2 * VP) re.ix[gfile, 'Elocal_' + str(ss)] = s / sum_s * (FP + FN) / (FP + FN + VP) else: re.ix[gfile, 'ESI_' + str(ss)] = 0 re.ix[gfile, 'Elocal_' + str(ss)] = 0 re.ix[gfile, 'ESI'] = sum([ re.ix[gfile, 'ESI_' + str(ss)] for ss in self.sulci_side_list ]) re.to_csv(self.error_file) print('Mean ESI: %.3f' % re['ESI'].mean()) print('Max ESI: %.3f' % re['ESI'].max()) print() for ss in self.sulci_side_list: print('%s Elocal mean: %.3f, max: %.3f' % (ss, re['Elocal_' + str(ss)].mean(), re['Elocal_' + str(ss)].max()))