Source code for bioarch.mouth

#!/usr/bin/env python


import logging
from typing import List, Optional, Union


from ensure import check, ensure_annotations
import pandas as pd


logger = logging.getLogger(__name__)


VALID_TEETH    = ('NA', '0', '1', 'A', 'B1', 'B2', 'C', 'D', 'E', 'F', 'G', 'H', 'I')  # noqa: E221
VALID_CALCULUS = ('NA', '0', '1', '2', '3')
VALID_EH       = ('NA', '0', '1')  # noqa: E221
VALID_CAVITIES = ('NA', '0', '1')
VALID_ABCESS   = ('NA', '0', '1')  # noqa: E221


[docs]class Tooth(object): """docstring for Tooth""" __slots__ = ['_tooth', '_calculus', '_eh', '_cavities', '_abcess'] @ensure_annotations def __init__(self, tooth: str, calculus: str, eh: str, cavities: str, abcess: str): check(tooth).is_in(VALID_TEETH).or_raise(ValueError) check(calculus).is_in(VALID_CALCULUS).or_raise(ValueError) check(eh).is_in(VALID_EH).or_raise(ValueError) check(cavities).is_in(VALID_CAVITIES).or_raise(ValueError) check(abcess).is_in(VALID_ABCESS).or_raise(ValueError) # If there is no tooth then there can be no calculus, eh or cavities. # Note there can be abcess if tooth == 'NA': check(calculus).equals('NA').or_raise(ValueError) check(eh).equals('NA').or_raise(ValueError) check(cavities).equals('NA').or_raise(ValueError) self._tooth: str = tooth self._calculus: str = calculus self._eh: str = eh self._cavities: str = cavities self._abcess: str = abcess @property def tooth(self) -> str: """ 'NA', '0', '1', 'A', 'B1', 'B2', 'C', 'D', 'E', 'F', 'G', 'H', 'I' """ return self._tooth @property def calculus(self) -> str: """ 'NA', '0', '1', '2', '3' """ return self._calculus @property def eh(self) -> str: """ 'NA', '0', '1' """ return self._eh @property def cavities(self) -> str: """ 'NA', '0', '1' """ return self._cavities @property def abcess(self) -> str: """ 'NA', '0', '1' """ return self._abcess
[docs] @staticmethod def empty(): return Tooth('NA', 'NA', 'NA', 'NA', 'NA')
def _to_pd_value(self, label: str) -> Optional[Union[int, bool]]: val = getattr(self, label) if val == 'NA': return None if label == 'tooth': return VALID_TEETH.index(val) - 1 if label == 'calculus': return VALID_CALCULUS.index(val) - 1 if label in ('eh', 'cavities', 'abcess'): return bool(int(val)) raise RuntimeError
[docs] def to_pd_series(self, prefix=''): labels = [] values = [] for label in self.__slots__: label = label[1:] labels.append(f'{prefix}{label}') val = getattr(self, label) values.append(val) labels.append(f'{prefix}{label}_val') values.append(self._to_pd_value(label)) return pd.Series(values, index=labels, copy=True)
def __eq__(self, other): if other is None: return False if type(other) != type(self): # pylint: disable=C0123 raise NotImplementedError return (self.tooth, self.calculus, self.eh, self.cavities, self.abcess) == (other.tooth, other.calculus, other.eh, other.cavities, other.abcess)
TOOTH_GROUPS = { 'all': [x - 1 for x in list(range(1, 33))], # noqa: E241 'molar': [x - 1 for x in [1, 2, 3, 14, 15, 16, 17, 18, 19, 30, 31, 32]], # noqa: E241 'premolars': [x - 1 for x in [4, 5, 12, 13, 20, 21, 28, 29]], 'canines': [x - 1 for x in [6, 11, 22, 27]], # noqa: E241 'incisors': [x - 1 for x in [7, 8, 9, 10, 23, 24, 25, 26]], # noqa: E241 }
[docs]class Mouth(object): """Object to hold the 32 teeth""" def __init__(self, teeth: List[Tooth]): if len(teeth) != 32: raise ValueError(f'Incorrect number of teeth: {len(teeth)}') self.teeth = teeth
[docs] @staticmethod def empty(): return Mouth([Tooth.empty()] * 32)
def _to_pd_series_group(self, group, prefix, include_all=False): prefix = f'{prefix}{group}_' teeth = [tooth for i, tooth in enumerate(self.teeth) if i in TOOTH_GROUPS[group]] per_tooth = pd.Series([], index=[]) for i, tooth in enumerate(teeth): per_tooth = per_tooth.append(tooth.to_pd_series(prefix=f'{prefix}tooth_{i}_')) number_of_teeth = sum([1 for t in teeth if t.tooth != 'NA']) summaray = pd.Series([number_of_teeth], index=[f'{prefix}number_of_teeth'], copy=True) for label in Tooth.__slots__: label = label[1:] subset = pd.Series([v for k, v in per_tooth.items() if label in k and k.endswith('_val')]) summaray = summaray.append(pd.Series([subset.mean(skipna=True)], index=[f'{prefix}{label}_mean'])) summaray = summaray.append(pd.Series([subset.max(skipna=True)], index=[f'{prefix}{label}_max'])) summaray = summaray.append(pd.Series([subset.min(skipna=True)], index=[f'{prefix}{label}_min'])) summaray = summaray.append(pd.Series([subset.count()], index=[f'{prefix}{label}_count'])) result = summaray if include_all: result = per_tooth.append(result) return result
[docs] def to_pd_series(self, prefix=''): return pd.concat([self._to_pd_series_group(group, prefix, include_all=(group == 'all')) for group in TOOTH_GROUPS])
if __name__ == "__main__": raise RuntimeError('No main available')