mirror of
https://github.com/leaders-of-digital-9-task/backend.git
synced 2024-11-22 01:16:33 +03:00
major fixes and updates
This commit is contained in:
parent
8c53cb6745
commit
dcac066a18
|
@ -1,11 +1,19 @@
|
||||||
from dicom.api.views import (
|
from dicom.api.views import (
|
||||||
|
AddDicomProjectApi,
|
||||||
CreateCircleApi,
|
CreateCircleApi,
|
||||||
|
CreateFreeHandApi,
|
||||||
CreateRoiApi,
|
CreateRoiApi,
|
||||||
|
CreateRulerApi,
|
||||||
|
DeleteDicomProjectApi,
|
||||||
ListCreateDicomApi,
|
ListCreateDicomApi,
|
||||||
|
ListCreateProjectApi,
|
||||||
ListUpdateDicomImageNumberApi,
|
ListUpdateDicomImageNumberApi,
|
||||||
RetrieveUpdateDeleteCircleApi,
|
RetrieveUpdateDeleteCircleApi,
|
||||||
RetrieveUpdateDeleteDicomApi,
|
RetrieveUpdateDeleteDicomApi,
|
||||||
|
RetrieveUpdateDeleteFreeHandApi,
|
||||||
|
RetrieveUpdateDeleteProjectApi,
|
||||||
RetrieveUpdateDeleteRoiApi,
|
RetrieveUpdateDeleteRoiApi,
|
||||||
|
RetrieveUpdateDeleteRulerApi,
|
||||||
SmartFileUploadApi,
|
SmartFileUploadApi,
|
||||||
)
|
)
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
@ -39,11 +47,21 @@ urlpatterns = [
|
||||||
CreateRoiApi.as_view(),
|
CreateRoiApi.as_view(),
|
||||||
name="create_roi",
|
name="create_roi",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"<str:slug>/free_hand",
|
||||||
|
CreateFreeHandApi.as_view(),
|
||||||
|
name="create_free_hand",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<str:slug>/circle",
|
"<str:slug>/circle",
|
||||||
CreateCircleApi.as_view(),
|
CreateCircleApi.as_view(),
|
||||||
name="create_circle",
|
name="create_circle",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"<str:slug>/ruler",
|
||||||
|
CreateRulerApi.as_view(),
|
||||||
|
name="create_ruler",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"<str:slug>/<int:layer>",
|
"<str:slug>/<int:layer>",
|
||||||
ListUpdateDicomImageNumberApi.as_view(),
|
ListUpdateDicomImageNumberApi.as_view(),
|
||||||
|
@ -61,11 +79,44 @@ urlpatterns = [
|
||||||
RetrieveUpdateDeleteRoiApi.as_view(),
|
RetrieveUpdateDeleteRoiApi.as_view(),
|
||||||
name="get_update_delete_roi",
|
name="get_update_delete_roi",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"free_hand/<int:id>",
|
||||||
|
RetrieveUpdateDeleteFreeHandApi.as_view(),
|
||||||
|
name="get_update_delete_free_hand",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"circle/<int:id>",
|
"circle/<int:id>",
|
||||||
RetrieveUpdateDeleteCircleApi.as_view(),
|
RetrieveUpdateDeleteCircleApi.as_view(),
|
||||||
name="get_update_delete_circle",
|
name="get_update_delete_circle",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"ruler/<int:id>",
|
||||||
|
RetrieveUpdateDeleteRulerApi.as_view(),
|
||||||
|
name="get_update_delete_ruler",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"project/",
|
||||||
|
include(
|
||||||
|
[
|
||||||
|
path("", ListCreateProjectApi.as_view(), name="list_create_project"),
|
||||||
|
path(
|
||||||
|
"<str:slug>",
|
||||||
|
RetrieveUpdateDeleteProjectApi.as_view(),
|
||||||
|
name="get_update_delete_project",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<str:slug>/upload",
|
||||||
|
AddDicomProjectApi.as_view(),
|
||||||
|
name="add_dicom_api",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<str:slug>/<str:dicom_slug>",
|
||||||
|
DeleteDicomProjectApi.as_view(),
|
||||||
|
name="delete_dicom_api",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from dicom.models import Circle, Dicom, Roi
|
from dicom.models import Circle, Dicom, FreeHand, Roi
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
admin.site.register(Dicom)
|
admin.site.register(Dicom)
|
||||||
admin.site.register(Circle)
|
admin.site.register(Circle)
|
||||||
admin.site.register(Roi)
|
admin.site.register(Roi)
|
||||||
|
admin.site.register(FreeHand)
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
from dicom.models import Circle, Coordinate, Dicom, Roi
|
from dicom.models import (
|
||||||
|
BaseShape,
|
||||||
|
Circle,
|
||||||
|
Coordinate,
|
||||||
|
Dicom,
|
||||||
|
FreeHand,
|
||||||
|
Project,
|
||||||
|
Roi,
|
||||||
|
Ruler,
|
||||||
|
)
|
||||||
|
from dicom.services import create_coordinate
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
|
|
||||||
|
|
||||||
def create_coordinate(coordinates, obj):
|
|
||||||
for coordinate in coordinates:
|
|
||||||
Coordinate.objects.create(
|
|
||||||
x=coordinate["x"],
|
|
||||||
y=coordinate["y"],
|
|
||||||
shape=obj,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CoordinateSerializer(serializers.ModelSerializer):
|
class CoordinateSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Coordinate
|
model = Coordinate
|
||||||
|
@ -34,13 +35,43 @@ class ListDicomSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class BaseShapeSerializer(serializers.Serializer):
|
class BaseShapeSerializer(serializers.Serializer):
|
||||||
type = serializers.ChoiceField(choices=["circle", "roi"])
|
model = BaseShape
|
||||||
|
|
||||||
|
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand"])
|
||||||
image_number = serializers.IntegerField()
|
image_number = serializers.IntegerField()
|
||||||
coordinates = CoordinateSerializer(many=True)
|
coordinates = CoordinateSerializer(many=True)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
if self.model.max_coordinates:
|
||||||
|
if len(validated_data["coordinates"]) > self.model.max_coordinates:
|
||||||
|
raise serializers.ValidationError
|
||||||
|
if self.model.min_coordinates:
|
||||||
|
if len(validated_data["coordinates"]) < self.model.min_coordinates:
|
||||||
|
raise serializers.ValidationError
|
||||||
|
dicom = get_object_or_404(
|
||||||
|
Dicom, slug=self.context["request"].parser_context["kwargs"]["slug"]
|
||||||
|
)
|
||||||
|
obj = self.model.objects.create(
|
||||||
|
dicom=dicom, image_number=validated_data["image_number"]
|
||||||
|
)
|
||||||
|
|
||||||
|
create_coordinate(validated_data["coordinates"], obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def update(self, obj, validated_data):
|
||||||
|
Coordinate.objects.filter(shape=obj).delete()
|
||||||
|
if self.model.max_coordinates:
|
||||||
|
if len(validated_data["coordinates"]) > self.model.max_coordinates:
|
||||||
|
raise serializers.ValidationError
|
||||||
|
if self.model.min_coordinates:
|
||||||
|
if len(validated_data["coordinates"]) < self.model.min_coordinates:
|
||||||
|
raise serializers.ValidationError
|
||||||
|
create_coordinate(validated_data["coordinates"], obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class BaseShapeLayerSerializer(serializers.Serializer):
|
class BaseShapeLayerSerializer(serializers.Serializer):
|
||||||
type = serializers.ChoiceField(choices=["circle", "roi"])
|
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand"])
|
||||||
radius = serializers.FloatField(required=False)
|
radius = serializers.FloatField(required=False)
|
||||||
coordinates = CoordinateSerializer(many=True)
|
coordinates = CoordinateSerializer(many=True)
|
||||||
|
|
||||||
|
@ -58,31 +89,34 @@ class DicomSerializer(serializers.ModelSerializer):
|
||||||
fields = ["file", "uploaded", "pathology_type", "shapes"]
|
fields = ["file", "uploaded", "pathology_type", "shapes"]
|
||||||
|
|
||||||
|
|
||||||
class RoiSerializer(serializers.ModelSerializer):
|
class RoiSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||||
coordinates = CoordinateSerializer(many=True)
|
coordinates = CoordinateSerializer(many=True)
|
||||||
|
model = Roi
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Roi
|
model = Roi
|
||||||
fields = ["id", "image_number", "coordinates"]
|
fields = ["id", "image_number", "coordinates"]
|
||||||
extra_kwargs = {"id": {"read_only": True}}
|
extra_kwargs = {"id": {"read_only": True}}
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
if "coordinates" not in validated_data:
|
|
||||||
raise serializers.ValidationError
|
|
||||||
dicom = get_object_or_404(
|
|
||||||
Dicom, slug=self.context["request"].parser_context["kwargs"]["slug"]
|
|
||||||
)
|
|
||||||
roi = Roi.objects.create(
|
|
||||||
dicom=dicom, image_number=validated_data["image_number"]
|
|
||||||
)
|
|
||||||
|
|
||||||
create_coordinate(validated_data["coordinates"], roi)
|
class FreeHandSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||||
return roi
|
coordinates = CoordinateSerializer(many=True)
|
||||||
|
model = FreeHand
|
||||||
|
|
||||||
def update(self, obj: Circle, validated_data):
|
class Meta:
|
||||||
Coordinate.objects.filter(shape=obj).delete()
|
model = FreeHand
|
||||||
create_coordinate(validated_data["coordinates"], obj)
|
fields = ["id", "image_number", "coordinates"]
|
||||||
return obj
|
extra_kwargs = {"id": {"read_only": True}}
|
||||||
|
|
||||||
|
|
||||||
|
class RulerSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||||
|
coordinates = CoordinateSerializer(many=True)
|
||||||
|
model = Ruler
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FreeHand
|
||||||
|
fields = ["id", "image_number", "coordinates"]
|
||||||
|
extra_kwargs = {"id": {"read_only": True}}
|
||||||
|
|
||||||
|
|
||||||
class CircleSerializer(serializers.ModelSerializer):
|
class CircleSerializer(serializers.ModelSerializer):
|
||||||
|
@ -122,3 +156,24 @@ class CircleSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class SmartFileUploadSerializer(serializers.Serializer):
|
class SmartFileUploadSerializer(serializers.Serializer):
|
||||||
file = serializers.FileField()
|
file = serializers.FileField()
|
||||||
|
|
||||||
|
|
||||||
|
class ListProjectSerializer(serializers.ModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(
|
||||||
|
view_name="get_update_delete_project", lookup_field="slug"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Project
|
||||||
|
fields = ["url", "created"]
|
||||||
|
extra_kwargs = {
|
||||||
|
"created": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSerializer(serializers.ModelSerializer):
|
||||||
|
files = ListDicomSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Project
|
||||||
|
fields = ["files", "created"]
|
||||||
|
|
|
@ -5,15 +5,19 @@ from rest_framework.generics import GenericAPIView, get_object_or_404
|
||||||
from rest_framework.parsers import FormParser, MultiPartParser
|
from rest_framework.parsers import FormParser, MultiPartParser
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from ..models import Circle, Dicom, Roi
|
from ..models import Circle, Dicom, Project, Roi
|
||||||
from ..services import process_files
|
from ..services import process_files
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
BaseShapeLayerSerializer,
|
BaseShapeLayerSerializer,
|
||||||
BaseShapeSerializer,
|
BaseShapeSerializer,
|
||||||
CircleSerializer,
|
CircleSerializer,
|
||||||
DicomSerializer,
|
DicomSerializer,
|
||||||
|
FreeHandSerializer,
|
||||||
ListDicomSerializer,
|
ListDicomSerializer,
|
||||||
|
ListProjectSerializer,
|
||||||
|
ProjectSerializer,
|
||||||
RoiSerializer,
|
RoiSerializer,
|
||||||
|
RulerSerializer,
|
||||||
SmartFileUploadSerializer,
|
SmartFileUploadSerializer,
|
||||||
create_coordinate,
|
create_coordinate,
|
||||||
)
|
)
|
||||||
|
@ -41,55 +45,110 @@ class CreateRoiApi(generics.CreateAPIView):
|
||||||
serializer_class = RoiSerializer
|
serializer_class = RoiSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CreateFreeHandApi(generics.CreateAPIView):
|
||||||
|
serializer_class = FreeHandSerializer
|
||||||
|
|
||||||
|
|
||||||
class CreateCircleApi(generics.CreateAPIView):
|
class CreateCircleApi(generics.CreateAPIView):
|
||||||
serializer_class = CircleSerializer
|
serializer_class = CircleSerializer
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDeleteRoiApi(generics.RetrieveUpdateDestroyAPIView):
|
class CreateRulerApi(generics.CreateAPIView):
|
||||||
|
serializer_class = RulerSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDeleteBaseShape(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
def get_object(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
self.serializer_class.Meta.model,
|
||||||
|
id=self.request.parser_context["kwargs"]["id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@extend_schema(description="Note: coordinated are dropped on update")
|
||||||
|
def put(self, request, *args, **kwargs):
|
||||||
|
return self.update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@extend_schema(description="Note: coordinated are dropped on update")
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
return self.partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDeleteRoiApi(RetrieveUpdateDeleteBaseShape):
|
||||||
serializer_class = RoiSerializer
|
serializer_class = RoiSerializer
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return get_object_or_404(Roi, id=self.request.parser_context["kwargs"]["id"])
|
|
||||||
|
|
||||||
@extend_schema(description="Note: coordinated are dropped on update")
|
class RetrieveUpdateDeleteFreeHandApi(RetrieveUpdateDeleteBaseShape):
|
||||||
def put(self, request, *args, **kwargs):
|
serializer_class = FreeHandSerializer
|
||||||
return self.update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@extend_schema(description="Note: coordinated are dropped on update")
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
return self.partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDeleteCircleApi(generics.RetrieveUpdateDestroyAPIView):
|
class RetrieveUpdateDeleteCircleApi(RetrieveUpdateDeleteBaseShape):
|
||||||
serializer_class = CircleSerializer
|
serializer_class = CircleSerializer
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return get_object_or_404(Circle, id=self.request.parser_context["kwargs"]["id"])
|
|
||||||
|
|
||||||
@extend_schema(description="Note: coordinated are dropped on update")
|
class RetrieveUpdateDeleteRulerApi(RetrieveUpdateDeleteBaseShape):
|
||||||
def patch(self, request, *args, **kwargs):
|
serializer_class = CircleSerializer
|
||||||
return self.partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@extend_schema(description="Note: coordinated are dropped on update")
|
|
||||||
def put(self, request, *args, **kwargs):
|
|
||||||
return self.update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SmartFileUploadApi(GenericAPIView):
|
class SmartFileUploadApi(GenericAPIView):
|
||||||
parser_classes = [MultiPartParser, FormParser]
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
serializer_class = SmartFileUploadSerializer
|
serializer_class = SmartFileUploadSerializer
|
||||||
|
|
||||||
@extend_schema(responses={201: DicomSerializer(many=True)})
|
@extend_schema(responses={201: ListDicomSerializer(many=True)})
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
if "file" not in request.data:
|
if "file" not in request.data:
|
||||||
raise ValidationError("no files")
|
raise ValidationError("no files")
|
||||||
d_list = process_files(request.FILES.getlist("file"), request.user)
|
project = process_files(
|
||||||
|
request.FILES.getlist("file"),
|
||||||
|
request.user,
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
DicomSerializer(d_list.files.all(), many=True).data,
|
ListDicomSerializer(project.files.all(), many=True).data,
|
||||||
status=status.HTTP_201_CREATED,
|
status=status.HTTP_201_CREATED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddDicomProjectApi(GenericAPIView):
|
||||||
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
|
serializer_class = SmartFileUploadSerializer
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
operation_id="add_dicom_to_project",
|
||||||
|
responses={201: ListDicomSerializer(many=True)},
|
||||||
|
)
|
||||||
|
def post(self, request, slug):
|
||||||
|
if "file" not in request.data:
|
||||||
|
raise ValidationError("no files")
|
||||||
|
get_object_or_404(Project, slug=slug)
|
||||||
|
project = process_files(
|
||||||
|
request.FILES.getlist("file"),
|
||||||
|
request.user,
|
||||||
|
slug,
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
ListDicomSerializer(project.files.all(), many=True).data,
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteDicomProjectApi(GenericAPIView):
|
||||||
|
serializer_class = SmartFileUploadSerializer
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
operation_id="add_dicom_to_project",
|
||||||
|
request=None,
|
||||||
|
responses={200: ListDicomSerializer(many=True)},
|
||||||
|
)
|
||||||
|
def delete(self, request, slug, dicom_slug):
|
||||||
|
project = get_object_or_404(Project, slug=slug)
|
||||||
|
project.files.filter(slug=dicom_slug).delete()
|
||||||
|
return Response(
|
||||||
|
ListDicomSerializer(
|
||||||
|
project.files.all(), many=True, context={"request": request}
|
||||||
|
).data,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ListUpdateDicomImageNumberApi(GenericAPIView):
|
class ListUpdateDicomImageNumberApi(GenericAPIView):
|
||||||
serializer_class = BaseShapeSerializer(many=True)
|
serializer_class = BaseShapeSerializer(many=True)
|
||||||
|
|
||||||
|
@ -134,3 +193,27 @@ class ListUpdateDicomImageNumberApi(GenericAPIView):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return Response(shapes, status=status.HTTP_200_OK)
|
return Response(shapes, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
responses={204: None},
|
||||||
|
operation_id="delete_dicom_layer",
|
||||||
|
)
|
||||||
|
def delete(self, request, slug, layer):
|
||||||
|
dicom = get_object_or_404(Dicom, slug=slug)
|
||||||
|
dicom.shapes.filter(image_number=layer).delete()
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCreateProjectApi(generics.ListCreateAPIView):
|
||||||
|
serializer_class = ListProjectSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Project.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDeleteProjectApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
serializer_class = ProjectSerializer
|
||||||
|
queryset = Project.objects.all()
|
||||||
|
|
||||||
|
lookup_field = "slug"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
from .base import Dicom, ListOfDicom
|
from .base import Dicom, Project
|
||||||
from .blocks import BaseShape, Circle, Coordinate, Roi
|
from .shapes import BaseShape, Circle, Coordinate, FreeHand, Roi, Ruler
|
||||||
|
|
|
@ -6,8 +6,14 @@ from utils.files import media_upload_path
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class ListOfDicom(models.Model):
|
class Project(models.Model):
|
||||||
pass
|
user = models.ForeignKey(User, related_name="projects", on_delete=models.CASCADE)
|
||||||
|
slug = models.SlugField(max_length=10)
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username}'s project"
|
||||||
|
|
||||||
|
|
||||||
class Dicom(models.Model):
|
class Dicom(models.Model):
|
||||||
|
@ -22,8 +28,8 @@ class Dicom(models.Model):
|
||||||
uploaded = models.DateTimeField(auto_now_add=True)
|
uploaded = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
pathology_type = models.IntegerField(choices=PathologyType.choices, default=0)
|
pathology_type = models.IntegerField(choices=PathologyType.choices, default=0)
|
||||||
list = models.ForeignKey(
|
project = models.ForeignKey(
|
||||||
ListOfDicom, related_name="files", null=True, on_delete=models.SET_NULL
|
Project, related_name="files", null=True, on_delete=models.SET_NULL
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -4,11 +4,26 @@ from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
|
|
||||||
class BaseShape(PolymorphicModel):
|
class BaseShape(PolymorphicModel):
|
||||||
|
TYPE = "no_type"
|
||||||
|
min_coordinates = None
|
||||||
|
max_coordinates = None
|
||||||
dicom = models.ForeignKey(Dicom, related_name="shapes", on_delete=models.CASCADE)
|
dicom = models.ForeignKey(Dicom, related_name="shapes", on_delete=models.CASCADE)
|
||||||
image_number = models.IntegerField()
|
image_number = models.IntegerField()
|
||||||
|
|
||||||
def serialize_self(self):
|
def serialize_self(self):
|
||||||
raise NotImplementedError
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"type": self.TYPE,
|
||||||
|
"image_number": self.image_number,
|
||||||
|
"coordinates": self.coordinates,
|
||||||
|
}
|
||||||
|
|
||||||
|
def serialize_self_without_layer(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"type": self.TYPE,
|
||||||
|
"coordinates": self.coordinates,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coordinates(self) -> [(int, int)]:
|
def coordinates(self) -> [(int, int)]:
|
||||||
|
@ -33,6 +48,7 @@ class Coordinate(models.Model):
|
||||||
|
|
||||||
class Circle(BaseShape):
|
class Circle(BaseShape):
|
||||||
radius = models.FloatField()
|
radius = models.FloatField()
|
||||||
|
max_coordinates = 1
|
||||||
|
|
||||||
def serialize_self(self):
|
def serialize_self(self):
|
||||||
return {
|
return {
|
||||||
|
@ -56,21 +72,23 @@ class Circle(BaseShape):
|
||||||
|
|
||||||
|
|
||||||
class Roi(BaseShape):
|
class Roi(BaseShape):
|
||||||
def serialize_self(self):
|
TYPE = "roi"
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"type": "roi",
|
|
||||||
"image_number": self.image_number,
|
|
||||||
"coordinates": self.coordinates,
|
|
||||||
}
|
|
||||||
|
|
||||||
def serialize_self_without_layer(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"type": "roi",
|
|
||||||
"radius": self.radius,
|
|
||||||
"coordinates": self.coordinates,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Roi on {self.dicom.file.name}"
|
return f"Roi on {self.dicom.file.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class FreeHand(BaseShape):
|
||||||
|
TYPE = "free_hand"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"FreeHand on {self.dicom.file.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class Ruler(BaseShape):
|
||||||
|
TYPE = "ruler"
|
||||||
|
max_coordinates = 2
|
||||||
|
min_coordinates = 2
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Ruler on {self.dicom.file.name}"
|
|
@ -5,18 +5,23 @@ import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from dicom.models import Dicom, ListOfDicom
|
from dicom.models import Coordinate, Dicom, Project
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
|
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
|
||||||
from utils.generators import generate_charset
|
from utils.generators import generate_charset
|
||||||
|
|
||||||
|
|
||||||
def process_files(files: list[TemporaryUploadedFile | InMemoryUploadedFile], user):
|
def process_files(
|
||||||
d_list = ListOfDicom.objects.create()
|
files: list[TemporaryUploadedFile | InMemoryUploadedFile], user, slug=None
|
||||||
|
):
|
||||||
|
if slug:
|
||||||
|
project = Project.objects.get(slug=slug)
|
||||||
|
else:
|
||||||
|
project = Project.objects.create(user=user)
|
||||||
for file in files:
|
for file in files:
|
||||||
content_type = magic.from_file(file.temporary_file_path())
|
content_type = magic.from_file(file.temporary_file_path())
|
||||||
if content_type == "DICOM medical imaging data":
|
if content_type == "DICOM medical imaging data":
|
||||||
Dicom.objects.create(file=file, list=d_list, user=user)
|
Dicom.objects.create(file=file, project=project, user=user)
|
||||||
elif "Zip" in content_type:
|
elif "Zip" in content_type:
|
||||||
dit_path = f"/tmp/{generate_charset(10)}"
|
dit_path = f"/tmp/{generate_charset(10)}"
|
||||||
os.mkdir(dit_path)
|
os.mkdir(dit_path)
|
||||||
|
@ -32,8 +37,17 @@ def process_files(files: list[TemporaryUploadedFile | InMemoryUploadedFile], use
|
||||||
with path.open(mode="rb") as f:
|
with path.open(mode="rb") as f:
|
||||||
Dicom.objects.create(
|
Dicom.objects.create(
|
||||||
file=File(f, name=file_in_d.split("/")[-1]),
|
file=File(f, name=file_in_d.split("/")[-1]),
|
||||||
list=d_list,
|
project=project,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
shutil.rmtree(dit_path)
|
shutil.rmtree(dit_path)
|
||||||
return d_list
|
return project
|
||||||
|
|
||||||
|
|
||||||
|
def create_coordinate(coordinates, obj):
|
||||||
|
for coordinate in coordinates:
|
||||||
|
Coordinate.objects.create(
|
||||||
|
x=coordinate["x"],
|
||||||
|
y=coordinate["y"],
|
||||||
|
shape=obj,
|
||||||
|
)
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
from dicom.models import Dicom
|
from dicom.models import Dicom, Project
|
||||||
from django.db.models.signals import pre_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from utils.generators import generate_charset
|
from utils.generators import generate_charset
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Dicom)
|
@receiver(post_save, sender=Project)
|
||||||
def create_dicom(sender, instance: Dicom, **kwargs):
|
def create_project(sender, instance: Project, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
slug = generate_charset(5)
|
||||||
|
while Project.objects.filter(slug=slug):
|
||||||
|
slug = generate_charset(5)
|
||||||
|
instance.slug = slug
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Dicom)
|
||||||
|
def create_dicom(sender, instance: Dicom, created, **kwargs):
|
||||||
|
if created:
|
||||||
slug = generate_charset(5)
|
slug = generate_charset(5)
|
||||||
while Dicom.objects.filter(slug=slug):
|
while Dicom.objects.filter(slug=slug):
|
||||||
slug = generate_charset(5)
|
slug = generate_charset(5)
|
||||||
instance.slug = slug
|
instance.slug = slug
|
||||||
|
instance.save()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user