def test_execute3(): c = ChestConventions() wc = c.GetChestWildCardName() paren_pheno = ParenchymaPhenotypes(chest_types=['Vessel']) df = paren_pheno.execute(ct, lm, 'simple', np.array([1., 1., 1.])) assert len(df.index) == 1, "Unexpected number of rows in dataframe" assert df['Region'].iloc[0] == wc, "Unexpected region in dataframe" assert df['Type'].iloc[0] == 'Vessel', "Unexpected type in dataframe"
def add_pheno(self, key_value, pheno_name, pheno_value): """Add a phenotype. Parameters ---------- key_value : list of strings This list indicates the specific DB key values that will be associated with the phenotype. E.g. ['WHOLELUNG', 'UNDEFINEDTYPE'] pheno_name : string The name of the phenotype. E.g. 'LAA-950' pheno_value : object The phenotype value. Can be a numerical value, string, etc """ c = ChestConventions() num_keys = len(key_value) # Make sure CID has been set assert self.static_names_handler_['CID'] is not None, \ "CID has not been set" # Make sure the pheno name is valid assert c.IsPhenotypeName(pheno_name) or pheno_name == \ c.GetChestWildCardName(), "Invalid phenotype name" # Check if key is valid for i in xrange(0, num_keys): assert key_value[i] in self.valid_key_values_[self.key_names_[i]], \ "Invalid key: %s" % key_value[i] # Check if pheno_name is valid assert pheno_name in self.pheno_names_, \ "Invalid phenotype name: %s" % pheno_name # Check if key already exists, otherwise add entry to data frame key_exists = True key_row = np.ones(len(self._df.index), dtype=bool) for i in xrange(0, len(self.key_names_)): key_row = \ np.logical_and(key_row, \ self._df[self.key_names_[i]] == key_value[i]) if np.sum(key_row) == 0: tmp = dict() for k in self.static_names_handler_.keys(): tmp[k] = self.static_names_handler_[k]() tmp[pheno_name] = pheno_value for i in xrange(0, num_keys): tmp[self.key_names_[i]] = key_value[i] self._df = self._df.append(tmp, ignore_index=True) else: self._df[pheno_name][np.where(key_row == True)[0][0]] = pheno_value
def get_chest_regions(self): """Get the explicit list of chest regions in the data set. Returns ------- chest_regions : array, shape ( N ) Explicit list of the N chest regions in the data set """ conventions = ChestConventions() tmp = [] for l in self.labels_: tmp.append(conventions.GetChestRegionFromValue(l)) chest_regions = np.unique(np.array(tmp, dtype=int)) return chest_regions
def get_chest_types(self): """Get the chest types in the data set Returns ------- chest_types : array, shape ( N ) All N chest types in the data set """ c = ChestConventions() tmp = [] for l in self.labels_: tmp.append(c.GetChestTypeFromValue(l)) chest_types = np.unique(np.array(tmp, dtype=int)) return chest_types
def test_execute(): c = ChestConventions() wc = c.GetChestWildCardName() laa = LAAPhenotypes() df = laa.execute(ct, lm, 'simple') for i in xrange(0, 14): r = df['Region'].iloc[i] t = df['Type'].iloc[i] val_950 = df['LAA950'].iloc[i] val_910 = df['LAA910'].iloc[i] val_856 = df['LAA856'].iloc[i] if (r == 'UNDEFINEDREGION' and t == wc) or \ (r == wc and t == 'UNDEFINEDTYPE') or \ (r == 'UNDEFINEDREGION' and t == 'UNDEFINEDTYPE'): assert val_950 == wc, 'Phenotype not as expected' assert val_910 == wc, 'Phenotype not as expected' assert val_856 == wc, 'Phenotype not as expected' elif (r == 'WHOLELUNG' and t == wc) or \ (r == 'RIGHTLUNG' and t == wc) or \ (r == 'LEFTLUNG' and t == wc): assert np.isclose(val_950, 0.1111111), 'Phenotype not as expected' assert np.isclose(val_910, 0.1111111), 'Phenotype not as expected' assert np.isclose(val_856, 0.1111111), 'Phenotype not as expected' elif (r == wc and t == 'AIRWAY') or \ (r == 'UNDEFINEDREGION' and t == 'AIRWAY') or \ (r == 'WHOLELUNG' and t == 'AIRWAY') or \ (r == 'RIGHTLUNG' and t == 'AIRWAY') or \ (r == 'LEFTLUNG' and t == 'AIRWAY'): assert np.isclose(val_950, 1.0), 'Phenotype not as expected' assert np.isclose(val_910, 1.0), 'Phenotype not as expected' assert np.isclose(val_856, 1.0), 'Phenotype not as expected' elif (r == 'WHOLELUNG' and t == 'UNDEFINEDTYPE') or \ (r == 'RIGHTLUNG' and t == 'UNDEFINEDTYPE') or \ (r == 'LEFTLUNG' and t == 'UNDEFINEDTYPE') or \ (r == 'WHOLELUNG' and t == 'VESSEL') or \ (r == 'RIGHTLUNG' and t == 'VESSEL') or \ (r == 'LEFTLUNG' and t == 'VESSEL') or \ (r == wc and t == 'VESSEL'): assert np.isclose(val_950, 0.0), 'Phenotype not as expected' assert np.isclose(val_910, 0.0), 'Phenotype not as expected' assert np.isclose(val_856, 0.0), 'Phenotype not as expected'
def __init__(self, chest_regions=None, chest_types=None, pairs=None, pheno_names=None): c = ChestConventions() self.chest_regions_ = None if chest_regions is not None: tmp = [] for m in xrange(0, len(chest_regions)): tmp.append(c.GetChestRegionValueFromName(chest_regions[m])) self.chest_regions_ = np.array(tmp) self.chest_types_ = None if chest_types is not None: tmp = [] for m in xrange(0, len(chest_types)): tmp.append(c.GetChestTypeValueFromName(chest_types[m])) self.chest_types_ = np.array(tmp) self.pairs_ = None if pairs is not None: self.pairs_ = np.zeros([len(pairs), 2]) inc = 0 for p in pairs: assert len(p)%2 == 0, \ "Specified region-type pairs not understood" r = c.GetChestRegionValueFromName(p[0]) t = c.GetChestTypeValueFromName(p[1]) self.pairs_[inc, 0] = r self.pairs_[inc, 1] = t inc += 1 self.requested_pheno_names = pheno_names Phenotypes.__init__(self)
def get_all_chest_regions(self): """Get all the chest regions in the data set, including those implicitly present as a result of the region hierarchy. Returns ------- chest_regions : array, shape ( N ) All chest regions in the data set """ c = ChestConventions() num_regions = c.GetNumberOfEnumeratedChestRegions() tmp = [] for l in self.labels_: r = c.GetChestRegionFromValue(l) for sup in xrange(0, num_regions): if c.CheckSubordinateSuperiorChestRegionRelationship(r, sup): tmp.append(sup) chest_regions = np.unique(np.array(tmp, dtype=int)) return chest_regions
def test_execute5(): c = ChestConventions() wc = c.GetChestWildCardName() paren_pheno = ParenchymaPhenotypes(chest_regions=['LeftLung'], \ pheno_names=['LAA950']) df = paren_pheno.execute(ct, lm, 'simple', np.array([1., 1., 1.])) assert len(df.index) == 1, "Unexpected number of rows in dataframe" assert df['Region'].iloc[0] == 'LeftLung', "Unexpected region in dataframe" assert df['Type'].iloc[0] == wc, "Unexpected type in dataframe" assert np.isnan(df.LAA910.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.LAA856.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HAA700.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HAA600.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HAA500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HAA250.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.Perc15.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.Perc10.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMean.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUStd.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUKurtosis.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUSkewness.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMode.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMedian.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMin.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMax.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMean500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUStd500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUKurtosis500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUSkewness500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMode500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMedian500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMin500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.HUMax500.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.Volume.iloc[0]), "Phenotype value should be NaN" assert np.isnan(df.Mass.iloc[0]), "Phenotype value should be NaN"
def valid_key_values(self): """Get the valid DB key values that each DB key can assume. Returns ------- valid_values : dictionary The returned dictionary keys are the valid DB key names. Each dictionary key value maps to a list of valid names that the DB key can assume. """ c = ChestConventions() valid_values = dict() region_names = [c.GetChestWildCardName()] for i in xrange(0, c.GetNumberOfEnumeratedChestRegions()): region_names.append(c.GetChestRegionName(i)) valid_values['Region'] = region_names type_names = [c.GetChestWildCardName()] for i in xrange(0, c.GetNumberOfEnumeratedChestTypes()): type_names.append(c.GetChestTypeName(i)) valid_values['Type'] = type_names return valid_values
def get_all_pairs(self): """Get all the region-type pairs, including implied pairs Returns ------- pairs : array, shape ( N, 2 ) All N chest-region chest-type pairs in the data set, including those implied by the region hierarchy. The first column indicates the chest region, and the second column represents the chest type. """ c = ChestConventions() num_regions = c.GetNumberOfEnumeratedChestRegions() tmp = [] for l in self.labels_: t = c.GetChestTypeFromValue(l) r = c.GetChestRegionFromValue(l) for sup in xrange(0, num_regions): if c.CheckSubordinateSuperiorChestRegionRelationship(r, sup): if not (sup, t) in tmp: tmp.append((sup, t)) pairs = np.array(tmp, dtype=int) return pairs
def test_execute(): c = ChestConventions() wc = c.GetChestWildCardName() paren_pheno = ParenchymaPhenotypes() df = paren_pheno.execute(ct, lm, 'simple', np.array([1., 1., 1.])) for i in xrange(0, 14): r = df['Region'].iloc[i] t = df['Type'].iloc[i] val_950 = df['LAA950'].iloc[i] val_910 = df['LAA910'].iloc[i] val_856 = df['LAA856'].iloc[i] if (r == 'UndefinedRegion' and t == wc) or \ (r == wc and t == 'UndefinedRegion') or \ (r == 'UndefinedRegion' and t == 'UndefinedType'): assert val_950 == wc, 'Phenotype not as expected' assert val_910 == wc, 'Phenotype not as expected' assert val_856 == wc, 'Phenotype not as expected' elif (r == 'WholeLung' and t == wc) or \ (r == 'RightLung' and t == wc) or \ (r == 'LeftLung' and t == wc): assert np.isclose(val_950, 0.1111111), 'Phenotype not as expected' assert np.isclose(val_910, 0.1111111), 'Phenotype not as expected' assert np.isclose(val_856, 0.1111111), 'Phenotype not as expected' elif (r == wc and t == 'Airway') or \ (r == 'UndefinedRegion' and t == 'AIRWAY') or \ (r == 'WholeLung' and t == 'Airway') or \ (r == 'RightLung' and t == 'Airway') or \ (r == 'LeftLung' and t == 'Airway'): assert np.isclose(val_950, 1.0), 'Phenotype not as expected' assert np.isclose(val_910, 1.0), 'Phenotype not as expected' assert np.isclose(val_856, 1.0), 'Phenotype not as expected' elif (r == 'WholeLung' and t == 'UndefinedType') or \ (r == 'RightLung' and t == 'UndefinedType') or \ (r == 'LeftLung' and t == 'UndefinedType') or \ (r == 'WholeLung' and t == 'Vessel') or \ (r == 'RightLung' and t == 'Vessel') or \ (r == 'LeftLung' and t == 'Vessel') or \ (r == wc and t == 'Vessel'): assert np.isclose(val_950, 0.0), 'Phenotype not as expected' assert np.isclose(val_910, 0.0), 'Phenotype not as expected' assert np.isclose(val_856, 0.0), 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert np.isclose(df['HAA700'].iloc[i], 0.1111111), \ 'Phenotype not as expected' if (r == wc and t == 'Vessel'): assert df['HAA700'].iloc[i] == 1., 'Phenotype not as expected' if (r == 'UndefinedRegion' and t == 'Airway'): assert df['HAA600'].iloc[i] == 0., 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Vessel'): assert df['HAA500'].iloc[i] == 1., 'Phenotype not as expected' if (r == 'WildCard' and t == 'Vessel'): assert df['HAA250'].iloc[i] == 1., 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Airway'): assert df['Perc10'].iloc[i] == -977, 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Vessel'): assert df['Perc15'].iloc[i] == -47, 'Phenotype not as expected' if (r == 'LeftLung' and t == wc): assert np.isclose(df['HUMean'].iloc[i], -773.333333), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert np.isclose(df['HUStd'].iloc[i], 256.9695), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == 'UndefinedType'): assert np.isclose(df['HUKurtosis'].iloc[i], -2.363636), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Vessel'): assert df['HUSkewness'].iloc[i] == 0.0, 'Phenotype not as expected' if (r == 'RightLung' and t == wc): assert df['HUMode'].iloc[i] == -800, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMedian'].iloc[i] == -825, 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Airway'): assert df['HUMin'].iloc[i] == -980, 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Airway'): assert df['HUMax'].iloc[i] == -950, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMean500'].iloc[ i] == -842.5, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert np.isclose(df['HUStd500'].iloc[i], 52.1416340), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert np.isclose(df['HUKurtosis500'].iloc[i], 2.37885301), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert np.isclose(df['HUSkewness500'].iloc[i], -1.613628), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMode500'].iloc[i] == -850, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMedian500'].iloc[ i] == -850, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMin500'].iloc[i] == -980, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMax500'].iloc[i] == -800, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['Volume'].iloc[i] == 18, 'Phenotype not as expected' if (r == 'RightLung' and t == wc): assert np.isclose(df['Mass'].iloc[i], 0.00247609596), \ 'Phenotype not as expected'
def execute(self, ct, lm, cid, spacing, chest_regions=None, chest_types=None, pairs=None, pheno_names=None): """Compute the phenotypes for the specified structures for the specified threshold values. The following values are computed for the specified structures. 'AxialCSA': Axial cross-sectional area 'CoronalCSA': Coronoal cross-sectional area 'SagittalCSA': Sagitall cross-sectional area 'HUMean': Mean value of the structure's HU values 'HUStd': Standard deviation of the structure's HU values 'HUKurtosis': Kurtosis of the structure's HU values. Fisher's definition is used, meaning that normal distribution has kurtosis of 0. The calculation is corrected for statistical bias. 'HUSkewness': Skewness of the structure's HU values. The calculation is corrected for statistical bias. 'HUMode': Mode of the structure's HU values 'HUMedian': Median of the structure's HU values 'HUMin': Min HU value for the structure 'HUMax': Max HU value for the structure The following set of phenotypes are identical to the above except that computation is isolated to the HU interval [-50, 90]. These phenotypes are capture lean muscle information and only have meaning for muscle structures. 'leanAxialCSA': Axial cross-sectional area 'leanCoronalCSA': Coronoal cross-sectional area 'leanSagittalCSA': Sagitall cross-sectional area 'leanHUMean': Mean value of the structure's HU values 'leanHUStd': Standard deviation of the structure's HU values 'leanHUKurtosis': Kurtosis of the structure's HU values. Fisher's definition is used, meaning that normal distribution has kurtosis of 0. The calculation is corrected for statistical bias. 'leanHUSkewness': Skewness of the structure's HU values. The calculation is corrected for statistical bias. 'leanHUMode': Mode of the structure's HU values 'leanHUMedian': Median of the structure's HU values 'leanHUMin': Min HU value for the structure 'leanHUMax': Max HU value for the structure Parameters ---------- ct : array, shape ( X, Y, Z ) The 3D CT image array lm : array, shape ( X, Y, Z ) The 3D label map array cid : string Case ID spacing : array, shape ( 3 ) The x, y, and z spacing, respectively, of the CT volume chest_regions : array, shape ( R ), optional Array of integers, with each element in the interval [0, 255], indicating the chest regions over which to compute the LAA. If none specified, the chest regions specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. chest_types : array, shape ( T ), optional Array of integers, with each element in the interval [0, 255], indicating the chest types over which to compute the LAA. If none specified, the chest types specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. pairs : array, shape ( P, 2 ), optional Array of chest-region chest-type pairs over which to compute the LAA. The first column indicates the chest region, and the second column indicates the chest type. Each element should be in the interal [0, 255]. If none specified, the pairs specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. pheno_names : list of strings, optional Names of phenotypes to compute. These names must conform to the accepted phenotype names, listed above. If none are given, all will be computed. Specified names given here will take precedence over any specified in the constructor. Returns ------- df : pandas dataframe Dataframe containing info about machine, run time, and chest region chest type phenotype quantities. """ assert len(ct.shape) == len(lm.shape), \ "CT and label map are not the same dimension" dim = len(ct.shape) for i in xrange(0, dim): assert ct.shape[0] == lm.shape[0], \ "Disagreement in CT and label map dimension" assert type(cid) == str, "cid must be a string" self.cid_ = cid self._spacing = spacing phenos_to_compute = self.pheno_names_ if pheno_names is not None: phenos_to_compute = pheno_names elif self.requested_pheno_names is not None: phenos_to_compute = self.requested_pheno_names rs = None ts = None ps = None if chest_regions is not None: rs = chest_regions elif self.chest_regions_ is not None: rs = self.chest_regions_ if chest_types is not None: ts = chest_types elif self.chest_types_ is not None: ts = self.chest_types_ if pairs is not None: ps = pairs elif self.pairs_ is not None: ps = self.pairs_ parser = RegionTypeParser(lm) if rs == None and ts == None and ps == None: rs = parser.get_all_chest_regions() ts = parser.get_chest_types() ps = parser.get_all_pairs() lean_ct_mask = np.logical_and(ct >= -50, ct <= 90) # Now compute the phenotypes and populate the data frame c = ChestConventions() if rs is not None: for r in rs: if r != 0: mask = parser.get_mask(chest_region=r) lean_mask = np.logical_and(mask, lean_ct_mask) for n in phenos_to_compute: if 'lean' in n: self.add_pheno_group(ct, lean_mask, c.GetChestRegionName(r), c.GetChestWildCardName(), n) else: self.add_pheno_group(ct, mask, c.GetChestRegionName(r), c.GetChestWildCardName(), n) if ts is not None: for t in ts: if t != 0: mask = parser.get_mask(chest_type=t) lean_mask = np.logical_and(mask, lean_ct_mask) for n in phenos_to_compute: if 'lean' in n: self.add_pheno_group(ct, lean_mask, c.GetChestWildCardName(), c.GetChestTypeName(r), n) else: self.add_pheno_group(ct, mask, c.GetChestWildCardName(), c.GetChestTypeName(r), n) if ps is not None: for p in ps: if not (p[0] == 0 and p[1] == 0): mask = parser.get_mask(chest_region=p[0], chest_type=p[1]) lean_mask = np.logical_and(mask, lean_ct_mask) for n in phenos_to_compute: if 'lean' in n: self.add_pheno_group(ct, lean_mask, c.GetChestRegionName(p[0]), c.GetChestTypeName(p[1]), n) else: self.add_pheno_group(ct, mask, c.GetChestRegionName(p[0]), c.GetChestTypeName(p[1]), n) return self._df
def execute(self, ct, lm, cid, chest_regions=None, chest_types=None, pairs=None): """Compute the phenotypes for the specified structures for the specified threshold values. Parameters ---------- ct : array, shape ( X, Y, Z ) The 3D CT image array lm : array, shape ( X, Y, Z ) The 3D label map array cid : string Case ID chest_regions : array, shape ( R ), optional Array of integers, with each element in the interval [0, 255], indicating the chest regions over which to compute the LAA. If none specified, the chest regions specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. chest_types : array, shape ( T ), optional Array of integers, with each element in the interval [0, 255], indicating the chest types over which to compute the LAA. If none specified, the chest types specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. pairs : array, shape ( P, 2 ), optional Array of chest-region chest-type pairs over which to compute the LAA. The first column indicates the chest region, and the second column indicates the chest type. Each element should be in the interal [0, 255]. If none specified, the pairs specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. Returns ------- df : pandas dataframe Dataframe containing info about machine, run time, and chest region chest type phenotype quantities. """ assert len(ct.shape) == len(lm.shape), \ "CT and label map are not the same dimension" dim = len(ct.shape) for i in xrange(0, dim): assert ct.shape[0] == lm.shape[0], \ "Disagreement in CT and label map dimension" assert type(cid) == str, "cid must be a string" self.cid_ = cid rs = None ts = None ps = None if chest_regions is not None: rs = chest_regions elif self.chest_regions_ is not None: rs = self.chest_regions_ if chest_types is not None: ts = chest_types elif self.chest_types_ is not None: ts = self.chest_types_ if pairs is not None: ps = pairs elif self.pairs_ is not None: ps = self.pairs_ parser = RegionTypeParser(lm) if rs == None and ts == None and ps == None: rs = parser.get_all_chest_regions() ts = parser.get_chest_types() ps = parser.get_all_pairs() # Now compute the phenotypes and populate the data frame c = ChestConventions() if rs is not None: for r in rs: if r != 0: mask = parser.get_mask(chest_region=r) for tt in self.threshs_: pheno_name = 'LAA' + str(int(np.abs(np.round(tt)))) pheno_val = float( np.sum(ct[mask] <= tt)) / np.sum(mask) self.add_pheno([ c.GetChestRegionName(r), c.GetChestWildCardName() ], pheno_name, pheno_val) if ts is not None: for t in ts: if t != 0: mask = parser.get_mask(chest_type=t) for tt in self.threshs_: pheno_name = 'LAA' + str(int(np.abs(np.round(tt)))) pheno_val = float( np.sum(ct[mask] <= tt)) / np.sum(mask) self.add_pheno( [c.GetChestWildCardName(), c.GetChestTypeName(t)], pheno_name, pheno_val) if ps is not None: for p in ps: if not (p[0] == 0 and p[1] == 0): mask = parser.get_mask(chest_region=p[0], chest_type=p[1]) for tt in self.threshs_: pheno_name = 'LAA' + str(int(np.abs(np.round(tt)))) pheno_val = float( np.sum(ct[mask] <= tt)) / np.sum(mask) self.add_pheno([ c.GetChestRegionName(p[0]), c.GetChestTypeName(p[1]) ], pheno_name, pheno_val) return self._df
def execute(self, ct, lm, cid, spacing, chest_regions=None, chest_types=None, pairs=None, pheno_names=None): """Compute the phenotypes for the specified structures for the specified threshold values. The following values are computed for the specified structures. 'LAA950': fraction of the structure's region with CT HU values <= -950 'LAA910': fraction of the structure's region with CT HU values <= -910 'LAA856': fraction of the structure's region with CT HU values <= -856 'HAA700': fraction of the structure's region with CT HU values >= -700 'HAA600': fraction of the structure's region with CT HU values >= -600 'HAA500': fraction of the structure's region with CT HU values >= -500 'HAA250': fraction of the structure's region with CT HU values >= -250 'Perc10': HU value at the 10th percentile of the structure's HU histogram 'Perc15': HU value at the 15th percentile of the structure's HU histogram 'HUMean': Mean value of the structure's HU values 'HUStd': Standard deviation of the structure's HU values 'HUKurtosis': Kurtosis of the structure's HU values. Fisher's definition is used, meaning that normal distribution has kurtosis of 0. The calculation is corrected for statistical bias. 'HUSkewness': Skewness of the structure's HU values. The calculation is corrected for statistical bias. 'HUMode': Mode of the structure's HU values 'HUMedian': Median of the structure's HU values 'HUMin': Min HU value for the structure 'HUMax': Max HU value for the structure 'HUMean500': Mean CT value of the structure, but only considering CT values that are <= -500 HU 'HUStd500': Standard deviation of the structure's CT values, but only considering CT values that are <= -500 HU 'HUKurtosis500': Kurtosis of the structure's HU values, but only considering CT values that are <= -500 HU 'HUSkewness500': Skewness of the structure's HU values, but only considering CT values that are <= -500 HU 'HUMode500': Mode of the structure's HU values, but only considering CT values that are <= -500 HU 'HUMedian500': Median of the structure's HU values, but only considering CT values that are <= -500 HU 'HUMin500': Min HU value for the structure, but only considering CT values that are <= -500 HU 'HUMax500': Max HU value for the structure, but only considering CT values that are <= -500 HU 'Volume': Volume of the structure, measured in liters 'Mass': Mass of the structure measure in grams Parameters ---------- ct : array, shape ( X, Y, Z ) The 3D CT image array lm : array, shape ( X, Y, Z ) The 3D label map array cid : string Case ID spacing : array, shape ( 3 ) The x, y, and z spacing, respectively, of the CT volume chest_regions : array, shape ( R ), optional Array of integers, with each element in the interval [0, 255], indicating the chest regions over which to compute the LAA. If none specified, the chest regions specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. chest_types : array, shape ( T ), optional Array of integers, with each element in the interval [0, 255], indicating the chest types over which to compute the LAA. If none specified, the chest types specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. pairs : array, shape ( P, 2 ), optional Array of chest-region chest-type pairs over which to compute the LAA. The first column indicates the chest region, and the second column indicates the chest type. Each element should be in the interal [0, 255]. If none specified, the pairs specified in the class constructor will be used. If chest regions, chest types, and chest pairs are left unspecified both here and in the constructor, then the complete set of entities found in the label map will be used. pheno_names : list of strings, optional Names of phenotypes to compute. These names must conform to the accepted phenotype names, listed above. If none are given, all will be computed. Specified names given here will take precedence over any specified in the constructor. Returns ------- df : pandas dataframe Dataframe containing info about machine, run time, and chest region chest type phenotype quantities. """ assert len(ct.shape) == len(lm.shape), \ "CT and label map are not the same dimension" dim = len(ct.shape) for i in xrange(0, dim): assert ct.shape[0] == lm.shape[0], \ "Disagreement in CT and label map dimension" assert type(cid) == str, "cid must be a string" self.cid_ = cid self._spacing = spacing phenos_to_compute = self.pheno_names_ if pheno_names is not None: phenos_to_compute = pheno_names elif self.requested_pheno_names is not None: phenos_to_compute = self.requested_pheno_names rs = None ts = None ps = None if chest_regions is not None: rs = chest_regions elif self.chest_regions_ is not None: rs = self.chest_regions_ if chest_types is not None: ts = chest_types elif self.chest_types_ is not None: ts = self.chest_types_ if pairs is not None: ps = pairs elif self.pairs_ is not None: ps = self.pairs_ parser = RegionTypeParser(lm) if rs == None and ts == None and ps == None: rs = parser.get_all_chest_regions() ts = parser.get_chest_types() ps = parser.get_all_pairs() # Now compute the phenotypes and populate the data frame c = ChestConventions() if rs is not None: for r in rs: if r != 0: mask = parser.get_mask(chest_region=r) for n in phenos_to_compute: self.add_pheno_group(ct, mask, c.GetChestRegionName(r), c.GetChestWildCardName(), n) if ts is not None: for t in ts: if t != 0: mask = parser.get_mask(chest_type=t) for n in phenos_to_compute: self.add_pheno_group(ct, mask, c.GetChestWildCardName(), c.GetChestTypeName(t), n) if ps is not None: for i in xrange(0, ps.shape[0]): if not (ps[i, 0] == 0 and ps[i, 1] == 0): mask = parser.get_mask(chest_region=int(ps[i, 0]), chest_type=int(ps[i, 1])) for n in phenos_to_compute: self.add_pheno_group( ct, mask, c.GetChestRegionName(int(ps[i, 0])), c.GetChestTypeName(int(ps[i, 1])), n) return self._df
def get_mask(self, chest_region=None, chest_type=None): """Get's boolean mask of all data indices that match the chest-region chest-type query. If only a chest region is specified, then all voxels in that region will be included in the mask, regardless of the voxel's chest type value (chest region hierarchy is honored). If only a type is specified, then all voxels having that type will be included in the mask, regardless of the voxel's chest region. If both a region and type are speficied, then only those voxels matching both the region and type will be included in the mask (the chest region hierarchy is honored). Parameters ---------- chest_region : int Integer value in the interval [0, 255] that indicates the chest region chest_type : int Integer value in the interval [0, 255] that indicates the chest type Returns ------- mask : array, shape ( X, Y, Z ) Boolean mask of all data indices that match the chest-region chest-type query. The chest region hierarchy is honored. """ if chest_region is not None: if type(chest_region) != int and type(chest_region) != np.int64: raise ValueError( 'chest_region must be an int between 0 and 255 inclusive') if chest_type is not None: if type(chest_type) != int and type(chest_type) != np.int64: raise ValueError( 'chest_type must be an int between 0 and 255 inclusive') conventions = ChestConventions() mask_labels = [] for l in self.labels_: r = conventions.GetChestRegionFromValue(l) t = conventions.GetChestTypeFromValue(l) if chest_region is not None and chest_type is not None: if t==chest_type and \ conventions.CheckSubordinateSuperiorChestRegionRelationship(\ r, chest_region): mask_labels.append(l) elif t == chest_type: mask_labels.append(l) elif chest_region is not None: if conventions.\ CheckSubordinateSuperiorChestRegionRelationship(r, \ chest_region): mask_labels.append(l) mask = np.empty(self._data.shape, dtype=bool) mask[:] = False for ml in mask_labels: mask = np.logical_or(mask, self._data == ml) return mask
def test_execute(): c = ChestConventions() wc = c.GetChestWildCardName() spacing = np.array([0.5, 0.4, 0.3]) bc_pheno = BodyCompositionPhenotypes() df = bc_pheno.execute(ct, lm, 'simple', spacing) for i in xrange(0, 14): r = df['Region'].iloc[i] t = df['Type'].iloc[i] if (r == 'LeftLung' and t == wc): assert np.isclose(df['HUMean'].iloc[i], -773.333333), \ 'Phenotype not as expected' assert np.isclose(df['AxialCSA'].iloc[i], \ 9*spacing[0]*spacing[1]), \ 'Phenotype not as expected' assert np.isclose(df['CoronalCSA'].iloc[i], \ 9*spacing[0]*spacing[2]), \ 'Phenotype not as expected' assert np.isclose(df['SagittalCSA'].iloc[i], \ 9*spacing[1]*spacing[2]), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['HUMedian'].iloc[i] == -825, 'Phenotype not as expected' assert np.isclose(df['HUStd'].iloc[i], 256.9695), \ 'Phenotype not as expected' assert np.isclose(df['AxialCSA'].iloc[i], \ 18*spacing[0]*spacing[1]), \ 'Phenotype not as expected' assert np.isclose(df['CoronalCSA'].iloc[i], \ 18*spacing[0]*spacing[2]), \ 'Phenotype not as expected' assert np.isclose(df['SagittalCSA'].iloc[i], \ 18*spacing[1]*spacing[2]), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == 'UndefinedType'): assert np.isclose(df['HUKurtosis'].iloc[i], -2.363636), \ 'Phenotype not as expected' assert np.isclose(df['AxialCSA'].iloc[i], \ 14*spacing[0]*spacing[1]), \ 'Phenotype not as expected' assert np.isclose(df['CoronalCSA'].iloc[i], \ 14*spacing[0]*spacing[2]), \ 'Phenotype not as expected' assert np.isclose(df['SagittalCSA'].iloc[i], \ 14*spacing[1]*spacing[2]), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Vessel'): assert df['HUSkewness'].iloc[i] == 0.0, 'Phenotype not as expected' assert np.isclose(df['AxialCSA'].iloc[i], \ 2*spacing[0]*spacing[1]), \ 'Phenotype not as expected' assert np.isclose(df['CoronalCSA'].iloc[i], \ 2*spacing[0]*spacing[2]), \ 'Phenotype not as expected' assert np.isclose(df['SagittalCSA'].iloc[i], \ 2*spacing[1]*spacing[2]), \ 'Phenotype not as expected' if (r == 'RightLung' and t == wc): assert df['HUMode'].iloc[i] == -800, 'Phenotype not as expected' assert np.isclose(df['AxialCSA'].iloc[i], \ 9*spacing[0]*spacing[1]), \ 'Phenotype not as expected' assert np.isclose(df['CoronalCSA'].iloc[i], \ 9*spacing[0]*spacing[2]), \ 'Phenotype not as expected' assert np.isclose(df['SagittalCSA'].iloc[i], \ 9*spacing[1]*spacing[2]), \ 'Phenotype not as expected' if (r == 'WholeLung' and t == 'Airway'): assert df['HUMin'].iloc[i] == -980, 'Phenotype not as expected' assert df['HUMax'].iloc[i] == -950, 'Phenotype not as expected' if (r == 'WholeLung' and t == wc): assert df['leanHUMin'].iloc[i] == -50, 'Phenotype not as expected' assert df['leanHUMax'].iloc[i] == -30, 'Phenotype not as expected' assert np.isclose(df['leanAxialCSA'].iloc[i], 2*spacing[0]*spacing[1]), 'Phenotype not as expected' assert np.isclose(df['leanCoronalCSA'].iloc[i], 2*spacing[0]*spacing[2]), 'Phenotype not as expected' assert np.isclose(df['leanSagittalCSA'].iloc[i], 2*spacing[2]*spacing[1]), 'Phenotype not as expected' assert df['leanHUMedian'].iloc[i] == -40, \ 'Phenotype not as expected' assert df['leanHUStd'].iloc[i] == 10, \ 'Phenotype not as expected' assert df['leanHUMode'].iloc[i] == -50, \ 'Phenotype not as expected' assert df['leanHUKurtosis'].iloc[i] == -2.0, \ 'Phenotype not as expected' assert df['leanHUSkewness'].iloc[i] == 0.0, \ 'Phenotype not as expected'
def remap_lm(lm, region_maps=None, type_maps=None, pair_maps=None): """Overwrites values in an input label map using the specified mappings. Parameters ---------- lm : array The input data. Each value is assumed to be an unsigned short (16 bit) data type, where the least significant 8 bits encode the chest region, and the most significant 8 bits encode the chest type. region_maps : list of lists, optional Each element of the list is a 2-element list or a list with two elements, where the individual elements are strings indicating chest regions. The 2D lists / tuples indicate the mappings to be performed. E.g. [('LeftLung', 'WholeLung')] indicates that all occurrences of 'LeftLung' should be replaced with 'WholeLung'. All type information is preserved. type_maps : list of lists, optional Each element of the list is a 2-element list or a list with two elements, where the individual elements are strings indicating chest types. The 2D lists / tuples indicate the mappings to be performed. E.g. [('Airway', 'UndefinedType')] indicates that all occurrences of 'Airway' should be replaced with 'UndefinedType'. All region information is preserved. pairs_maps : list of lists of lists, optional Each element of the list is a 2-element list: the firs element is itself a 2-element list indicating a region-type pair; the second element is a 2-element region-type pair to be mapped to. String names are used to designate the regions and types. E.g. [['LeftLung', 'Vessel'], ['WholeLung', 'UndefinedType']] will map all occurrences of the 'LeftLung'-'Vessel' pair to 'WholeLung'-'UndefinedType'. Returns ------- remapped_lm : array A label map with the same dimensions as the input with labelings redefined according to the specified rules. """ remapped_lm = np.copy(lm) parser = RegionTypeParser(lm) lm_types = lm >> 8 lm_regions = lm - (lm >> 8 << 8) c = ChestConventions() if region_maps is not None: for m in xrange(0, len(region_maps)): assert len(region_maps[m]) == 2, "Mapping not understood" mask = \ parser.get_mask(chest_region=\ c.GetChestRegionValueFromName(region_maps[m][0])) lm_regions[mask] = \ c.GetChestRegionValueFromName(region_maps[m][1]) if type_maps is not None: for m in xrange(0, len(type_maps)): assert len(type_maps[m]) == 2, "Mapping not understood" mask = \ parser.get_mask(chest_type=\ c.GetChestTypeValueFromName(type_maps[m][0])) lm_types[mask] = \ c.GetChestTypeValueFromName(type_maps[m][1]) if pair_maps is not None: for m in xrange(0, len(pair_maps)): assert len(pair_maps[m]) == 2, "Mapping not understood" assert len(pair_maps[m][0]) == 2, "Mapping not understood" assert len(pair_maps[m][1]) == 2, "Mapping not understood" mask = \ parser.get_mask(chest_region=\ c.GetChestRegionValueFromName(pair_maps[m][0][0]), chest_type=\ c.GetChestTypeValueFromName(pair_maps[m][0][1])) lm_regions[mask] = \ c.GetChestRegionValueFromName(pair_maps[m][1][0]) lm_types[mask] = \ c.GetChestTypeValueFromName(pair_maps[m][1][1]) remapped_lm = (lm_types << 8) | lm_regions return remapped_lm