Source code for nrrdAux

import pyvista as pv
import numpy as np
import string
import glob 
import os 
import nrrd

nrrdLibraryPath='/Users/alessandromastrofini/LocalFiles/DataPatient/Clinical_data/'

[docs] def cleanPtName(pt): """ Cleans the patient's name by removing any letters and underscores, leaving only numeric characters. Args: pt (str): The name of the patient (string with potential letters and underscores). Returns: str: A cleaned string containing only the numeric part of the patient name. """ pt_no_letter = pt.translate(str.maketrans('', '', string.ascii_letters)) pt_num=pt_no_letter.replace('_','') return pt_num
[docs] def findAllPtFields(pt): """ Finds all available NRRD fields for a given patient by searching the NRRD library directory. Args: pt (str): The name of the patient. Returns: list: A list of available field names extracted from the NRRD files. """ ptNum=cleanPtName(pt) directory=nrrdLibraryPath+ptNum name_pattern='*.nrrd' files = glob.glob(f'{directory}/{name_pattern}') files=[os.path.basename(file) for file in files] files_useful = [file for file in files if 'multi' not in file] files_useful = [file.replace('.nrrd','') for file in files_useful] files_useful = [file.split('_')[-1] for file in files_useful] #print(files_useful) return files_useful
[docs] def findPtData(pt, mat, strToRemove='multi'): """ Finds the path to a specific NRRD data file for the patient, given the material name (e.g., 'lumen'). Args: pt (str): The name of the patient. mat (str): The material name to search for (e.g., 'lumen'). strToRemove (str, optional): String to filter out unwanted files (e.g., 'multi'). Defaults to 'multi'. Returns: str: The full file path to the patient's NRRD data file. """ ptNum=cleanPtName(pt) directory=nrrdLibraryPath+ptNum name_pattern='*'+mat+'*.nrrd' files = glob.glob(f'{directory}/{name_pattern}') files_useful = [file for file in files if strToRemove not in file] return files_useful[0]
[docs] def setUpNRRDField(self): filename=findPtData(self.name,mat='lumen') reader = pv.get_reader(filename) mesh = reader.read() data, header = nrrd.read(filename) scale=[header['space directions'][i,i] for i in range(3)] mesh=mesh.scale(scale) mesh=mesh.scale(0.001) self.clinicalData=mesh self.clinicalData.point_data["ImageFile"]=np.where(mesh.point_data["ImageFile"] < 0, 0, 0) #empty field return self.clinicalData
[docs] def loadNRRD(self, mat='general'): """ Loads a specific NRRD file for the patient and integrates the data into the patient's clinical mesh. The mesh is scaled based on the header's spatial information, and the data is thresholded to create a segmentation field. Args: mat (str, optional): The material type to load (e.g., 'lumen', 'wall'). Defaults to 'general'. Returns: pyvista.PolyData: The updated clinical data mesh with the new segmentation field. """ filename=findPtData(self.name,mat=mat) #print(filename) data, header = nrrd.read(filename) #print(header['space directions']) scale=[header['space directions'][i,i] for i in range(3)] #print(scale) reader = pv.get_reader(filename) mesh = reader.read() #print(mesh.bounds) mesh=mesh.scale(scale) mesh=mesh.scale(0.001) #print(mesh.bounds) data = mesh.point_data["ImageFile"] data = data-np.min(data) #makw all data positive threshold = np.percentile(data, 10) #self.clinicalData=mesh self.clinicalData.point_data[mat]=np.where(data < threshold, 1, 0) self.nrrdHeader[mat]=header return self.clinicalData
[docs] def loadNRRDcomposition(self): """ Loads the NRRD composition file for the patient, which contains multi-region data (e.g., plaque composition). The data for each region is thresholded and combined with the wall data to compute segmented fields for each region. Returns: self: The patient object with updated clinical data and available region information. """ filename=findPtData(self.name,mat='composition',strToRemove='Batch') #remove Batch_xxx # this is the complex header containing the composition of the plaque # like (:,:,:,i) where i is the number of the material data, header = nrrd.read(filename) regions = [] for i in range(data.shape[-1]): regions.append(header['slice'+str(i)+'_region']) #print(regions) self.info['AvailableRegions']=regions # now we can add the composition to the clinicalData data_wall=self.clinicalData.point_data['wall'] data_bin=np.zeros((len(regions),len(data_wall))) for i in range(len(regions)): data_temp=data[:,:,:,i].flatten(order='F') data_temp=data_temp-np.min(data_temp) #makw all data positive threshold = np.percentile(data_temp, 10) # Set this to whatever threshold you want data_bin[i]=np.where(data_temp<threshold, 1, 0) # boolean product between wall and region self.clinicalData.point_data[regions[i]]=data_bin[i]*data_wall self.nrrdHeader['composition']=header self.nrrdHeader['composition']['region']='General composition' return self
[docs] def postProcessNRRD(self): """ Post-processes the loaded clinical data by computing the wall segmentation as the difference between the total wall and the lumen segmentation. Returns: pyvista.PolyData: The updated clinical data mesh with the new 'wall' field. """ self.clinicalData.point_data['lumen']=self.clinicalData.point_data['lumenSegmentation'] lumenData=self.clinicalData.point_data['lumenSegmentation'] wallData=self.clinicalData.point_data['wallSegmentation'] self.clinicalData.point_data['wall']=np.where(wallData-lumenData>0, 1, 0) return self.clinicalData
[docs] def computeVolumetricData(mesh, mat='ImageFile', returnBounds=False): """ Computes the volumetric data for a specified region of the mesh, based on a scalar field (e.g., 'ImageFile'). The mesh is thresholded, and the volume and bounds are computed. Args: mesh (pyvista.PolyData): The clinical data mesh. mat (str, optional): The scalar field to threshold and compute. Defaults to 'ImageFile'. returnBounds (bool, optional): If True, returns the bounds and sizes of the volume. Defaults to False. Returns: float: The volume of the thresholded region if `returnBounds` is False. tuple: If `returnBounds` is True, returns the volume, bounds, and sizes of the region. """ thresholded = mesh.threshold([0.5,1],scalars=mat) bounds=thresholded.bounds sizes=[bounds[1]-bounds[0],bounds[3]-bounds[2],bounds[5]-bounds[4]] #vol=sizes[0]*sizes[1]*sizes[2] vol=thresholded.volume if returnBounds: return vol, bounds, sizes else: return thresholded