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
openrem/remapp/extractors/dx.py
CHANGED
|
@@ -1,952 +1,1033 @@
|
|
|
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
|
-
# This file (dx.py) is intended to extract radiation dose related data from
|
|
26
|
-
# DX images. It is based on mam.py.
|
|
27
|
-
# David Platten, 28/3/2014
|
|
28
|
-
#
|
|
29
|
-
"""
|
|
30
|
-
.. module:: dx.
|
|
31
|
-
:synopsis: Module to extract radiation dose related data from DX image objects.
|
|
32
|
-
|
|
33
|
-
.. moduleauthor:: David Platten, Ed McDonagh
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
from datetime import datetime
|
|
37
|
-
from decimal import Decimal, DecimalException
|
|
38
|
-
import logging
|
|
39
|
-
import os
|
|
40
|
-
from random import random
|
|
41
|
-
import sys
|
|
42
|
-
from time import sleep
|
|
43
|
-
|
|
44
|
-
import django
|
|
45
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
46
|
-
import pydicom
|
|
47
|
-
from pydicom
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
from ..tools
|
|
57
|
-
from ..tools.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
filters
|
|
180
|
-
filters.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
and
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
kv
|
|
275
|
-
kv.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
detector.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
detector.
|
|
335
|
-
detector.
|
|
336
|
-
detector.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
source
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
source.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
source.
|
|
377
|
-
source.
|
|
378
|
-
source.
|
|
379
|
-
source.
|
|
380
|
-
source.
|
|
381
|
-
source.
|
|
382
|
-
source.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
and
|
|
403
|
-
and
|
|
404
|
-
and "
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
#
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
# dist.
|
|
418
|
-
dist.
|
|
419
|
-
|
|
420
|
-
#
|
|
421
|
-
dist.
|
|
422
|
-
dist.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
mech.
|
|
434
|
-
mech.
|
|
435
|
-
mech.
|
|
436
|
-
mech.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
mech.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
event
|
|
449
|
-
event.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
event.
|
|
462
|
-
event.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
accum
|
|
538
|
-
accum.
|
|
539
|
-
|
|
540
|
-
accumint
|
|
541
|
-
accumint.
|
|
542
|
-
accumint.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
proj
|
|
564
|
-
proj.
|
|
565
|
-
proj.
|
|
566
|
-
proj.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
proj.
|
|
571
|
-
proj.
|
|
572
|
-
proj.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
equip.
|
|
583
|
-
equip.
|
|
584
|
-
equip.
|
|
585
|
-
equip.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
equip.
|
|
590
|
-
equip.
|
|
591
|
-
equip.
|
|
592
|
-
equip.
|
|
593
|
-
equip.
|
|
594
|
-
equip.
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
patientatt.
|
|
641
|
-
patientatt.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
g.
|
|
654
|
-
g.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
g.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
g.
|
|
674
|
-
g.
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
and
|
|
728
|
-
and "
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
and
|
|
735
|
-
and "
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
g
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
#
|
|
756
|
-
#
|
|
757
|
-
# Digital x-ray image storage - for
|
|
758
|
-
#
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
if
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
and dataset.SOPClassUID != "1.2.840.10008.5.1.4.1.1.1.1
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
this_study
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
)
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
elif study_in_db
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
"""
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
+
# This file (dx.py) is intended to extract radiation dose related data from
|
|
26
|
+
# DX images. It is based on mam.py.
|
|
27
|
+
# David Platten, 28/3/2014
|
|
28
|
+
#
|
|
29
|
+
"""
|
|
30
|
+
.. module:: dx.
|
|
31
|
+
:synopsis: Module to extract radiation dose related data from DX image objects.
|
|
32
|
+
|
|
33
|
+
.. moduleauthor:: David Platten, Ed McDonagh
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
from datetime import datetime
|
|
37
|
+
from decimal import Decimal, DecimalException
|
|
38
|
+
import logging
|
|
39
|
+
import os
|
|
40
|
+
from random import random
|
|
41
|
+
import sys
|
|
42
|
+
from time import sleep
|
|
43
|
+
|
|
44
|
+
import django
|
|
45
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
46
|
+
import pydicom
|
|
47
|
+
from pydicom import config
|
|
48
|
+
from pydicom.valuerep import MultiValue
|
|
49
|
+
|
|
50
|
+
from openrem.remapp.tools.background import (
|
|
51
|
+
record_task_error_exit,
|
|
52
|
+
record_task_related_query,
|
|
53
|
+
record_task_info,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from ..tools import check_uid
|
|
57
|
+
from ..tools.dcmdatetime import get_date, get_time, make_date_time
|
|
58
|
+
from ..tools.get_values import (
|
|
59
|
+
get_value_kw,
|
|
60
|
+
get_value_num,
|
|
61
|
+
get_or_create_cid,
|
|
62
|
+
get_seq_code_value,
|
|
63
|
+
get_seq_code_meaning,
|
|
64
|
+
list_to_string,
|
|
65
|
+
)
|
|
66
|
+
from ..tools.hash_id import hash_id
|
|
67
|
+
|
|
68
|
+
# setup django/OpenREM
|
|
69
|
+
basepath = os.path.dirname(__file__)
|
|
70
|
+
projectpath = os.path.abspath(os.path.join(basepath, "..", ".."))
|
|
71
|
+
if projectpath not in sys.path:
|
|
72
|
+
sys.path.insert(1, projectpath)
|
|
73
|
+
os.environ["DJANGO_SETTINGS_MODULE"] = "openremproject.settings"
|
|
74
|
+
django.setup()
|
|
75
|
+
|
|
76
|
+
from .extract_common import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
77
|
+
get_study_check_dup,
|
|
78
|
+
populate_dx_rf_summary,
|
|
79
|
+
patient_module_attributes,
|
|
80
|
+
add_standard_names,
|
|
81
|
+
)
|
|
82
|
+
from remapp.models import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
83
|
+
AccumXRayDose,
|
|
84
|
+
AccumIntegratedProjRadiogDose,
|
|
85
|
+
DicomDeleteSettings,
|
|
86
|
+
DoseRelatedDistanceMeasurements,
|
|
87
|
+
Exposure,
|
|
88
|
+
GeneralEquipmentModuleAttr,
|
|
89
|
+
GeneralStudyModuleAttr,
|
|
90
|
+
IrradEventXRayData,
|
|
91
|
+
IrradEventXRayDetectorData,
|
|
92
|
+
IrradEventXRayMechanicalData,
|
|
93
|
+
IrradEventXRaySourceData,
|
|
94
|
+
Kvp,
|
|
95
|
+
PatientIDSettings,
|
|
96
|
+
PatientStudyModuleAttr,
|
|
97
|
+
ProjectionXRayRadiationDose,
|
|
98
|
+
UniqueEquipmentNames,
|
|
99
|
+
XrayFilters,
|
|
100
|
+
XrayGrid,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
logger = logging.getLogger(
|
|
104
|
+
"remapp.extractors.dx"
|
|
105
|
+
) # Explicitly named so that it is still handled when using __main__
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _xrayfilters(filttype, material, thickmax, thickmin, source):
|
|
109
|
+
|
|
110
|
+
filters = XrayFilters.objects.create(irradiation_event_xray_source_data=source)
|
|
111
|
+
if filttype:
|
|
112
|
+
filter_types = {
|
|
113
|
+
"STRIP": {"code": "113650", "meaning": "Strip filter"},
|
|
114
|
+
"WEDGE": {"code": "113651", "meaning": "Wedge filter"},
|
|
115
|
+
"BUTTERFLY": {"code:": "113652", "meaning": "Butterfly filter"},
|
|
116
|
+
"NONE": {"code": "111609", "meaning": "No filter"},
|
|
117
|
+
"FLAT": {"code": "113653", "meaning": "Flat filter"},
|
|
118
|
+
}
|
|
119
|
+
if filttype in filter_types:
|
|
120
|
+
filters.xray_filter_type = get_or_create_cid(
|
|
121
|
+
filter_types[filttype]["code"], filter_types[filttype]["meaning"]
|
|
122
|
+
)
|
|
123
|
+
if material:
|
|
124
|
+
logger.debug(
|
|
125
|
+
f"In _xrayfilters, attempting to match material {material.strip().lower()}"
|
|
126
|
+
)
|
|
127
|
+
if material.strip().lower() == "molybdenum":
|
|
128
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
129
|
+
"C-150F9", "Molybdenum or Molybdenum compound"
|
|
130
|
+
)
|
|
131
|
+
if material.strip().lower() == "rhodium":
|
|
132
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
133
|
+
"C-167F9", "Rhodium or Rhodium compound"
|
|
134
|
+
)
|
|
135
|
+
if material.strip().lower() == "silver":
|
|
136
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
137
|
+
"C-137F9", "Silver or Silver compound"
|
|
138
|
+
)
|
|
139
|
+
if material.strip().lower() in [
|
|
140
|
+
"aluminum",
|
|
141
|
+
"aluminium",
|
|
142
|
+
]: # Illegal spelling of Aluminium found in Philips DiDi
|
|
143
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
144
|
+
"C-120F9", "Aluminum or Aluminum compound"
|
|
145
|
+
)
|
|
146
|
+
if material.strip().lower() == "copper":
|
|
147
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
148
|
+
"C-127F9", "Copper or Copper compound"
|
|
149
|
+
)
|
|
150
|
+
if material.strip().lower() == "niobium":
|
|
151
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
152
|
+
"C-1190E", "Niobium or Niobium compound"
|
|
153
|
+
)
|
|
154
|
+
if material.strip().lower() == "europium":
|
|
155
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
156
|
+
"C-1190F", "Europium or Europium compound"
|
|
157
|
+
)
|
|
158
|
+
if material.strip().lower() == "lead":
|
|
159
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
160
|
+
"C-132F9", "Lead or Lead compound"
|
|
161
|
+
)
|
|
162
|
+
if material.strip().lower() == "tantalum":
|
|
163
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
164
|
+
"C-156F9", "Tantalum or Tantalum compound"
|
|
165
|
+
)
|
|
166
|
+
if thickmax is not None and thickmin is not None:
|
|
167
|
+
if thickmax < thickmin:
|
|
168
|
+
tempmin = thickmax
|
|
169
|
+
thickmax = thickmin
|
|
170
|
+
thickmin = tempmin
|
|
171
|
+
if thickmax is not None:
|
|
172
|
+
filters.xray_filter_thickness_maximum = thickmax
|
|
173
|
+
if thickmin is not None:
|
|
174
|
+
filters.xray_filter_thickness_minimum = thickmin
|
|
175
|
+
filters.save()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _xrayfiltersnone(source):
|
|
179
|
+
filters = XrayFilters.objects.create(irradiation_event_xray_source_data=source)
|
|
180
|
+
filters.xray_filter_type = get_or_create_cid("111609", "No filter")
|
|
181
|
+
filters.save()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _xray_filters_multiple(
|
|
185
|
+
xray_filter_material,
|
|
186
|
+
xray_filter_thickness_maximum,
|
|
187
|
+
xray_filter_thickness_minimum,
|
|
188
|
+
source,
|
|
189
|
+
):
|
|
190
|
+
for i, material in enumerate(xray_filter_material):
|
|
191
|
+
try:
|
|
192
|
+
thickmax = None
|
|
193
|
+
thickmin = None
|
|
194
|
+
if isinstance(xray_filter_thickness_maximum, list):
|
|
195
|
+
thickmax = xray_filter_thickness_maximum[i]
|
|
196
|
+
if isinstance(xray_filter_thickness_minimum, list):
|
|
197
|
+
thickmin = xray_filter_thickness_minimum[i]
|
|
198
|
+
_xrayfilters("FLAT", material, thickmax, thickmin, source)
|
|
199
|
+
except IndexError:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _xray_filters_prep(dataset, source):
|
|
204
|
+
xray_filter_type = get_value_kw("FilterType", dataset)
|
|
205
|
+
xray_filter_material = get_value_kw("FilterMaterial", dataset)
|
|
206
|
+
|
|
207
|
+
# Explicit no filter, register as such
|
|
208
|
+
if xray_filter_type == "NONE":
|
|
209
|
+
_xrayfiltersnone(source)
|
|
210
|
+
return
|
|
211
|
+
# Implicit no filter, just ignore
|
|
212
|
+
if xray_filter_type is None:
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
# Get multiple filters into pydicom MultiValue or lists
|
|
216
|
+
if (
|
|
217
|
+
xray_filter_material
|
|
218
|
+
and "," in xray_filter_material
|
|
219
|
+
and not isinstance(xray_filter_material, MultiValue)
|
|
220
|
+
):
|
|
221
|
+
xray_filter_material = xray_filter_material.split(",")
|
|
222
|
+
|
|
223
|
+
xray_filter_thickness_minimum = get_value_kw("FilterThicknessMinimum", dataset)
|
|
224
|
+
xray_filter_thickness_maximum = get_value_kw("FilterThicknessMaximum", dataset)
|
|
225
|
+
if xray_filter_thickness_minimum and not isinstance(
|
|
226
|
+
xray_filter_thickness_minimum, (MultiValue, list)
|
|
227
|
+
):
|
|
228
|
+
try:
|
|
229
|
+
float(xray_filter_thickness_minimum)
|
|
230
|
+
except ValueError:
|
|
231
|
+
if "," in xray_filter_thickness_minimum:
|
|
232
|
+
xray_filter_thickness_minimum = xray_filter_thickness_minimum.split(",")
|
|
233
|
+
if xray_filter_thickness_maximum and not isinstance(
|
|
234
|
+
xray_filter_thickness_maximum, (MultiValue, list)
|
|
235
|
+
):
|
|
236
|
+
try:
|
|
237
|
+
float(xray_filter_thickness_maximum)
|
|
238
|
+
except ValueError:
|
|
239
|
+
if "," in xray_filter_thickness_maximum:
|
|
240
|
+
xray_filter_thickness_maximum = xray_filter_thickness_maximum.split(",")
|
|
241
|
+
|
|
242
|
+
if xray_filter_material and isinstance(xray_filter_material, (MultiValue, list)):
|
|
243
|
+
_xray_filters_multiple(
|
|
244
|
+
xray_filter_material,
|
|
245
|
+
xray_filter_thickness_maximum,
|
|
246
|
+
xray_filter_thickness_minimum,
|
|
247
|
+
source,
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
# deal with known Siemens filter records
|
|
251
|
+
siemens_filters = ("CU_0.1_MM", "CU_0.2_MM", "CU_0.3_MM")
|
|
252
|
+
if xray_filter_type in siemens_filters:
|
|
253
|
+
if xray_filter_type == "CU_0.1_MM":
|
|
254
|
+
thickmax = 0.1
|
|
255
|
+
thickmin = 0.1
|
|
256
|
+
elif xray_filter_type == "CU_0.2_MM":
|
|
257
|
+
thickmax = 0.2
|
|
258
|
+
thickmin = 0.2
|
|
259
|
+
elif xray_filter_type == "CU_0.3_MM":
|
|
260
|
+
thickmax = 0.3
|
|
261
|
+
thickmin = 0.3
|
|
262
|
+
_xrayfilters("FLAT", "COPPER", thickmax, thickmin, source)
|
|
263
|
+
else:
|
|
264
|
+
_xrayfilters(
|
|
265
|
+
xray_filter_type,
|
|
266
|
+
xray_filter_material,
|
|
267
|
+
xray_filter_thickness_maximum,
|
|
268
|
+
xray_filter_thickness_minimum,
|
|
269
|
+
source,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _kvp(dataset, source):
|
|
274
|
+
kv = Kvp.objects.create(irradiation_event_xray_source_data=source)
|
|
275
|
+
kv.kvp = get_value_kw("KVP", dataset)
|
|
276
|
+
kv.save()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _exposure(dataset, source):
|
|
280
|
+
exp = Exposure.objects.create(irradiation_event_xray_source_data=source)
|
|
281
|
+
|
|
282
|
+
exp.exposure = get_value_kw("ExposureInuAs", dataset) # uAs
|
|
283
|
+
if not exp.exposure:
|
|
284
|
+
exposure = get_value_kw("Exposure", dataset)
|
|
285
|
+
if exposure:
|
|
286
|
+
exp.exposure = exposure * 1000
|
|
287
|
+
exp.save()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _xraygrid(gridcode, source):
|
|
291
|
+
grid = XrayGrid.objects.create(irradiation_event_xray_source_data=source)
|
|
292
|
+
if gridcode == "111646":
|
|
293
|
+
grid.xray_grid = get_or_create_cid("111646", "No grid")
|
|
294
|
+
elif gridcode == "111641":
|
|
295
|
+
grid.xray_grid = get_or_create_cid("111641", "Fixed grid")
|
|
296
|
+
elif gridcode == "111642":
|
|
297
|
+
grid.xray_grid = get_or_create_cid("111642", "Focused grid")
|
|
298
|
+
elif gridcode == "111643":
|
|
299
|
+
grid.xray_grid = get_or_create_cid("111643", "Reciprocating grid")
|
|
300
|
+
elif gridcode == "111644":
|
|
301
|
+
grid.xray_grid = get_or_create_cid("111644", "Parallel grid")
|
|
302
|
+
elif gridcode == "111645":
|
|
303
|
+
grid.xray_grid = get_or_create_cid("111645", "Crossed grid")
|
|
304
|
+
grid.save()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _irradiationeventxraydetectordata(dataset, event):
|
|
308
|
+
detector = IrradEventXRayDetectorData.objects.create(
|
|
309
|
+
irradiation_event_xray_data=event
|
|
310
|
+
)
|
|
311
|
+
detector.exposure_index = get_value_kw("ExposureIndex", dataset)
|
|
312
|
+
detector.relative_xray_exposure = get_value_kw("RelativeXRayExposure", dataset)
|
|
313
|
+
manufacturer = detector.irradiation_event_xray_data.projection_xray_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.all()[
|
|
314
|
+
0
|
|
315
|
+
].manufacturer.lower()
|
|
316
|
+
if "fuji" in manufacturer:
|
|
317
|
+
detector.relative_exposure_unit = "S ()"
|
|
318
|
+
elif "carestream" in manufacturer:
|
|
319
|
+
detector.relative_exposure_unit = "EI (Mbels)"
|
|
320
|
+
elif "kodak" in manufacturer:
|
|
321
|
+
detector.relative_exposure_unit = "EI (Mbels)"
|
|
322
|
+
elif "agfa" in manufacturer:
|
|
323
|
+
detector.relative_exposure_unit = "lgM (Bels)"
|
|
324
|
+
elif "konica" in manufacturer:
|
|
325
|
+
detector.relative_exposure_unit = "S ()"
|
|
326
|
+
elif "canon" in manufacturer:
|
|
327
|
+
detector.relative_exposure_unit = "REX ()"
|
|
328
|
+
elif "swissray" in manufacturer:
|
|
329
|
+
detector.relative_exposure_unit = "DI ()"
|
|
330
|
+
elif "philips" in manufacturer:
|
|
331
|
+
detector.relative_exposure_unit = "EI ()"
|
|
332
|
+
elif "siemens" in manufacturer:
|
|
333
|
+
detector.relative_exposure_unit = "EXI (μGy)"
|
|
334
|
+
detector.sensitivity = get_value_kw("Sensitivity", dataset)
|
|
335
|
+
detector.target_exposure_index = get_value_kw("TargetExposureIndex", dataset)
|
|
336
|
+
detector.deviation_index = get_value_kw("DeviationIndex", dataset)
|
|
337
|
+
detector.save()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _irradiationeventxraysourcedata(dataset, event):
|
|
341
|
+
# TODO: review model to convert to cid where appropriate, and add additional fields such as field height and width
|
|
342
|
+
source = IrradEventXRaySourceData.objects.create(irradiation_event_xray_data=event)
|
|
343
|
+
source.average_xray_tube_current = get_value_kw("XRayTubeCurrent", dataset)
|
|
344
|
+
if not source.average_xray_tube_current:
|
|
345
|
+
source.average_xray_tube_current = get_value_kw(
|
|
346
|
+
"AverageXRayTubeCurrent", dataset
|
|
347
|
+
)
|
|
348
|
+
source.exposure_time = get_value_kw("ExposureTime", dataset)
|
|
349
|
+
source.focal_spot_size = get_value_kw("FocalSpots", dataset)
|
|
350
|
+
collimated_field_area = get_value_kw("FieldOfViewDimensions", dataset)
|
|
351
|
+
if collimated_field_area:
|
|
352
|
+
source.collimated_field_area = (
|
|
353
|
+
float(collimated_field_area[0]) * float(collimated_field_area[1]) / 1000000
|
|
354
|
+
)
|
|
355
|
+
exp_ctrl_mode = get_value_kw("ExposureControlMode", dataset)
|
|
356
|
+
if exp_ctrl_mode:
|
|
357
|
+
source.exposure_control_mode = exp_ctrl_mode
|
|
358
|
+
xray_grid = get_value_kw("Grid", dataset)
|
|
359
|
+
if xray_grid:
|
|
360
|
+
if xray_grid == "NONE":
|
|
361
|
+
_xraygrid("111646", source)
|
|
362
|
+
else:
|
|
363
|
+
for gtype in xray_grid:
|
|
364
|
+
if (
|
|
365
|
+
"FI" in gtype
|
|
366
|
+
): # Fixed; abbreviated due to fitting two keywords in 16 characters
|
|
367
|
+
_xraygrid("111641", source)
|
|
368
|
+
elif "FO" in gtype: # Focused
|
|
369
|
+
_xraygrid("111642", source)
|
|
370
|
+
elif "RE" in gtype: # Reciprocating
|
|
371
|
+
_xraygrid("111643", source)
|
|
372
|
+
elif "PA" in gtype: # Parallel
|
|
373
|
+
_xraygrid("111644", source)
|
|
374
|
+
elif "CR" in gtype: # Crossed
|
|
375
|
+
_xraygrid("111645", source)
|
|
376
|
+
source.grid_absorbing_material = get_value_kw("GridAbsorbingMaterial", dataset)
|
|
377
|
+
source.grid_spacing_material = get_value_kw("GridSpacingMaterial", dataset)
|
|
378
|
+
source.grid_thickness = get_value_kw("GridThickness", dataset)
|
|
379
|
+
source.grid_pitch = get_value_kw("GridPitch", dataset)
|
|
380
|
+
source.grid_aspect_ratio = get_value_kw("GridAspectRatio", dataset)
|
|
381
|
+
source.grid_period = get_value_kw("GridPeriod", dataset)
|
|
382
|
+
source.grid_focal_distance = get_value_kw("GridFocalDistance", dataset)
|
|
383
|
+
source.save()
|
|
384
|
+
_xray_filters_prep(dataset, source)
|
|
385
|
+
_kvp(dataset, source)
|
|
386
|
+
_exposure(dataset, source)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _doserelateddistancemeasurements(dataset, mech):
|
|
390
|
+
dist = DoseRelatedDistanceMeasurements.objects.create(
|
|
391
|
+
irradiation_event_xray_mechanical_data=mech
|
|
392
|
+
)
|
|
393
|
+
manufacturer = dist.irradiation_event_xray_mechanical_data.irradiation_event_xray_data.projection_xray_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.all()[
|
|
394
|
+
0
|
|
395
|
+
].manufacturer
|
|
396
|
+
model_name = dist.irradiation_event_xray_mechanical_data.irradiation_event_xray_data.projection_xray_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.all()[
|
|
397
|
+
0
|
|
398
|
+
].manufacturer_model_name
|
|
399
|
+
dist.distance_source_to_detector = get_value_kw("DistanceSourceToDetector", dataset)
|
|
400
|
+
if (
|
|
401
|
+
dist.distance_source_to_detector
|
|
402
|
+
and manufacturer
|
|
403
|
+
and model_name
|
|
404
|
+
and "kodak" in manufacturer.lower()
|
|
405
|
+
and "dr 7500" in model_name.lower()
|
|
406
|
+
):
|
|
407
|
+
dist.distance_source_to_detector *= 100 # convert dm to mm
|
|
408
|
+
dist.distance_source_to_entrance_surface = get_value_kw(
|
|
409
|
+
"DistanceSourceToPatient", dataset
|
|
410
|
+
)
|
|
411
|
+
dist.distance_source_to_isocenter = get_value_kw(
|
|
412
|
+
"DistanceSourceToIsocenter", dataset
|
|
413
|
+
)
|
|
414
|
+
# DistanceSourceToReferencePoint isn't a DICOM tag. Same as DistanceSourceToPatient?
|
|
415
|
+
# dist.distance_source_to_reference_point = get_value_kw('DistanceSourceToReferencePoint',dataset)
|
|
416
|
+
# Table longitudinal and lateral positions not DICOM elements.
|
|
417
|
+
# dist.table_longitudinal_position = get_value_kw('TableLongitudinalPosition',dataset)
|
|
418
|
+
# dist.table_lateral_position = get_value_kw('TableLateralPosition',dataset)
|
|
419
|
+
dist.table_height_position = get_value_kw("TableHeight", dataset)
|
|
420
|
+
# DistanceSourceToTablePlane not a DICOM tag.
|
|
421
|
+
# dist.distance_source_to_table_plane = get_value_kw('DistanceSourceToTablePlane',dataset)
|
|
422
|
+
dist.radiological_thickness = get_value_num(0x00451049, dataset)
|
|
423
|
+
dist.save()
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _irradiationeventxraymechanicaldata(dataset, event):
|
|
427
|
+
mech = IrradEventXRayMechanicalData.objects.create(
|
|
428
|
+
irradiation_event_xray_data=event
|
|
429
|
+
)
|
|
430
|
+
mech.magnification_factor = get_value_kw(
|
|
431
|
+
"EstimatedRadiographicMagnificationFactor", dataset
|
|
432
|
+
)
|
|
433
|
+
mech.primary_angle = get_value_kw("PositionerPrimaryAngle", dataset)
|
|
434
|
+
mech.secondary_angle = get_value_kw("PositionerSecondaryAngle", dataset)
|
|
435
|
+
mech.column_angulation = get_value_kw("ColumnAngulation", dataset)
|
|
436
|
+
mech.table_head_tilt_angle = get_value_kw("TableHeadTiltAngle", dataset)
|
|
437
|
+
mech.table_horizontal_rotation_angle = get_value_kw(
|
|
438
|
+
"TableHorizontalRotationAngle", dataset
|
|
439
|
+
)
|
|
440
|
+
mech.table_cradle_tilt_angle = get_value_kw("TableCradleTiltAngle", dataset)
|
|
441
|
+
mech.save()
|
|
442
|
+
_doserelateddistancemeasurements(dataset, mech)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _irradiationeventxraydata(dataset, proj): # TID 10003
|
|
446
|
+
# TODO: review model to convert to cid where appropriate, and add additional fields
|
|
447
|
+
|
|
448
|
+
event = IrradEventXRayData.objects.create(projection_xray_radiation_dose=proj)
|
|
449
|
+
event.acquisition_plane = get_or_create_cid("113622", "Single Plane")
|
|
450
|
+
event.irradiation_event_uid = get_value_kw("SOPInstanceUID", dataset)
|
|
451
|
+
event_time = get_value_kw("AcquisitionTime", dataset)
|
|
452
|
+
if not event_time:
|
|
453
|
+
event_time = get_value_kw("ContentTime", dataset)
|
|
454
|
+
if not event_time:
|
|
455
|
+
event_time = get_value_kw("StudyTime", dataset)
|
|
456
|
+
event_date = get_value_kw("AcquisitionDate", dataset)
|
|
457
|
+
if not event_date:
|
|
458
|
+
event_date = get_value_kw("ContentDate", dataset)
|
|
459
|
+
if not event_date:
|
|
460
|
+
event_date = get_value_kw("StudyDate", dataset)
|
|
461
|
+
event.date_time_started = make_date_time("{0}{1}".format(event_date, event_time))
|
|
462
|
+
event.irradiation_event_type = get_or_create_cid("113611", "Stationary Acquisition")
|
|
463
|
+
event.acquisition_protocol = get_value_kw("ProtocolName", dataset)
|
|
464
|
+
if not event.acquisition_protocol:
|
|
465
|
+
manufacturer = get_value_kw("Manufacturer", dataset)
|
|
466
|
+
software_versions = get_value_kw("SoftwareVersions", dataset)
|
|
467
|
+
if manufacturer == "TOSHIBA_MEC" and software_versions == "TM_TFD_1.0":
|
|
468
|
+
event.acquisition_protocol = get_value_kw("ImageComments", dataset)
|
|
469
|
+
if not event.acquisition_protocol:
|
|
470
|
+
event.acquisition_protocol = get_value_kw("SeriesDescription", dataset)
|
|
471
|
+
if not event.acquisition_protocol:
|
|
472
|
+
event.acquisition_protocol = get_seq_code_meaning(
|
|
473
|
+
"PerformedProtocolCodeSequence", dataset
|
|
474
|
+
)
|
|
475
|
+
series_description = get_value_kw("SeriesDescription", dataset)
|
|
476
|
+
if series_description:
|
|
477
|
+
event.comment = series_description
|
|
478
|
+
event.anatomical_structure = get_or_create_cid(
|
|
479
|
+
get_seq_code_value("AnatomicRegionSequence", dataset),
|
|
480
|
+
get_seq_code_meaning("AnatomicRegionSequence", dataset),
|
|
481
|
+
)
|
|
482
|
+
laterality = get_value_kw("ImageLaterality", dataset)
|
|
483
|
+
if laterality:
|
|
484
|
+
if laterality.strip() == "R":
|
|
485
|
+
event.laterality = get_or_create_cid("G-A100", "Right")
|
|
486
|
+
if laterality.strip() == "L":
|
|
487
|
+
event.laterality = get_or_create_cid("G-A101", "Left")
|
|
488
|
+
|
|
489
|
+
event.image_view = get_or_create_cid(
|
|
490
|
+
get_seq_code_value("ViewCodeSequence", dataset),
|
|
491
|
+
get_seq_code_meaning("ViewCodeSequence", dataset),
|
|
492
|
+
)
|
|
493
|
+
if not event.image_view:
|
|
494
|
+
projection = get_value_kw("ViewPosition", dataset)
|
|
495
|
+
if projection == "AP":
|
|
496
|
+
event.image_view = get_or_create_cid("R-10206", "antero-posterior")
|
|
497
|
+
elif projection == "PA":
|
|
498
|
+
event.image_view = get_or_create_cid("R-10214", "postero-anterior")
|
|
499
|
+
elif projection == "LL":
|
|
500
|
+
event.image_view = get_or_create_cid("R-10236", "left lateral")
|
|
501
|
+
elif projection == "RL":
|
|
502
|
+
event.image_view = get_or_create_cid("R-10232", "right lateral")
|
|
503
|
+
# http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,5101) lists four other views: RLD (Right Lateral Decubitus),
|
|
504
|
+
# LLD (Left Lateral Decubitus), RLO (Right Lateral Oblique) and LLO (Left Lateral Oblique). There isn't an exact
|
|
505
|
+
# match for these views in the CID 4010 DX View (http://dicom.nema.org/medical/dicom/current/output/chtml/part16/sect_CID_4010.html)
|
|
506
|
+
|
|
507
|
+
# image view modifier?
|
|
508
|
+
if event.anatomical_structure:
|
|
509
|
+
event.target_region = event.anatomical_structure
|
|
510
|
+
event.entrance_exposure_at_rp = get_value_kw("EntranceDoseInmGy", dataset)
|
|
511
|
+
# reference point definition?
|
|
512
|
+
pc_fibroglandular = get_value_kw("CommentsOnRadiationDose", dataset)
|
|
513
|
+
if pc_fibroglandular:
|
|
514
|
+
if "%" in pc_fibroglandular:
|
|
515
|
+
event.percent_fibroglandular_tissue = pc_fibroglandular.replace(
|
|
516
|
+
"%", ""
|
|
517
|
+
).strip()
|
|
518
|
+
exposure_control = get_value_kw("ExposureControlModeDescription", dataset)
|
|
519
|
+
|
|
520
|
+
if event.comment and exposure_control:
|
|
521
|
+
event.comment = event.comment + ", " + exposure_control
|
|
522
|
+
|
|
523
|
+
dap = get_value_kw("ImageAndFluoroscopyAreaDoseProduct", dataset)
|
|
524
|
+
if dap:
|
|
525
|
+
event.dose_area_product = (
|
|
526
|
+
dap / 100000
|
|
527
|
+
) # Value of DICOM tag (0018,115e) in dGy.cm2, converted to Gy.m2
|
|
528
|
+
event.save()
|
|
529
|
+
|
|
530
|
+
_irradiationeventxraydetectordata(dataset, event)
|
|
531
|
+
_irradiationeventxraysourcedata(dataset, event)
|
|
532
|
+
_irradiationeventxraymechanicaldata(dataset, event)
|
|
533
|
+
_accumulatedxraydose_update(event)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _accumulatedxraydose(proj):
|
|
537
|
+
accum = AccumXRayDose.objects.create(projection_xray_radiation_dose=proj)
|
|
538
|
+
accum.acquisition_plane = get_or_create_cid("113622", "Single Plane")
|
|
539
|
+
accum.save()
|
|
540
|
+
accumint = AccumIntegratedProjRadiogDose.objects.create(accumulated_xray_dose=accum)
|
|
541
|
+
accumint.dose_area_product_total = 0.0
|
|
542
|
+
accumint.total_number_of_radiographic_frames = 0
|
|
543
|
+
accumint.save()
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _accumulatedxraydose_update(event):
|
|
547
|
+
accumint = (
|
|
548
|
+
event.projection_xray_radiation_dose.accumxraydose_set.get().accumintegratedprojradiogdose_set.get()
|
|
549
|
+
)
|
|
550
|
+
if accumint.total_number_of_radiographic_frames is not None:
|
|
551
|
+
accumint.total_number_of_radiographic_frames = (
|
|
552
|
+
accumint.total_number_of_radiographic_frames + 1
|
|
553
|
+
)
|
|
554
|
+
else:
|
|
555
|
+
accumint.total_number_of_radiographic_frames = 1
|
|
556
|
+
|
|
557
|
+
if event.dose_area_product:
|
|
558
|
+
accumint.dose_area_product_total += Decimal(event.dose_area_product)
|
|
559
|
+
accumint.save()
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _projectionxrayradiationdose(dataset, g):
|
|
563
|
+
proj = ProjectionXRayRadiationDose.objects.create(general_study_module_attributes=g)
|
|
564
|
+
proj.procedure_reported = get_or_create_cid("113704", "Projection X-Ray")
|
|
565
|
+
proj.has_intent = get_or_create_cid("R-408C3", "Diagnostic Intent")
|
|
566
|
+
proj.scope_of_accumulation = get_or_create_cid("113014", "Study")
|
|
567
|
+
proj.source_of_dose_information = get_or_create_cid(
|
|
568
|
+
"113866", "Copied From Image Attributes"
|
|
569
|
+
)
|
|
570
|
+
proj.xray_detector_data_available = get_or_create_cid("R-00339", "No")
|
|
571
|
+
proj.xray_source_data_available = get_or_create_cid("R-0038D", "Yes")
|
|
572
|
+
proj.xray_mechanical_data_available = get_or_create_cid("R-0038D", "Yes")
|
|
573
|
+
proj.save()
|
|
574
|
+
_accumulatedxraydose(proj)
|
|
575
|
+
_irradiationeventxraydata(dataset, proj)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _generalequipmentmoduleattributes(dataset, study):
|
|
579
|
+
equip = GeneralEquipmentModuleAttr.objects.create(
|
|
580
|
+
general_study_module_attributes=study
|
|
581
|
+
)
|
|
582
|
+
equip.manufacturer = get_value_kw("Manufacturer", dataset)
|
|
583
|
+
equip.institution_name = get_value_kw("InstitutionName", dataset)
|
|
584
|
+
equip.institution_address = get_value_kw("InstitutionAddress", dataset)
|
|
585
|
+
equip.station_name = get_value_kw("StationName", dataset)
|
|
586
|
+
equip.institutional_department_name = get_value_kw(
|
|
587
|
+
"InstitutionalDepartmentName", dataset
|
|
588
|
+
)
|
|
589
|
+
equip.manufacturer_model_name = get_value_kw("ManufacturerModelName", dataset)
|
|
590
|
+
equip.device_serial_number = get_value_kw("DeviceSerialNumber", dataset)
|
|
591
|
+
equip.software_versions = get_value_kw("SoftwareVersions", dataset)
|
|
592
|
+
equip.gantry_id = get_value_kw("GantryID", dataset)
|
|
593
|
+
equip.spatial_resolution = get_value_kw("SpatialResolution", dataset)
|
|
594
|
+
equip.date_of_last_calibration = get_date("DateOfLastCalibration", dataset)
|
|
595
|
+
equip.time_of_last_calibration = get_time("TimeOfLastCalibration", dataset)
|
|
596
|
+
|
|
597
|
+
equip_display_name, created = UniqueEquipmentNames.objects.get_or_create(
|
|
598
|
+
manufacturer=equip.manufacturer,
|
|
599
|
+
manufacturer_hash=hash_id(equip.manufacturer),
|
|
600
|
+
institution_name=equip.institution_name,
|
|
601
|
+
institution_name_hash=hash_id(equip.institution_name),
|
|
602
|
+
station_name=equip.station_name,
|
|
603
|
+
station_name_hash=hash_id(equip.station_name),
|
|
604
|
+
institutional_department_name=equip.institutional_department_name,
|
|
605
|
+
institutional_department_name_hash=hash_id(equip.institutional_department_name),
|
|
606
|
+
manufacturer_model_name=equip.manufacturer_model_name,
|
|
607
|
+
manufacturer_model_name_hash=hash_id(equip.manufacturer_model_name),
|
|
608
|
+
device_serial_number=equip.device_serial_number,
|
|
609
|
+
device_serial_number_hash=hash_id(equip.device_serial_number),
|
|
610
|
+
software_versions=equip.software_versions,
|
|
611
|
+
software_versions_hash=hash_id(equip.software_versions),
|
|
612
|
+
gantry_id=equip.gantry_id,
|
|
613
|
+
gantry_id_hash=hash_id(equip.gantry_id),
|
|
614
|
+
hash_generated=True,
|
|
615
|
+
device_observer_uid=None,
|
|
616
|
+
device_observer_uid_hash=None,
|
|
617
|
+
)
|
|
618
|
+
if created:
|
|
619
|
+
if equip.institution_name and equip.station_name:
|
|
620
|
+
equip_display_name.display_name = (
|
|
621
|
+
equip.institution_name + " " + equip.station_name
|
|
622
|
+
)
|
|
623
|
+
elif equip.institution_name:
|
|
624
|
+
equip_display_name.display_name = equip.institution_name
|
|
625
|
+
elif equip.station_name:
|
|
626
|
+
equip_display_name.display_name = equip.station_name
|
|
627
|
+
else:
|
|
628
|
+
equip_display_name.display_name = "Blank"
|
|
629
|
+
equip_display_name.save()
|
|
630
|
+
|
|
631
|
+
equip.unique_equipment_name = UniqueEquipmentNames(pk=equip_display_name.pk)
|
|
632
|
+
|
|
633
|
+
equip.save()
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _patientstudymoduleattributes(dataset, g): # C.7.2.2
|
|
637
|
+
patientatt = PatientStudyModuleAttr.objects.create(
|
|
638
|
+
general_study_module_attributes=g
|
|
639
|
+
)
|
|
640
|
+
patientatt.patient_age = get_value_kw("PatientAge", dataset)
|
|
641
|
+
patientatt.patient_weight = get_value_kw("PatientWeight", dataset)
|
|
642
|
+
patientatt.patient_size = get_value_kw("PatientSize", dataset)
|
|
643
|
+
try:
|
|
644
|
+
Decimal(patientatt.patient_size)
|
|
645
|
+
except DecimalException:
|
|
646
|
+
patientatt.patient_size = None
|
|
647
|
+
except TypeError:
|
|
648
|
+
pass
|
|
649
|
+
patientatt.save()
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _generalstudymoduleattributes(dataset, g):
|
|
653
|
+
g.study_date = get_date("StudyDate", dataset)
|
|
654
|
+
g.study_time = get_time("StudyTime", dataset)
|
|
655
|
+
g.study_workload_chart_time = datetime.combine(
|
|
656
|
+
datetime.date(datetime(1900, 1, 1)), datetime.time(g.study_time)
|
|
657
|
+
)
|
|
658
|
+
g.referring_physician_name = list_to_string(
|
|
659
|
+
get_value_kw("ReferringPhysicianName", dataset)
|
|
660
|
+
)
|
|
661
|
+
g.study_id = get_value_kw("StudyID", dataset)
|
|
662
|
+
accession_number = get_value_kw("AccessionNumber", dataset)
|
|
663
|
+
patient_id_settings = PatientIDSettings.objects.get()
|
|
664
|
+
if accession_number and patient_id_settings.accession_hashed:
|
|
665
|
+
accession_number = hash_id(accession_number)
|
|
666
|
+
g.accession_hashed = True
|
|
667
|
+
g.accession_number = accession_number
|
|
668
|
+
g.study_description = get_value_kw("StudyDescription", dataset)
|
|
669
|
+
if not g.study_description:
|
|
670
|
+
g.study_description = get_value_kw("SeriesDescription", dataset)
|
|
671
|
+
if not g.study_description:
|
|
672
|
+
g.study_description = get_seq_code_meaning("ProcedureCodeSequence", dataset)
|
|
673
|
+
g.modality_type = get_value_kw("Modality", dataset)
|
|
674
|
+
g.physician_of_record = list_to_string(get_value_kw("PhysiciansOfRecord", dataset))
|
|
675
|
+
g.name_of_physician_reading_study = list_to_string(
|
|
676
|
+
get_value_kw("NameOfPhysiciansReadingStudy", dataset)
|
|
677
|
+
)
|
|
678
|
+
g.performing_physician_name = list_to_string(
|
|
679
|
+
get_value_kw("PerformingPhysicianName", dataset)
|
|
680
|
+
)
|
|
681
|
+
g.operator_name = list_to_string(get_value_kw("OperatorsName", dataset))
|
|
682
|
+
# Being used to summarise protocol for study:
|
|
683
|
+
g.procedure_code_meaning = get_seq_code_meaning("ProcedureCodeSequence", dataset)
|
|
684
|
+
if not g.procedure_code_meaning:
|
|
685
|
+
g.procedure_code_meaning = get_value_kw("ProtocolName", dataset)
|
|
686
|
+
if not g.procedure_code_meaning:
|
|
687
|
+
g.procedure_code_meaning = get_value_kw("StudyDescription", dataset)
|
|
688
|
+
if not g.procedure_code_meaning:
|
|
689
|
+
g.procedure_code_meaning = get_value_kw("SeriesDescription", dataset)
|
|
690
|
+
g.requested_procedure_code_value = get_seq_code_value(
|
|
691
|
+
"RequestedProcedureCodeSequence", dataset
|
|
692
|
+
)
|
|
693
|
+
g.requested_procedure_code_meaning = get_seq_code_meaning(
|
|
694
|
+
"RequestedProcedureCodeSequence", dataset
|
|
695
|
+
)
|
|
696
|
+
if not g.requested_procedure_code_value:
|
|
697
|
+
g.requested_procedure_code_value = get_seq_code_value(
|
|
698
|
+
"RequestAttributesSequence", dataset
|
|
699
|
+
)
|
|
700
|
+
if not g.requested_procedure_code_value:
|
|
701
|
+
g.requested_procedure_code_value = get_seq_code_value(
|
|
702
|
+
"ProcedureCodeSequence", dataset
|
|
703
|
+
)
|
|
704
|
+
if not g.requested_procedure_code_value:
|
|
705
|
+
g.requested_procedure_code_value = get_seq_code_value(
|
|
706
|
+
"PerformedProtocolCodeSequence", dataset
|
|
707
|
+
)
|
|
708
|
+
if not g.requested_procedure_code_meaning:
|
|
709
|
+
g.requested_procedure_code_meaning = get_seq_code_meaning(
|
|
710
|
+
"RequestAttributesSequence", dataset
|
|
711
|
+
)
|
|
712
|
+
if not g.requested_procedure_code_meaning:
|
|
713
|
+
g.requested_procedure_code_meaning = get_seq_code_meaning(
|
|
714
|
+
"ProcedureCodeSequence", dataset
|
|
715
|
+
)
|
|
716
|
+
if not g.requested_procedure_code_meaning:
|
|
717
|
+
g.requested_procedure_code_meaning = get_value_num(0x00321060, dataset)
|
|
718
|
+
if not g.requested_procedure_code_meaning:
|
|
719
|
+
g.requested_procedure_code_meaning = get_seq_code_meaning(
|
|
720
|
+
"PerformedProtocolCodeSequence", dataset
|
|
721
|
+
)
|
|
722
|
+
if not g.requested_procedure_code_meaning:
|
|
723
|
+
manufacturer = get_value_kw("Manufacturer", dataset)
|
|
724
|
+
model = get_value_kw("ManufacturerModelName", dataset)
|
|
725
|
+
if (
|
|
726
|
+
manufacturer
|
|
727
|
+
and model
|
|
728
|
+
and "canon" in manufacturer.lower()
|
|
729
|
+
and "cxdi" in model.lower()
|
|
730
|
+
):
|
|
731
|
+
g.requested_procedure_code_meaning = get_value_num(0x00081030, dataset)
|
|
732
|
+
if (
|
|
733
|
+
manufacturer
|
|
734
|
+
and model
|
|
735
|
+
and "carestream health" in manufacturer.lower()
|
|
736
|
+
and "drx-revolution" in model.lower()
|
|
737
|
+
):
|
|
738
|
+
g.requested_procedure_code_meaning = get_value_num(0x00081030, dataset)
|
|
739
|
+
g.save()
|
|
740
|
+
|
|
741
|
+
_generalequipmentmoduleattributes(dataset, g)
|
|
742
|
+
_projectionxrayradiationdose(dataset, g)
|
|
743
|
+
_patientstudymoduleattributes(dataset, g)
|
|
744
|
+
patient_module_attributes(dataset, g)
|
|
745
|
+
populate_dx_rf_summary(g)
|
|
746
|
+
g.number_of_events = (
|
|
747
|
+
g.projectionxrayradiationdose_set.get().irradeventxraydata_set.count()
|
|
748
|
+
)
|
|
749
|
+
g.save()
|
|
750
|
+
|
|
751
|
+
# Add standard names
|
|
752
|
+
add_standard_names(g)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
# The routine will accept three types of image:
|
|
756
|
+
# CR image storage (SOP UID = '1.2.840.10008.5.1.4.1.1.1')
|
|
757
|
+
# Digital x-ray image storage - for presentation (SOP UID = '1.2.840.10008.5.1.4.1.1.1.1')
|
|
758
|
+
# Digital x-ray image storage - for processing (SOP UID = '1.2.840.10008.5.1.4.1.1.1.1.1')
|
|
759
|
+
# These SOP UIDs were taken from http://www.dicomlibrary.com/dicom/sop/
|
|
760
|
+
def _test_if_dx(dataset):
|
|
761
|
+
"""Test if dicom object passed is a DX or CR radiographic file by looking at SOP Class UID"""
|
|
762
|
+
if (
|
|
763
|
+
dataset.SOPClassUID != "1.2.840.10008.5.1.4.1.1.1"
|
|
764
|
+
and dataset.SOPClassUID != "1.2.840.10008.5.1.4.1.1.1.1"
|
|
765
|
+
and dataset.SOPClassUID != "1.2.840.10008.5.1.4.1.1.1.1.1"
|
|
766
|
+
):
|
|
767
|
+
return 0
|
|
768
|
+
return 1
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _dx2db(dataset):
|
|
772
|
+
study_uid = get_value_kw("StudyInstanceUID", dataset)
|
|
773
|
+
if not study_uid:
|
|
774
|
+
error = "In dx import: No UID returned"
|
|
775
|
+
logger.error(error)
|
|
776
|
+
record_task_error_exit(error)
|
|
777
|
+
return
|
|
778
|
+
record_task_info(f"UID: {study_uid.replace('.', '. ')}")
|
|
779
|
+
record_task_related_query(study_uid)
|
|
780
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
781
|
+
|
|
782
|
+
if study_in_db:
|
|
783
|
+
sleep(
|
|
784
|
+
2.0
|
|
785
|
+
) # Give initial event a chance to get to save on _projectionxrayradiationdose
|
|
786
|
+
this_study = get_study_check_dup(dataset, modality="DX")
|
|
787
|
+
if this_study:
|
|
788
|
+
_irradiationeventxraydata(
|
|
789
|
+
dataset, this_study.projectionxrayradiationdose_set.get()
|
|
790
|
+
)
|
|
791
|
+
populate_dx_rf_summary(this_study)
|
|
792
|
+
this_study.number_of_events = (
|
|
793
|
+
this_study.projectionxrayradiationdose_set.get().irradeventxraydata_set.count()
|
|
794
|
+
)
|
|
795
|
+
this_study.save()
|
|
796
|
+
|
|
797
|
+
# Update any matching standard names
|
|
798
|
+
add_standard_names(this_study)
|
|
799
|
+
|
|
800
|
+
else:
|
|
801
|
+
error = f"Study {study_uid.replace('.', '. ')} already in DB"
|
|
802
|
+
logger.error(error)
|
|
803
|
+
record_task_error_exit(error)
|
|
804
|
+
return
|
|
805
|
+
|
|
806
|
+
if not study_in_db:
|
|
807
|
+
# study doesn't exist, start from scratch
|
|
808
|
+
g = GeneralStudyModuleAttr.objects.create()
|
|
809
|
+
g.study_instance_uid = get_value_kw("StudyInstanceUID", dataset)
|
|
810
|
+
g.save()
|
|
811
|
+
logger.debug(
|
|
812
|
+
"Started importing DX with Study Instance UID of {0}".format(
|
|
813
|
+
g.study_instance_uid
|
|
814
|
+
)
|
|
815
|
+
)
|
|
816
|
+
event_uid = get_value_kw("SOPInstanceUID", dataset)
|
|
817
|
+
check_uid.record_sop_instance_uid(g, event_uid)
|
|
818
|
+
# check study again
|
|
819
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
820
|
+
if study_in_db == 1:
|
|
821
|
+
_generalstudymoduleattributes(dataset, g)
|
|
822
|
+
elif not study_in_db:
|
|
823
|
+
error = "Something went wrong, GeneralStudyModuleAttr wasn't created"
|
|
824
|
+
record_task_error_exit(error)
|
|
825
|
+
logger.error(error)
|
|
826
|
+
return
|
|
827
|
+
elif study_in_db > 1:
|
|
828
|
+
sleep(random()) # nosec - not being used for cryptography
|
|
829
|
+
# Check if other instance(s) has deleted the study yet
|
|
830
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
831
|
+
if study_in_db == 1:
|
|
832
|
+
_generalstudymoduleattributes(dataset, g)
|
|
833
|
+
elif study_in_db > 1:
|
|
834
|
+
g.delete()
|
|
835
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
836
|
+
if not study_in_db:
|
|
837
|
+
# both must have been deleted simultaneously!
|
|
838
|
+
sleep(random()) # nosec - not being used for cryptography
|
|
839
|
+
# Check if other instance has created the study again yet
|
|
840
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
841
|
+
if study_in_db == 1:
|
|
842
|
+
sleep(
|
|
843
|
+
2.0
|
|
844
|
+
) # Give initial event a chance to get to save on _projectionxrayradiationdose
|
|
845
|
+
this_study = get_study_check_dup(dataset, modality="DX")
|
|
846
|
+
if this_study:
|
|
847
|
+
_irradiationeventxraydata(
|
|
848
|
+
dataset,
|
|
849
|
+
this_study.projectionxrayradiationdose_set.get(),
|
|
850
|
+
)
|
|
851
|
+
while not study_in_db:
|
|
852
|
+
g = GeneralStudyModuleAttr.objects.create()
|
|
853
|
+
g.study_instance_uid = get_value_kw("StudyInstanceUID", dataset)
|
|
854
|
+
g.save()
|
|
855
|
+
check_uid.record_sop_instance_uid(g, event_uid)
|
|
856
|
+
# check again
|
|
857
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
858
|
+
if study_in_db == 1:
|
|
859
|
+
_generalstudymoduleattributes(dataset, g)
|
|
860
|
+
elif study_in_db > 1:
|
|
861
|
+
g.delete()
|
|
862
|
+
sleep(random()) # nosec - not being used for cryptography
|
|
863
|
+
study_in_db = check_uid.check_uid(study_uid)
|
|
864
|
+
if study_in_db == 1:
|
|
865
|
+
sleep(
|
|
866
|
+
2.0
|
|
867
|
+
) # Give initial event a chance to get to save on _projectionxrayradiationdose
|
|
868
|
+
this_study = get_study_check_dup(dataset, modality="DX")
|
|
869
|
+
if this_study:
|
|
870
|
+
_irradiationeventxraydata(
|
|
871
|
+
dataset,
|
|
872
|
+
this_study.projectionxrayradiationdose_set.get(),
|
|
873
|
+
)
|
|
874
|
+
elif study_in_db == 1:
|
|
875
|
+
sleep(
|
|
876
|
+
2.0
|
|
877
|
+
) # Give initial event a chance to get to save on _projectionxrayradiationdose
|
|
878
|
+
this_study = get_study_check_dup(dataset, modality="DX")
|
|
879
|
+
if this_study:
|
|
880
|
+
_irradiationeventxraydata(
|
|
881
|
+
dataset,
|
|
882
|
+
this_study.projectionxrayradiationdose_set.get(),
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _fix_kodak_filters(dataset):
|
|
887
|
+
"""
|
|
888
|
+
Replace floats with commas in with multivalue floats: as found in older Carestream/Kodak units such as the DR7500
|
|
889
|
+
:param dataset: DICOM dataset
|
|
890
|
+
:return: Repaired DICOM dataset
|
|
891
|
+
"""
|
|
892
|
+
|
|
893
|
+
try: # Black magic pydicom method suggested by Darcy Mason: https://groups.google.com/forum/?hl=en-GB#!topic/pydicom/x_WsC2gCLck
|
|
894
|
+
xray_filter_thickness_minimum = get_value_kw("FilterThicknessMinimum", dataset)
|
|
895
|
+
except (
|
|
896
|
+
ValueError
|
|
897
|
+
): # Assumes ValueError will be a comma separated pair of numbers, as per Kodak.
|
|
898
|
+
thick = dict.__getitem__(
|
|
899
|
+
dataset, 0x187052
|
|
900
|
+
) # pydicom black magic as suggested by
|
|
901
|
+
thickval = thick.__getattribute__("value")
|
|
902
|
+
if "," in thickval:
|
|
903
|
+
thickval = thickval.replace(",", "\\")
|
|
904
|
+
thick2 = thick._replace(value=thickval)
|
|
905
|
+
dict.__setitem__(dataset, 0x187052, thick2)
|
|
906
|
+
|
|
907
|
+
try:
|
|
908
|
+
xray_filter_thickness_maximum = get_value_kw("FilterThicknessMaximum", dataset)
|
|
909
|
+
except (
|
|
910
|
+
ValueError
|
|
911
|
+
): # Assumes ValueError will be a comma separated pair of numbers, as per Kodak.
|
|
912
|
+
thick = dict.__getitem__(
|
|
913
|
+
dataset, 0x187054
|
|
914
|
+
) # pydicom black magic as suggested by
|
|
915
|
+
thickval = thick.__getattribute__("value")
|
|
916
|
+
if "," in thickval:
|
|
917
|
+
thickval = thickval.replace(",", "\\")
|
|
918
|
+
thick2 = thick._replace(value=thickval)
|
|
919
|
+
dict.__setitem__(dataset, 0x187054, thick2)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def _remove_spaces_decimal(value):
|
|
923
|
+
"""Remove padding and convert to decimal"""
|
|
924
|
+
while value and value.endswith((" ", "\x00")):
|
|
925
|
+
value = value[:-1]
|
|
926
|
+
return Decimal(value)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _fix_exposure_values(dataset):
|
|
930
|
+
"""
|
|
931
|
+
Replace decimal values in ExposureTime, XRayTubeCurrent and Exposure with integer values; move original values to
|
|
932
|
+
appropriate fields
|
|
933
|
+
:param dataset: DICOM dataset
|
|
934
|
+
:return: Repaired DICOM dataset
|
|
935
|
+
"""
|
|
936
|
+
try:
|
|
937
|
+
dataset.ExposureTime
|
|
938
|
+
except TypeError:
|
|
939
|
+
exposure_time = dataset.get_item("ExposureTime").value.decode()
|
|
940
|
+
exposure_time = _remove_spaces_decimal(exposure_time)
|
|
941
|
+
del dataset.ExposureTime
|
|
942
|
+
dataset.ExposureTime = round(exposure_time, 0)
|
|
943
|
+
if "ExposureTimeInuS" not in dataset:
|
|
944
|
+
dataset.ExposureTimeInuS = round(exposure_time * 1000, 0)
|
|
945
|
+
logger.warning(
|
|
946
|
+
f"ExposureTime (VR=IS) contained illegal value {exposure_time}. Now set "
|
|
947
|
+
f"to {dataset.ExposureTime}. ExposureTimeInuS now set to "
|
|
948
|
+
f"{dataset.ExposureTimeInuS}."
|
|
949
|
+
)
|
|
950
|
+
try:
|
|
951
|
+
dataset.XRayTubeCurrent
|
|
952
|
+
except TypeError:
|
|
953
|
+
xray_tube_current = dataset.get_item("XRayTubeCurrent").value.decode()
|
|
954
|
+
xray_tube_current = _remove_spaces_decimal(xray_tube_current)
|
|
955
|
+
del dataset.XRayTubeCurrent
|
|
956
|
+
dataset.XRayTubeCurrent = round(xray_tube_current, 0)
|
|
957
|
+
if "XRayTubeCurrentInuA" not in dataset:
|
|
958
|
+
dataset.XRayTubeCurrentInuA = round(xray_tube_current * 1000, 0)
|
|
959
|
+
logger.warning(
|
|
960
|
+
f"ExposureTime (VR=IS) contained illegal value {xray_tube_current}. Now set "
|
|
961
|
+
f"to {dataset.XRayTubeCurrent}. ExposureTimeInuS now set to "
|
|
962
|
+
f"{dataset.XRayTubeCurrentInuA}."
|
|
963
|
+
)
|
|
964
|
+
try:
|
|
965
|
+
dataset.Exposure
|
|
966
|
+
except TypeError:
|
|
967
|
+
exposure = dataset.get_item("Exposure").value.decode()
|
|
968
|
+
exposure = _remove_spaces_decimal(exposure)
|
|
969
|
+
del dataset.Exposure
|
|
970
|
+
dataset.Exposure = round(exposure, 0)
|
|
971
|
+
if "ExposureInuAs" not in dataset:
|
|
972
|
+
dataset.ExposureInuAs = round(exposure * 1000, 0)
|
|
973
|
+
logger.warning(
|
|
974
|
+
f"Exposure (VR=IS) contained illegal value {exposure}. Now set "
|
|
975
|
+
f"to {dataset.Exposure}. ExposureTimeInuS now set to "
|
|
976
|
+
f"{dataset.ExposureInuAs}."
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def dx(dig_file):
|
|
981
|
+
"""Extract radiation dose structured report related data from DX radiographic images
|
|
982
|
+
|
|
983
|
+
:param filename: relative or absolute path to DICOM DX radiographic image file.
|
|
984
|
+
:type filename: str.
|
|
985
|
+
|
|
986
|
+
"""
|
|
987
|
+
|
|
988
|
+
try:
|
|
989
|
+
del_settings = DicomDeleteSettings.objects.get()
|
|
990
|
+
del_dx_im = del_settings.del_dx_im
|
|
991
|
+
except ObjectDoesNotExist:
|
|
992
|
+
del_dx_im = False
|
|
993
|
+
|
|
994
|
+
# Set convert_wrong_length_to_UN = True to prevent the wrong length causing an error.
|
|
995
|
+
config.convert_wrong_length_to_UN = True
|
|
996
|
+
|
|
997
|
+
logger.debug("About to read DX")
|
|
998
|
+
|
|
999
|
+
try:
|
|
1000
|
+
dataset = pydicom.dcmread(dig_file)
|
|
1001
|
+
except FileNotFoundError:
|
|
1002
|
+
logger.warning(
|
|
1003
|
+
f"dx.py not attempting to extract from {dig_file}, the file does not exist"
|
|
1004
|
+
)
|
|
1005
|
+
record_task_error_exit(
|
|
1006
|
+
f"Not attempting to extract from {dig_file}, the file does not exist"
|
|
1007
|
+
)
|
|
1008
|
+
return 1
|
|
1009
|
+
|
|
1010
|
+
try:
|
|
1011
|
+
dataset.decode()
|
|
1012
|
+
except ValueError as err:
|
|
1013
|
+
if "could not convert string to float" in str(err):
|
|
1014
|
+
_fix_kodak_filters(dataset)
|
|
1015
|
+
dataset.decode()
|
|
1016
|
+
except TypeError as err:
|
|
1017
|
+
if "Could not convert value to integer without loss" in str(err):
|
|
1018
|
+
_fix_exposure_values(dataset)
|
|
1019
|
+
dataset.decode()
|
|
1020
|
+
isdx = _test_if_dx(dataset)
|
|
1021
|
+
if not isdx:
|
|
1022
|
+
error = "{0} is not a DICOM DX radiographic image".format(dig_file)
|
|
1023
|
+
logger.error(error)
|
|
1024
|
+
record_task_error_exit(error)
|
|
1025
|
+
return 1
|
|
1026
|
+
|
|
1027
|
+
logger.debug("About to launch _dx2db")
|
|
1028
|
+
_dx2db(dataset)
|
|
1029
|
+
|
|
1030
|
+
if del_dx_im:
|
|
1031
|
+
os.remove(dig_file)
|
|
1032
|
+
|
|
1033
|
+
return 0
|