Skip to content

Commit

Permalink
Improve radis api
Browse files Browse the repository at this point in the history
  • Loading branch information
medihack committed Feb 5, 2024
1 parent 868396a commit b9853a8
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 93 deletions.
141 changes: 114 additions & 27 deletions notebooks/radis_api.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
"cells": [
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Status Code: 201\n",
"{'id': 7, 'document_id': 'gepacs_3dfidii5858-6633i4-ii398841', 'pacs_aet': 'gepacs', 'pacs_name': 'GE PACS', 'patient_id': '1234578', 'patient_birth_date': '1976-05-23', 'patient_sex': 'M', 'study_instance_uid': '34343-34343-34343', 'accession_number': '345348389', 'study_description': 'CT of the Thorax', 'study_datetime': '2000-08-10T00:00:00+02:00', 'series_instance_uid': '34343-676556-3343', 'modalities_in_study': ['CT', 'PET'], 'sop_instance_uid': '35858-384834-3843', 'references': ['http://gepacs.com/34343-34343-34343'], 'body': 'This is the report', 'institutes': [2]}\n"
"Status Code: 400\n",
"{'document_id': ['report with this document id already exists.']}\n"
]
}
],
Expand All @@ -20,8 +20,10 @@
"\n",
"base_url = \"http://localhost:8000/api/\"\n",
"\n",
"document_id = \"gepacs_3dfidii5858-6633i4-ii398841\"\n",
"\n",
"data = {\n",
" \"document_id\": \"gepacs_3dfidii5858-6633i4-ii398841\",\n",
" \"document_id\": document_id,\n",
" \"groups\": [2],\n",
" \"pacs_aet\": \"gepacs\",\n",
" \"pacs_name\": \"GE PACS\",\n",
Expand All @@ -40,43 +42,110 @@
"}\n",
"\n",
"auth_token = \"f2e7412ca332a85e37f3fce88c6a1904fe35ad63\"\n",
"# response = requests.post(base_url + \"reports/\", json=data, headers={\"Authorization\": f\"Token {auth_token}\"})\n",
"response = requests.post(base_url + \"reports/\", json=data)\n",
"response = requests.post(base_url + \"reports/\", json=data, headers={\"Authorization\": f\"Token {auth_token}\"})\n",
"\n",
"print(f\"Status Code: {response.status_code}\")\n",
"print(response.json())\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'id': 102,\n",
" 'document_id': 'gepacs_3dfidii5858-6633i4-ii398841',\n",
" 'pacs_aet': 'gepacs',\n",
" 'pacs_name': 'GE PACS',\n",
" 'patient_id': '1234578',\n",
" 'patient_birth_date': '1976-05-23',\n",
" 'patient_sex': 'M',\n",
" 'study_instance_uid': '34343-34343-34343',\n",
" 'accession_number': '345348389',\n",
" 'study_description': 'CT of the Thorax',\n",
" 'study_datetime': '2000-08-10T00:00:00+02:00',\n",
" 'series_instance_uid': '34343-676556-3343',\n",
" 'modalities_in_study': ['CT', 'PET'],\n",
" 'sop_instance_uid': '35858-384834-3843',\n",
" 'references': ['http://gepacs.com/34343-34343-34343'],\n",
" 'body': 'This is the updated report',\n",
" 'groups': [2]}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data = {\n",
" \"document_id\": \"gepacs_3dfidii5858-6633i4-ii398841\",\n",
" \"groups\": [2],\n",
" \"pacs_aet\": \"gepacs\",\n",
" \"pacs_name\": \"GE PACS\",\n",
" \"patient_id\": \"1234578\",\n",
" \"patient_birth_date\": date(1976, 5, 23).isoformat(),\n",
" \"patient_sex\": \"M\",\n",
" \"study_instance_uid\": \"34343-34343-34343\",\n",
" \"accession_number\": \"345348389\",\n",
" \"study_description\": \"CT of the Thorax\",\n",
" \"study_datetime\": datetime(2000, 8, 10).isoformat(),\n",
" \"series_instance_uid\": \"34343-676556-3343\",\n",
" \"modalities_in_study\": [\"CT\", \"PET\"],\n",
" \"sop_instance_uid\": \"35858-384834-3843\",\n",
" \"references\": [\"http://gepacs.com/34343-34343-34343\"],\n",
" \"body\": \"This is the updated report\"\n",
"}\n",
"\n",
"response = requests.put(base_url + f\"reports/{document_id}/\", json=data, headers={\"Authorization\": f\"Token {auth_token}\"})\n",
"\n",
"# document_id = response.json()[\"id\"]\n"
"response.json()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'pathId': '/document/v1/report/report/docid/gepacs_35858-384834-3843',\n",
" 'id': 'id:report:report::gepacs_35858-384834-3843',\n",
" 'fields': {'pacs_name': 'GE PACS',\n",
" 'modalities_in_study': ['CT', 'PET'],\n",
" 'patient_id': '1234578',\n",
" 'patient_birth_date': 201657600,\n",
" 'body': 'This is the report',\n",
" 'references': ['http://gepacs.com/34343-34343-34343'],\n",
" 'sop_instance_uid': '35858-384834-3843',\n",
" 'patient_sex': 'M',\n",
" 'study_description': 'CT of the Thorax',\n",
" 'study_instance_uid': '34343-34343-34343',\n",
" 'accession_number': '345348389',\n",
" 'series_instance_uid': '34343-676556-3343',\n",
" 'pacs_aet': 'gepacs',\n",
" 'institutes': ['Neuroradiologie'],\n",
" 'study_datetime': 965858400}}"
"{'id': 102,\n",
" 'document_id': 'gepacs_3dfidii5858-6633i4-ii398841',\n",
" 'pacs_aet': 'gepacs',\n",
" 'pacs_name': 'GE PACS',\n",
" 'patient_id': '1234578',\n",
" 'patient_birth_date': '1976-05-23',\n",
" 'patient_sex': 'M',\n",
" 'study_instance_uid': '34343-34343-34343',\n",
" 'accession_number': '345348389',\n",
" 'study_description': 'CT of the Thorax',\n",
" 'study_datetime': '2000-08-10T00:00:00+02:00',\n",
" 'series_instance_uid': '34343-676556-3343',\n",
" 'modalities_in_study': ['CT', 'PET'],\n",
" 'sop_instance_uid': '35858-384834-3843',\n",
" 'references': ['http://gepacs.com/34343-34343-34343'],\n",
" 'body': 'This is the updated report',\n",
" 'groups': [2],\n",
" 'vespa': {'pathId': '/document/v1/report/report/docid/gepacs_3dfidii5858-6633i4-ii398841',\n",
" 'id': 'id:report:report::gepacs_3dfidii5858-6633i4-ii398841',\n",
" 'fields': {'pacs_name': 'GE PACS',\n",
" 'modalities_in_study': ['CT', 'PET'],\n",
" 'patient_birth_date': 201657600,\n",
" 'body': 'This is the updated report',\n",
" 'references': ['http://gepacs.com/34343-34343-34343'],\n",
" 'patient_sex': 'M',\n",
" 'study_description': 'CT of the Thorax',\n",
" 'groups': [2],\n",
" 'pacs_aet': 'gepacs',\n",
" 'study_datetime': 965858400}}}"
]
},
"execution_count": 5,
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
Expand All @@ -86,6 +155,24 @@
"\n",
"response.json()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<Response [204]>\n"
]
}
],
"source": [
"response = requests.delete(base_url + f\"reports/{document_id}\", headers={\"Authorization\": f\"Token {auth_token}\"})\n",
"print(response)"
]
}
],
"metadata": {
Expand All @@ -104,7 +191,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.11.7"
},
"orig_nbformat": 4
},
Expand Down
29 changes: 28 additions & 1 deletion radis/api/site.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable, Literal
from typing import Any, Callable, Literal, NamedTuple

