Source code for endoreg_db.models.medical.patient.patient_finding_morphology

from django.db import models
from typing import TYPE_CHECKING, Dict

# Corrected imports for type hints
if TYPE_CHECKING:
    from ..finding import (
        FindingMorphologyClassification, # Corrected
        FindingMorphologyClassificationChoice, # Corrected
    )
    from .patient_finding import PatientFinding

[docs] class PatientFindingMorphology(models.Model): """ Represents the morphological description of a specific patient finding. Links a PatientFinding to a specific choice within a morphology classification, and stores associated subcategory values and numerical descriptors. """ finding = models.ForeignKey( 'PatientFinding', on_delete=models.CASCADE, related_name='morphologies' ) morphology_classification = models.ForeignKey( 'FindingMorphologyClassification', on_delete=models.CASCADE, related_name='patient_finding_morphologies' ) morphology_choice = models.ForeignKey( 'FindingMorphologyClassificationChoice', on_delete=models.CASCADE, related_name='patient_finding_morphologies' ) subcategories = models.JSONField(default=dict) numerical_descriptors = models.JSONField(default=dict) if TYPE_CHECKING: patient_finding: "PatientFinding" # Corrected name morphology_classification: "FindingMorphologyClassification" # Corrected type morphology_choice: "FindingMorphologyClassificationChoice" # Corrected type subcategories: Dict[str, Dict[str, str]] # Corrected type numerical_descriptors: Dict[str, Dict[str, str]] # Corrected type class Meta: verbose_name = 'Patient Finding Morphology' verbose_name_plural = 'Patient Finding Morphologies' ordering = ['morphology_classification', 'morphology_choice'] def __str__(self): """Returns a string representation including classification, choice, and any set values.""" _str = f"{self.morphology_classification} - {self.morphology_choice}" if self.subcategories: for key, _dict in self.subcategories.items(): value = _dict.get("value", None) if value: _str += f" - {key}: {value}" if self.numerical_descriptors: for key, _dict in self.numerical_descriptors.items(): value = _dict.get("value", None) if value: _str += f" - {key}: {value}" return _str
[docs] def set_subcategory(self, subcategory_name, subcategory_value): """Sets the value for a specific subcategory.""" assert subcategory_name in self.subcategories, "Subcategory must be in subcategories." self.subcategories[subcategory_name]["value"] = subcategory_value self.save() return self.subcategories[subcategory_name]
[docs] def save(self, *args, **kwargs): """ Overrides save to validate choice and initialize subcategories/descriptors from the morphology choice if they are not already set. """ if self.morphology_choice not in self.morphology_classification.choices.all(): raise ValueError("morphology_choice must be in morphology_classification.choices") if not self.subcategories: self.subcategories = self.morphology_choice.subcategories if not self.subcategories: self.subcategories = {} if not self.numerical_descriptors: self.numerical_descriptors = self.morphology_choice.numerical_descriptors if not self.numerical_descriptors: self.numerical_descriptors = {} super().save(*args, **kwargs)
[docs] def get_subcategories(self): """Returns the dictionary of subcategories, ensuring it's initialized.""" if not self.subcategories: self.save() return self.subcategories
[docs] def get_numerical_descriptors(self): """Returns the dictionary of numerical descriptors, ensuring it's initialized.""" if not self.numerical_descriptors: self.save() return self.numerical_descriptors
[docs] def get_random_value_for_numerical_descriptor(self, descriptor_name): """Generates a random value for a specified numerical descriptor based on its definition.""" import numpy as np assert descriptor_name in self.numerical_descriptors, "Descriptor must be in numerical descriptors." descriptor = self.numerical_descriptors[descriptor_name] min_val = descriptor.get("min", 0) max_val = descriptor.get("max", 1) distribution = descriptor.get("distribution", "normal") if distribution == "normal": mean = descriptor.get("mean", 0.5) std = descriptor.get("std", 0.1) value = np.random.normal(mean, std) # clip value to min and max value = np.clip(value, min_val, max_val) elif distribution == "uniform": value = np.random.uniform(min_val, max_val) else: raise ValueError("Distribution not supported") return value
[docs] def set_numerical_descriptor_random(self, descriptor_name): """Sets a random value for a specified numerical descriptor.""" assert descriptor_name in self.numerical_descriptors, "Descriptor must be in numerical descriptors." value = self.get_random_value_for_numerical_descriptor(descriptor_name) self.numerical_descriptors[descriptor_name]["value"] = value self.save() return self.numerical_descriptors[descriptor_name]
[docs] def set_random_numerical_descriptors(self): #TODO Update """Sets random values for all required numerical descriptors based on their definitions.""" import numpy as np # get numerical descriptors from morphology_choice try: numerical_descriptors = self.morphology_choice.numerical_descriptors assert numerical_descriptors except Exception as e: # print(f"Numerical descriptors not found for {self.morphology_choice}") return None # If available, numerical descriptors is a dict like this: # { # "DESCRIPTOR_NAME": { # "min": 0.5, # "max": 1.5, # "unit": "mm" # "mean": 1.0 # "std": 0.1 # "required": True # } #} # Iterate over all numerical descriptors # if required is true, set random values following the constraints and distribution # available distributions are: normal, uniform for descriptor_name, descriptor in numerical_descriptors.items(): required = descriptor.get("required", False) if required: min_val = descriptor.get("min", 0) max_val = descriptor.get("max", 1) distribution = descriptor.get("distribution", "normal") if distribution == "normal": mean = descriptor.get("mean", 0.5) std = descriptor.get("std", 0.1) value = np.random.normal(mean, std) # clip value to min and max value = np.clip(value, min_val, max_val) elif distribution == "uniform": value = np.random.uniform(min_val, max_val) else: raise ValueError("Distribution not supported") self.numerical_descriptors[descriptor_name]["value"] = value self.save()