added polygon and circle

This commit is contained in:
Alexander Karpov 2022-10-28 21:52:02 +03:00
parent 4f15a29b07
commit 4ec02a8729
7 changed files with 238 additions and 7 deletions

View File

@ -1,4 +1,11 @@
from dicom.api.views import ListCreateDicomApi, RetrieveUpdateDeleteDicomApi from dicom.api.views import (
CreateCircleApi,
CreatePolygonApi,
ListCreateDicomApi,
RetrieveUpdateDeleteCircleApi,
RetrieveUpdateDeleteDicomApi,
RetrieveUpdateDeletePolygonApi,
)
from django.urls import include, path from django.urls import include, path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from users.api.views import RegisterView from users.api.views import RegisterView
@ -24,6 +31,33 @@ urlpatterns = [
RetrieveUpdateDeleteDicomApi.as_view(), RetrieveUpdateDeleteDicomApi.as_view(),
name="get_update_delete_dicom", name="get_update_delete_dicom",
), ),
path(
"<str:slug>/polygon",
CreatePolygonApi.as_view(),
name="create_polygon",
),
path(
"<str:slug>/circle",
CreateCircleApi.as_view(),
name="create_circle",
),
]
),
),
path(
"shapes/",
include(
[
path(
"polygon/<int:id>",
RetrieveUpdateDeletePolygonApi.as_view(),
name="get_update_delete_polygon",
),
path(
"circle/<int:id>",
RetrieveUpdateDeleteCircleApi.as_view(),
name="get_update_delete_circle",
),
] ]
), ),
), ),

View File

@ -309,8 +309,6 @@ REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
} }
# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
CORS_URLS_REGEX = r"^/api/.*$"
# By Default swagger ui is available only to admin user(s). You can change permission classes to change that # By Default swagger ui is available only to admin user(s). You can change permission classes to change that
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings # See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {

View File

@ -1,5 +1,22 @@
from dicom.models import Dicom from dicom.models import Circle, Coordinate, Dicom, Polygon
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
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 Meta:
model = Coordinate
fields = ["x", "y"]
class ListDicomSerializer(serializers.ModelSerializer): class ListDicomSerializer(serializers.ModelSerializer):
@ -16,9 +33,82 @@ class ListDicomSerializer(serializers.ModelSerializer):
return Dicom.objects.create(**validated_data, user=self.context["request"].user) return Dicom.objects.create(**validated_data, user=self.context["request"].user)
class BaseShapeSerializer(serializers.Serializer):
type = serializers.ChoiceField(choices=["circle", "polygon"])
image_number = serializers.IntegerField()
coordinates = CoordinateSerializer(many=True)
class DicomSerializer(serializers.ModelSerializer): class DicomSerializer(serializers.ModelSerializer):
file = serializers.FileField() file = serializers.FileField()
shapes = serializers.SerializerMethodField("get_dicom_shapes")
@extend_schema_field(field=BaseShapeSerializer)
def get_dicom_shapes(self, obj):
return [x.serialize_self() for x in obj.shapes.all()]
class Meta: class Meta:
model = Dicom model = Dicom
fields = ["file", "uploaded"] fields = ["file", "uploaded", "shapes"]
class PolygonSerializer(serializers.ModelSerializer):
coordinates = CoordinateSerializer(many=True)
class Meta:
model = Polygon
fields = ["id", "image_number", "coordinates"]
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"]
)
polygon = Polygon.objects.create(
dicom=dicom, image_number=validated_data["image_number"]
)
create_coordinate(validated_data["coordinates"], polygon)
return polygon
def update(self, obj: Circle, validated_data):
Coordinate.objects.filter(shape=obj).delete()
create_coordinate(validated_data["coordinates"], obj)
return obj
class CircleSerializer(serializers.ModelSerializer):
coordinates = CoordinateSerializer(many=True)
class Meta:
model = Circle
fields = ["id", "image_number", "radius", "coordinates"]
extra_kwargs = {"id": {"read_only": True}}
def create(self, validated_data):
if (
"coordinates" not in validated_data
and len(validated_data["coordinates"]) > 1
):
raise serializers.ValidationError
dicom = get_object_or_404(
Dicom, slug=self.context["request"].parser_context["kwargs"]["slug"]
)
circle = Circle.objects.create(
dicom=dicom,
image_number=validated_data["image_number"],
radius=validated_data["radius"],
)
create_coordinate(validated_data["coordinates"], circle)
return circle
def update(self, obj: Circle, validated_data):
Coordinate.objects.filter(shape=obj).delete()
create_coordinate(validated_data["coordinates"], obj)
if "radius" in validated_data:
obj.radius = validated_data["radius"]
obj.save()
return obj

View File

@ -1,9 +1,15 @@
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from rest_framework import generics from rest_framework import generics
from rest_framework.generics import get_object_or_404
from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.parsers import FormParser, MultiPartParser
from ..models import Dicom from ..models import Circle, Dicom, Polygon
from .serializers import DicomSerializer, ListDicomSerializer from .serializers import (
CircleSerializer,
DicomSerializer,
ListDicomSerializer,
PolygonSerializer,
)
class ListCreateDicomApi(generics.ListCreateAPIView): class ListCreateDicomApi(generics.ListCreateAPIView):
@ -34,3 +40,43 @@ class RetrieveUpdateDeleteDicomApi(generics.RetrieveUpdateDestroyAPIView):
parser_classes = [MultiPartParser, FormParser] parser_classes = [MultiPartParser, FormParser]
lookup_field = "slug" lookup_field = "slug"
class CreatePolygonApi(generics.CreateAPIView):
serializer_class = PolygonSerializer
class CreateCircleApi(generics.CreateAPIView):
serializer_class = CircleSerializer
class RetrieveUpdateDeletePolygonApi(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PolygonSerializer
def get_object(self):
return get_object_or_404(
Polygon, 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 RetrieveUpdateDeleteCircleApi(generics.RetrieveUpdateDestroyAPIView):
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")
def patch(self, request, *args, **kwargs):
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)

View File

@ -0,0 +1,3 @@
# flake8: noqa
from .base import Dicom
from .blocks import BaseShape, Circle, Coordinate, Polygon

View File

@ -0,0 +1,60 @@
from dicom.models import Dicom
from django.db import models
from polymorphic.models import PolymorphicModel
class BaseShape(PolymorphicModel):
dicom = models.ForeignKey(Dicom, related_name="shapes", on_delete=models.CASCADE)
image_number = models.IntegerField()
def serialize_self(self):
raise NotImplementedError
@property
def coordinates(self) -> [(int, int)]:
return self.shape_coordinates.all().values("x", "y")
def __str__(self):
return self.dicom.file.name
class Coordinate(models.Model):
x = models.IntegerField()
y = models.IntegerField()
shape = models.ForeignKey(
to=BaseShape,
null=False,
blank=False,
on_delete=models.CASCADE,
related_name="shape_coordinates",
)
class Circle(BaseShape):
radius = models.IntegerField()
def serialize_self(self):
return {
"id": self.id,
"type": "circle",
"image_number": self.image_number,
"radius": self.radius,
"coordinates": self.coordinates,
}
def __str__(self):
return f"circle on {self.dicom.file.name}"
class Polygon(BaseShape):
def serialize_self(self):
return {
"id": self.id,
"type": "polygon",
"image_number": self.image_number,
"coordinates": self.coordinates,
}
def __str__(self):
return f"polygon on {self.dicom.file.name}"