-
Notifications
You must be signed in to change notification settings - Fork 2
laparoscopy #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
laparoscopy #12
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Generated by Django 5.2.4 on 2026-05-04 09:08 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('common', '0023_rename_common_file_domain_22309d_idx_maxillo_fil_domain_760eb4_idx_and_more'), | ||
| ('laparoscopy', '0001_initial'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='fileregistry', | ||
| name='laparoscopy_patient', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='files', to='laparoscopy.patient'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='fileregistry', | ||
| name='laparoscopy_voice_caption', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='files', to='laparoscopy.voicecaption'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='job', | ||
| name='laparoscopy_patient', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='laparoscopy.patient'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='job', | ||
| name='laparoscopy_voice_caption', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='laparoscopy.voicecaption'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='processingjob', | ||
| name='laparoscopy_patient', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='processing_jobs', to='laparoscopy.patient'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='processingjob', | ||
| name='laparoscopy_voice_caption', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='processing_jobs', to='laparoscopy.voicecaption'), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='fileregistry', | ||
| name='domain', | ||
| field=models.CharField(choices=[('maxillo', 'Maxillo'), ('brain', 'Brain'), ('laparoscopy', 'Laparoscopy')], default='maxillo', max_length=20), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='fileregistry', | ||
| name='file_type', | ||
| field=models.CharField(choices=[('cbct_raw', 'CBCT Raw'), ('cbct_processed', 'CBCT Processed'), ('ios_raw_upper', 'IOS Raw Upper'), ('ios_raw_lower', 'IOS Raw Lower'), ('ios_processed_upper', 'IOS Processed Upper'), ('ios_processed_lower', 'IOS Processed Lower'), ('audio_raw', 'Audio Raw'), ('audio_processed', 'Audio Processed Text'), ('bite_classification', 'Bite Classification Results'), ('rgb_image', 'RGB Image'), ('volume_raw', 'Volume Raw'), ('volume_processed', 'Volume Processed'), ('image_raw', 'Image Raw'), ('image_processed', 'Image Processed'), ('generic_raw', 'Generic Raw'), ('generic_processed', 'Generic Processed'), ('braintumor_mri_t1_raw', 'Brain MRI T1 Raw'), ('braintumor_mri_t1_processed', 'Brain MRI T1 Processed'), ('braintumor_mri_t1c_raw', 'Brain MRI T1c Raw'), ('braintumor_mri_t1c_processed', 'Brain MRI T1c Processed'), ('braintumor_mri_t2_raw', 'Brain MRI T2 Raw'), ('braintumor_mri_t2_processed', 'Brain MRI T2 Processed'), ('braintumor_mri_flair_raw', 'Brain MRI FLAIR Raw'), ('braintumor_mri_flair_processed', 'Brain MRI FLAIR Processed'), ('intraoral_raw', 'Intraoral Photographs Raw'), ('intraoral_processed', 'Intraoral Photographs Processed'), ('teleradiography_raw', 'Teleradiography Raw'), ('teleradiography_processed', 'Teleradiography Processed'), ('panoramic_raw', 'panoramic Raw'), ('panoramic_processed', 'panoramic Processed'), ('video_raw', 'Video Raw'), ('video_processed', 'Video Processed')], max_length=255), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='job', | ||
| name='domain', | ||
| field=models.CharField(choices=[('maxillo', 'Maxillo'), ('brain', 'Brain'), ('laparoscopy', 'Laparoscopy')], default='maxillo', max_length=20), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='processingjob', | ||
| name='domain', | ||
| field=models.CharField(choices=[('maxillo', 'Maxillo'), ('brain', 'Brain'), ('laparoscopy', 'Laparoscopy')], default='maxillo', max_length=20), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,6 +159,7 @@ class Job(models.Model): | |
| DOMAIN_CHOICES = [ | ||
| ('maxillo', 'Maxillo'), | ||
| ('brain', 'Brain'), | ||
| ('laparoscopy', 'Laparoscopy'), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Atra cosa che sarebbe meglio segnarsi per correggerlo, sta roba e' ripetuta troppe volte in questo file, cosi' come altri campi |
||
| ] | ||
|
|
||
| STATUS_CHOICES = [ | ||
|
|
@@ -177,8 +178,10 @@ class Job(models.Model): | |
| domain = models.CharField(max_length=20, choices=DOMAIN_CHOICES, default='maxillo') | ||
| patient = models.ForeignKey('maxillo.Patient', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
| brain_patient = models.ForeignKey('brain.Patient', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
| laparoscopy_patient = models.ForeignKey('laparoscopy.Patient', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
| voice_caption = models.ForeignKey('maxillo.VoiceCaption', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
| brain_voice_caption = models.ForeignKey('brain.VoiceCaption', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
| laparoscopy_voice_caption = models.ForeignKey('laparoscopy.VoiceCaption', on_delete=models.CASCADE, related_name='jobs', null=True, blank=True) | ||
|
|
||
| # IO | ||
| input_file_path = models.CharField(max_length=500, help_text='Primary input object key', blank=True) | ||
|
|
@@ -289,6 +292,7 @@ class ProcessingJob(models.Model): | |
| DOMAIN_CHOICES = [ | ||
| ('maxillo', 'Maxillo'), | ||
| ('brain', 'Brain'), | ||
| ('laparoscopy', 'Laparoscopy'), | ||
| ] | ||
|
|
||
| JOB_TYPE_CHOICES = [ | ||
|
|
@@ -314,8 +318,10 @@ class ProcessingJob(models.Model): | |
| domain = models.CharField(max_length=20, choices=DOMAIN_CHOICES, default='maxillo') | ||
| patient = models.ForeignKey('maxillo.Patient', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
| brain_patient = models.ForeignKey('brain.Patient', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
| laparoscopy_patient = models.ForeignKey('laparoscopy.Patient', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
| voice_caption = models.ForeignKey('maxillo.VoiceCaption', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
| brain_voice_caption = models.ForeignKey('brain.VoiceCaption', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
| laparoscopy_voice_caption = models.ForeignKey('laparoscopy.VoiceCaption', on_delete=models.CASCADE, related_name='processing_jobs', null=True, blank=True) | ||
|
|
||
| # File paths | ||
| input_file_path = models.CharField(max_length=500, help_text='Input object key') | ||
|
|
@@ -430,6 +436,7 @@ class FileRegistry(models.Model): | |
| DOMAIN_CHOICES = [ | ||
| ('maxillo', 'Maxillo'), | ||
| ('brain', 'Brain'), | ||
| ('laparoscopy', 'Laparoscopy'), | ||
| ] | ||
|
|
||
| FILE_TYPE_CHOICES = [ | ||
|
|
@@ -465,6 +472,9 @@ class FileRegistry(models.Model): | |
| ('teleradiography_processed', 'Teleradiography Processed'), | ||
| ('panoramic_raw', 'panoramic Raw'), | ||
| ('panoramic_processed', 'panoramic Processed'), | ||
| # Generic video modality (used by laparoscopy and any future video domain) | ||
| ('video_raw', 'Video Raw'), | ||
| ('video_processed', 'Video Processed'), | ||
| ] | ||
|
|
||
| file_type = models.CharField(max_length=255, choices=FILE_TYPE_CHOICES) | ||
|
|
@@ -477,8 +487,10 @@ class FileRegistry(models.Model): | |
| domain = models.CharField(max_length=20, choices=DOMAIN_CHOICES, default='maxillo') | ||
| patient = models.ForeignKey('maxillo.Patient', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| brain_patient = models.ForeignKey('brain.Patient', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| laparoscopy_patient = models.ForeignKey('laparoscopy.Patient', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| voice_caption = models.ForeignKey('maxillo.VoiceCaption', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| brain_voice_caption = models.ForeignKey('brain.VoiceCaption', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| laparoscopy_voice_caption = models.ForeignKey('laparoscopy.VoiceCaption', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| processing_job = models.ForeignKey('common.Job', on_delete=models.CASCADE, related_name='files', null=True, blank=True) | ||
| created_at = models.DateTimeField(auto_now_add=True) | ||
| metadata = models.JSONField(default=dict, blank=True, help_text='Additional file metadata') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,12 @@ def __init__( | |
| config=Config( | ||
| s3={"addressing_style": self.addressing_style or "path"}, | ||
| retries={"max_attempts": 3, "mode": "standard"}, | ||
| # Garage can advertise flexible checksums that botocore then | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Questa roba perche' e' apparsa? leggo an optimization for video streams ma non sono sicurissimo di aver capito. Se riesci fai un check che sia veramente necessaria e fai in modo che il commento sopra sia un po' piu' chiaro |
||
| # rejects on plain get_object reads. Keep validation only for | ||
| # operations that explicitly require it. | ||
| # An optimization for video stream | ||
| request_checksum_calculation="when_required", | ||
| response_checksum_validation="when_required", | ||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -164,6 +170,24 @@ def get(self, key: str) -> Tuple[BinaryIO, ObjectInfo]: | |
| ) | ||
| return resp["Body"], info | ||
|
|
||
| def get_range(self, key: str, byte_range: str) -> Tuple[BinaryIO, ObjectInfo]: | ||
| """Fetch a byte range of an object (e.g. byte_range='bytes=0-1023').""" | ||
| key_n = self.normalize_key(key) | ||
| try: | ||
| resp = self._client.get_object(Bucket=self.bucket, Key=key_n, Range=byte_range) | ||
| except ClientError as exc: | ||
| code = self._client_error_code(exc) | ||
| if code in {"NoSuchKey", "404", "NotFound"}: | ||
| raise FileNotFoundError(key) from exc | ||
| raise ObjectStorageError(str(exc)) from exc | ||
| info = ObjectInfo( | ||
| key=key, | ||
| content_length=resp.get("ContentLength"), | ||
| content_type=resp.get("ContentType"), | ||
| etag=(resp.get("ETag") or "").strip('"') or None, | ||
| ) | ||
| return resp["Body"], info | ||
|
|
||
| def iter_bytes( | ||
| self, key: str, *, chunk_size: int = 1024 * 1024 | ||
| ) -> Generator[bytes, None, None]: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| from django.contrib import admin | ||
| from django.utils.html import format_html | ||
|
|
||
| from .models import ( | ||
| Classification, | ||
| Dataset, | ||
| Export, | ||
| Folder, | ||
| Patient, | ||
| QuadrantClassificationMarker, | ||
| QuadrantType, | ||
| Tag, | ||
| VoiceCaption, | ||
| ) | ||
|
|
||
|
|
||
| class QuadrantClassificationMarkerInline(admin.TabularInline): | ||
| model = QuadrantClassificationMarker | ||
| extra = 0 | ||
| fields = ['time_ms', 'quadrant_type', 'created_by', 'updated_by', 'updated_at'] | ||
| readonly_fields = ['created_by', 'updated_by', 'updated_at'] | ||
| ordering = ['time_ms', 'id'] | ||
|
|
||
|
|
||
| @admin.register(Dataset) | ||
| class DatasetAdmin(admin.ModelAdmin): | ||
| list_display = ['name', 'created_at', 'created_by'] | ||
| search_fields = ['name', 'description'] | ||
|
|
||
|
|
||
| @admin.register(Folder) | ||
| class FolderAdmin(admin.ModelAdmin): | ||
| list_display = ['name', 'parent', 'created_at', 'created_by'] | ||
| search_fields = ['name'] | ||
|
|
||
|
|
||
| @admin.register(Tag) | ||
| class TagAdmin(admin.ModelAdmin): | ||
| list_display = ['name', 'created_at'] | ||
| search_fields = ['name'] | ||
|
|
||
|
|
||
| @admin.register(Patient) | ||
| class PatientAdmin(admin.ModelAdmin): | ||
| list_display = ['patient_id', 'name', 'visibility', 'folder', 'uploaded_at', 'uploaded_by'] | ||
| list_filter = ['visibility', 'uploaded_at'] | ||
| search_fields = ['patient_id', 'name'] | ||
| inlines = [QuadrantClassificationMarkerInline] | ||
|
|
||
|
|
||
| @admin.register(Classification) | ||
| class ClassificationAdmin(admin.ModelAdmin): | ||
| list_display = ['id', 'patient', 'classifier', 'timestamp'] | ||
| list_filter = ['classifier', 'timestamp'] | ||
|
|
||
|
|
||
| @admin.register(VoiceCaption) | ||
| class VoiceCaptionAdmin(admin.ModelAdmin): | ||
| list_display = ['id', 'patient', 'user', 'modality', 'processing_status', 'created_at'] | ||
| list_filter = ['modality', 'processing_status', 'created_at'] | ||
|
|
||
|
|
||
| @admin.register(Export) | ||
| class ExportAdmin(admin.ModelAdmin): | ||
| list_display = ['id', 'user', 'status', 'patient_count', 'file_size', 'created_at', 'completed_at'] | ||
| list_filter = ['status', 'share_mode', 'created_at', 'completed_at'] | ||
| search_fields = ['id', 'user__username', 'query_summary', 'file_path', 'share_token'] | ||
| readonly_fields = ['created_at', 'started_at', 'completed_at', 'shared_at'] | ||
|
|
||
|
|
||
| @admin.register(QuadrantType) | ||
| class QuadrantTypeAdmin(admin.ModelAdmin): | ||
| list_display = ['id', 'project', 'name', 'color_preview', 'color', 'order', 'marker_count'] | ||
| list_filter = ['project'] | ||
| search_fields = ['name', 'project__name', 'project__slug'] | ||
| ordering = ['project__name', 'order', 'name'] | ||
|
|
||
| @admin.display(description='Color') | ||
| def color_preview(self, obj): | ||
| return format_html( | ||
| '<span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:{};border:1px solid #ccc;"></span>', | ||
| obj.color, | ||
| ) | ||
|
|
||
| @admin.display(description='Markers') | ||
| def marker_count(self, obj): | ||
| return obj.markers.count() | ||
|
|
||
|
|
||
| @admin.register(QuadrantClassificationMarker) | ||
| class QuadrantClassificationMarkerAdmin(admin.ModelAdmin): | ||
| list_display = ['id', 'patient', 'patient_name', 'quadrant_type', 'time_seconds', 'created_by', 'updated_by', 'updated_at'] | ||
| list_filter = ['quadrant_type', 'patient__visibility', 'created_at', 'updated_at'] | ||
| search_fields = ['patient__patient_id', 'patient__name', 'quadrant_type__name'] | ||
| autocomplete_fields = ['patient', 'quadrant_type', 'created_by', 'updated_by'] | ||
| ordering = ['patient_id', 'time_ms', 'id'] | ||
|
|
||
| @admin.display(description='Patient Name') | ||
| def patient_name(self, obj): | ||
| return obj.patient.name | ||
|
|
||
| @admin.display(description='Time (s)', ordering='time_ms') | ||
| def time_seconds(self, obj): | ||
| return f'{obj.time_ms / 1000:.3f}' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class LaparoscopyConfig(AppConfig): | ||
| default_auto_field = 'django.db.models.BigAutoField' | ||
| name = 'laparoscopy' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Questa change mi ha fatto notare che "maxillo" non e' esplicito qui. Riusciresti a correggere? Oppure creo io una issue per aumentare di poco le probabilita' che non ci dimentichiamo