from __future__ import annotations
import enum
import logging
from typing import Any
logger = logging.getLogger(__name__)
_VARIANT_TYPE_DISPLAY_NAME = {
"invalid": "inv",
"substitution": "sub",
"insertion": "ins",
"deletion": "del",
"comp": "comp",
"cnv_p": "cnv+",
"cnv_m": "cnv-",
}
_ROLE_DISPLAY_NAME = {
"maternal_grandmother": "Maternal Grandmother",
"maternal_grandfather": "Maternal Grandfather",
"paternal_grandmother": "Paternal Grandmother",
"paternal_grandfather": "Paternal Grandfather",
"mom": "Mom",
"dad": "Dad",
"parent": "Parent",
"prb": "Proband",
"sib": "Sibling",
"child": "Child",
"maternal_half_sibling": "Maternal Half Sibling",
"paternal_half_sibling": "Paternal Half Sibling",
"half_sibling": "Half Sibling",
"maternal_aunt": "Maternal Aunt",
"maternal_uncle": "Maternal Uncle",
"paternal_aunt": "Paternal Aunt",
"paternal_uncle": "Paternal Uncle",
"maternal_cousin": "Maternal Cousin",
"paternal_cousin": "Paternal Cousin",
"step_mom": "Step Mom",
"step_dad": "Step Dad",
"spouse": "Spouse",
"unknown": "Unknown",
}
_ROLE_SYNONYMS = {
"maternal grandmother": "maternal_grandmother",
"maternal grandfather": "maternal_grandfather",
"paternal grandmother": "paternal_grandmother",
"paternal grandfather": "paternal_grandfather",
"mother": "mom",
"father": "dad",
"proband": "prb",
"initially identified proband": "prb",
"sibling": "sib",
"younger sibling": "sib",
"older sibling": "sib",
"maternal half sibling": "maternal_half_sibling",
"paternal half sibling": "paternal_half_sibling",
# "half sibling": "half_sibling",
"maternal aunt": "maternal_aunt",
"maternal uncle": "maternal_uncle",
"paternal aunt": "paternal_aunt",
"paternal uncle": "paternal_uncle",
"maternal cousin": "maternal_cousin",
"paternal cousin": "paternal_cousin",
"step mom": "step_mom",
"step dad": "step_dad",
"step mother": "step_mom",
"step father": "step_dad",
}
[docs]
class Role(enum.Enum):
"""Enumerator for a person's role in a pedigree."""
# pylint: disable=invalid-name
maternal_grandmother = 1
maternal_grandfather = 1 << 1
paternal_grandmother = 1 << 2
paternal_grandfather = 1 << 3
mom = 1 << 4
dad = 1 << 5
parent = 1 << 6
prb = 1 << 7
sib = 1 << 8
child = 1 << 9
maternal_half_sibling = 1 << 10
paternal_half_sibling = 1 << 11
half_sibling = 1 << 12
maternal_aunt = 1 << 16
maternal_uncle = 1 << 17
paternal_aunt = 1 << 18
paternal_uncle = 1 << 19
maternal_cousin = 1 << 20
paternal_cousin = 1 << 21
step_mom = 1 << 22
step_dad = 1 << 23
spouse = 1 << 24
unknown = 0
@property
def display_name(self) -> str:
return _ROLE_DISPLAY_NAME[self.name]
def __repr__(self) -> str:
return self.name
def __str__(self) -> str:
return self.name
[docs]
@staticmethod
def from_name(name: str | int | None) -> Role:
"""Construct and return a Role from it's string representation."""
if isinstance(name, Role):
return name
if isinstance(name, int):
return Role.from_value(name)
if isinstance(name, str):
key = name.lower()
if key in Role.__members__:
return Role[key]
if key in _ROLE_SYNONYMS:
return Role[_ROLE_SYNONYMS[key]]
logger.debug("unexpected role name: <%s>", name)
return Role.unknown
[docs]
@staticmethod
def to_value(name: str | int | None) -> int:
role = Role.from_name(name)
return role.value
[docs]
@staticmethod
def to_name(value: int) -> str:
return Role(value).name
[docs]
@staticmethod
def from_value(val: int) -> Role:
return Role(int(val))
[docs]
@staticmethod
def not_role(value: int) -> int:
return (1 << 24) - 1 - value
[docs]
class Sex(enum.Enum):
"""Enumerator for a person's sex."""
M = 1
F = 2
U = 0
# pylint: disable=invalid-name
male = M
female = F
unspecified = U
[docs]
@staticmethod
def from_name(name: int | str | None) -> Sex:
"""Construct and return person Sex from string."""
if name is None:
return Sex.U
if isinstance(name, Sex):
return name
if isinstance(name, int):
return Sex.from_value(name)
assert isinstance(name, str)
name = name.lower()
if name in {"male", "m", "1"}:
return Sex.male
if name in {"female", "f", "2"}:
return Sex.female
if name in {"unspecified", "u", "0", "unknown"}:
return Sex.unspecified
raise ValueError(f"unexpected sex name: {name}")
[docs]
@staticmethod
def to_value(name: int | str | None) -> int:
sex = Sex.from_name(name)
return sex.value
[docs]
@staticmethod
def to_name(value: int) -> str:
return Sex(value).name
[docs]
@staticmethod
def from_value(val: int) -> Sex:
return Sex(int(val))
def __repr__(self) -> str:
return self.name
def __str__(self) -> str:
return self.name
[docs]
def short(self) -> str:
return self.name[0].upper()
def __lt__(self, other: Sex) -> bool:
return bool(self.value < other.value)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Sex):
return False
return bool(self.value == other.value)
def __hash__(self) -> int:
return self.value
[docs]
class Status(enum.Enum):
"""Enumerator for a person's status."""
# pylint: disable=invalid-name
unaffected = 1
affected = 2
unspecified = 0
[docs]
@staticmethod
def from_name(name: int | str | None) -> Status:
"""Construct and return person status from string."""
if name is None:
return Status.unspecified
if isinstance(name, Status):
return name
if isinstance(name, int):
return Status.from_value(name)
assert isinstance(name, str)
name = name.lower()
if name in {"unaffected", "1", "false"}:
return Status.unaffected
if name in {"affected", "2", "true"}:
return Status.affected
if name in {"unspecified", "-", "0", "unknown"}:
return Status.unspecified
raise ValueError(f"unexpected status type: {name}")
[docs]
@staticmethod
def to_value(name: int | str | None) -> int:
status = Status.from_name(name)
return status.value
[docs]
@staticmethod
def to_name(value: int) -> str:
return Status(value).name
[docs]
@staticmethod
def from_value(val: int) -> Status:
return Status(int(val))
def __repr__(self) -> str:
return self.name
def __str__(self) -> str:
return self.name
[docs]
def short(self) -> str:
return self.name[0].upper()
def __ge__(self, other: Status) -> bool:
if self.__class__ is other.__class__:
return bool(self.value >= other.value)
return NotImplemented
def __gt__(self, other: Status) -> bool:
if self.__class__ is other.__class__:
return bool(self.value > other.value)
return NotImplemented
def __le__(self, other: Status) -> bool:
if self.__class__ is other.__class__:
return bool(self.value <= other.value)
return NotImplemented
def __lt__(self, other: Status) -> bool:
if self.__class__ is other.__class__:
return bool(self.value < other.value)
return NotImplemented
[docs]
class Inheritance(enum.Enum):
"""Enumerator for variant inheritance type."""
# pylint: disable=invalid-name
reference = 1
mendelian = 1 << 1
denovo = 1 << 2
possible_denovo = 1 << 3
omission = 1 << 4
possible_omission = 1 << 5
other = 1 << 6
missing = 1 << 7
unknown = 1 << 8
[docs]
@staticmethod
def from_name(name: str) -> Inheritance:
assert (
name in Inheritance.__members__
), f"Inheritance type {name} does not exist!"
return Inheritance[name]
[docs]
@staticmethod
def from_value(value: int) -> Inheritance:
return Inheritance(value)
def __repr__(self) -> str:
return self.name
def __str__(self) -> str:
return self.name
[docs]
def bitmask2inheritance(bitmask: int) -> set[Inheritance]:
"""Convert a bitmask to set of inheritance."""
all_inheritance = {
Inheritance.reference,
Inheritance.mendelian,
Inheritance.denovo,
Inheritance.omission,
Inheritance.possible_denovo,
Inheritance.possible_omission,
Inheritance.other,
Inheritance.missing,
Inheritance.unknown,
}
result: set[Inheritance] = set()
for inh in all_inheritance:
if bitmask & inh.value:
result.add(inh)
return result
[docs]
class GeneticModel(enum.Enum):
# pylint: disable=invalid-name
autosomal = 1
autosomal_broken = 2
pseudo_autosomal = 3
X = 4
X_broken = 5
[docs]
class TransmissionType(enum.IntEnum):
# pylint: disable=invalid-name
unknown = 0
transmitted = 1
denovo = 2