from radis.reports.models import Report

Expand All @@ -9,4 +9,31 @@


def register_report_handler(handler: ReportEventHandler) -> None:
"""Register a report event handler.
The report handler gets notified a report is created, updated, or deleted in
PostgreSQL database. It can be used to sync report documents in other
databases like Vespa.
"""
report_event_handlers.append(handler)


FetchDocument = Callable[[Report], dict[str, Any] | None]


class DocumentFetcher(NamedTuple):
source: str
fetch: FetchDocument


document_fetchers: list[DocumentFetcher] = []


def register_document_fetcher(source: str, fetch: FetchDocument) -> None:
"""Register a document fetcher.
A document fetcher is a function that takes a report from the PostgreSQL
database and returns a document in the form of a dictionary from another
database (like Vespa).
"""
document_fetchers.append(DocumentFetcher(source, fetch))
48 changes: 45 additions & 3 deletions radis/api/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
from rest_framework import viewsets
from typing import Any

from rest_framework import mixins, viewsets
from rest_framework.exceptions import MethodNotAllowed
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import BaseSerializer

from radis.reports.models import Report

from .serializers import ReportSerializer
from .site import report_event_handlers
from .site import document_fetchers, report_event_handlers


class ReportViewSet(
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet,
):
"""ViewSet for fetch, creating, updating, and deleting Reports.
Only admins (staff users) can do that.
"""

class ReportViewSet(viewsets.ModelViewSet):
serializer_class = ReportSerializer
queryset = Report.objects.all()
lookup_field = "document_id"
permission_classes = [IsAdminUser]

def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Response:
"""Retrieve a single Report.
It also fetches the associated document from the Vespa database.
"""
instance: Report = self.get_object()

extra = {}
for fetcher in document_fetchers:
document = fetcher.fetch(instance)
if document:
extra[fetcher.source] = document

serializer = self.get_serializer(instance)
data = serializer.data
data.update(extra)

return Response(data)

def perform_create(self, serializer: BaseSerializer) -> None:
super().perform_create(serializer)
Expand All @@ -25,6 +63,10 @@ def perform_update(self, serializer: BaseSerializer) -> None:
for handler in report_event_handlers:
handler("updated", report)

def partial_update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
assert request.method
raise MethodNotAllowed(request.method)

def perform_destroy(self, instance: Report) -> None:
super().perform_destroy(instance)
for handler in report_event_handlers:
Expand Down
2 changes: 1 addition & 1 deletion radis/core/management/commands/populate_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def feed_report(body: str):
report = ReportFactory.create(body=body)
groups = fake.random_elements(elements=list(Group.objects.all()), unique=True)
report.groups.set(groups)
ReportDocument.from_report_model(report).create()
ReportDocument(report).create()


def feed_reports():
Expand Down
2 changes: 1 addition & 1 deletion radis/core/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


def is_html_response(response):
return response["Content-Type"].startswith("text/html")
return response.has_header("Content-Type") and response["Content-Type"].startswith("text/html")


class MaintenanceMiddleware:
Expand Down
5 changes: 3 additions & 2 deletions radis/search/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ def ready(self):


def register_app():
from radis.api.site import register_report_handler
from radis.api.site import register_document_fetcher, register_report_handler

from .models import handle_report
from .models import fetch_document, handle_report

register_main_menu_item(
url_name="search",
label="Search",
)

register_report_handler(handle_report)
register_document_fetcher("vespa", fetch_document)


def init_db(**kwargs):
Expand Down
Loading

0 comments on commit b9853a8

Please sign in to comment.