mirror of
https://github.com/leaders-of-digital-9-task/backend.git
synced 2024-11-28 04:03:43 +03:00
added layers
This commit is contained in:
parent
23db12cf8a
commit
9019d31d48
|
@ -2,6 +2,7 @@ from dicom.api.views import (
|
|||
AddDicomProjectApi,
|
||||
CreateCircleApi,
|
||||
CreateFreeHandApi,
|
||||
CreateLayerApi,
|
||||
CreateRoiApi,
|
||||
CreateRulerApi,
|
||||
DeleteDicomProjectApi,
|
||||
|
@ -12,6 +13,7 @@ from dicom.api.views import (
|
|||
RetrieveUpdateDeleteCircleApi,
|
||||
RetrieveUpdateDeleteDicomApi,
|
||||
RetrieveUpdateDeleteFreeHandApi,
|
||||
RetrieveUpdateDeleteLayerApi,
|
||||
RetrieveUpdateDeleteProjectApi,
|
||||
RetrieveUpdateDeleteRoiApi,
|
||||
RetrieveUpdateDeleteRulerApi,
|
||||
|
@ -129,4 +131,17 @@ urlpatterns = [
|
|||
]
|
||||
),
|
||||
),
|
||||
path(
|
||||
"layer/<str:dicom_slug>/",
|
||||
include(
|
||||
[
|
||||
path("", CreateLayerApi.as_view(), name="create_layer"),
|
||||
path(
|
||||
"<str:slug>",
|
||||
RetrieveUpdateDeleteLayerApi.as_view(),
|
||||
name="get_update_delete_project",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -4,6 +4,7 @@ from dicom.models import (
|
|||
Coordinate,
|
||||
Dicom,
|
||||
FreeHand,
|
||||
Layer,
|
||||
Project,
|
||||
Roi,
|
||||
Ruler,
|
||||
|
@ -20,6 +21,27 @@ class CoordinateSerializer(serializers.ModelSerializer):
|
|||
fields = ["x", "y"]
|
||||
|
||||
|
||||
class ListProjectSerializer(serializers.ModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="get_update_delete_project", lookup_field="slug"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["name", "pathology_type", "slug", "url", "created"]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"created": {"read_only": True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
return Project.objects.create(
|
||||
user=self.context["request"].user,
|
||||
name=validated_data["name"],
|
||||
pathology_type=validated_data["pathology_type"],
|
||||
)
|
||||
|
||||
|
||||
class ListDicomSerializer(serializers.ModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="get_update_delete_dicom", lookup_field="slug"
|
||||
|
@ -28,17 +50,25 @@ class ListDicomSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Dicom
|
||||
fields = ["file", "uploaded", "pathology_type", "url"]
|
||||
fields = ["file", "uploaded", "url"]
|
||||
|
||||
def create(self, validated_data):
|
||||
return Dicom.objects.create(**validated_data, user=self.context["request"].user)
|
||||
|
||||
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
files = ListDicomSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["files", "slug", "created", "stl"]
|
||||
|
||||
|
||||
class BaseShapeSerializer(serializers.Serializer):
|
||||
model = BaseShape
|
||||
|
||||
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand"])
|
||||
image_number = serializers.IntegerField()
|
||||
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand", "ruler"])
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
|
@ -51,9 +81,12 @@ class BaseShapeSerializer(serializers.Serializer):
|
|||
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"]
|
||||
)
|
||||
if validated_data["layer"]:
|
||||
layer = get_object_or_404(Layer, slug=validated_data["layer"])
|
||||
else:
|
||||
layer = dicom.layers.filter(parent__isnull=True).first()
|
||||
|
||||
obj = self.model.objects.create(layer_fk=layer)
|
||||
|
||||
create_coordinate(validated_data["coordinates"], obj)
|
||||
return obj
|
||||
|
@ -66,65 +99,121 @@ class BaseShapeSerializer(serializers.Serializer):
|
|||
if self.model.min_coordinates:
|
||||
if len(validated_data["coordinates"]) < self.model.min_coordinates:
|
||||
raise serializers.ValidationError
|
||||
if validated_data["layer"]:
|
||||
layer = get_object_or_404(Layer, slug=validated_data["layer"])
|
||||
else:
|
||||
layer = obj.dicom.layers.filter(parent__isnull=True).first()
|
||||
if obj.layer_fk != layer:
|
||||
obj.layer_fk = layer
|
||||
obj.save()
|
||||
create_coordinate(validated_data["coordinates"], obj)
|
||||
return obj
|
||||
|
||||
|
||||
class BaseShapeLayerSerializer(serializers.Serializer):
|
||||
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand"])
|
||||
type = serializers.ChoiceField(choices=["circle", "roi", "free_hand", "ruler"])
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
radius = serializers.FloatField(required=False)
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
|
||||
|
||||
class LayerChildSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Layer
|
||||
fields = ["name", "slug"]
|
||||
|
||||
|
||||
class LayerSerializer(serializers.ModelSerializer):
|
||||
children = LayerChildSerializer(many=True, read_only=True)
|
||||
parent = serializers.SlugField(max_length=8, allow_blank=True, write_only=True)
|
||||
|
||||
def validate_parent(self, val):
|
||||
if val:
|
||||
return get_object_or_404(Layer, slug=val)
|
||||
return (
|
||||
get_object_or_404(
|
||||
Dicom,
|
||||
slug=self.context["request"].parser_context["kwargs"]["dicom_slug"],
|
||||
)
|
||||
.layers.filter(parent__isnull=True)
|
||||
.first()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Layer
|
||||
fields = ["name", "slug", "children", "parent"]
|
||||
extra_kwargs = {
|
||||
"children": {"read_only": True},
|
||||
"slug": {"read_only": True},
|
||||
"parent": {"write_only": True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
return Layer.objects.create(
|
||||
name=validated_data["name"],
|
||||
dicom=validated_data["parent"].dicom,
|
||||
parent=validated_data["parent"],
|
||||
)
|
||||
|
||||
|
||||
class DicomSerializer(serializers.ModelSerializer):
|
||||
file = serializers.FileField()
|
||||
shapes = serializers.SerializerMethodField("get_dicom_shapes")
|
||||
layers = serializers.SerializerMethodField("get_dicom_layers")
|
||||
|
||||
@extend_schema_field(field=BaseShapeSerializer)
|
||||
@extend_schema_field(field=BaseShapeSerializer(many=True))
|
||||
def get_dicom_shapes(self, obj):
|
||||
return [x.serialize_self() for x in obj.shapes.all()]
|
||||
|
||||
@extend_schema_field(field=LayerSerializer(many=True))
|
||||
def get_dicom_layers(self, obj):
|
||||
return obj.get_layers()
|
||||
|
||||
class Meta:
|
||||
model = Dicom
|
||||
fields = ["file", "uploaded", "pathology_type", "shapes"]
|
||||
fields = ["file", "uploaded", "shapes", "layers"]
|
||||
|
||||
|
||||
class RoiSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
model = Roi
|
||||
|
||||
class Meta:
|
||||
model = Roi
|
||||
fields = ["id", "image_number", "coordinates"]
|
||||
fields = ["id", "layer", "coordinates"]
|
||||
extra_kwargs = {"id": {"read_only": True}}
|
||||
|
||||
|
||||
class FreeHandSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
model = FreeHand
|
||||
|
||||
class Meta:
|
||||
model = FreeHand
|
||||
fields = ["id", "image_number", "coordinates"]
|
||||
fields = ["id", "layer", "coordinates"]
|
||||
extra_kwargs = {"id": {"read_only": True}}
|
||||
|
||||
|
||||
class RulerSerializer(BaseShapeSerializer, serializers.ModelSerializer):
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
model = Ruler
|
||||
|
||||
class Meta:
|
||||
model = FreeHand
|
||||
fields = ["id", "image_number", "coordinates"]
|
||||
fields = ["id", "layer", "coordinates"]
|
||||
extra_kwargs = {"id": {"read_only": True}}
|
||||
|
||||
|
||||
class CircleSerializer(serializers.ModelSerializer):
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
layer = serializers.SlugField(max_length=8, required=False, allow_blank=True)
|
||||
|
||||
class Meta:
|
||||
model = Circle
|
||||
fields = ["id", "image_number", "radius", "coordinates"]
|
||||
fields = ["id", "layer", "radius", "coordinates"]
|
||||
extra_kwargs = {"id": {"read_only": True}}
|
||||
|
||||
def create(self, validated_data):
|
||||
|
@ -136,9 +225,12 @@ class CircleSerializer(serializers.ModelSerializer):
|
|||
dicom = get_object_or_404(
|
||||
Dicom, slug=self.context["request"].parser_context["kwargs"]["slug"]
|
||||
)
|
||||
if validated_data["layer"]:
|
||||
layer = get_object_or_404(Layer, slug=validated_data["layer"])
|
||||
else:
|
||||
layer = dicom.layers.filter(parent__isnull=True).first()
|
||||
circle = Circle.objects.create(
|
||||
dicom=dicom,
|
||||
image_number=validated_data["image_number"],
|
||||
layer_fk=layer,
|
||||
radius=validated_data["radius"],
|
||||
)
|
||||
|
||||
|
@ -148,8 +240,13 @@ class CircleSerializer(serializers.ModelSerializer):
|
|||
def update(self, obj: Circle, validated_data):
|
||||
Coordinate.objects.filter(shape=obj).delete()
|
||||
create_coordinate(validated_data["coordinates"], obj)
|
||||
if validated_data["layer"]:
|
||||
layer = get_object_or_404(Layer, slug=validated_data["layer"])
|
||||
else:
|
||||
layer = obj.dicom.layers.filter(parent__isnull=True).first()
|
||||
if "radius" in validated_data:
|
||||
obj.radius = validated_data["radius"]
|
||||
obj.layer_fk = layer
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
@ -158,31 +255,6 @@ class SmartFileUploadSerializer(serializers.Serializer):
|
|||
file = serializers.FileField()
|
||||
|
||||
|
||||
class ListProjectSerializer(serializers.ModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="get_update_delete_project", lookup_field="slug"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["slug", "url", "created"]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"created": {"read_only": True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
return Project.objects.create(user=self.context["request"].user)
|
||||
|
||||
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
files = ListDicomSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["files", "slug", "created", "stl"]
|
||||
|
||||
|
||||
class PatologyGenerateSerializer(serializers.Serializer):
|
||||
project_slug = serializers.CharField()
|
||||
points = serializers.ListField(child=CoordinateSerializer())
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework.generics import GenericAPIView, get_object_or_404
|
|||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from ..models import Circle, Dicom, Project, Roi
|
||||
from ..models import Circle, Dicom, Layer, Project, Roi
|
||||
from ..services import process_files
|
||||
from .serializers import (
|
||||
BaseShapeLayerSerializer,
|
||||
|
@ -13,6 +13,7 @@ from .serializers import (
|
|||
CircleSerializer,
|
||||
DicomSerializer,
|
||||
FreeHandSerializer,
|
||||
LayerSerializer,
|
||||
ListDicomSerializer,
|
||||
ListProjectSerializer,
|
||||
PatologyGenerateSerializer,
|
||||
|
@ -29,12 +30,12 @@ class ListCreateDicomApi(generics.ListCreateAPIView):
|
|||
parser_classes = [MultiPartParser, FormParser]
|
||||
|
||||
def get_queryset(self):
|
||||
return Dicom.objects.filter(user=self.request.user)
|
||||
return Dicom.objects.all()
|
||||
|
||||
|
||||
class RetrieveUpdateDeleteDicomApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
def get_queryset(self):
|
||||
return Dicom.objects.filter(user=self.request.user)
|
||||
return Dicom.objects.all()
|
||||
|
||||
serializer_class = DicomSerializer
|
||||
parser_classes = [MultiPartParser, FormParser]
|
||||
|
@ -211,6 +212,23 @@ class ListCreateProjectApi(generics.ListCreateAPIView):
|
|||
def get_queryset(self):
|
||||
return Project.objects.filter(user=self.request.user)
|
||||
|
||||
@extend_schema(
|
||||
description="""(0, 'Без патологий'),
|
||||
(1, 'COVID-19; все доли; многочисленные; размер любой'),
|
||||
(2, 'COVID-19; Нижняя доля правого лёгкого, Нижняя доля левого лёгкого'),
|
||||
(3, 'Немногочисленные; 10-20 мм'),
|
||||
(4, 'Рак лёгкого; Нижняя доля правого лёгкого, Единичное; 10-20 мм'),
|
||||
(5, 'Рак лёгкого; Средняя доля правого лёгкого, Единичное; >20 мм'),
|
||||
(6, 'Рак лёгкого; Нижняя доля левого лёгкого, Единичное; 10-20 мм'),
|
||||
(7, 'Рак лёгкого; Верхняя доля правого лёгкого, Единичное; 5-10 мм'),
|
||||
(8, 'Рак лёгкого; Верхняя доля левого лёгкого, Единичное; 5-10 мм'),
|
||||
(9, 'Метастатическое поражение лёгких; Все доли; Многочисленные; 5-10 мм'),
|
||||
(10, 'Метастатическое поражение лёгких; Все доли; Многочисленные; 10-20 мм'),
|
||||
(11, 'Метастатическое поражение лёгких; Все доли; Немногочисленные; 5-10 мм')"""
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.create(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RetrieveUpdateDeleteProjectApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = ProjectSerializer
|
||||
|
@ -226,3 +244,14 @@ class GeneratePatology(generics.CreateAPIView):
|
|||
# data = self.get_serializer(request.data).data
|
||||
# bbox = get_bbox(data["project_slug"], data["points"], data["depth"])
|
||||
return Response(data={}, status=200)
|
||||
|
||||
|
||||
class CreateLayerApi(generics.CreateAPIView):
|
||||
serializer_class = LayerSerializer
|
||||
|
||||
|
||||
class RetrieveUpdateDeleteLayerApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = LayerSerializer
|
||||
queryset = Layer.objects.all()
|
||||
|
||||
lookup_field = "slug"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from dicom.models.shapes import BaseShape
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
@ -75,6 +76,13 @@ class Dicom(models.Model):
|
|||
def __str__(self):
|
||||
return self.file.name
|
||||
|
||||
@property
|
||||
def shapes(self):
|
||||
return BaseShape.objects.filter(layer_fk__dicom=self)
|
||||
|
||||
def get_layers(self):
|
||||
return self.layers.filter(parent__isnull=True).first().serialize_self()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("get_update_delete_dicom", kwargs={"slug": self.slug})
|
||||
|
||||
|
@ -87,5 +95,12 @@ class Layer(models.Model):
|
|||
name = models.CharField(max_length=200)
|
||||
slug = models.SlugField(max_length=8)
|
||||
|
||||
def serialize_self(self):
|
||||
return {
|
||||
"slug": self.slug,
|
||||
"name": self.name,
|
||||
"children": [x.serialize_self() for x in self.children.all()],
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"layer on {self.dicom}"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from dicom.models import Layer
|
||||
from django.db import models
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
|
@ -7,14 +6,16 @@ class BaseShape(PolymorphicModel):
|
|||
TYPE = "no_type"
|
||||
min_coordinates = None
|
||||
max_coordinates = None
|
||||
layer = models.ForeignKey(Layer, related_name="shapes", on_delete=models.CASCADE)
|
||||
layer_fk = models.ForeignKey(
|
||||
"dicom.Layer", related_name="shapes", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
def serialize_self(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": self.TYPE,
|
||||
"image_number": self.image_number,
|
||||
"coordinates": self.coordinates,
|
||||
"layer": self.layer,
|
||||
}
|
||||
|
||||
def serialize_self_without_layer(self):
|
||||
|
@ -24,12 +25,16 @@ class BaseShape(PolymorphicModel):
|
|||
"coordinates": self.coordinates,
|
||||
}
|
||||
|
||||
@property
|
||||
def layer(self):
|
||||
return self.layer_fk.slug
|
||||
|
||||
@property
|
||||
def coordinates(self) -> [(int, int)]:
|
||||
return self.shape_coordinates.all().values("x", "y")
|
||||
|
||||
def __str__(self):
|
||||
return self.dicom.file.name
|
||||
return f"{self.TYPE} on {self.layer}"
|
||||
|
||||
|
||||
class Coordinate(models.Model):
|
||||
|
@ -53,7 +58,6 @@ class Circle(BaseShape):
|
|||
return {
|
||||
"id": self.id,
|
||||
"type": "circle",
|
||||
"image_number": self.image_number,
|
||||
"radius": self.radius,
|
||||
"coordinates": self.coordinates,
|
||||
}
|
||||
|
@ -66,28 +70,16 @@ class Circle(BaseShape):
|
|||
"coordinates": self.coordinates,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"circle on {self.dicom.file.name}"
|
||||
|
||||
|
||||
class Roi(BaseShape):
|
||||
TYPE = "roi"
|
||||
|
||||
def __str__(self):
|
||||
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}"
|
||||
|
|
|
@ -43,7 +43,6 @@ def process_files(
|
|||
Dicom.objects.create(
|
||||
file=File(f, name=file_in_d.split("/")[-1]),
|
||||
project=project,
|
||||
user=user,
|
||||
)
|
||||
shutil.rmtree(dit_path)
|
||||
tasks.process_project.apply_async(kwargs={"pk": project.pk}, countdown=3)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from dicom.models import Dicom, Project
|
||||
from dicom.models import Dicom, Layer, Project
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from utils.generators import generate_charset
|
||||
|
@ -22,3 +22,14 @@ def create_dicom(sender, instance: Dicom, created, **kwargs):
|
|||
slug = generate_charset(5)
|
||||
instance.slug = slug
|
||||
instance.save()
|
||||
Layer.objects.create(parent=None, dicom=instance, name="root")
|
||||
|
||||
|
||||
@receiver(post_save, sender=Layer)
|
||||
def create_layer(sender, instance: Layer, created, **kwargs):
|
||||
if created:
|
||||
slug = generate_charset(8)
|
||||
while Layer.objects.filter(slug=slug):
|
||||
slug = generate_charset(8)
|
||||
instance.slug = slug
|
||||
instance.save()
|
||||
|
|
Loading…
Reference in New Issue
Block a user