diff --git a/promort/clinical_annotations_manager/migrations/0029_auto_20230531_1444.py b/promort/clinical_annotations_manager/migrations/0029_auto_20230531_1444.py new file mode 100644 index 0000000..1616bb5 --- /dev/null +++ b/promort/clinical_annotations_manager/migrations/0029_auto_20230531_1444.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.13 on 2023-05-31 14:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('clinical_annotations_manager', '0028_auto_20221107_1442'), + ] + + operations = [ + migrations.AlterField( + model_name='coreannotation', + name='gleason_group', + field=models.CharField(choices=[('GG1', 'GRADE_GROUP_1'), ('GG2', 'GRADE_GROUP_2'), ('GG3', 'GRADE_GROUP_3'), ('GG4', 'GRADE_GROUP_4'), ('GG5', 'GRADE_GROUP_5')], default=None, max_length=3, null=True), + ), + migrations.AlterField( + model_name='coreannotation', + name='primary_gleason', + field=models.IntegerField(default=None, null=True), + ), + migrations.AlterField( + model_name='coreannotation', + name='secondary_gleason', + field=models.IntegerField(default=None, null=True), + ), + ] diff --git a/promort/clinical_annotations_manager/models.py b/promort/clinical_annotations_manager/models.py index 6421ca4..a17e2e4 100644 --- a/promort/clinical_annotations_manager/models.py +++ b/promort/clinical_annotations_manager/models.py @@ -23,6 +23,8 @@ from reviews_manager.models import ClinicalAnnotationStep from rois_manager.models import Slice, Core, FocusRegion +from collections import Counter + class SliceAnnotation(models.Model): author = models.ForeignKey(User, on_delete=models.PROTECT, blank=False) @@ -95,10 +97,10 @@ class CoreAnnotation(models.Model): action_start_time = models.DateTimeField(null=True, default=None) action_complete_time = models.DateTimeField(null=True, default=None) creation_date = models.DateTimeField(default=timezone.now) - primary_gleason = models.IntegerField(blank=False) - secondary_gleason = models.IntegerField(blank=False) + primary_gleason = models.IntegerField(null=True, default=None) + secondary_gleason = models.IntegerField(null=True, default=None) gleason_group = models.CharField( - max_length=3, choices=GLEASON_GROUP_WHO_16, blank=False + max_length=3, choices=GLEASON_GROUP_WHO_16, null=True, default=None ) # acquire ONLY if at least one Cribriform Pattern (under GleasonPattern type 4) exists nuclear_grade_size = models.CharField(max_length=1, null=True, default=None) @@ -119,6 +121,52 @@ class CoreAnnotation(models.Model): class Meta: unique_together = ('core', 'annotation_step') + def _get_gleason_elements(self): + gleason_elements = list() + for fr in self.core.focus_regions.all(): + gleason_elements.extend( + GleasonPattern.objects.filter( + focus_region = fr, + annotation_step = self.annotation_step + ).all() + ) + return gleason_elements + + def _get_gleason_coverage(self): + g_elems = self._get_gleason_elements() + total_gleason_area = 0 + gleason_patterns_area = Counter() + for g_el in g_elems: + total_gleason_area += g_el.area + gleason_patterns_area[g_el.gleason_type] += g_el.area + gleason_coverage = dict() + for gp, gpa in gleason_patterns_area.items(): + gleason_coverage[gp] = (100 * gpa/total_gleason_area) + return gleason_coverage + + def _get_primary_and_secondary_gleason(self): + gleason_coverage = self._get_gleason_coverage() + if len(gleason_coverage) == 0: + return None, None + primary_gleason = max(gleason_coverage, key=gleason_coverage.get) + gleason_coverage.pop(primary_gleason) + if len(gleason_coverage) == 0: + secondary_gleason = primary_gleason + else: + secondary_gleason = max(gleason_coverage) + return primary_gleason, secondary_gleason + + def get_primary_gleason(self): + primary_gleason, _ = self._get_primary_and_secondary_gleason() + return primary_gleason + + def get_secondary_gleason(self): + _, secondary_gleason = self._get_primary_and_secondary_gleason() + return secondary_gleason + + def get_gleason_group(self): + pass + def get_gleason_4_total_area(self): gleason_4_total_area = 0.0 for focus_region in self.core.focus_regions.all(): @@ -198,7 +246,7 @@ class Meta: def get_gleason_elements(self): gleason_elements_map = dict() for gp in self.annotation_step.gleason_annotations.filter(focus_region=self.focus_region).all(): - gleason_elements_map[gp.gleason_type] = gp + gleason_elements_map.setdefault(gp.gleason_type, []).append(gp) return gleason_elements_map def get_gleason_4_elements(self): diff --git a/promort/clinical_annotations_manager/serializers.py b/promort/clinical_annotations_manager/serializers.py index 6e1a69f..a323c95 100644 --- a/promort/clinical_annotations_manager/serializers.py +++ b/promort/clinical_annotations_manager/serializers.py @@ -77,8 +77,7 @@ class CoreAnnotationSerializer(serializers.ModelSerializer): class Meta: model = CoreAnnotation fields = ('id', 'author', 'core', 'annotation_step', 'action_start_time', 'action_complete_time', - 'creation_date', 'primary_gleason', 'secondary_gleason', 'gleason_score', - 'gleason_4_percentage', 'gleason_group', 'nuclear_grade_size', + 'creation_date', 'gleason_score', 'gleason_4_percentage', 'nuclear_grade_size', 'intraluminal_acinar_differentiation_grade', 'intraluminal_secretions', 'central_maturation', 'extra_cribriform_gleason_score', 'largest_confluent_sheet', 'total_cribriform_area', 'predominant_rsg', @@ -91,7 +90,7 @@ class Meta: @staticmethod def get_gleason_score(obj): - return '%d + %d' % (obj.primary_gleason, obj.secondary_gleason) + return '{0} + {1}'.format(obj.get_primary_gleason(), obj.get_secondary_gleason()) @staticmethod def get_gleason_4_percentage(obj): diff --git a/promort/src/js/clinical_annotations_manager/clinical_annotations_manager.controllers.js b/promort/src/js/clinical_annotations_manager/clinical_annotations_manager.controllers.js index 95d566e..ae9c9e1 100644 --- a/promort/src/js/clinical_annotations_manager/clinical_annotations_manager.controllers.js +++ b/promort/src/js/clinical_annotations_manager/clinical_annotations_manager.controllers.js @@ -573,6 +573,7 @@ break; case 'focus_region': vm.activateNewFocusRegionAnnotationMode(roi_id); + break; } } } else { @@ -585,6 +586,7 @@ break; case 'focus_region': vm.activateShowFocusRegionAnnotationMode(roi_id); + break; } } } @@ -1259,8 +1261,7 @@ } function formValid() { - return ((typeof vm.primaryGleason !== 'undefined') && - (typeof vm.secondaryGleason !== 'undefined')); + return true; } function destroy() { @@ -1313,9 +1314,6 @@ closeByDocument: false }); var obj_config = { - primary_gleason: Number(vm.primaryGleason), - secondary_gleason: Number(vm.secondaryGleason), - gleason_group: vm.gradeGroupWho, action_start_time: vm.actionStartTime, action_complete_time: new Date(), predominant_rsg: vm.predominant_rsg,