OpenREM 1.0.0b2__py3-none-any.whl → 1.0.0b3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openrem/locale/de/LC_MESSAGES/django.po +1060 -1059
- openrem/locale/django.pot +973 -972
- openrem/locale/es_MX/LC_MESSAGES/django.po +1049 -1048
- openrem/locale/it/LC_MESSAGES/django.po +1044 -1043
- openrem/locale/lt/LC_MESSAGES/django.po +989 -988
- openrem/locale/nb_NO/LC_MESSAGES/django.po +985 -984
- openrem/locale/pt_BR/LC_MESSAGES/django.po +1003 -1002
- openrem/manage.py +10 -10
- openrem/openremproject/__init__.py +1 -1
- openrem/openremproject/local_settings.py.linux +128 -128
- openrem/openremproject/local_settings.py.windows +144 -144
- openrem/openremproject/local_settings.py.windows-sqlite3 +129 -129
- openrem/openremproject/settings.py +278 -278
- openrem/openremproject/urls.py +32 -32
- openrem/openremproject/wsgi.py.example +28 -28
- openrem/remapp/__init__.py +2 -2
- openrem/remapp/admin.py +31 -31
- openrem/remapp/exports/ct_export.py +780 -753
- openrem/remapp/exports/dx_export.py +817 -805
- openrem/remapp/exports/export_common.py +931 -951
- openrem/remapp/exports/export_common_pandas.py +2422 -0
- openrem/remapp/exports/exportviews.py +815 -860
- openrem/remapp/exports/mg_csv_nhsbsp.py +292 -292
- openrem/remapp/exports/mg_export.py +673 -510
- openrem/remapp/exports/nm_export.py +796 -575
- openrem/remapp/exports/rf_export.py +1418 -1431
- openrem/remapp/extractors/ct_philips.py +424 -414
- openrem/remapp/extractors/ct_toshiba.py +2116 -2108
- openrem/remapp/extractors/dx.py +1033 -952
- openrem/remapp/extractors/extract_common.py +817 -817
- openrem/remapp/extractors/import_views.py +426 -426
- openrem/remapp/extractors/mam.py +685 -672
- openrem/remapp/extractors/nm_image.py +439 -431
- openrem/remapp/extractors/ptsizecsv2db.py +368 -368
- openrem/remapp/extractors/rdsr.py +667 -654
- openrem/remapp/extractors/rdsr_methods.py +1771 -1768
- openrem/remapp/extractors/rrdsr_methods.py +630 -622
- openrem/remapp/fixtures/openskin_safelist.json +11 -11
- openrem/remapp/forms.py +2286 -2277
- openrem/remapp/interface/chart_functions.py +2412 -2393
- openrem/remapp/interface/mod_filters.py +1241 -1243
- openrem/remapp/migrations/0001_initial.py.1-0-upgrade +1043 -1043
- openrem/remapp/models.py +3418 -3407
- openrem/remapp/netdicom/dicomviews.py +681 -683
- openrem/remapp/netdicom/qrscu.py +2646 -2646
- openrem/remapp/netdicom/tools.py +134 -134
- openrem/remapp/static/css/bootstrap-theme.css +587 -587
- openrem/remapp/static/css/bootstrap-theme.min.css +4 -4
- openrem/remapp/static/css/bootstrap.css +6800 -6800
- openrem/remapp/static/css/bootstrap.min.css +4 -4
- openrem/remapp/static/css/datepicker3.css +790 -790
- openrem/remapp/static/css/jquery.qtip.min.css +2 -2
- openrem/remapp/static/css/openrem-extra.css +442 -442
- openrem/remapp/static/css/openrem.css +96 -96
- openrem/remapp/static/css/registration.css +34 -34
- openrem/remapp/static/fonts/glyphicons-halflings-regular.svg +287 -287
- openrem/remapp/static/js/bootstrap-datepicker.js +1671 -1671
- openrem/remapp/static/js/bootstrap.js +2363 -2363
- openrem/remapp/static/js/bootstrap.min.js +6 -6
- openrem/remapp/static/js/charts/chartCommonFunctions.js +75 -75
- openrem/remapp/static/js/charts/chartFullScreen.js +41 -41
- openrem/remapp/static/js/charts/ctChartAjax.js +331 -331
- openrem/remapp/static/js/charts/dxChartAjax.js +290 -290
- openrem/remapp/static/js/charts/mgChartAjax.js +144 -144
- openrem/remapp/static/js/charts/nmChartAjax.js +64 -64
- openrem/remapp/static/js/charts/plotly-2.35.2.min.js +8 -0
- openrem/remapp/static/js/charts/rfChartAjax.js +128 -128
- openrem/remapp/static/js/chroma.min.js +32 -32
- openrem/remapp/static/js/datepicker.js +5 -5
- openrem/remapp/static/js/dicom.js +115 -115
- openrem/remapp/static/js/django_reverse/reverse.js +13 -13
- openrem/remapp/static/js/formatDate.js +7 -7
- openrem/remapp/static/js/html5shiv.min.js +8 -8
- openrem/remapp/static/js/jquery-1.11.0.min.js +4 -4
- openrem/remapp/static/js/npm.js +12 -12
- openrem/remapp/static/js/respond.min.js +4 -4
- openrem/remapp/static/js/skin-dose-maps/jquery.qtip.min.js +4 -4
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dHUDObject.js +112 -112
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dObject.js +367 -367
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dPersonObject.js +158 -158
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapColourScaleObject.js +153 -153
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapObject.js +367 -367
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping.js +584 -584
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping3d.js +255 -255
- openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMappingAjax.js +267 -212
- openrem/remapp/static/js/skin-dose-maps/three.min.js +835 -835
- openrem/remapp/static/js/sorttable.js +495 -495
- openrem/remapp/templates/base.html +253 -253
- openrem/remapp/templates/registration/changepassword.html +25 -25
- openrem/remapp/templates/registration/changepassworddone.html +12 -12
- openrem/remapp/templates/registration/login.html +42 -42
- openrem/remapp/templates/remapp/backgroundtaskmaximumrows_form.html +29 -29
- openrem/remapp/templates/remapp/base.html +1 -1
- openrem/remapp/templates/remapp/ctdetail.html +235 -235
- openrem/remapp/templates/remapp/ctfiltered.html +310 -310
- openrem/remapp/templates/remapp/dicomdeletesettings_form.html +31 -31
- openrem/remapp/templates/remapp/dicomqr.html +147 -147
- openrem/remapp/templates/remapp/dicomquerydetails.html +83 -83
- openrem/remapp/templates/remapp/dicomqueryimages.html +49 -49
- openrem/remapp/templates/remapp/dicomqueryseries.html +109 -109
- openrem/remapp/templates/remapp/dicomquerysummary.html +48 -48
- openrem/remapp/templates/remapp/dicomremoteqr_confirm_delete.html +60 -60
- openrem/remapp/templates/remapp/dicomremoteqr_form.html +32 -32
- openrem/remapp/templates/remapp/dicomstorescp_confirm_delete.html +53 -53
- openrem/remapp/templates/remapp/dicomstorescp_form.html +48 -48
- openrem/remapp/templates/remapp/dicomsummary.html +257 -257
- openrem/remapp/templates/remapp/displaychartoptions.html +184 -184
- openrem/remapp/templates/remapp/displayhomepageoptions.html +57 -57
- openrem/remapp/templates/remapp/displayname-count.html +6 -6
- openrem/remapp/templates/remapp/displayname-last-date.html +3 -3
- openrem/remapp/templates/remapp/displayname-modality.html +86 -105
- openrem/remapp/templates/remapp/displayname-skinmap.html +18 -18
- openrem/remapp/templates/remapp/displaynameupdate.html +100 -100
- openrem/remapp/templates/remapp/displaynameview.html +222 -219
- openrem/remapp/templates/remapp/dxdetail.html +176 -176
- openrem/remapp/templates/remapp/dxfiltered.html +324 -324
- openrem/remapp/templates/remapp/exports-active.html +25 -25
- openrem/remapp/templates/remapp/exports-complete.html +35 -35
- openrem/remapp/templates/remapp/exports-error.html +26 -26
- openrem/remapp/templates/remapp/exports-queue.html +18 -18
- openrem/remapp/templates/remapp/exports.html +191 -191
- openrem/remapp/templates/remapp/failed_summary_list.html +27 -27
- openrem/remapp/templates/remapp/filteredbase.html +162 -162
- openrem/remapp/templates/remapp/highdosemetricalertsettings_form.html +76 -76
- openrem/remapp/templates/remapp/home-list-modalities.html +94 -94
- openrem/remapp/templates/remapp/home.html +202 -202
- openrem/remapp/templates/remapp/list_filters.html +24 -24
- openrem/remapp/templates/remapp/mgdetail.html +160 -138
- openrem/remapp/templates/remapp/mgfiltered.html +311 -311
- openrem/remapp/templates/remapp/nmdetail.html +300 -300
- openrem/remapp/templates/remapp/nmfiltered.html +255 -255
- openrem/remapp/templates/remapp/notpatient.html +190 -190
- openrem/remapp/templates/remapp/notpatientindicators_form_base.html +81 -81
- openrem/remapp/templates/remapp/notpatientindicatorsid_confirm_delete.html +54 -54
- openrem/remapp/templates/remapp/notpatientindicatorsid_form.html +23 -23
- openrem/remapp/templates/remapp/notpatientindicatorsname_confirm_delete.html +54 -54
- openrem/remapp/templates/remapp/notpatientindicatorsname_form.html +23 -23
- openrem/remapp/templates/remapp/notpatientindicatorsname_form_base.html +85 -85
- openrem/remapp/templates/remapp/openskinsafelist_add.html +130 -130
- openrem/remapp/templates/remapp/openskinsafelist_confirm_delete.html +100 -100
- openrem/remapp/templates/remapp/openskinsafelist_form.html +207 -207
- openrem/remapp/templates/remapp/patientidsettings_form.html +83 -83
- openrem/remapp/templates/remapp/populate_summary_progress.html +83 -83
- openrem/remapp/templates/remapp/populate_summary_progress_error.html +36 -36
- openrem/remapp/templates/remapp/review_failed_imports.html +157 -157
- openrem/remapp/templates/remapp/review_failed_study.html +41 -41
- openrem/remapp/templates/remapp/review_studies_delete_button.html +20 -20
- openrem/remapp/templates/remapp/review_study.html +19 -19
- openrem/remapp/templates/remapp/review_summary_list.html +245 -245
- openrem/remapp/templates/remapp/rf_dose_alert_email_template.html +14 -1
- openrem/remapp/templates/remapp/rfalertnotificationsview.html +59 -59
- openrem/remapp/templates/remapp/rfdetail.html +547 -543
- openrem/remapp/templates/remapp/rfdetailbase.html +18 -18
- openrem/remapp/templates/remapp/rffiltered.html +404 -404
- openrem/remapp/templates/remapp/sizeimports.html +119 -119
- openrem/remapp/templates/remapp/sizeprocess.html +96 -96
- openrem/remapp/templates/remapp/sizeupload.html +110 -110
- openrem/remapp/templates/remapp/skindosemapcalcsettings_form.html +28 -28
- openrem/remapp/templates/remapp/standardname-modality.html +69 -69
- openrem/remapp/templates/remapp/standardnames_confirm_delete.html +71 -71
- openrem/remapp/templates/remapp/standardnames_form.html +87 -87
- openrem/remapp/templates/remapp/standardnamesettings_form.html +41 -41
- openrem/remapp/templates/remapp/standardnamesrefreshall.html +92 -92
- openrem/remapp/templates/remapp/standardnameview.html +103 -103
- openrem/remapp/templates/remapp/study_confirm_delete.html +147 -147
- openrem/remapp/templates/remapp/task_admin.html +265 -265
- openrem/remapp/templates/remapp/tasks.html +76 -76
- openrem/remapp/templatetags/formfilters.py +13 -13
- openrem/remapp/templatetags/proper_paginate.py +38 -38
- openrem/remapp/templatetags/remappduration.py +36 -36
- openrem/remapp/templatetags/sigdig.py +38 -38
- openrem/remapp/templatetags/sort_class_property_value.py +15 -15
- openrem/remapp/templatetags/update_variable.py +20 -20
- openrem/remapp/templatetags/url_replace.py +25 -25
- openrem/remapp/tests/test_charts_common.py +202 -202
- openrem/remapp/tests/test_charts_ct.py +7111 -7111
- openrem/remapp/tests/test_charts_dx.py +3513 -3513
- openrem/remapp/tests/test_charts_mg.py +1116 -1115
- openrem/remapp/tests/test_dcmdatetime.py +189 -189
- openrem/remapp/tests/test_dicom_qr.py +2580 -2580
- openrem/remapp/tests/test_display_name.py +274 -274
- openrem/remapp/tests/test_export_ct_xlsx.py +272 -248
- openrem/remapp/tests/test_export_dx_xlsx.py +137 -134
- openrem/remapp/tests/test_export_mammo_csv.py +242 -242
- openrem/remapp/tests/test_export_rf_xlsx.py +246 -246
- openrem/remapp/tests/test_files/DX-Im-DRGEM.dcm +0 -0
- openrem/remapp/tests/test_files/MG-RDSR-GEPristina-2D.dcm +0 -0
- openrem/remapp/tests/test_files/MG-RDSR-GEPristina-DBT.dcm +0 -0
- openrem/remapp/tests/test_files/MG-RDSR-Giotto-DBT.dcm +0 -0
- openrem/remapp/tests/test_files/skin_map_alphenix.py +590 -590
- openrem/remapp/tests/test_files/skin_map_zee.py +354 -354
- openrem/remapp/tests/test_filters_ct.py +321 -321
- openrem/remapp/tests/test_filters_dx.py +92 -92
- openrem/remapp/tests/test_filters_mammo.py +183 -183
- openrem/remapp/tests/test_filters_rf.py +118 -118
- openrem/remapp/tests/test_get_values.py +72 -72
- openrem/remapp/tests/test_hash_id.py +65 -65
- openrem/remapp/tests/test_import_ct_esr_ge.py +3034 -3034
- openrem/remapp/tests/test_import_ct_philips_rdsr.py +42 -42
- openrem/remapp/tests/test_import_ct_rdsr_multiple.py +256 -256
- openrem/remapp/tests/test_import_ct_rdsr_siemens.py +827 -827
- openrem/remapp/tests/test_import_ct_rdsr_spectrumdynamics.py +91 -91
- openrem/remapp/tests/test_import_ct_rdsr_toshiba_dosecheck.py +67 -67
- openrem/remapp/tests/test_import_ct_rdsr_toshiba_multivaluesd.py +33 -33
- openrem/remapp/tests/test_import_ct_rdsr_toshiba_pixelmed.py +118 -118
- openrem/remapp/tests/test_import_ct_sc_philips.py +44 -44
- openrem/remapp/tests/test_import_dual_rdsr.py +110 -110
- openrem/remapp/tests/test_import_dx.py +1267 -1191
- openrem/remapp/tests/test_import_dx_rdsr.py +1250 -1253
- openrem/remapp/tests/test_import_mam.py +438 -438
- openrem/remapp/tests/test_import_mg_im_hol_proj.py +46 -46
- openrem/remapp/tests/test_import_mg_rdsr.py +586 -586
- openrem/remapp/tests/test_import_nm_image.py +420 -420
- openrem/remapp/tests/test_import_nm_siemens_rdsr.py +396 -396
- openrem/remapp/tests/test_import_px.py +161 -161
- openrem/remapp/tests/test_import_rf_rdsr.py +420 -418
- openrem/remapp/tests/test_missing_date.py +42 -42
- openrem/remapp/tests/test_not_patient.py +60 -60
- openrem/remapp/tests/test_openskin.py +272 -272
- openrem/remapp/tests/test_patient_id_settings.py +72 -72
- openrem/remapp/tests/test_pt_size_import.py +232 -232
- openrem/remapp/tests/test_rf_detail.py +113 -113
- openrem/remapp/tests/test_rf_high_dose_alert.py +361 -361
- openrem/remapp/tools/background.py +361 -361
- openrem/remapp/tools/check_standard_name_status.py +47 -0
- openrem/remapp/tools/check_uid.py +70 -70
- openrem/remapp/tools/dcmdatetime.py +248 -248
- openrem/remapp/tools/default_import.py +44 -47
- openrem/remapp/tools/get_values.py +230 -230
- openrem/remapp/tools/hash_id.py +58 -58
- openrem/remapp/tools/make_skin_map.py +448 -406
- openrem/remapp/tools/not_patient_indicators.py +72 -72
- openrem/remapp/tools/openskin/calc_exp_map.py +173 -173
- openrem/remapp/tools/openskin/geomclass.py +475 -475
- openrem/remapp/tools/openskin/geomfunc.py +433 -432
- openrem/remapp/tools/openskin/skinmap.py +417 -417
- openrem/remapp/tools/populate_summary.py +185 -193
- openrem/remapp/tools/save_skin_map_structure.py +73 -73
- openrem/remapp/tools/send_high_dose_alert_emails.py +238 -207
- openrem/remapp/urls.py +456 -448
- openrem/remapp/version.py +11 -11
- openrem/remapp/views.py +1147 -1052
- openrem/remapp/views_admin.py +3876 -3936
- openrem/remapp/views_charts_ct.py +2110 -2058
- openrem/remapp/views_charts_dx.py +1906 -1836
- openrem/remapp/views_charts_mg.py +1349 -1196
- openrem/remapp/views_charts_nm.py +535 -535
- openrem/remapp/views_charts_rf.py +1219 -1241
- openrem/remapp/views_openskin.py +379 -384
- openrem/sample-config/openrem-consumer.service +12 -12
- openrem/sample-config/openrem-gunicorn.service +13 -13
- openrem/sample-config/openrem-server +14 -13
- openrem/sample-config/openrem_orthanc_config_linux.lua +454 -454
- openrem/sample-config/openrem_orthanc_config_windows.lua +455 -455
- openrem/sample-config/queue-init.bat +73 -73
- openrem/scripts/openrem_ctphilips.py +25 -25
- openrem/scripts/openrem_cttoshiba.py +28 -28
- openrem/scripts/openrem_dx.py +22 -22
- openrem/scripts/openrem_mg.py +22 -22
- openrem/scripts/openrem_nm.py +22 -22
- openrem/scripts/openrem_ptsizecsv.py +17 -17
- openrem/scripts/openrem_qr.py +12 -12
- openrem/scripts/openrem_rdsr.py +25 -25
- {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/METADATA +39 -29
- openrem-1.0.0b3.dist-info/RECORD +379 -0
- {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/WHEEL +1 -1
- {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/COPYING-GPLv3 +674 -674
- {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/LICENSE +22 -22
- OpenREM-1.0.0b2.dist-info/RECORD +0 -373
- openrem/remapp/static/js/charts/plotly-2.17.1.min.js +0 -8
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ctphilips.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_cttoshiba.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_dx.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_mg.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_nm.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ptsizecsv.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_qr.py +0 -0
- {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_rdsr.py +0 -0
- {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,414 +1,424 @@
|
|
|
1
|
-
# This Python file uses the following encoding: utf-8
|
|
2
|
-
# OpenREM - Radiation Exposure Monitoring tools for the physicist
|
|
3
|
-
# Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
|
|
4
|
-
#
|
|
5
|
-
# This program is free software: you can redistribute it and/or modify
|
|
6
|
-
# it under the terms of the GNU General Public License as published by
|
|
7
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
# (at your option) any later version.
|
|
9
|
-
#
|
|
10
|
-
# This program is distributed in the hope that it will be useful,
|
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
# GNU General Public License for more details.
|
|
14
|
-
#
|
|
15
|
-
# Additional permission under section 7 of GPLv3:
|
|
16
|
-
# You shall not make any use of the name of The Royal Marsden NHS
|
|
17
|
-
# Foundation trust in connection with this Program in any press or
|
|
18
|
-
# other public announcement without the prior written consent of
|
|
19
|
-
# The Royal Marsden NHS Foundation Trust.
|
|
20
|
-
#
|
|
21
|
-
# You should have received a copy of the GNU General Public License
|
|
22
|
-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
.. module:: ctphilips.
|
|
26
|
-
:synopsis: Module to extract radiation dose structured report related data from Philips CT dose report images
|
|
27
|
-
|
|
28
|
-
.. moduleauthor:: Ed McDonagh
|
|
29
|
-
|
|
30
|
-
"""
|
|
31
|
-
from datetime import datetime, timedelta
|
|
32
|
-
import logging
|
|
33
|
-
import os
|
|
34
|
-
import sys
|
|
35
|
-
|
|
36
|
-
from decimal import Decimal
|
|
37
|
-
import django
|
|
38
|
-
from django.db.models import Max, Min, ObjectDoesNotExist
|
|
39
|
-
import pydicom
|
|
40
|
-
|
|
41
|
-
from openrem.remapp.tools.background import (
|
|
42
|
-
record_task_error_exit,
|
|
43
|
-
record_task_related_query,
|
|
44
|
-
record_task_info,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
from ..tools.dcmdatetime import get_date_time, get_date, get_time
|
|
48
|
-
from ..tools.get_values import (
|
|
49
|
-
get_value_kw,
|
|
50
|
-
get_value_num,
|
|
51
|
-
get_or_create_cid,
|
|
52
|
-
get_seq_code_meaning,
|
|
53
|
-
get_seq_code_value,
|
|
54
|
-
list_to_string,
|
|
55
|
-
)
|
|
56
|
-
from ..tools.hash_id import hash_id
|
|
57
|
-
|
|
58
|
-
# setup django/OpenREM
|
|
59
|
-
basepath = os.path.dirname(__file__)
|
|
60
|
-
projectpath = os.path.abspath(os.path.join(basepath, "..", ".."))
|
|
61
|
-
if projectpath not in sys.path:
|
|
62
|
-
sys.path.insert(1, projectpath)
|
|
63
|
-
os.environ["DJANGO_SETTINGS_MODULE"] = "openremproject.settings"
|
|
64
|
-
django.setup()
|
|
65
|
-
|
|
66
|
-
from .extract_common import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
67
|
-
ct_event_type_count,
|
|
68
|
-
patient_module_attributes,
|
|
69
|
-
add_standard_names,
|
|
70
|
-
)
|
|
71
|
-
from remapp.models import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
72
|
-
CtAccumulatedDoseData,
|
|
73
|
-
CtIrradiationEventData,
|
|
74
|
-
CtRadiationDose,
|
|
75
|
-
CtXRaySourceParameters,
|
|
76
|
-
DicomDeleteSettings,
|
|
77
|
-
GeneralEquipmentModuleAttr,
|
|
78
|
-
GeneralStudyModuleAttr,
|
|
79
|
-
PatientIDSettings,
|
|
80
|
-
PatientStudyModuleAttr,
|
|
81
|
-
ScanningLength,
|
|
82
|
-
UniqueEquipmentNames,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
logger = logging.getLogger(__name__)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _scanninglength(dataset, event): # TID 10014
|
|
89
|
-
scanlen = ScanningLength.objects.create(ct_irradiation_event_data=event)
|
|
90
|
-
scanlen.scanning_length = get_value_kw("ScanLength", dataset)
|
|
91
|
-
scanlen.save()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _ctxraysourceparameters(dataset, event):
|
|
95
|
-
param = CtXRaySourceParameters.objects.create(ct_irradiation_event_data=event)
|
|
96
|
-
param.identification_of_the_xray_source = "A"
|
|
97
|
-
param.kvp = get_value_kw("KVP", dataset)
|
|
98
|
-
mA = get_value_kw("XRayTubeCurrentInuA", dataset)
|
|
99
|
-
if mA:
|
|
100
|
-
param.xray_tube_current = mA / 1000.0
|
|
101
|
-
# exposure time per rotation mandatory for non-localizer exposures, but we don't have it.
|
|
102
|
-
param.save()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _ctirradiationeventdata(dataset, ct): # TID 10013
|
|
106
|
-
event = CtIrradiationEventData.objects.create(ct_radiation_dose=ct)
|
|
107
|
-
event.acquisition_protocol = get_value_kw("SeriesDescription", dataset)
|
|
108
|
-
# target region is mandatory, but I don't have it
|
|
109
|
-
acqtype = get_value_kw("AcquisitionType", dataset)
|
|
110
|
-
if acqtype == "CONSTANT_ANGLE":
|
|
111
|
-
event.ct_acquisition_type = get_or_create_cid(
|
|
112
|
-
"113805", "Constant Angle Acquisition"
|
|
113
|
-
)
|
|
114
|
-
elif acqtype == "SPIRAL":
|
|
115
|
-
event.ct_acquisition_type = get_or_create_cid("P5-08001", "Spiral Acquisition")
|
|
116
|
-
elif acqtype == "SEQUENCED": # guessed
|
|
117
|
-
event.ct_acquisition_type = get_or_create_cid("113804", "Sequenced Acquisition")
|
|
118
|
-
elif acqtype == "STATIONARY": # guessed
|
|
119
|
-
event.ct_acquisition_type = get_or_create_cid(
|
|
120
|
-
"113806", "Stationary Acquisition"
|
|
121
|
-
)
|
|
122
|
-
elif acqtype == "FREE": # guessed, for completeness
|
|
123
|
-
event.ct_acquisition_type = get_or_create_cid("113807", "Free Acquisition")
|
|
124
|
-
# procedure context is optional and not reported (contrast or not)
|
|
125
|
-
# irradiation event uid would be available in image headers, but assuming just working from dose report image:
|
|
126
|
-
event.irradiation_event_uid = pydicom.uid.generate_uid()
|
|
127
|
-
exptime = get_value_kw("ExposureTime", dataset)
|
|
128
|
-
if exptime:
|
|
129
|
-
event.exposure_time = exptime / 1000.0
|
|
130
|
-
_scanninglength(dataset, event)
|
|
131
|
-
event.nominal_single_collimation_width = get_value_kw(
|
|
132
|
-
"SingleCollimationWidth", dataset
|
|
133
|
-
)
|
|
134
|
-
event.nominal_total_collimation_width = get_value_kw(
|
|
135
|
-
"TotalCollimationWidth", dataset
|
|
136
|
-
)
|
|
137
|
-
event.pitch_factor = get_value_kw(
|
|
138
|
-
"SpiralPitchFactor", dataset
|
|
139
|
-
) # not sure what would be there for an axial scan: SequencedPitchFactor?
|
|
140
|
-
event.number_of_xray_sources = 1
|
|
141
|
-
ctdiwphantom = get_value_num(0x01E11026, dataset) # Philips private tag
|
|
142
|
-
if ctdiwphantom == "16 CM":
|
|
143
|
-
event.ctdiw_phantom_type = get_or_create_cid(
|
|
144
|
-
"113690", "IEC Head Dosimetry Phantom"
|
|
145
|
-
)
|
|
146
|
-
if ctdiwphantom == "32 CM":
|
|
147
|
-
event.ctdiw_phantom_type = get_or_create_cid(
|
|
148
|
-
"113691", "IEC Body Dosimetry Phantom"
|
|
149
|
-
)
|
|
150
|
-
event.save()
|
|
151
|
-
_ctxraysourceparameters(dataset, event)
|
|
152
|
-
event.mean_ctdivol = get_value_kw("CTDIvol", dataset)
|
|
153
|
-
event.dlp = Decimal(get_value_num(0x00E11021, dataset)) # Philips private tag
|
|
154
|
-
event.date_time_started = get_date_time("AcquisitionDateTime", dataset)
|
|
155
|
-
# event.series_description = get_value_kw('SeriesDescription',dataset)
|
|
156
|
-
event.save()
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _ctaccumulateddosedata(dataset, ct): # TID 10012
|
|
160
|
-
ctacc = CtAccumulatedDoseData.objects.create(ct_radiation_dose=ct)
|
|
161
|
-
ctacc.total_number_of_irradiation_events = get_value_kw(
|
|
162
|
-
"TotalNumberOfExposures", dataset
|
|
163
|
-
)
|
|
164
|
-
try:
|
|
165
|
-
ctacc.ct_dose_length_product_total = Decimal(
|
|
166
|
-
get_value_num(0x00E11021, dataset)
|
|
167
|
-
) # Philips private tag
|
|
168
|
-
except TypeError:
|
|
169
|
-
pass
|
|
170
|
-
ctacc.comment = get_value_kw("CommentsOnRadiationDose", dataset)
|
|
171
|
-
ctacc.save()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _ctradiationdose(dataset, g):
|
|
175
|
-
proj = CtRadiationDose.objects.create(general_study_module_attributes=g)
|
|
176
|
-
proj.procedure_reported = get_or_create_cid("P5-08000", "Computed Tomography X-Ray")
|
|
177
|
-
proj.has_intent = get_or_create_cid("R-408C3", "Diagnostic Intent")
|
|
178
|
-
proj.scope_of_accumulation = get_or_create_cid("113014", "Study")
|
|
179
|
-
comment_dose = get_value_kw("CommentsOnRadiationDose", dataset)
|
|
180
|
-
comment_protocol_file = get_value_num(0x00E11061, dataset)
|
|
181
|
-
comment_study_description = get_value_kw("StudyDescription", dataset)
|
|
182
|
-
if not comment_dose:
|
|
183
|
-
comment_dose = ""
|
|
184
|
-
if not comment_protocol_file:
|
|
185
|
-
comment_protocol_file = ""
|
|
186
|
-
if not comment_study_description:
|
|
187
|
-
comment_study_description = ""
|
|
188
|
-
proj.comment = (
|
|
189
|
-
f"StudyDescription: {comment_study_description}. Comments on radiation dose: {comment_dose}. "
|
|
190
|
-
f"ProtocolFilename: {comment_protocol_file}"
|
|
191
|
-
)
|
|
192
|
-
proj.source_of_dose_information = get_or_create_cid(
|
|
193
|
-
"113866", "Copied From Image Attributes"
|
|
194
|
-
)
|
|
195
|
-
proj.save()
|
|
196
|
-
_ctaccumulateddosedata(dataset, proj)
|
|
197
|
-
for series in dataset.ExposureDoseSequence:
|
|
198
|
-
if "AcquisitionType" in series:
|
|
199
|
-
_ctirradiationeventdata(series, proj)
|
|
200
|
-
events = proj.ctirradiationeventdata_set.all()
|
|
201
|
-
if not events:
|
|
202
|
-
logger.warning(
|
|
203
|
-
f"There were no events in ct_philips import, or they couldn't be read. "
|
|
204
|
-
f"{get_value_kw('StationName', dataset)}"
|
|
205
|
-
f"{get_value_kw('Manufacturer', dataset)}"
|
|
206
|
-
f"{get_value_kw('ManufacturerModelName', dataset)}"
|
|
207
|
-
f"{g.study_date.date()}"
|
|
208
|
-
f"{g.study_time.time()}"
|
|
209
|
-
f"{g.accession_number}"
|
|
210
|
-
)
|
|
211
|
-
else:
|
|
212
|
-
# Come back and set start and end of irradiation after creating the x-ray events
|
|
213
|
-
proj.start_of_xray_irradiation = events.aggregate(Min("date_time_started"))[
|
|
214
|
-
"date_time_started__min"
|
|
215
|
-
]
|
|
216
|
-
try:
|
|
217
|
-
latestlength = int(
|
|
218
|
-
events.latest("date_time_started").exposure_time * 1000
|
|
219
|
-
) # in microseconds
|
|
220
|
-
lastevent = events.aggregate(Max("date_time_started"))[
|
|
221
|
-
"date_time_started__max"
|
|
222
|
-
]
|
|
223
|
-
if lastevent and latestlength:
|
|
224
|
-
last = lastevent + timedelta(microseconds=latestlength)
|
|
225
|
-
proj.end_of_xray_irradiation = last
|
|
226
|
-
except TypeError:
|
|
227
|
-
pass
|
|
228
|
-
proj.save()
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _generalequipmentmoduleattributes(dataset, study):
|
|
232
|
-
equip = GeneralEquipmentModuleAttr.objects.create(
|
|
233
|
-
general_study_module_attributes=study
|
|
234
|
-
)
|
|
235
|
-
equip.manufacturer = get_value_kw("Manufacturer", dataset)
|
|
236
|
-
equip.institution_name = get_value_kw("InstitutionName", dataset)
|
|
237
|
-
equip.institution_address = get_value_kw("InstitutionAddress", dataset)
|
|
238
|
-
equip.station_name = get_value_kw("StationName", dataset)
|
|
239
|
-
equip.institutional_department_name = get_value_kw(
|
|
240
|
-
"InstitutionalDepartmentName", dataset
|
|
241
|
-
)
|
|
242
|
-
equip.manufacturer_model_name = get_value_kw("ManufacturerModelName", dataset)
|
|
243
|
-
equip.device_serial_number = get_value_kw("DeviceSerialNumber", dataset)
|
|
244
|
-
equip.software_versions = get_value_kw("SoftwareVersions", dataset)
|
|
245
|
-
equip.gantry_id = get_value_kw("GantryID", dataset)
|
|
246
|
-
equip.spatial_resolution = get_value_kw(
|
|
247
|
-
"SpatialResolution", dataset
|
|
248
|
-
) # might fall over if field present but blank - check!
|
|
249
|
-
equip.date_of_last_calibration = get_date("DateOfLastCalibration", dataset)
|
|
250
|
-
equip.time_of_last_calibration = get_time("TimeOfLastCalibration", dataset)
|
|
251
|
-
|
|
252
|
-
equip_display_name, created = UniqueEquipmentNames.objects.get_or_create(
|
|
253
|
-
manufacturer=equip.manufacturer,
|
|
254
|
-
manufacturer_hash=hash_id(equip.manufacturer),
|
|
255
|
-
institution_name=equip.institution_name,
|
|
256
|
-
institution_name_hash=hash_id(equip.institution_name),
|
|
257
|
-
station_name=equip.station_name,
|
|
258
|
-
station_name_hash=hash_id(equip.station_name),
|
|
259
|
-
institutional_department_name=equip.institutional_department_name,
|
|
260
|
-
institutional_department_name_hash=hash_id(equip.institutional_department_name),
|
|
261
|
-
manufacturer_model_name=equip.manufacturer_model_name,
|
|
262
|
-
manufacturer_model_name_hash=hash_id(equip.manufacturer_model_name),
|
|
263
|
-
device_serial_number=equip.device_serial_number,
|
|
264
|
-
device_serial_number_hash=hash_id(equip.device_serial_number),
|
|
265
|
-
software_versions=equip.software_versions,
|
|
266
|
-
software_versions_hash=hash_id(equip.software_versions),
|
|
267
|
-
gantry_id=equip.gantry_id,
|
|
268
|
-
gantry_id_hash=hash_id(equip.gantry_id),
|
|
269
|
-
hash_generated=True,
|
|
270
|
-
device_observer_uid=None,
|
|
271
|
-
device_observer_uid_hash=None,
|
|
272
|
-
)
|
|
273
|
-
if created:
|
|
274
|
-
if equip.institution_name and equip.station_name:
|
|
275
|
-
equip_display_name.display_name = (
|
|
276
|
-
equip.institution_name + " " + equip.station_name
|
|
277
|
-
)
|
|
278
|
-
elif equip.institution_name:
|
|
279
|
-
equip_display_name.display_name = equip.institution_name
|
|
280
|
-
elif equip.station_name:
|
|
281
|
-
equip_display_name.display_name = equip.station_name
|
|
282
|
-
else:
|
|
283
|
-
equip_display_name.display_name = "Blank"
|
|
284
|
-
equip_display_name.save()
|
|
285
|
-
|
|
286
|
-
equip.unique_equipment_name = UniqueEquipmentNames(pk=equip_display_name.pk)
|
|
287
|
-
|
|
288
|
-
equip.save()
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def _patientstudymoduleattributes(dataset, g): # C.7.2.2
|
|
292
|
-
patientatt = PatientStudyModuleAttr.objects.create(
|
|
293
|
-
general_study_module_attributes=g
|
|
294
|
-
)
|
|
295
|
-
patientatt.patient_age = get_value_kw("PatientAge", dataset)
|
|
296
|
-
patientatt.patient_weight = get_value_kw("PatientWeight", dataset)
|
|
297
|
-
patientatt.save()
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _generalstudymoduleattributes(dataset, g):
|
|
301
|
-
g.study_instance_uid = get_value_kw("StudyInstanceUID", dataset)
|
|
302
|
-
g.study_date = get_date("StudyDate", dataset)
|
|
303
|
-
g.study_time = get_time("StudyTime", dataset)
|
|
304
|
-
g.study_workload_chart_time = datetime.combine(
|
|
305
|
-
datetime.date(datetime(1900, 1, 1)), datetime.time(g.study_time)
|
|
306
|
-
)
|
|
307
|
-
g.referring_physician_name = list_to_string(
|
|
308
|
-
get_value_kw("RequestingPhysician", dataset)
|
|
309
|
-
)
|
|
310
|
-
g.study_id = get_value_kw("StudyID", dataset)
|
|
311
|
-
accession_number = get_value_kw("AccessionNumber", dataset)
|
|
312
|
-
patient_id_settings = PatientIDSettings.objects.get()
|
|
313
|
-
if accession_number and patient_id_settings.accession_hashed:
|
|
314
|
-
accession_number = hash_id(accession_number)
|
|
315
|
-
g.accession_hashed = True
|
|
316
|
-
g.accession_number = accession_number
|
|
317
|
-
g.modality_type = "CT"
|
|
318
|
-
g.study_description = get_value_kw("ProtocolName", dataset)
|
|
319
|
-
g.operator_name = list_to_string(get_value_kw("OperatorsName", dataset))
|
|
320
|
-
if "RequestAttributesSequence" in dataset:
|
|
321
|
-
g.procedure_code_value = get_seq_code_value(
|
|
322
|
-
"ScheduledProtocolCodeSequence", dataset.RequestAttributesSequence[0]
|
|
323
|
-
)
|
|
324
|
-
g.procedure_code_meaning = get_seq_code_meaning(
|
|
325
|
-
"ScheduledProtocolCodeSequence", dataset.RequestAttributesSequence[0]
|
|
326
|
-
)
|
|
327
|
-
g.requested_procedure_code_meaning = get_value_kw(
|
|
328
|
-
"RequestedProcedureDescription", dataset
|
|
329
|
-
)
|
|
330
|
-
g.save()
|
|
331
|
-
_ctradiationdose(dataset, g)
|
|
332
|
-
try:
|
|
333
|
-
g.number_of_events = (
|
|
334
|
-
g.ctradiationdose_set.get().ctirradiationeventdata_set.count()
|
|
335
|
-
)
|
|
336
|
-
g.save()
|
|
337
|
-
except ObjectDoesNotExist:
|
|
338
|
-
logger.warning(
|
|
339
|
-
"Study UID {0} of modality {1}. Unable to get event count!".format(
|
|
340
|
-
g.study_instance_uid, get_value_kw("ManufacturerModelName", dataset)
|
|
341
|
-
)
|
|
342
|
-
)
|
|
343
|
-
ct_event_type_count(g)
|
|
344
|
-
try:
|
|
345
|
-
g.total_dlp = (
|
|
346
|
-
g.ctradiationdose_set.get()
|
|
347
|
-
.ctaccumulateddosedata_set.get()
|
|
348
|
-
.ct_dose_length_product_total
|
|
349
|
-
)
|
|
350
|
-
g.save()
|
|
351
|
-
except ObjectDoesNotExist:
|
|
352
|
-
logger.warning(
|
|
353
|
-
"Study UID {0} of modality {1}. Unable to set summary total_dlp".format(
|
|
354
|
-
g.study_instance_uid, get_value_kw("ManufacturerModelName", dataset)
|
|
355
|
-
)
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def _philips_ct2db(dataset):
|
|
360
|
-
if "StudyInstanceUID" in dataset:
|
|
361
|
-
study_instance_uid = dataset.StudyInstanceUID
|
|
362
|
-
record_task_info(f"UID: {study_instance_uid.replace('.', '. ')}")
|
|
363
|
-
record_task_related_query(study_instance_uid)
|
|
364
|
-
existing = GeneralStudyModuleAttr.objects.filter(
|
|
365
|
-
study_instance_uid__exact=study_instance_uid
|
|
366
|
-
)
|
|
367
|
-
if existing:
|
|
368
|
-
return
|
|
369
|
-
|
|
370
|
-
g = GeneralStudyModuleAttr.objects.create()
|
|
371
|
-
_generalstudymoduleattributes(dataset, g)
|
|
372
|
-
_generalequipmentmoduleattributes(dataset, g)
|
|
373
|
-
_patientstudymoduleattributes(dataset, g)
|
|
374
|
-
patient_module_attributes(dataset, g)
|
|
375
|
-
|
|
376
|
-
# Add standard names
|
|
377
|
-
add_standard_names(g)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def ct_philips(philips_file):
|
|
381
|
-
"""Extract radiation dose structured report related data from Philips CT dose report images
|
|
382
|
-
|
|
383
|
-
:param filename: relative or absolute path to Philips CT dose report DICOM image file.
|
|
384
|
-
:type filename: str.
|
|
385
|
-
|
|
386
|
-
Tested with:
|
|
387
|
-
* Philips Gemini TF PET-CT v2.3.0
|
|
388
|
-
* Brilliance BigBore v3.5.4.17001.
|
|
389
|
-
"""
|
|
390
|
-
|
|
391
|
-
try:
|
|
392
|
-
del_settings = DicomDeleteSettings.objects.get()
|
|
393
|
-
del_ct_phil = del_settings.del_ct_phil
|
|
394
|
-
except ObjectDoesNotExist:
|
|
395
|
-
del_ct_phil = False
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
1
|
+
# This Python file uses the following encoding: utf-8
|
|
2
|
+
# OpenREM - Radiation Exposure Monitoring tools for the physicist
|
|
3
|
+
# Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# Additional permission under section 7 of GPLv3:
|
|
16
|
+
# You shall not make any use of the name of The Royal Marsden NHS
|
|
17
|
+
# Foundation trust in connection with this Program in any press or
|
|
18
|
+
# other public announcement without the prior written consent of
|
|
19
|
+
# The Royal Marsden NHS Foundation Trust.
|
|
20
|
+
#
|
|
21
|
+
# You should have received a copy of the GNU General Public License
|
|
22
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
.. module:: ctphilips.
|
|
26
|
+
:synopsis: Module to extract radiation dose structured report related data from Philips CT dose report images
|
|
27
|
+
|
|
28
|
+
.. moduleauthor:: Ed McDonagh
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
from datetime import datetime, timedelta
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
from decimal import Decimal
|
|
37
|
+
import django
|
|
38
|
+
from django.db.models import Max, Min, ObjectDoesNotExist
|
|
39
|
+
import pydicom
|
|
40
|
+
|
|
41
|
+
from openrem.remapp.tools.background import (
|
|
42
|
+
record_task_error_exit,
|
|
43
|
+
record_task_related_query,
|
|
44
|
+
record_task_info,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from ..tools.dcmdatetime import get_date_time, get_date, get_time
|
|
48
|
+
from ..tools.get_values import (
|
|
49
|
+
get_value_kw,
|
|
50
|
+
get_value_num,
|
|
51
|
+
get_or_create_cid,
|
|
52
|
+
get_seq_code_meaning,
|
|
53
|
+
get_seq_code_value,
|
|
54
|
+
list_to_string,
|
|
55
|
+
)
|
|
56
|
+
from ..tools.hash_id import hash_id
|
|
57
|
+
|
|
58
|
+
# setup django/OpenREM
|
|
59
|
+
basepath = os.path.dirname(__file__)
|
|
60
|
+
projectpath = os.path.abspath(os.path.join(basepath, "..", ".."))
|
|
61
|
+
if projectpath not in sys.path:
|
|
62
|
+
sys.path.insert(1, projectpath)
|
|
63
|
+
os.environ["DJANGO_SETTINGS_MODULE"] = "openremproject.settings"
|
|
64
|
+
django.setup()
|
|
65
|
+
|
|
66
|
+
from .extract_common import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
67
|
+
ct_event_type_count,
|
|
68
|
+
patient_module_attributes,
|
|
69
|
+
add_standard_names,
|
|
70
|
+
)
|
|
71
|
+
from remapp.models import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
72
|
+
CtAccumulatedDoseData,
|
|
73
|
+
CtIrradiationEventData,
|
|
74
|
+
CtRadiationDose,
|
|
75
|
+
CtXRaySourceParameters,
|
|
76
|
+
DicomDeleteSettings,
|
|
77
|
+
GeneralEquipmentModuleAttr,
|
|
78
|
+
GeneralStudyModuleAttr,
|
|
79
|
+
PatientIDSettings,
|
|
80
|
+
PatientStudyModuleAttr,
|
|
81
|
+
ScanningLength,
|
|
82
|
+
UniqueEquipmentNames,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
logger = logging.getLogger(__name__)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _scanninglength(dataset, event): # TID 10014
|
|
89
|
+
scanlen = ScanningLength.objects.create(ct_irradiation_event_data=event)
|
|
90
|
+
scanlen.scanning_length = get_value_kw("ScanLength", dataset)
|
|
91
|
+
scanlen.save()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _ctxraysourceparameters(dataset, event):
|
|
95
|
+
param = CtXRaySourceParameters.objects.create(ct_irradiation_event_data=event)
|
|
96
|
+
param.identification_of_the_xray_source = "A"
|
|
97
|
+
param.kvp = get_value_kw("KVP", dataset)
|
|
98
|
+
mA = get_value_kw("XRayTubeCurrentInuA", dataset)
|
|
99
|
+
if mA:
|
|
100
|
+
param.xray_tube_current = mA / 1000.0
|
|
101
|
+
# exposure time per rotation mandatory for non-localizer exposures, but we don't have it.
|
|
102
|
+
param.save()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _ctirradiationeventdata(dataset, ct): # TID 10013
|
|
106
|
+
event = CtIrradiationEventData.objects.create(ct_radiation_dose=ct)
|
|
107
|
+
event.acquisition_protocol = get_value_kw("SeriesDescription", dataset)
|
|
108
|
+
# target region is mandatory, but I don't have it
|
|
109
|
+
acqtype = get_value_kw("AcquisitionType", dataset)
|
|
110
|
+
if acqtype == "CONSTANT_ANGLE":
|
|
111
|
+
event.ct_acquisition_type = get_or_create_cid(
|
|
112
|
+
"113805", "Constant Angle Acquisition"
|
|
113
|
+
)
|
|
114
|
+
elif acqtype == "SPIRAL":
|
|
115
|
+
event.ct_acquisition_type = get_or_create_cid("P5-08001", "Spiral Acquisition")
|
|
116
|
+
elif acqtype == "SEQUENCED": # guessed
|
|
117
|
+
event.ct_acquisition_type = get_or_create_cid("113804", "Sequenced Acquisition")
|
|
118
|
+
elif acqtype == "STATIONARY": # guessed
|
|
119
|
+
event.ct_acquisition_type = get_or_create_cid(
|
|
120
|
+
"113806", "Stationary Acquisition"
|
|
121
|
+
)
|
|
122
|
+
elif acqtype == "FREE": # guessed, for completeness
|
|
123
|
+
event.ct_acquisition_type = get_or_create_cid("113807", "Free Acquisition")
|
|
124
|
+
# procedure context is optional and not reported (contrast or not)
|
|
125
|
+
# irradiation event uid would be available in image headers, but assuming just working from dose report image:
|
|
126
|
+
event.irradiation_event_uid = pydicom.uid.generate_uid()
|
|
127
|
+
exptime = get_value_kw("ExposureTime", dataset)
|
|
128
|
+
if exptime:
|
|
129
|
+
event.exposure_time = exptime / 1000.0
|
|
130
|
+
_scanninglength(dataset, event)
|
|
131
|
+
event.nominal_single_collimation_width = get_value_kw(
|
|
132
|
+
"SingleCollimationWidth", dataset
|
|
133
|
+
)
|
|
134
|
+
event.nominal_total_collimation_width = get_value_kw(
|
|
135
|
+
"TotalCollimationWidth", dataset
|
|
136
|
+
)
|
|
137
|
+
event.pitch_factor = get_value_kw(
|
|
138
|
+
"SpiralPitchFactor", dataset
|
|
139
|
+
) # not sure what would be there for an axial scan: SequencedPitchFactor?
|
|
140
|
+
event.number_of_xray_sources = 1
|
|
141
|
+
ctdiwphantom = get_value_num(0x01E11026, dataset) # Philips private tag
|
|
142
|
+
if ctdiwphantom == "16 CM":
|
|
143
|
+
event.ctdiw_phantom_type = get_or_create_cid(
|
|
144
|
+
"113690", "IEC Head Dosimetry Phantom"
|
|
145
|
+
)
|
|
146
|
+
if ctdiwphantom == "32 CM":
|
|
147
|
+
event.ctdiw_phantom_type = get_or_create_cid(
|
|
148
|
+
"113691", "IEC Body Dosimetry Phantom"
|
|
149
|
+
)
|
|
150
|
+
event.save()
|
|
151
|
+
_ctxraysourceparameters(dataset, event)
|
|
152
|
+
event.mean_ctdivol = get_value_kw("CTDIvol", dataset)
|
|
153
|
+
event.dlp = Decimal(get_value_num(0x00E11021, dataset)) # Philips private tag
|
|
154
|
+
event.date_time_started = get_date_time("AcquisitionDateTime", dataset)
|
|
155
|
+
# event.series_description = get_value_kw('SeriesDescription',dataset)
|
|
156
|
+
event.save()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _ctaccumulateddosedata(dataset, ct): # TID 10012
|
|
160
|
+
ctacc = CtAccumulatedDoseData.objects.create(ct_radiation_dose=ct)
|
|
161
|
+
ctacc.total_number_of_irradiation_events = get_value_kw(
|
|
162
|
+
"TotalNumberOfExposures", dataset
|
|
163
|
+
)
|
|
164
|
+
try:
|
|
165
|
+
ctacc.ct_dose_length_product_total = Decimal(
|
|
166
|
+
get_value_num(0x00E11021, dataset)
|
|
167
|
+
) # Philips private tag
|
|
168
|
+
except TypeError:
|
|
169
|
+
pass
|
|
170
|
+
ctacc.comment = get_value_kw("CommentsOnRadiationDose", dataset)
|
|
171
|
+
ctacc.save()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _ctradiationdose(dataset, g):
|
|
175
|
+
proj = CtRadiationDose.objects.create(general_study_module_attributes=g)
|
|
176
|
+
proj.procedure_reported = get_or_create_cid("P5-08000", "Computed Tomography X-Ray")
|
|
177
|
+
proj.has_intent = get_or_create_cid("R-408C3", "Diagnostic Intent")
|
|
178
|
+
proj.scope_of_accumulation = get_or_create_cid("113014", "Study")
|
|
179
|
+
comment_dose = get_value_kw("CommentsOnRadiationDose", dataset)
|
|
180
|
+
comment_protocol_file = get_value_num(0x00E11061, dataset)
|
|
181
|
+
comment_study_description = get_value_kw("StudyDescription", dataset)
|
|
182
|
+
if not comment_dose:
|
|
183
|
+
comment_dose = ""
|
|
184
|
+
if not comment_protocol_file:
|
|
185
|
+
comment_protocol_file = ""
|
|
186
|
+
if not comment_study_description:
|
|
187
|
+
comment_study_description = ""
|
|
188
|
+
proj.comment = (
|
|
189
|
+
f"StudyDescription: {comment_study_description}. Comments on radiation dose: {comment_dose}. "
|
|
190
|
+
f"ProtocolFilename: {comment_protocol_file}"
|
|
191
|
+
)
|
|
192
|
+
proj.source_of_dose_information = get_or_create_cid(
|
|
193
|
+
"113866", "Copied From Image Attributes"
|
|
194
|
+
)
|
|
195
|
+
proj.save()
|
|
196
|
+
_ctaccumulateddosedata(dataset, proj)
|
|
197
|
+
for series in dataset.ExposureDoseSequence:
|
|
198
|
+
if "AcquisitionType" in series:
|
|
199
|
+
_ctirradiationeventdata(series, proj)
|
|
200
|
+
events = proj.ctirradiationeventdata_set.all()
|
|
201
|
+
if not events:
|
|
202
|
+
logger.warning(
|
|
203
|
+
f"There were no events in ct_philips import, or they couldn't be read. "
|
|
204
|
+
f"{get_value_kw('StationName', dataset)}"
|
|
205
|
+
f"{get_value_kw('Manufacturer', dataset)}"
|
|
206
|
+
f"{get_value_kw('ManufacturerModelName', dataset)}"
|
|
207
|
+
f"{g.study_date.date()}"
|
|
208
|
+
f"{g.study_time.time()}"
|
|
209
|
+
f"{g.accession_number}"
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
# Come back and set start and end of irradiation after creating the x-ray events
|
|
213
|
+
proj.start_of_xray_irradiation = events.aggregate(Min("date_time_started"))[
|
|
214
|
+
"date_time_started__min"
|
|
215
|
+
]
|
|
216
|
+
try:
|
|
217
|
+
latestlength = int(
|
|
218
|
+
events.latest("date_time_started").exposure_time * 1000
|
|
219
|
+
) # in microseconds
|
|
220
|
+
lastevent = events.aggregate(Max("date_time_started"))[
|
|
221
|
+
"date_time_started__max"
|
|
222
|
+
]
|
|
223
|
+
if lastevent and latestlength:
|
|
224
|
+
last = lastevent + timedelta(microseconds=latestlength)
|
|
225
|
+
proj.end_of_xray_irradiation = last
|
|
226
|
+
except TypeError:
|
|
227
|
+
pass
|
|
228
|
+
proj.save()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _generalequipmentmoduleattributes(dataset, study):
|
|
232
|
+
equip = GeneralEquipmentModuleAttr.objects.create(
|
|
233
|
+
general_study_module_attributes=study
|
|
234
|
+
)
|
|
235
|
+
equip.manufacturer = get_value_kw("Manufacturer", dataset)
|
|
236
|
+
equip.institution_name = get_value_kw("InstitutionName", dataset)
|
|
237
|
+
equip.institution_address = get_value_kw("InstitutionAddress", dataset)
|
|
238
|
+
equip.station_name = get_value_kw("StationName", dataset)
|
|
239
|
+
equip.institutional_department_name = get_value_kw(
|
|
240
|
+
"InstitutionalDepartmentName", dataset
|
|
241
|
+
)
|
|
242
|
+
equip.manufacturer_model_name = get_value_kw("ManufacturerModelName", dataset)
|
|
243
|
+
equip.device_serial_number = get_value_kw("DeviceSerialNumber", dataset)
|
|
244
|
+
equip.software_versions = get_value_kw("SoftwareVersions", dataset)
|
|
245
|
+
equip.gantry_id = get_value_kw("GantryID", dataset)
|
|
246
|
+
equip.spatial_resolution = get_value_kw(
|
|
247
|
+
"SpatialResolution", dataset
|
|
248
|
+
) # might fall over if field present but blank - check!
|
|
249
|
+
equip.date_of_last_calibration = get_date("DateOfLastCalibration", dataset)
|
|
250
|
+
equip.time_of_last_calibration = get_time("TimeOfLastCalibration", dataset)
|
|
251
|
+
|
|
252
|
+
equip_display_name, created = UniqueEquipmentNames.objects.get_or_create(
|
|
253
|
+
manufacturer=equip.manufacturer,
|
|
254
|
+
manufacturer_hash=hash_id(equip.manufacturer),
|
|
255
|
+
institution_name=equip.institution_name,
|
|
256
|
+
institution_name_hash=hash_id(equip.institution_name),
|
|
257
|
+
station_name=equip.station_name,
|
|
258
|
+
station_name_hash=hash_id(equip.station_name),
|
|
259
|
+
institutional_department_name=equip.institutional_department_name,
|
|
260
|
+
institutional_department_name_hash=hash_id(equip.institutional_department_name),
|
|
261
|
+
manufacturer_model_name=equip.manufacturer_model_name,
|
|
262
|
+
manufacturer_model_name_hash=hash_id(equip.manufacturer_model_name),
|
|
263
|
+
device_serial_number=equip.device_serial_number,
|
|
264
|
+
device_serial_number_hash=hash_id(equip.device_serial_number),
|
|
265
|
+
software_versions=equip.software_versions,
|
|
266
|
+
software_versions_hash=hash_id(equip.software_versions),
|
|
267
|
+
gantry_id=equip.gantry_id,
|
|
268
|
+
gantry_id_hash=hash_id(equip.gantry_id),
|
|
269
|
+
hash_generated=True,
|
|
270
|
+
device_observer_uid=None,
|
|
271
|
+
device_observer_uid_hash=None,
|
|
272
|
+
)
|
|
273
|
+
if created:
|
|
274
|
+
if equip.institution_name and equip.station_name:
|
|
275
|
+
equip_display_name.display_name = (
|
|
276
|
+
equip.institution_name + " " + equip.station_name
|
|
277
|
+
)
|
|
278
|
+
elif equip.institution_name:
|
|
279
|
+
equip_display_name.display_name = equip.institution_name
|
|
280
|
+
elif equip.station_name:
|
|
281
|
+
equip_display_name.display_name = equip.station_name
|
|
282
|
+
else:
|
|
283
|
+
equip_display_name.display_name = "Blank"
|
|
284
|
+
equip_display_name.save()
|
|
285
|
+
|
|
286
|
+
equip.unique_equipment_name = UniqueEquipmentNames(pk=equip_display_name.pk)
|
|
287
|
+
|
|
288
|
+
equip.save()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _patientstudymoduleattributes(dataset, g): # C.7.2.2
|
|
292
|
+
patientatt = PatientStudyModuleAttr.objects.create(
|
|
293
|
+
general_study_module_attributes=g
|
|
294
|
+
)
|
|
295
|
+
patientatt.patient_age = get_value_kw("PatientAge", dataset)
|
|
296
|
+
patientatt.patient_weight = get_value_kw("PatientWeight", dataset)
|
|
297
|
+
patientatt.save()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _generalstudymoduleattributes(dataset, g):
|
|
301
|
+
g.study_instance_uid = get_value_kw("StudyInstanceUID", dataset)
|
|
302
|
+
g.study_date = get_date("StudyDate", dataset)
|
|
303
|
+
g.study_time = get_time("StudyTime", dataset)
|
|
304
|
+
g.study_workload_chart_time = datetime.combine(
|
|
305
|
+
datetime.date(datetime(1900, 1, 1)), datetime.time(g.study_time)
|
|
306
|
+
)
|
|
307
|
+
g.referring_physician_name = list_to_string(
|
|
308
|
+
get_value_kw("RequestingPhysician", dataset)
|
|
309
|
+
)
|
|
310
|
+
g.study_id = get_value_kw("StudyID", dataset)
|
|
311
|
+
accession_number = get_value_kw("AccessionNumber", dataset)
|
|
312
|
+
patient_id_settings = PatientIDSettings.objects.get()
|
|
313
|
+
if accession_number and patient_id_settings.accession_hashed:
|
|
314
|
+
accession_number = hash_id(accession_number)
|
|
315
|
+
g.accession_hashed = True
|
|
316
|
+
g.accession_number = accession_number
|
|
317
|
+
g.modality_type = "CT"
|
|
318
|
+
g.study_description = get_value_kw("ProtocolName", dataset)
|
|
319
|
+
g.operator_name = list_to_string(get_value_kw("OperatorsName", dataset))
|
|
320
|
+
if "RequestAttributesSequence" in dataset:
|
|
321
|
+
g.procedure_code_value = get_seq_code_value(
|
|
322
|
+
"ScheduledProtocolCodeSequence", dataset.RequestAttributesSequence[0]
|
|
323
|
+
)
|
|
324
|
+
g.procedure_code_meaning = get_seq_code_meaning(
|
|
325
|
+
"ScheduledProtocolCodeSequence", dataset.RequestAttributesSequence[0]
|
|
326
|
+
)
|
|
327
|
+
g.requested_procedure_code_meaning = get_value_kw(
|
|
328
|
+
"RequestedProcedureDescription", dataset
|
|
329
|
+
)
|
|
330
|
+
g.save()
|
|
331
|
+
_ctradiationdose(dataset, g)
|
|
332
|
+
try:
|
|
333
|
+
g.number_of_events = (
|
|
334
|
+
g.ctradiationdose_set.get().ctirradiationeventdata_set.count()
|
|
335
|
+
)
|
|
336
|
+
g.save()
|
|
337
|
+
except ObjectDoesNotExist:
|
|
338
|
+
logger.warning(
|
|
339
|
+
"Study UID {0} of modality {1}. Unable to get event count!".format(
|
|
340
|
+
g.study_instance_uid, get_value_kw("ManufacturerModelName", dataset)
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
ct_event_type_count(g)
|
|
344
|
+
try:
|
|
345
|
+
g.total_dlp = (
|
|
346
|
+
g.ctradiationdose_set.get()
|
|
347
|
+
.ctaccumulateddosedata_set.get()
|
|
348
|
+
.ct_dose_length_product_total
|
|
349
|
+
)
|
|
350
|
+
g.save()
|
|
351
|
+
except ObjectDoesNotExist:
|
|
352
|
+
logger.warning(
|
|
353
|
+
"Study UID {0} of modality {1}. Unable to set summary total_dlp".format(
|
|
354
|
+
g.study_instance_uid, get_value_kw("ManufacturerModelName", dataset)
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _philips_ct2db(dataset):
|
|
360
|
+
if "StudyInstanceUID" in dataset:
|
|
361
|
+
study_instance_uid = dataset.StudyInstanceUID
|
|
362
|
+
record_task_info(f"UID: {study_instance_uid.replace('.', '. ')}")
|
|
363
|
+
record_task_related_query(study_instance_uid)
|
|
364
|
+
existing = GeneralStudyModuleAttr.objects.filter(
|
|
365
|
+
study_instance_uid__exact=study_instance_uid
|
|
366
|
+
)
|
|
367
|
+
if existing:
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
g = GeneralStudyModuleAttr.objects.create()
|
|
371
|
+
_generalstudymoduleattributes(dataset, g)
|
|
372
|
+
_generalequipmentmoduleattributes(dataset, g)
|
|
373
|
+
_patientstudymoduleattributes(dataset, g)
|
|
374
|
+
patient_module_attributes(dataset, g)
|
|
375
|
+
|
|
376
|
+
# Add standard names
|
|
377
|
+
add_standard_names(g)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def ct_philips(philips_file):
|
|
381
|
+
"""Extract radiation dose structured report related data from Philips CT dose report images
|
|
382
|
+
|
|
383
|
+
:param filename: relative or absolute path to Philips CT dose report DICOM image file.
|
|
384
|
+
:type filename: str.
|
|
385
|
+
|
|
386
|
+
Tested with:
|
|
387
|
+
* Philips Gemini TF PET-CT v2.3.0
|
|
388
|
+
* Brilliance BigBore v3.5.4.17001.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
del_settings = DicomDeleteSettings.objects.get()
|
|
393
|
+
del_ct_phil = del_settings.del_ct_phil
|
|
394
|
+
except ObjectDoesNotExist:
|
|
395
|
+
del_ct_phil = False
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
dataset = pydicom.dcmread(philips_file)
|
|
399
|
+
except FileNotFoundError:
|
|
400
|
+
logger.warning(
|
|
401
|
+
f"ct_philips.py not attempting to extract from {philips_file}, the file does not exist"
|
|
402
|
+
)
|
|
403
|
+
record_task_error_exit(
|
|
404
|
+
f"Not attempting to extract from {philips_file}, the file does not exist"
|
|
405
|
+
)
|
|
406
|
+
return 1
|
|
407
|
+
|
|
408
|
+
dataset.decode()
|
|
409
|
+
if (
|
|
410
|
+
dataset.SOPClassUID != "1.2.840.10008.5.1.4.1.1.7"
|
|
411
|
+
or dataset.Manufacturer != "Philips"
|
|
412
|
+
or dataset.SeriesDescription != "Dose Info"
|
|
413
|
+
):
|
|
414
|
+
error = "{0} is not a Philips CT dose report image".format(philips_file)
|
|
415
|
+
logger.error(error)
|
|
416
|
+
record_task_error_exit(error)
|
|
417
|
+
return 1
|
|
418
|
+
|
|
419
|
+
_philips_ct2db(dataset)
|
|
420
|
+
|
|
421
|
+
if del_ct_phil:
|
|
422
|
+
os.remove(philips_file)
|
|
423
|
+
|
|
424
|
+
return 0
|