Compare commits
No commits in common. "master" and "main" have entirely different histories.
226
.gitignore
vendored
Normal file
226
.gitignore
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# ---> Composer
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
# composer.lock
|
||||
|
||||
# ---> C++
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,16 +0,0 @@
|
||||
"""
|
||||
ASGI config for backend project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
@ -1,145 +0,0 @@
|
||||
"""
|
||||
Django settings for backend project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.1.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-z8m_e(v=^%)1f-=hgcl1d7a2%rg_#w$r$ig%x%l8ua_cj(ts)w'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework', # Django Rest Framework
|
||||
'orthanc', # Наше приложение
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'backend.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'backend.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'xray',
|
||||
'USER': 'postgres',
|
||||
'PASSWORD': '1240069630Bk!',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
),
|
||||
}
|
||||
|
||||
# Настройки токенов
|
||||
from datetime import timedelta
|
||||
SIMPLE_JWT = {
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), # Срок действия токена
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||
'AUTH_HEADER_TYPES': ('Bearer',),
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
"""
|
||||
URL configuration for backend project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
# path('patients/', include('orthanc.urls')),
|
||||
path('auth/', include('orthanc.urls')),
|
||||
]
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
"""
|
||||
WSGI config for backend project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
22
manage.py
22
manage.py
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OrthancConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'orthanc'
|
||||
@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-26 03:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Patient',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('orthanc_id', models.CharField(max_length=255, unique=True)),
|
||||
('patient_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('patient_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('patient_sex', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('patient_birth_date', models.CharField(blank=True, max_length=20, null=True)),
|
||||
('studies', models.JSONField(blank=True, null=True)),
|
||||
('patient_metadata', models.JSONField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,39 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-26 08:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orthanc', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Series',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('orthanc_id', models.CharField(max_length=255, unique=True)),
|
||||
('parent_study', models.CharField(max_length=255)),
|
||||
('main_dicom_tags', models.JSONField(blank=True, null=True)),
|
||||
('status', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('is_stable', models.BooleanField(default=False)),
|
||||
('last_update', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('patient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='series', to='orthanc.patient')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Instance',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('orthanc_id', models.CharField(max_length=255, unique=True)),
|
||||
('file_size', models.IntegerField(blank=True, null=True)),
|
||||
('file_uuid', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('main_dicom_tags', models.JSONField(blank=True, null=True)),
|
||||
('index_in_series', models.IntegerField(blank=True, null=True)),
|
||||
('parent_series', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='orthanc.series')),
|
||||
],
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,36 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
class Patient(models.Model):
|
||||
orthanc_id = models.CharField(max_length=255, unique=True)
|
||||
patient_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
patient_name = models.CharField(max_length=255, null=True, blank=True)
|
||||
patient_sex = models.CharField(max_length=10, null=True, blank=True)
|
||||
patient_birth_date = models.CharField(max_length=20, null=True, blank=True)
|
||||
studies = models.JSONField(null=True, blank=True) # Массив ID исследований
|
||||
patient_metadata = models.JSONField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.patient_name or "Unknown Patient"
|
||||
|
||||
class Series(models.Model):
|
||||
orthanc_id = models.CharField(max_length=255, unique=True)
|
||||
parent_study = models.CharField(max_length=255)
|
||||
patient = models.ForeignKey(Patient, related_name="series", on_delete=models.CASCADE)
|
||||
main_dicom_tags = models.JSONField(null=True, blank=True)
|
||||
status = models.CharField(max_length=50, null=True, blank=True)
|
||||
is_stable = models.BooleanField(default=False)
|
||||
last_update = models.CharField(max_length=50, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.main_dicom_tags.get("SeriesDescription", "Unknown Series")
|
||||
|
||||
class Instance(models.Model):
|
||||
orthanc_id = models.CharField(max_length=255, unique=True)
|
||||
parent_series = models.ForeignKey(Series, related_name="instances", on_delete=models.CASCADE)
|
||||
file_size = models.IntegerField(null=True, blank=True)
|
||||
file_uuid = models.CharField(max_length=255, null=True, blank=True)
|
||||
main_dicom_tags = models.JSONField(null=True, blank=True)
|
||||
index_in_series = models.IntegerField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.main_dicom_tags.get("SOPInstanceUID", "Unknown Instance")
|
||||
@ -1,7 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Patient
|
||||
|
||||
class PatientSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Patient
|
||||
fields = "__all__"
|
||||
108
orthanc/tasks.py
108
orthanc/tasks.py
@ -1,108 +0,0 @@
|
||||
import requests
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
from .models import Patient, Series, Instance
|
||||
|
||||
# Отключение предупреждений о небезопасном соединении
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
ORTHANC_BASE_URL = "http://192.168.2.60:8042"
|
||||
ORTHANC_USERNAME = "writehost"
|
||||
ORTHANC_PASSWORD = "writehost"
|
||||
|
||||
# 1. Получение всех пациентов
|
||||
def fetch_patients():
|
||||
url = f"{ORTHANC_BASE_URL}/patients"
|
||||
response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# 2. Получение информации о конкретном пациенте
|
||||
def fetch_patient_details(orthanc_id):
|
||||
url = f"{ORTHANC_BASE_URL}/patients/{orthanc_id}"
|
||||
response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# 3. Получение данных об исследовании
|
||||
def fetch_study_details(study_id):
|
||||
url = f"{ORTHANC_BASE_URL}/studies/{study_id}"
|
||||
response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# 4. Получение данных о серии
|
||||
def fetch_series_details(series_id):
|
||||
url = f"{ORTHANC_BASE_URL}/series/{series_id}"
|
||||
response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# 5. Получение данных об изображении (инстансе)
|
||||
def fetch_instance_details(instance_id):
|
||||
url = f"{ORTHANC_BASE_URL}/instances/{instance_id}"
|
||||
response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# Основная синхронизация
|
||||
def sync_patients():
|
||||
patient_ids = fetch_patients()
|
||||
for patient_id in patient_ids:
|
||||
patient_data = fetch_patient_details(patient_id)
|
||||
|
||||
# Синхронизация пациента
|
||||
patient, created = Patient.objects.update_or_create(
|
||||
orthanc_id=patient_id,
|
||||
defaults={
|
||||
"patient_id": patient_data["MainDicomTags"].get("PatientID"),
|
||||
"patient_name": patient_data["MainDicomTags"].get("PatientName"),
|
||||
"patient_sex": patient_data["MainDicomTags"].get("PatientSex"),
|
||||
"patient_birth_date": patient_data["MainDicomTags"].get("PatientBirthDate"),
|
||||
"studies": patient_data.get("Studies", []),
|
||||
"patient_metadata": patient_data.get("Labels", []),
|
||||
},
|
||||
)
|
||||
|
||||
# Синхронизация исследований (Studies)
|
||||
for study_id in patient_data.get("Studies", []):
|
||||
sync_study(study_id, patient)
|
||||
|
||||
# Синхронизация исследования
|
||||
def sync_study(study_id, patient):
|
||||
study_data = fetch_study_details(study_id)
|
||||
for series_id in study_data.get("Series", []):
|
||||
sync_series(series_id, patient)
|
||||
|
||||
# Синхронизация серии
|
||||
def sync_series(series_id, patient):
|
||||
series_data = fetch_series_details(series_id)
|
||||
series, created = Series.objects.update_or_create(
|
||||
orthanc_id=series_id,
|
||||
defaults={
|
||||
"parent_study": series_data["ParentStudy"],
|
||||
"patient": patient,
|
||||
"main_dicom_tags": series_data.get("MainDicomTags", {}),
|
||||
"status": series_data.get("Status"),
|
||||
"is_stable": series_data.get("IsStable", False),
|
||||
"last_update": series_data.get("LastUpdate"),
|
||||
},
|
||||
)
|
||||
|
||||
# Синхронизация снимков (Instances)
|
||||
for instance_id in series_data.get("Instances", []):
|
||||
sync_instance(instance_id, series)
|
||||
|
||||
# Синхронизация снимка
|
||||
def sync_instance(instance_id, series):
|
||||
instance_data = fetch_instance_details(instance_id)
|
||||
Instance.objects.update_or_create(
|
||||
orthanc_id=instance_id,
|
||||
defaults={
|
||||
"parent_series": series,
|
||||
"file_size": instance_data.get("FileSize"),
|
||||
"file_uuid": instance_data.get("FileUuid"),
|
||||
"main_dicom_tags": instance_data.get("MainDicomTags", {}),
|
||||
"index_in_series": instance_data.get("IndexInSeries"),
|
||||
},
|
||||
)
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,12 +0,0 @@
|
||||
from django.urls import path
|
||||
from .views import PatientListView, SyncPatientsView, ProtectedView
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||
|
||||
urlpatterns = [
|
||||
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # Получение токена
|
||||
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Обновление токена
|
||||
path('', PatientListView.as_view(), name='patient-list'),
|
||||
path('sync/', SyncPatientsView.as_view(), name='sync-patients'),
|
||||
path('protected/', ProtectedView.as_view(), name='protected'),
|
||||
|
||||
]
|
||||
@ -1,76 +0,0 @@
|
||||
# from rest_framework.views import APIView
|
||||
# from rest_framework.response import Response
|
||||
# from rest_framework import status
|
||||
# from .models import Patient
|
||||
# from .serializers import PatientSerializer
|
||||
# from .tasks import fetch_patients, fetch_patient_details, sync_patients
|
||||
#
|
||||
#
|
||||
# class PatientListView(APIView):
|
||||
# def get(self, request):
|
||||
# patients = Patient.objects.all()
|
||||
# serializer = PatientSerializer(patients, many=True)
|
||||
# return Response(serializer.data)
|
||||
#
|
||||
# class SyncPatientsView(APIView):
|
||||
# def post(self, request):
|
||||
# try:
|
||||
# patient_ids = fetch_patients()
|
||||
# for orthanc_id in patient_ids:
|
||||
# patient_data = fetch_patient_details(orthanc_id)
|
||||
# patient, created = Patient.objects.update_or_create(
|
||||
# orthanc_id=orthanc_id,
|
||||
# defaults={
|
||||
# "patient_id": patient_data["MainDicomTags"].get("PatientID"),
|
||||
# "patient_name": patient_data["MainDicomTags"].get("PatientName"),
|
||||
# "patient_sex": patient_data["MainDicomTags"].get("PatientSex"),
|
||||
# "patient_birth_date": patient_data["MainDicomTags"].get("PatientBirthDate"),
|
||||
# "studies": patient_data.get("Studies", []),
|
||||
# "patient_metadata": patient_data,
|
||||
# },
|
||||
# )
|
||||
# return Response({"message": "Synchronization completed successfully"})
|
||||
# except Exception as e:
|
||||
# return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
#
|
||||
# class SyncPatientsView(APIView):
|
||||
# def post(self, request):
|
||||
# try:
|
||||
# sync_patients()
|
||||
# return Response({"detail": "Synchronization completed successfully."}, status=status.HTTP_200_OK)
|
||||
# except Exception as e:
|
||||
# return Response({"detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from .models import Patient
|
||||
from .serializers import PatientSerializer
|
||||
from .tasks import fetch_patients, fetch_patient_details, sync_patients
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
|
||||
|
||||
class ProtectedView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
return Response({"message": f"Привет, {request.user.username}! Вы авторизованы."})
|
||||
|
||||
# Эндпоинт для получения списка пациентов
|
||||
class PatientListView(APIView):
|
||||
def get(self, request):
|
||||
patients = Patient.objects.all()
|
||||
serializer = PatientSerializer(patients, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
# Эндпоинт для синхронизации данных с Orthanc
|
||||
class SyncPatientsView(APIView):
|
||||
def post(self, request):
|
||||
try:
|
||||
sync_patients()
|
||||
return Response({"detail": "Synchronization completed successfully."}, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Loading…
x
Reference in New Issue
Block a user