diff --git a/config/api_router.py b/config/api_router.py index b59dc46..6ad7268 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -5,6 +5,7 @@ from dicom.api.views import ( CreateRoiApi, CreateRulerApi, DeleteDicomProjectApi, + GeneratePatology, ListCreateDicomApi, ListCreateProjectApi, ListUpdateDicomImageNumberApi, @@ -15,8 +16,6 @@ from dicom.api.views import ( RetrieveUpdateDeleteRoiApi, RetrieveUpdateDeleteRulerApi, SmartFileUploadApi, - GeneratePatology, - GeneratePointCloud ) from django.urls import include, path from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView @@ -123,12 +122,11 @@ urlpatterns = [ ), ), path( - 'generate/', + "generate/", include( [ - path('patology', GeneratePatology.as_view(), name='generate_patology'), - path('point_cloud', GeneratePointCloud.as_view(), name='generate_patology') + path("patology", GeneratePatology.as_view(), name="generate_patology"), ] ), - ) + ), ] diff --git a/config/settings/local.py b/config/settings/local.py index b4cd5a0..a58c295 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -13,7 +13,13 @@ SECRET_KEY = env( default="XgO9NMQfMY5CNeVNh98WrKRXiQZnvtPzHJrF9ROPhAFLVEG1FvDD2ZRKTdJKVu8p", ) # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "dev.akarpov.ru"] +ALLOWED_HOSTS = [ + "localhost", + "0.0.0.0", + "127.0.0.1", + "dev.akarpov.ru", + "dev2.akarpov.ru", +] # CACHES # ------------------------------------------------------------------------------ diff --git a/image_markuper/dicom/api/serializers.py b/image_markuper/dicom/api/serializers.py index 462cd50..8db9a5c 100644 --- a/image_markuper/dicom/api/serializers.py +++ b/image_markuper/dicom/api/serializers.py @@ -181,3 +181,9 @@ class ProjectSerializer(serializers.ModelSerializer): class Meta: model = Project fields = ["files", "slug", "created"] + + +class PatologyGenerateSerializer(serializers.Serializer): + project_slug = serializers.CharField() + points = serializers.ListField(child=CoordinateSerializer()) + depth = serializers.ListField(child=serializers.IntegerField()) diff --git a/image_markuper/dicom/api/views.py b/image_markuper/dicom/api/views.py index 82d6e68..233b072 100644 --- a/image_markuper/dicom/api/views.py +++ b/image_markuper/dicom/api/views.py @@ -6,7 +6,7 @@ from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.response import Response from ..models import Circle, Dicom, Project, Roi -from ..services import generate_3d_point_cloud, get_bbox, process_files +from ..services import process_files from .serializers import ( BaseShapeLayerSerializer, BaseShapeSerializer, @@ -15,13 +15,12 @@ from .serializers import ( FreeHandSerializer, ListDicomSerializer, ListProjectSerializer, - PointCloudSerializer, + PatologyGenerateSerializer, ProjectSerializer, RoiSerializer, RulerSerializer, SmartFileUploadSerializer, create_coordinate, - PatologyGenerateSerializer ) @@ -224,17 +223,6 @@ class GeneratePatology(generics.CreateAPIView): serializer_class = PatologyGenerateSerializer def create(self, request, *args, **kwargs): - data = self.get_serializer(request.data).data - bbox = get_bbox(data['project_slug'], data['points'], data['depth']) + # data = self.get_serializer(request.data).data + # bbox = get_bbox(data["project_slug"], data["points"], data["depth"]) return Response(data={}, status=200) - - -class GeneratePointCloud(generics.CreateAPIView): - serializer_class = PointCloudSerializer - - def create(self, request, *args, **kwargs): - print(request.data) - data = request.data - point_cloud = generate_3d_point_cloud(data['project_slug']) - print(point_cloud[0:5]) - return Response(data={'voxels': point_cloud}, status=200) \ No newline at end of file diff --git a/image_markuper/dicom/services.py b/image_markuper/dicom/services.py index d6531fd..747452d 100644 --- a/image_markuper/dicom/services.py +++ b/image_markuper/dicom/services.py @@ -1,4 +1,3 @@ -from functools import lru_cache import glob import os import shutil @@ -6,18 +5,18 @@ import zipfile from pathlib import Path import magic +import numpy as np +import pydicom from dicom.models import Coordinate, Dicom, Project from django.core.files import File from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile +from skimage import measure +from stl import mesh from utils.generators import generate_charset -from typing import List, Union -from django.conf import settings -import pydicom - def process_files( - files: List[Union[TemporaryUploadedFile, InMemoryUploadedFile]], user, slug=None + files: list[TemporaryUploadedFile | InMemoryUploadedFile], user, slug=None ): if slug: project = Project.objects.get(slug=slug) @@ -60,35 +59,56 @@ def create_coordinate(coordinates, obj): def get_bbox(project_id, points, image_range): project: Project = Project.objects.get(slug=project_id) - #print(Dicom.objects.all()) + # print(Dicom.objects.all()) files = project.files.all() bbox_data = [] - for file_number in range(image_range[0], image_range[1]+1): - print(points[0]['x']) + for file_number in range(image_range[0], image_range[1] + 1): + print(points[0]["x"]) bbox_data.append( - pydicom.dcmread( - files[file_number].file.path).pixel_array[int(points[0]['x']):int(points[1]['x']), int(points[0]['y']):int(points[1]['y'])].tolist()) + pydicom.dcmread(files[file_number].file.path) + .pixel_array[ + int(points[0]["x"]) : int(points[1]["x"]), # noqa + int(points[0]["y"]) : int(points[1]["y"]), # noqa + ] + .tolist() + ) print(pydicom.dcmread(files[file_number].file.path).pixel_array) print(bbox_data) - #print(project.files.all(), "files", project) + # print(project.files.all(), "files", project) return [] -@lru_cache(512) + def generate_3d_point_cloud(project_slug: str): project = Project.objects.get(slug=project_slug) point_clouds = [] - for fileindex, file in enumerate(project.files.all()[::3]): - print(fileindex) - pixel_array = pydicom.dcmread( - file.file.path - ).pixel_array - for iindex, i in enumerate(pixel_array[::3]): - for jindex, j in enumerate(i[::3]): + for file_index, file in enumerate(project.files.all()[::10]): + print(file_index) + pixel_array = pydicom.dcmread(file.file.path).pixel_array + for iindex, i in enumerate(pixel_array[::10]): + for jindex, j in enumerate(i[::10]): if j <= 240: pass - #point_clouds.append({'x': jindex, 'y': iindex, 'z': fileindex, 'value': 0}) + # point_clouds.append({'x': jindex, 'y': iindex, 'z': fileindex, 'value': 0}) else: - point_clouds.append([jindex, iindex, fileindex, j]) + point_clouds.append([jindex, iindex, file_index, j]) return point_clouds + + +def generate_3d_model(project: Project, thr=800) -> str: + image = [] + for file in project.files.all(): + image.append(pydicom.dcmread(file.file.path).pixel_array) + + p = np.array(image).transpose(2, 1, 0) + verts, faces, normals, values = measure.marching_cubes(p, thr, step_size=1) + + solid = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype)) + for i, f in enumerate(faces): + for j in range(3): + solid.vectors[i][j] = verts[f[j], :] + + pth = f"/tmp/{generate_charset(4)}.stl" + solid.save(pth) + return pth diff --git a/image_markuper/dicom/tasks.py b/image_markuper/dicom/tasks.py new file mode 100644 index 0000000..56eb49d --- /dev/null +++ b/image_markuper/dicom/tasks.py @@ -0,0 +1,9 @@ +from celery import shared_task +from dicom.models import Project +from dicom.services import generate_3d_model + + +@shared_task() +def process_dicom(pk: int): + generate_3d_model(Project.objects.get(pk=pk)) + return pk diff --git a/requirements/base.txt b/requirements/base.txt index 36f60c6..de359a7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -25,5 +25,9 @@ django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers djangorestframework-simplejwt==5.2.2 # DRF-spectacular for api documentation drf-spectacular==0.24.2 # https://github.com/tfranzel/drf-spectacular + + +pydicom==2.3.0 numpy==1.23.4 -pydicom==2.3.0 \ No newline at end of file +numpy-stl==2.17.1 +scikit-image==0.19.3