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,1768 +1,1771 @@
|
|
|
1
|
-
# OpenREM - Radiation Exposure Monitoring tools for the physicist
|
|
2
|
-
# Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
|
|
3
|
-
#
|
|
4
|
-
# This program is free software: you can redistribute it and/or modify
|
|
5
|
-
# it under the terms of the GNU General Public License as published by
|
|
6
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
# (at your option) any later version.
|
|
8
|
-
#
|
|
9
|
-
# This program is distributed in the hope that it will be useful,
|
|
10
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
# GNU General Public License for more details.
|
|
13
|
-
#
|
|
14
|
-
# Additional permission under section 7 of GPLv3:
|
|
15
|
-
# You shall not make any use of the name of The Royal Marsden NHS
|
|
16
|
-
# Foundation trust in connection with this Program in any press or
|
|
17
|
-
# other public announcement without the prior written consent of
|
|
18
|
-
# The Royal Marsden NHS Foundation Trust.
|
|
19
|
-
#
|
|
20
|
-
# You should have received a copy of the GNU General Public License
|
|
21
|
-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
.. module:: rdsr_methods
|
|
26
|
-
:synopsis: methods used to read the standard irradiation part of rdsr
|
|
27
|
-
objects. This is used by rdsr.py.
|
|
28
|
-
|
|
29
|
-
.. moduleauthor:: Ed McDonagh
|
|
30
|
-
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
from decimal import Decimal
|
|
34
|
-
import logging
|
|
35
|
-
|
|
36
|
-
from defusedxml.ElementTree import fromstring, ParseError
|
|
37
|
-
from django.db.models import Avg, ObjectDoesNotExist
|
|
38
|
-
from django.core.exceptions import ValidationError
|
|
39
|
-
|
|
40
|
-
from ..tools.dcmdatetime import make_date_time
|
|
41
|
-
from ..tools.get_values import (
|
|
42
|
-
get_or_create_cid,
|
|
43
|
-
test_numeric_value,
|
|
44
|
-
)
|
|
45
|
-
from .extract_common import person_participant, observercontext
|
|
46
|
-
from remapp.models import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
47
|
-
AccumCassetteBsdProjRadiogDose,
|
|
48
|
-
AccumIntegratedProjRadiogDose,
|
|
49
|
-
AccumMammographyXRayDose,
|
|
50
|
-
AccumProjXRayDose,
|
|
51
|
-
AccumXRayDose,
|
|
52
|
-
Calibration,
|
|
53
|
-
CtAccumulatedDoseData,
|
|
54
|
-
CtDoseCheckDetails,
|
|
55
|
-
CtIrradiationEventData,
|
|
56
|
-
CtRadiationDose,
|
|
57
|
-
CtXRaySourceParameters,
|
|
58
|
-
DeviceParticipant,
|
|
59
|
-
DoseRelatedDistanceMeasurements,
|
|
60
|
-
Exposure,
|
|
61
|
-
GeneralEquipmentModuleAttr,
|
|
62
|
-
ImageViewModifier,
|
|
63
|
-
IrradEventXRayData,
|
|
64
|
-
IrradEventXRayDetectorData,
|
|
65
|
-
IrradEventXRayMechanicalData,
|
|
66
|
-
IrradEventXRaySourceData,
|
|
67
|
-
Kvp,
|
|
68
|
-
ObserverContext,
|
|
69
|
-
ProjectionXRayRadiationDose,
|
|
70
|
-
PulseWidth,
|
|
71
|
-
ScanningLength,
|
|
72
|
-
XrayFilters,
|
|
73
|
-
XrayGrid,
|
|
74
|
-
XrayTubeCurrent,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
logger = logging.getLogger("remapp.extractors.rdsr")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _deviceparticipant(dataset, eventdatatype, foreignkey):
|
|
81
|
-
|
|
82
|
-
if eventdatatype == "detector":
|
|
83
|
-
device = DeviceParticipant.objects.create(
|
|
84
|
-
irradiation_event_xray_detector_data=foreignkey
|
|
85
|
-
)
|
|
86
|
-
elif eventdatatype == "source":
|
|
87
|
-
device = DeviceParticipant.objects.create(
|
|
88
|
-
irradiation_event_xray_source_data=foreignkey
|
|
89
|
-
)
|
|
90
|
-
elif eventdatatype == "accumulated":
|
|
91
|
-
device = DeviceParticipant.objects.create(accumulated_xray_dose=foreignkey)
|
|
92
|
-
elif eventdatatype == "ct_accumulated":
|
|
93
|
-
device = DeviceParticipant.objects.create(ct_accumulated_dose_data=foreignkey)
|
|
94
|
-
elif eventdatatype == "ct_event":
|
|
95
|
-
device = DeviceParticipant.objects.create(ct_irradiation_event_data=foreignkey)
|
|
96
|
-
else:
|
|
97
|
-
logger.warning(
|
|
98
|
-
f"RDSR import, in _deviceparticipant, but no suitable eventdatatype (is {eventdatatype})"
|
|
99
|
-
)
|
|
100
|
-
return
|
|
101
|
-
for cont in dataset.ContentSequence:
|
|
102
|
-
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Device Role in Procedure":
|
|
103
|
-
device.device_role_in_procedure = get_or_create_cid(
|
|
104
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
105
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
106
|
-
)
|
|
107
|
-
for cont2 in cont.ContentSequence:
|
|
108
|
-
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Device Name":
|
|
109
|
-
device.device_name = cont2.TextValue
|
|
110
|
-
elif (
|
|
111
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
112
|
-
== "Device Manufacturer"
|
|
113
|
-
):
|
|
114
|
-
device.device_manufacturer = cont2.TextValue
|
|
115
|
-
elif (
|
|
116
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning == "Device Model Name"
|
|
117
|
-
):
|
|
118
|
-
device.device_model_name = cont2.TextValue
|
|
119
|
-
elif (
|
|
120
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
121
|
-
== "Device Serial Number"
|
|
122
|
-
):
|
|
123
|
-
device.device_serial_number = cont2.TextValue
|
|
124
|
-
elif (
|
|
125
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
126
|
-
== "Device Observer UID"
|
|
127
|
-
):
|
|
128
|
-
device.device_observer_uid = cont2.UID
|
|
129
|
-
device.save()
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _pulsewidth(pulse_width_value, source):
|
|
133
|
-
"""Takes pulse width values and populates PulseWidth table
|
|
134
|
-
|
|
135
|
-
:param pulse_width_value: Decimal or list of decimals
|
|
136
|
-
:param source: database object in IrradEventXRaySourceData table
|
|
137
|
-
:return: None
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
pulse = PulseWidth.objects.create(irradiation_event_xray_source_data=source)
|
|
142
|
-
pulse.pulse_width = pulse_width_value
|
|
143
|
-
pulse.save()
|
|
144
|
-
except (ValueError, TypeError, ValidationError):
|
|
145
|
-
if not hasattr(pulse_width_value, "strip") and (
|
|
146
|
-
hasattr(pulse_width_value, "__getitem__")
|
|
147
|
-
or hasattr(pulse_width_value, "__iter__")
|
|
148
|
-
):
|
|
149
|
-
for per_pulse_pulse_width in pulse_width_value:
|
|
150
|
-
pulse = PulseWidth.objects.create(
|
|
151
|
-
irradiation_event_xray_source_data=source
|
|
152
|
-
)
|
|
153
|
-
pulse.pulse_width = per_pulse_pulse_width
|
|
154
|
-
pulse.save()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def _kvptable(kvp_value, source):
|
|
158
|
-
"""Takes kVp values and populates kvp table
|
|
159
|
-
|
|
160
|
-
:param kvp_value: Decimal or list of decimals
|
|
161
|
-
:param source: database object in IrradEventXRaySourceData table
|
|
162
|
-
:return: None
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
try:
|
|
166
|
-
kvpdata = Kvp.objects.create(irradiation_event_xray_source_data=source)
|
|
167
|
-
kvpdata.kvp = kvp_value
|
|
168
|
-
kvpdata.save()
|
|
169
|
-
except (ValueError, TypeError, ValidationError):
|
|
170
|
-
if not hasattr(kvp_value, "strip") and (
|
|
171
|
-
hasattr(kvp_value, "__getitem__") or hasattr(kvp_value, "__iter__")
|
|
172
|
-
):
|
|
173
|
-
for per_pulse_kvp in kvp_value:
|
|
174
|
-
kvp = Kvp.objects.create(irradiation_event_xray_source_data=source)
|
|
175
|
-
kvp.kvp = per_pulse_kvp
|
|
176
|
-
kvp.save()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def _xraytubecurrent(current_value, source):
|
|
180
|
-
"""Takes X-ray tube current values and populates XrayTubeCurrent table
|
|
181
|
-
|
|
182
|
-
:param current_value: Decimal or list of decimals
|
|
183
|
-
:param source: database object in IrradEventXRaySourceData table
|
|
184
|
-
:return: None
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
try:
|
|
188
|
-
tubecurrent = XrayTubeCurrent.objects.create(
|
|
189
|
-
irradiation_event_xray_source_data=source
|
|
190
|
-
)
|
|
191
|
-
tubecurrent.xray_tube_current = current_value
|
|
192
|
-
tubecurrent.save()
|
|
193
|
-
except (ValueError, TypeError, ValidationError):
|
|
194
|
-
if not hasattr(current_value, "strip") and (
|
|
195
|
-
hasattr(current_value, "__getitem__") or hasattr(current_value, "__iter__")
|
|
196
|
-
):
|
|
197
|
-
for per_pulse_current in current_value:
|
|
198
|
-
tubecurrent = XrayTubeCurrent.objects.create(
|
|
199
|
-
irradiation_event_xray_source_data=source
|
|
200
|
-
)
|
|
201
|
-
tubecurrent.xray_tube_current = per_pulse_current
|
|
202
|
-
tubecurrent.save()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def _exposure(exposure_value, source):
|
|
206
|
-
"""Takes exposure (uA.s) values and populates Exposure table
|
|
207
|
-
|
|
208
|
-
:param exposure_value: Decimal or list of decimals
|
|
209
|
-
:param source: database object in IrradEventXRaySourceData table
|
|
210
|
-
:return: None
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
try:
|
|
214
|
-
exposure = Exposure.objects.create(irradiation_event_xray_source_data=source)
|
|
215
|
-
exposure.exposure = exposure_value
|
|
216
|
-
exposure.save()
|
|
217
|
-
except (ValueError, TypeError, ValidationError):
|
|
218
|
-
if not hasattr(exposure_value, "strip") and (
|
|
219
|
-
hasattr(exposure_value, "__getitem__")
|
|
220
|
-
or hasattr(exposure_value, "__iter__")
|
|
221
|
-
):
|
|
222
|
-
for per_pulse_exposure in exposure_value:
|
|
223
|
-
exposure = Exposure.objects.create(
|
|
224
|
-
irradiation_event_xray_source_data=source
|
|
225
|
-
)
|
|
226
|
-
exposure.exposure = per_pulse_exposure
|
|
227
|
-
exposure.save()
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def _xrayfilters(content_sequence, source):
|
|
231
|
-
|
|
232
|
-
filters = XrayFilters.objects.create(irradiation_event_xray_source_data=source)
|
|
233
|
-
for cont2 in content_sequence:
|
|
234
|
-
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filter Type":
|
|
235
|
-
filters.xray_filter_type = get_or_create_cid(
|
|
236
|
-
cont2.ConceptCodeSequence[0].CodeValue,
|
|
237
|
-
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
238
|
-
)
|
|
239
|
-
elif cont2.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filter Material":
|
|
240
|
-
filters.xray_filter_material = get_or_create_cid(
|
|
241
|
-
cont2.ConceptCodeSequence[0].CodeValue,
|
|
242
|
-
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
243
|
-
)
|
|
244
|
-
elif (
|
|
245
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
246
|
-
== "X-Ray Filter Thickness Minimum"
|
|
247
|
-
):
|
|
248
|
-
filters.xray_filter_thickness_minimum = test_numeric_value(
|
|
249
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
250
|
-
)
|
|
251
|
-
elif (
|
|
252
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
253
|
-
== "X-Ray Filter Thickness Maximum"
|
|
254
|
-
):
|
|
255
|
-
filters.xray_filter_thickness_maximum = test_numeric_value(
|
|
256
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
257
|
-
)
|
|
258
|
-
filters.save()
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def _doserelateddistancemeasurements(dataset, mech): # CID 10008
|
|
262
|
-
|
|
263
|
-
distance = DoseRelatedDistanceMeasurements.objects.create(
|
|
264
|
-
irradiation_event_xray_mechanical_data=mech
|
|
265
|
-
)
|
|
266
|
-
codes = {
|
|
267
|
-
"Distance Source to Isocenter": "distance_source_to_isocenter",
|
|
268
|
-
"Distance Source to Reference Point": "distance_source_to_reference_point",
|
|
269
|
-
"Distance Source to Detector": "distance_source_to_detector",
|
|
270
|
-
"Table Longitudinal Position": "table_longitudinal_position",
|
|
271
|
-
"Table Lateral Position": "table_lateral_position",
|
|
272
|
-
"Table Height Position": "table_height_position",
|
|
273
|
-
"Distance Source to Table Plane": "distance_source_to_table_plane",
|
|
274
|
-
}
|
|
275
|
-
# For Philips Allura XPer systems you get the privately defined 'Table Height Position' with CodingSchemeDesignator
|
|
276
|
-
# '99PHI-IXR-XPER' instead of the DICOM defined 'Table Height Position'.
|
|
277
|
-
# It seems they are defined the same
|
|
278
|
-
for cont in dataset.ContentSequence:
|
|
279
|
-
try:
|
|
280
|
-
setattr(
|
|
281
|
-
distance,
|
|
282
|
-
codes[cont.ConceptNameCodeSequence[0].CodeMeaning],
|
|
283
|
-
cont.MeasuredValueSequence[0].NumericValue,
|
|
284
|
-
)
|
|
285
|
-
except KeyError:
|
|
286
|
-
pass
|
|
287
|
-
distance.save()
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def _irradiationeventxraymechanicaldata(dataset, event): # TID 10003c
|
|
291
|
-
|
|
292
|
-
mech = IrradEventXRayMechanicalData.objects.create(
|
|
293
|
-
irradiation_event_xray_data=event
|
|
294
|
-
)
|
|
295
|
-
for cont in dataset.ContentSequence:
|
|
296
|
-
if (
|
|
297
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
298
|
-
== "CR/DR Mechanical Configuration"
|
|
299
|
-
):
|
|
300
|
-
mech.crdr_mechanical_configuration = get_or_create_cid(
|
|
301
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
302
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
303
|
-
)
|
|
304
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Positioner Primary Angle":
|
|
305
|
-
try:
|
|
306
|
-
mech.positioner_primary_angle = test_numeric_value(
|
|
307
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
308
|
-
)
|
|
309
|
-
except IndexError:
|
|
310
|
-
pass
|
|
311
|
-
elif (
|
|
312
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning == "Positioner Secondary Angle"
|
|
313
|
-
):
|
|
314
|
-
try:
|
|
315
|
-
mech.positioner_secondary_angle = test_numeric_value(
|
|
316
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
317
|
-
)
|
|
318
|
-
except IndexError:
|
|
319
|
-
pass
|
|
320
|
-
elif (
|
|
321
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
322
|
-
== "Positioner Primary End Angle"
|
|
323
|
-
):
|
|
324
|
-
try:
|
|
325
|
-
mech.positioner_primary_end_angle = test_numeric_value(
|
|
326
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
327
|
-
)
|
|
328
|
-
except IndexError:
|
|
329
|
-
pass
|
|
330
|
-
elif (
|
|
331
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
332
|
-
== "Positioner Secondary End Angle"
|
|
333
|
-
):
|
|
334
|
-
try:
|
|
335
|
-
mech.positioner_secondary_end_angle = test_numeric_value(
|
|
336
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
337
|
-
)
|
|
338
|
-
except IndexError:
|
|
339
|
-
pass
|
|
340
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Column Angulation":
|
|
341
|
-
try:
|
|
342
|
-
mech.column_angulation = test_numeric_value(
|
|
343
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
344
|
-
)
|
|
345
|
-
except IndexError:
|
|
346
|
-
pass
|
|
347
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Table Head Tilt Angle":
|
|
348
|
-
try:
|
|
349
|
-
mech.table_head_tilt_angle = test_numeric_value(
|
|
350
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
351
|
-
)
|
|
352
|
-
except IndexError:
|
|
353
|
-
pass
|
|
354
|
-
elif (
|
|
355
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
356
|
-
== "Table Horizontal Rotation Angle"
|
|
357
|
-
):
|
|
358
|
-
try:
|
|
359
|
-
mech.table_horizontal_rotation_angle = test_numeric_value(
|
|
360
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
361
|
-
)
|
|
362
|
-
except IndexError:
|
|
363
|
-
pass
|
|
364
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Table Cradle Tilt Angle":
|
|
365
|
-
try:
|
|
366
|
-
mech.table_cradle_tilt_angle = test_numeric_value(
|
|
367
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
368
|
-
)
|
|
369
|
-
except IndexError:
|
|
370
|
-
pass
|
|
371
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Compression Thickness":
|
|
372
|
-
try:
|
|
373
|
-
mech.compression_thickness = test_numeric_value(
|
|
374
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
375
|
-
)
|
|
376
|
-
except IndexError:
|
|
377
|
-
pass
|
|
378
|
-
elif cont.ConceptNameCodeSequence[0].CodeValue == "111647": # Compression Force
|
|
379
|
-
try:
|
|
380
|
-
mech.compression_force = test_numeric_value(
|
|
381
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
382
|
-
)
|
|
383
|
-
except IndexError:
|
|
384
|
-
pass
|
|
385
|
-
elif (
|
|
386
|
-
cont.ConceptNameCodeSequence[0].CodeValue == "111648"
|
|
387
|
-
): # Compression Pressure
|
|
388
|
-
try:
|
|
389
|
-
mech.compression_pressure = test_numeric_value(
|
|
390
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
391
|
-
)
|
|
392
|
-
except IndexError:
|
|
393
|
-
pass
|
|
394
|
-
elif (
|
|
395
|
-
cont.ConceptNameCodeSequence[0].CodeValue == "111649"
|
|
396
|
-
): # Compression Contact Area
|
|
397
|
-
try:
|
|
398
|
-
mech.compression_contact_area = test_numeric_value(
|
|
399
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
400
|
-
)
|
|
401
|
-
except IndexError:
|
|
402
|
-
pass
|
|
403
|
-
_doserelateddistancemeasurements(dataset, mech)
|
|
404
|
-
mech.save()
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _check_dap_units(dap_sequence):
|
|
408
|
-
"""Check for non-conformant DAP units of dGycm2 before storing value
|
|
409
|
-
|
|
410
|
-
:param dap_sequence: MeasuredValueSequence[0] from ConceptNameCodeSequence of any of the Dose Area Products
|
|
411
|
-
:return: dose_area_product in Gy.m2
|
|
412
|
-
"""
|
|
413
|
-
dap = test_numeric_value(dap_sequence.NumericValue)
|
|
414
|
-
try:
|
|
415
|
-
if dap and dap_sequence.MeasurementUnitsCodeSequence[0].CodeValue == "dGy.cm2":
|
|
416
|
-
return dap * 0.00001
|
|
417
|
-
else:
|
|
418
|
-
return dap
|
|
419
|
-
except AttributeError:
|
|
420
|
-
return dap
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
def _check_rp_dose_units(rp_dose_sequence):
|
|
424
|
-
"""Check for non-conformant dose at reference point units of mGy before storing value
|
|
425
|
-
|
|
426
|
-
:param rp_dose_sequence: MeasuredValueSequence[0] from ConceptNameCodeSequence of any dose at RP
|
|
427
|
-
:return: dose at reference point in Gy
|
|
428
|
-
"""
|
|
429
|
-
rp_dose = test_numeric_value(rp_dose_sequence.NumericValue)
|
|
430
|
-
try:
|
|
431
|
-
if (
|
|
432
|
-
rp_dose
|
|
433
|
-
and rp_dose_sequence.MeasurementUnitsCodeSequence[0].CodeValue == "mGy"
|
|
434
|
-
):
|
|
435
|
-
return rp_dose * 0.001
|
|
436
|
-
else:
|
|
437
|
-
return rp_dose
|
|
438
|
-
except AttributeError:
|
|
439
|
-
return rp_dose
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
def _irradiationeventxraysourcedata(dataset, event): # TID 10003b
|
|
443
|
-
# Name in DICOM standard for TID 10003B is Irradiation Event X-Ray Source Data
|
|
444
|
-
# See http://dicom.nema.org/medical/dicom/current/output/chtml/part16/sect_TID_10003B.html
|
|
445
|
-
# TODO: review model to convert to cid where appropriate, and add additional fields
|
|
446
|
-
|
|
447
|
-
# Variables below are used if privately defined parameters are available
|
|
448
|
-
private_collimated_field_height = None
|
|
449
|
-
private_collimated_field_width = None
|
|
450
|
-
private_collimated_field_area = None
|
|
451
|
-
|
|
452
|
-
source = IrradEventXRaySourceData.objects.create(irradiation_event_xray_data=event)
|
|
453
|
-
for cont in dataset.ContentSequence:
|
|
454
|
-
try:
|
|
455
|
-
if cont.ConceptNameCodeSequence[0].CodeValue == "113738": # = 'Dose (RP)'
|
|
456
|
-
source.dose_rp = _check_rp_dose_units(cont.MeasuredValueSequence[0])
|
|
457
|
-
elif (
|
|
458
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
459
|
-
== "Reference Point Definition"
|
|
460
|
-
):
|
|
461
|
-
try:
|
|
462
|
-
source.reference_point_definition_code = get_or_create_cid(
|
|
463
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
464
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
465
|
-
)
|
|
466
|
-
except AttributeError:
|
|
467
|
-
source.reference_point_definition = cont.TextValue
|
|
468
|
-
elif (
|
|
469
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning == "Average Glandular Dose"
|
|
470
|
-
):
|
|
471
|
-
source.average_glandular_dose = test_numeric_value(
|
|
472
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
473
|
-
)
|
|
474
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Fluoro Mode":
|
|
475
|
-
source.fluoro_mode = get_or_create_cid(
|
|
476
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
477
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
478
|
-
)
|
|
479
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Pulse Rate":
|
|
480
|
-
source.pulse_rate = test_numeric_value(
|
|
481
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
482
|
-
)
|
|
483
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Number of Pulses":
|
|
484
|
-
source.number_of_pulses = test_numeric_value(
|
|
485
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
486
|
-
)
|
|
487
|
-
elif (
|
|
488
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning == "Number of Frames"
|
|
489
|
-
) and (
|
|
490
|
-
cont.ConceptNameCodeSequence[0].CodingSchemeDesignator
|
|
491
|
-
== "99PHI-IXR-XPER"
|
|
492
|
-
):
|
|
493
|
-
# Philips Allura XPer systems: Private coding scheme designator: 99PHI-IXR-XPER; [number of pulses]
|
|
494
|
-
source.number_of_pulses = test_numeric_value(
|
|
495
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
496
|
-
)
|
|
497
|
-
# should be a derivation thing in here for when the no. pulses is estimated
|
|
498
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Duration":
|
|
499
|
-
source.irradiation_duration = test_numeric_value(
|
|
500
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
501
|
-
)
|
|
502
|
-
elif (
|
|
503
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
504
|
-
== "Average X-Ray Tube Current"
|
|
505
|
-
):
|
|
506
|
-
source.average_xray_tube_current = test_numeric_value(
|
|
507
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
508
|
-
)
|
|
509
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Time":
|
|
510
|
-
source.exposure_time = test_numeric_value(
|
|
511
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
512
|
-
)
|
|
513
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Focal Spot Size":
|
|
514
|
-
source.focal_spot_size = test_numeric_value(
|
|
515
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
516
|
-
)
|
|
517
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Anode Target Material":
|
|
518
|
-
source.anode_target_material = get_or_create_cid(
|
|
519
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
520
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
521
|
-
)
|
|
522
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Collimated Field Area":
|
|
523
|
-
source.collimated_field_area = test_numeric_value(
|
|
524
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
525
|
-
)
|
|
526
|
-
# TODO: xray_grid no longer exists in this table - it is a model on its own...
|
|
527
|
-
# See https://bitbucket.org/openrem/openrem/issue/181
|
|
528
|
-
elif cont.ConceptNameCodeSequence[0].CodeValue == "111635": # 'X-Ray Grid'
|
|
529
|
-
grid = XrayGrid.objects.create(
|
|
530
|
-
irradiation_event_xray_source_data=source
|
|
531
|
-
)
|
|
532
|
-
grid.xray_grid = get_or_create_cid(
|
|
533
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
534
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
535
|
-
)
|
|
536
|
-
grid.save()
|
|
537
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Pulse Width":
|
|
538
|
-
_pulsewidth(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
539
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "KVP":
|
|
540
|
-
_kvptable(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
541
|
-
elif (
|
|
542
|
-
cont.ConceptNameCodeSequence[0].CodeValue == "113734"
|
|
543
|
-
): # 'X-Ray Tube Current':
|
|
544
|
-
_xraytubecurrent(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
545
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure":
|
|
546
|
-
_exposure(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
547
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filters":
|
|
548
|
-
_xrayfilters(cont.ContentSequence, source)
|
|
549
|
-
# Maybe we have a Philips Xper system and we can use the privately defined information
|
|
550
|
-
elif (
|
|
551
|
-
cont.ConceptNameCodeSequence[0].CodeMeaning == "Wedges and Shutters"
|
|
552
|
-
) and (
|
|
553
|
-
cont.ConceptNameCodeSequence[0].CodingSchemeDesignator
|
|
554
|
-
== "99PHI-IXR-XPER"
|
|
555
|
-
):
|
|
556
|
-
# According to DICOM Conformance statement:
|
|
557
|
-
# http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/
|
|
558
|
-
# 5144772/DICOM_Conformance_Allura_8.2.pdf%3fnodeid%3d10125540%26vernum%3d-2
|
|
559
|
-
# "Actual shutter distance from centerpoint of collimator specified in the plane at 1 meter.
|
|
560
|
-
# Unit: mm. End of run value is used."
|
|
561
|
-
bottom_shutter_pos = None
|
|
562
|
-
left_shutter_pos = None
|
|
563
|
-
right_shutter_pos = None
|
|
564
|
-
top_shutter_pos = None
|
|
565
|
-
try:
|
|
566
|
-
for cont2 in cont.ContentSequence:
|
|
567
|
-
if (
|
|
568
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
569
|
-
== "Bottom Shutter"
|
|
570
|
-
):
|
|
571
|
-
bottom_shutter_pos = test_numeric_value(
|
|
572
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
573
|
-
)
|
|
574
|
-
if (
|
|
575
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
576
|
-
== "Left Shutter"
|
|
577
|
-
):
|
|
578
|
-
left_shutter_pos = test_numeric_value(
|
|
579
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
580
|
-
)
|
|
581
|
-
if (
|
|
582
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
583
|
-
== "Right Shutter"
|
|
584
|
-
):
|
|
585
|
-
right_shutter_pos = test_numeric_value(
|
|
586
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
587
|
-
)
|
|
588
|
-
if (
|
|
589
|
-
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
590
|
-
== "Top Shutter"
|
|
591
|
-
):
|
|
592
|
-
top_shutter_pos = test_numeric_value(
|
|
593
|
-
cont2.MeasuredValueSequence[0].NumericValue
|
|
594
|
-
)
|
|
595
|
-
# Get distance_source_to_detector (Sdd) in meters
|
|
596
|
-
# Philips Allura XPer only notes distance_source_to_detector if it changed
|
|
597
|
-
try:
|
|
598
|
-
Sdd = (
|
|
599
|
-
float(
|
|
600
|
-
event.irradeventxraymechanicaldata_set.get()
|
|
601
|
-
.doserelateddistancemeasurements_set.get()
|
|
602
|
-
.distance_source_to_detector
|
|
603
|
-
)
|
|
604
|
-
/ 1000
|
|
605
|
-
)
|
|
606
|
-
except (ObjectDoesNotExist, TypeError):
|
|
607
|
-
Sdd = None
|
|
608
|
-
if (
|
|
609
|
-
bottom_shutter_pos
|
|
610
|
-
and left_shutter_pos
|
|
611
|
-
and right_shutter_pos
|
|
612
|
-
and top_shutter_pos
|
|
613
|
-
and Sdd
|
|
614
|
-
):
|
|
615
|
-
# calculate collimated field area, collimated Field Height and Collimated Field Width
|
|
616
|
-
# at image receptor (shutter positions are defined at 1 meter)
|
|
617
|
-
private_collimated_field_height = (
|
|
618
|
-
right_shutter_pos + left_shutter_pos
|
|
619
|
-
) * Sdd # in mm
|
|
620
|
-
private_collimated_field_width = (
|
|
621
|
-
bottom_shutter_pos + top_shutter_pos
|
|
622
|
-
) * Sdd # in mm
|
|
623
|
-
private_collimated_field_area = (
|
|
624
|
-
private_collimated_field_height
|
|
625
|
-
* private_collimated_field_width
|
|
626
|
-
) / 1000000 # in m2
|
|
627
|
-
except AttributeError:
|
|
628
|
-
pass
|
|
629
|
-
except IndexError:
|
|
630
|
-
pass
|
|
631
|
-
_deviceparticipant(dataset, "source", source)
|
|
632
|
-
try:
|
|
633
|
-
source.ii_field_size = (
|
|
634
|
-
fromstring(source.irradiation_event_xray_data.comment)
|
|
635
|
-
.find("iiDiameter")
|
|
636
|
-
.get("SRData")
|
|
637
|
-
)
|
|
638
|
-
except (ParseError, AttributeError, TypeError):
|
|
639
|
-
logger.debug(
|
|
640
|
-
"Failed in attempt to get II field size from comment (aimed at Siemens)"
|
|
641
|
-
)
|
|
642
|
-
if (not source.collimated_field_height) and private_collimated_field_height:
|
|
643
|
-
source.collimated_field_height = private_collimated_field_height
|
|
644
|
-
if (not source.collimated_field_width) and private_collimated_field_width:
|
|
645
|
-
source.collimated_field_width = private_collimated_field_width
|
|
646
|
-
if (not source.collimated_field_area) and private_collimated_field_area:
|
|
647
|
-
source.collimated_field_area = private_collimated_field_area
|
|
648
|
-
source.save()
|
|
649
|
-
if not source.exposure_time and source.number_of_pulses:
|
|
650
|
-
try:
|
|
651
|
-
avg_pulse_width = source.pulsewidth_set.all().aggregate(Avg("pulse_width"))[
|
|
652
|
-
"pulse_width__avg"
|
|
653
|
-
]
|
|
654
|
-
if avg_pulse_width:
|
|
655
|
-
source.exposure_time = avg_pulse_width * Decimal(
|
|
656
|
-
source.number_of_pulses
|
|
657
|
-
)
|
|
658
|
-
source.save()
|
|
659
|
-
except ObjectDoesNotExist:
|
|
660
|
-
pass
|
|
661
|
-
if not source.average_xray_tube_current:
|
|
662
|
-
if source.xraytubecurrent_set.all().count() > 0:
|
|
663
|
-
source.average_xray_tube_current = (
|
|
664
|
-
source.xraytubecurrent_set.all().aggregate(Avg("xray_tube_current"))[
|
|
665
|
-
"xray_tube_current__avg"
|
|
666
|
-
]
|
|
667
|
-
)
|
|
668
|
-
source.save()
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
def _irradiationeventxraydetectordata(dataset, event): # TID 10003a
|
|
672
|
-
|
|
673
|
-
detector = IrradEventXRayDetectorData.objects.create(
|
|
674
|
-
irradiation_event_xray_data=event
|
|
675
|
-
)
|
|
676
|
-
for cont in dataset.ContentSequence:
|
|
677
|
-
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Index":
|
|
678
|
-
detector.exposure_index = test_numeric_value(
|
|
679
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
680
|
-
)
|
|
681
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Exposure Index":
|
|
682
|
-
detector.target_exposure_index = test_numeric_value(
|
|
683
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
684
|
-
)
|
|
685
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Deviation Index":
|
|
686
|
-
detector.deviation_index = test_numeric_value(
|
|
687
|
-
cont.MeasuredValueSequence[0].NumericValue
|
|
688
|
-
)
|
|
689
|
-
_deviceparticipant(dataset, "detector", detector)
|
|
690
|
-
detector.save()
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
def _imageviewmodifier(dataset, event):
|
|
694
|
-
|
|
695
|
-
modifier = ImageViewModifier.objects.create(irradiation_event_xray_data=event)
|
|
696
|
-
for cont in dataset.ContentSequence:
|
|
697
|
-
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Image View Modifier":
|
|
698
|
-
modifier.image_view_modifier = get_or_create_cid(
|
|
699
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
700
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
701
|
-
)
|
|
702
|
-
# TODO: Projection Eponymous Name should be in here - needs db change
|
|
703
|
-
modifier.save()
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
def _get_patient_position_from_xml_string(event, xml_string):
|
|
707
|
-
"""Use XML parser to extract patient position information from Comment value in Siemens RF RDSR
|
|
708
|
-
|
|
709
|
-
:param event: IrradEventXRayData object
|
|
710
|
-
:param xml_string: Comment value
|
|
711
|
-
:return:
|
|
712
|
-
"""
|
|
713
|
-
|
|
714
|
-
if not xml_string:
|
|
715
|
-
return
|
|
716
|
-
try:
|
|
717
|
-
orientation = (
|
|
718
|
-
fromstring(xml_string)
|
|
719
|
-
.find("PatientPosition")
|
|
720
|
-
.find("Position")
|
|
721
|
-
.get("SRData")
|
|
722
|
-
)
|
|
723
|
-
if orientation.strip().lower() == "hfs":
|
|
724
|
-
event.patient_table_relationship_cid = get_or_create_cid(
|
|
725
|
-
"F-10470", "headfirst"
|
|
726
|
-
)
|
|
727
|
-
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
728
|
-
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
729
|
-
"F-10340", "supine"
|
|
730
|
-
)
|
|
731
|
-
elif orientation.strip().lower() == "hfp":
|
|
732
|
-
event.patient_table_relationship_cid = get_or_create_cid(
|
|
733
|
-
"F-10470", "headfirst"
|
|
734
|
-
)
|
|
735
|
-
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
736
|
-
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
737
|
-
"F-10310", "prone"
|
|
738
|
-
)
|
|
739
|
-
elif orientation.strip().lower() == "ffs":
|
|
740
|
-
event.patient_table_relationship_cid = get_or_create_cid(
|
|
741
|
-
"F-10480", "feet-first"
|
|
742
|
-
)
|
|
743
|
-
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
744
|
-
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
745
|
-
"F-10340", "supine"
|
|
746
|
-
)
|
|
747
|
-
elif orientation.strip().lower() == "ffp":
|
|
748
|
-
event.patient_table_relationship_cid = get_or_create_cid(
|
|
749
|
-
"F-10480", "feet-first"
|
|
750
|
-
)
|
|
751
|
-
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
752
|
-
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
753
|
-
"F-10310", "prone"
|
|
754
|
-
)
|
|
755
|
-
else:
|
|
756
|
-
event.patient_table_relationship_cid = None
|
|
757
|
-
event.patient_orientation_cid = None
|
|
758
|
-
event.patient_orientation_modifier_cid = None
|
|
759
|
-
event.save()
|
|
760
|
-
except (ParseError, AttributeError, TypeError):
|
|
761
|
-
logger.debug(
|
|
762
|
-
"Failed to extract patient orientation from comment string (aimed at Siemens)"
|
|
763
|
-
)
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
def _irradiationeventxraydata(dataset, proj, fulldataset): # TID 10003
|
|
767
|
-
# TODO: review model to convert to cid where appropriate, and add additional fields
|
|
768
|
-
|
|
769
|
-
event = IrradEventXRayData.objects.create(projection_xray_radiation_dose=proj)
|
|
770
|
-
for cont in dataset.ContentSequence:
|
|
771
|
-
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Plane":
|
|
772
|
-
event.acquisition_plane = get_or_create_cid(
|
|
773
|
-
cont.ConceptCodeSequence[0].CodeValue,
|
|
774
|
-
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
775
|
-
)
|
|
776
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event UID":
|
|
777
|
-
event.irradiation_event_uid = cont.UID
|
|
778
|
-
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event Label":
|
|
779
|
-
event.irradiation_event_label = cont.TextValue
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
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
|
-
cont.
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
accummammo
|
|
955
|
-
|
|
956
|
-
)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
event.ct_radiation_dose.general_study_module_attributes.
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
event
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
)
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
)
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
)
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
proj.
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
proj.general_study_module_attributes.
|
|
1734
|
-
proj.
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1
|
+
# OpenREM - Radiation Exposure Monitoring tools for the physicist
|
|
2
|
+
# Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# Additional permission under section 7 of GPLv3:
|
|
15
|
+
# You shall not make any use of the name of The Royal Marsden NHS
|
|
16
|
+
# Foundation trust in connection with this Program in any press or
|
|
17
|
+
# other public announcement without the prior written consent of
|
|
18
|
+
# The Royal Marsden NHS Foundation Trust.
|
|
19
|
+
#
|
|
20
|
+
# You should have received a copy of the GNU General Public License
|
|
21
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
.. module:: rdsr_methods
|
|
26
|
+
:synopsis: methods used to read the standard irradiation part of rdsr
|
|
27
|
+
objects. This is used by rdsr.py.
|
|
28
|
+
|
|
29
|
+
.. moduleauthor:: Ed McDonagh
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from decimal import Decimal
|
|
34
|
+
import logging
|
|
35
|
+
|
|
36
|
+
from defusedxml.ElementTree import fromstring, ParseError
|
|
37
|
+
from django.db.models import Avg, ObjectDoesNotExist
|
|
38
|
+
from django.core.exceptions import ValidationError
|
|
39
|
+
|
|
40
|
+
from ..tools.dcmdatetime import make_date_time
|
|
41
|
+
from ..tools.get_values import (
|
|
42
|
+
get_or_create_cid,
|
|
43
|
+
test_numeric_value,
|
|
44
|
+
)
|
|
45
|
+
from .extract_common import person_participant, observercontext
|
|
46
|
+
from remapp.models import ( # pylint: disable=wrong-import-order, wrong-import-position
|
|
47
|
+
AccumCassetteBsdProjRadiogDose,
|
|
48
|
+
AccumIntegratedProjRadiogDose,
|
|
49
|
+
AccumMammographyXRayDose,
|
|
50
|
+
AccumProjXRayDose,
|
|
51
|
+
AccumXRayDose,
|
|
52
|
+
Calibration,
|
|
53
|
+
CtAccumulatedDoseData,
|
|
54
|
+
CtDoseCheckDetails,
|
|
55
|
+
CtIrradiationEventData,
|
|
56
|
+
CtRadiationDose,
|
|
57
|
+
CtXRaySourceParameters,
|
|
58
|
+
DeviceParticipant,
|
|
59
|
+
DoseRelatedDistanceMeasurements,
|
|
60
|
+
Exposure,
|
|
61
|
+
GeneralEquipmentModuleAttr,
|
|
62
|
+
ImageViewModifier,
|
|
63
|
+
IrradEventXRayData,
|
|
64
|
+
IrradEventXRayDetectorData,
|
|
65
|
+
IrradEventXRayMechanicalData,
|
|
66
|
+
IrradEventXRaySourceData,
|
|
67
|
+
Kvp,
|
|
68
|
+
ObserverContext,
|
|
69
|
+
ProjectionXRayRadiationDose,
|
|
70
|
+
PulseWidth,
|
|
71
|
+
ScanningLength,
|
|
72
|
+
XrayFilters,
|
|
73
|
+
XrayGrid,
|
|
74
|
+
XrayTubeCurrent,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
logger = logging.getLogger("remapp.extractors.rdsr")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _deviceparticipant(dataset, eventdatatype, foreignkey):
|
|
81
|
+
|
|
82
|
+
if eventdatatype == "detector":
|
|
83
|
+
device = DeviceParticipant.objects.create(
|
|
84
|
+
irradiation_event_xray_detector_data=foreignkey
|
|
85
|
+
)
|
|
86
|
+
elif eventdatatype == "source":
|
|
87
|
+
device = DeviceParticipant.objects.create(
|
|
88
|
+
irradiation_event_xray_source_data=foreignkey
|
|
89
|
+
)
|
|
90
|
+
elif eventdatatype == "accumulated":
|
|
91
|
+
device = DeviceParticipant.objects.create(accumulated_xray_dose=foreignkey)
|
|
92
|
+
elif eventdatatype == "ct_accumulated":
|
|
93
|
+
device = DeviceParticipant.objects.create(ct_accumulated_dose_data=foreignkey)
|
|
94
|
+
elif eventdatatype == "ct_event":
|
|
95
|
+
device = DeviceParticipant.objects.create(ct_irradiation_event_data=foreignkey)
|
|
96
|
+
else:
|
|
97
|
+
logger.warning(
|
|
98
|
+
f"RDSR import, in _deviceparticipant, but no suitable eventdatatype (is {eventdatatype})"
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
for cont in dataset.ContentSequence:
|
|
102
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Device Role in Procedure":
|
|
103
|
+
device.device_role_in_procedure = get_or_create_cid(
|
|
104
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
105
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
106
|
+
)
|
|
107
|
+
for cont2 in cont.ContentSequence:
|
|
108
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Device Name":
|
|
109
|
+
device.device_name = cont2.TextValue
|
|
110
|
+
elif (
|
|
111
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
112
|
+
== "Device Manufacturer"
|
|
113
|
+
):
|
|
114
|
+
device.device_manufacturer = cont2.TextValue
|
|
115
|
+
elif (
|
|
116
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning == "Device Model Name"
|
|
117
|
+
):
|
|
118
|
+
device.device_model_name = cont2.TextValue
|
|
119
|
+
elif (
|
|
120
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
121
|
+
== "Device Serial Number"
|
|
122
|
+
):
|
|
123
|
+
device.device_serial_number = cont2.TextValue
|
|
124
|
+
elif (
|
|
125
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
126
|
+
== "Device Observer UID"
|
|
127
|
+
):
|
|
128
|
+
device.device_observer_uid = cont2.UID
|
|
129
|
+
device.save()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _pulsewidth(pulse_width_value, source):
|
|
133
|
+
"""Takes pulse width values and populates PulseWidth table
|
|
134
|
+
|
|
135
|
+
:param pulse_width_value: Decimal or list of decimals
|
|
136
|
+
:param source: database object in IrradEventXRaySourceData table
|
|
137
|
+
:return: None
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
pulse = PulseWidth.objects.create(irradiation_event_xray_source_data=source)
|
|
142
|
+
pulse.pulse_width = pulse_width_value
|
|
143
|
+
pulse.save()
|
|
144
|
+
except (ValueError, TypeError, ValidationError):
|
|
145
|
+
if not hasattr(pulse_width_value, "strip") and (
|
|
146
|
+
hasattr(pulse_width_value, "__getitem__")
|
|
147
|
+
or hasattr(pulse_width_value, "__iter__")
|
|
148
|
+
):
|
|
149
|
+
for per_pulse_pulse_width in pulse_width_value:
|
|
150
|
+
pulse = PulseWidth.objects.create(
|
|
151
|
+
irradiation_event_xray_source_data=source
|
|
152
|
+
)
|
|
153
|
+
pulse.pulse_width = per_pulse_pulse_width
|
|
154
|
+
pulse.save()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _kvptable(kvp_value, source):
|
|
158
|
+
"""Takes kVp values and populates kvp table
|
|
159
|
+
|
|
160
|
+
:param kvp_value: Decimal or list of decimals
|
|
161
|
+
:param source: database object in IrradEventXRaySourceData table
|
|
162
|
+
:return: None
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
kvpdata = Kvp.objects.create(irradiation_event_xray_source_data=source)
|
|
167
|
+
kvpdata.kvp = kvp_value
|
|
168
|
+
kvpdata.save()
|
|
169
|
+
except (ValueError, TypeError, ValidationError):
|
|
170
|
+
if not hasattr(kvp_value, "strip") and (
|
|
171
|
+
hasattr(kvp_value, "__getitem__") or hasattr(kvp_value, "__iter__")
|
|
172
|
+
):
|
|
173
|
+
for per_pulse_kvp in kvp_value:
|
|
174
|
+
kvp = Kvp.objects.create(irradiation_event_xray_source_data=source)
|
|
175
|
+
kvp.kvp = per_pulse_kvp
|
|
176
|
+
kvp.save()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _xraytubecurrent(current_value, source):
|
|
180
|
+
"""Takes X-ray tube current values and populates XrayTubeCurrent table
|
|
181
|
+
|
|
182
|
+
:param current_value: Decimal or list of decimals
|
|
183
|
+
:param source: database object in IrradEventXRaySourceData table
|
|
184
|
+
:return: None
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
tubecurrent = XrayTubeCurrent.objects.create(
|
|
189
|
+
irradiation_event_xray_source_data=source
|
|
190
|
+
)
|
|
191
|
+
tubecurrent.xray_tube_current = current_value
|
|
192
|
+
tubecurrent.save()
|
|
193
|
+
except (ValueError, TypeError, ValidationError):
|
|
194
|
+
if not hasattr(current_value, "strip") and (
|
|
195
|
+
hasattr(current_value, "__getitem__") or hasattr(current_value, "__iter__")
|
|
196
|
+
):
|
|
197
|
+
for per_pulse_current in current_value:
|
|
198
|
+
tubecurrent = XrayTubeCurrent.objects.create(
|
|
199
|
+
irradiation_event_xray_source_data=source
|
|
200
|
+
)
|
|
201
|
+
tubecurrent.xray_tube_current = per_pulse_current
|
|
202
|
+
tubecurrent.save()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _exposure(exposure_value, source):
|
|
206
|
+
"""Takes exposure (uA.s) values and populates Exposure table
|
|
207
|
+
|
|
208
|
+
:param exposure_value: Decimal or list of decimals
|
|
209
|
+
:param source: database object in IrradEventXRaySourceData table
|
|
210
|
+
:return: None
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
exposure = Exposure.objects.create(irradiation_event_xray_source_data=source)
|
|
215
|
+
exposure.exposure = exposure_value
|
|
216
|
+
exposure.save()
|
|
217
|
+
except (ValueError, TypeError, ValidationError):
|
|
218
|
+
if not hasattr(exposure_value, "strip") and (
|
|
219
|
+
hasattr(exposure_value, "__getitem__")
|
|
220
|
+
or hasattr(exposure_value, "__iter__")
|
|
221
|
+
):
|
|
222
|
+
for per_pulse_exposure in exposure_value:
|
|
223
|
+
exposure = Exposure.objects.create(
|
|
224
|
+
irradiation_event_xray_source_data=source
|
|
225
|
+
)
|
|
226
|
+
exposure.exposure = per_pulse_exposure
|
|
227
|
+
exposure.save()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _xrayfilters(content_sequence, source):
|
|
231
|
+
|
|
232
|
+
filters = XrayFilters.objects.create(irradiation_event_xray_source_data=source)
|
|
233
|
+
for cont2 in content_sequence:
|
|
234
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filter Type":
|
|
235
|
+
filters.xray_filter_type = get_or_create_cid(
|
|
236
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
237
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
238
|
+
)
|
|
239
|
+
elif cont2.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filter Material":
|
|
240
|
+
filters.xray_filter_material = get_or_create_cid(
|
|
241
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
242
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
243
|
+
)
|
|
244
|
+
elif (
|
|
245
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
246
|
+
== "X-Ray Filter Thickness Minimum"
|
|
247
|
+
):
|
|
248
|
+
filters.xray_filter_thickness_minimum = test_numeric_value(
|
|
249
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
250
|
+
)
|
|
251
|
+
elif (
|
|
252
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
253
|
+
== "X-Ray Filter Thickness Maximum"
|
|
254
|
+
):
|
|
255
|
+
filters.xray_filter_thickness_maximum = test_numeric_value(
|
|
256
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
257
|
+
)
|
|
258
|
+
filters.save()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _doserelateddistancemeasurements(dataset, mech): # CID 10008
|
|
262
|
+
|
|
263
|
+
distance = DoseRelatedDistanceMeasurements.objects.create(
|
|
264
|
+
irradiation_event_xray_mechanical_data=mech
|
|
265
|
+
)
|
|
266
|
+
codes = {
|
|
267
|
+
"Distance Source to Isocenter": "distance_source_to_isocenter",
|
|
268
|
+
"Distance Source to Reference Point": "distance_source_to_reference_point",
|
|
269
|
+
"Distance Source to Detector": "distance_source_to_detector",
|
|
270
|
+
"Table Longitudinal Position": "table_longitudinal_position",
|
|
271
|
+
"Table Lateral Position": "table_lateral_position",
|
|
272
|
+
"Table Height Position": "table_height_position",
|
|
273
|
+
"Distance Source to Table Plane": "distance_source_to_table_plane",
|
|
274
|
+
}
|
|
275
|
+
# For Philips Allura XPer systems you get the privately defined 'Table Height Position' with CodingSchemeDesignator
|
|
276
|
+
# '99PHI-IXR-XPER' instead of the DICOM defined 'Table Height Position'.
|
|
277
|
+
# It seems they are defined the same
|
|
278
|
+
for cont in dataset.ContentSequence:
|
|
279
|
+
try:
|
|
280
|
+
setattr(
|
|
281
|
+
distance,
|
|
282
|
+
codes[cont.ConceptNameCodeSequence[0].CodeMeaning],
|
|
283
|
+
cont.MeasuredValueSequence[0].NumericValue,
|
|
284
|
+
)
|
|
285
|
+
except KeyError:
|
|
286
|
+
pass
|
|
287
|
+
distance.save()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _irradiationeventxraymechanicaldata(dataset, event): # TID 10003c
|
|
291
|
+
|
|
292
|
+
mech = IrradEventXRayMechanicalData.objects.create(
|
|
293
|
+
irradiation_event_xray_data=event
|
|
294
|
+
)
|
|
295
|
+
for cont in dataset.ContentSequence:
|
|
296
|
+
if (
|
|
297
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
298
|
+
== "CR/DR Mechanical Configuration"
|
|
299
|
+
):
|
|
300
|
+
mech.crdr_mechanical_configuration = get_or_create_cid(
|
|
301
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
302
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
303
|
+
)
|
|
304
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Positioner Primary Angle":
|
|
305
|
+
try:
|
|
306
|
+
mech.positioner_primary_angle = test_numeric_value(
|
|
307
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
308
|
+
)
|
|
309
|
+
except IndexError:
|
|
310
|
+
pass
|
|
311
|
+
elif (
|
|
312
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Positioner Secondary Angle"
|
|
313
|
+
):
|
|
314
|
+
try:
|
|
315
|
+
mech.positioner_secondary_angle = test_numeric_value(
|
|
316
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
317
|
+
)
|
|
318
|
+
except IndexError:
|
|
319
|
+
pass
|
|
320
|
+
elif (
|
|
321
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
322
|
+
== "Positioner Primary End Angle"
|
|
323
|
+
):
|
|
324
|
+
try:
|
|
325
|
+
mech.positioner_primary_end_angle = test_numeric_value(
|
|
326
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
327
|
+
)
|
|
328
|
+
except IndexError:
|
|
329
|
+
pass
|
|
330
|
+
elif (
|
|
331
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
332
|
+
== "Positioner Secondary End Angle"
|
|
333
|
+
):
|
|
334
|
+
try:
|
|
335
|
+
mech.positioner_secondary_end_angle = test_numeric_value(
|
|
336
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
337
|
+
)
|
|
338
|
+
except IndexError:
|
|
339
|
+
pass
|
|
340
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Column Angulation":
|
|
341
|
+
try:
|
|
342
|
+
mech.column_angulation = test_numeric_value(
|
|
343
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
344
|
+
)
|
|
345
|
+
except IndexError:
|
|
346
|
+
pass
|
|
347
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Table Head Tilt Angle":
|
|
348
|
+
try:
|
|
349
|
+
mech.table_head_tilt_angle = test_numeric_value(
|
|
350
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
351
|
+
)
|
|
352
|
+
except IndexError:
|
|
353
|
+
pass
|
|
354
|
+
elif (
|
|
355
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
356
|
+
== "Table Horizontal Rotation Angle"
|
|
357
|
+
):
|
|
358
|
+
try:
|
|
359
|
+
mech.table_horizontal_rotation_angle = test_numeric_value(
|
|
360
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
361
|
+
)
|
|
362
|
+
except IndexError:
|
|
363
|
+
pass
|
|
364
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Table Cradle Tilt Angle":
|
|
365
|
+
try:
|
|
366
|
+
mech.table_cradle_tilt_angle = test_numeric_value(
|
|
367
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
368
|
+
)
|
|
369
|
+
except IndexError:
|
|
370
|
+
pass
|
|
371
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Compression Thickness":
|
|
372
|
+
try:
|
|
373
|
+
mech.compression_thickness = test_numeric_value(
|
|
374
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
375
|
+
)
|
|
376
|
+
except IndexError:
|
|
377
|
+
pass
|
|
378
|
+
elif cont.ConceptNameCodeSequence[0].CodeValue == "111647": # Compression Force
|
|
379
|
+
try:
|
|
380
|
+
mech.compression_force = test_numeric_value(
|
|
381
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
382
|
+
)
|
|
383
|
+
except IndexError:
|
|
384
|
+
pass
|
|
385
|
+
elif (
|
|
386
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "111648"
|
|
387
|
+
): # Compression Pressure
|
|
388
|
+
try:
|
|
389
|
+
mech.compression_pressure = test_numeric_value(
|
|
390
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
391
|
+
)
|
|
392
|
+
except IndexError:
|
|
393
|
+
pass
|
|
394
|
+
elif (
|
|
395
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "111649"
|
|
396
|
+
): # Compression Contact Area
|
|
397
|
+
try:
|
|
398
|
+
mech.compression_contact_area = test_numeric_value(
|
|
399
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
400
|
+
)
|
|
401
|
+
except IndexError:
|
|
402
|
+
pass
|
|
403
|
+
_doserelateddistancemeasurements(dataset, mech)
|
|
404
|
+
mech.save()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _check_dap_units(dap_sequence):
|
|
408
|
+
"""Check for non-conformant DAP units of dGycm2 before storing value
|
|
409
|
+
|
|
410
|
+
:param dap_sequence: MeasuredValueSequence[0] from ConceptNameCodeSequence of any of the Dose Area Products
|
|
411
|
+
:return: dose_area_product in Gy.m2
|
|
412
|
+
"""
|
|
413
|
+
dap = test_numeric_value(dap_sequence.NumericValue)
|
|
414
|
+
try:
|
|
415
|
+
if dap and dap_sequence.MeasurementUnitsCodeSequence[0].CodeValue == "dGy.cm2":
|
|
416
|
+
return dap * 0.00001
|
|
417
|
+
else:
|
|
418
|
+
return dap
|
|
419
|
+
except AttributeError:
|
|
420
|
+
return dap
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _check_rp_dose_units(rp_dose_sequence):
|
|
424
|
+
"""Check for non-conformant dose at reference point units of mGy before storing value
|
|
425
|
+
|
|
426
|
+
:param rp_dose_sequence: MeasuredValueSequence[0] from ConceptNameCodeSequence of any dose at RP
|
|
427
|
+
:return: dose at reference point in Gy
|
|
428
|
+
"""
|
|
429
|
+
rp_dose = test_numeric_value(rp_dose_sequence.NumericValue)
|
|
430
|
+
try:
|
|
431
|
+
if (
|
|
432
|
+
rp_dose
|
|
433
|
+
and rp_dose_sequence.MeasurementUnitsCodeSequence[0].CodeValue == "mGy"
|
|
434
|
+
):
|
|
435
|
+
return rp_dose * 0.001
|
|
436
|
+
else:
|
|
437
|
+
return rp_dose
|
|
438
|
+
except AttributeError:
|
|
439
|
+
return rp_dose
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _irradiationeventxraysourcedata(dataset, event): # TID 10003b
|
|
443
|
+
# Name in DICOM standard for TID 10003B is Irradiation Event X-Ray Source Data
|
|
444
|
+
# See http://dicom.nema.org/medical/dicom/current/output/chtml/part16/sect_TID_10003B.html
|
|
445
|
+
# TODO: review model to convert to cid where appropriate, and add additional fields
|
|
446
|
+
|
|
447
|
+
# Variables below are used if privately defined parameters are available
|
|
448
|
+
private_collimated_field_height = None
|
|
449
|
+
private_collimated_field_width = None
|
|
450
|
+
private_collimated_field_area = None
|
|
451
|
+
|
|
452
|
+
source = IrradEventXRaySourceData.objects.create(irradiation_event_xray_data=event)
|
|
453
|
+
for cont in dataset.ContentSequence:
|
|
454
|
+
try:
|
|
455
|
+
if cont.ConceptNameCodeSequence[0].CodeValue == "113738": # = 'Dose (RP)'
|
|
456
|
+
source.dose_rp = _check_rp_dose_units(cont.MeasuredValueSequence[0])
|
|
457
|
+
elif (
|
|
458
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
459
|
+
== "Reference Point Definition"
|
|
460
|
+
):
|
|
461
|
+
try:
|
|
462
|
+
source.reference_point_definition_code = get_or_create_cid(
|
|
463
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
464
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
465
|
+
)
|
|
466
|
+
except AttributeError:
|
|
467
|
+
source.reference_point_definition = cont.TextValue
|
|
468
|
+
elif (
|
|
469
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Average Glandular Dose"
|
|
470
|
+
):
|
|
471
|
+
source.average_glandular_dose = test_numeric_value(
|
|
472
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
473
|
+
)
|
|
474
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Fluoro Mode":
|
|
475
|
+
source.fluoro_mode = get_or_create_cid(
|
|
476
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
477
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
478
|
+
)
|
|
479
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Pulse Rate":
|
|
480
|
+
source.pulse_rate = test_numeric_value(
|
|
481
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
482
|
+
)
|
|
483
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Number of Pulses":
|
|
484
|
+
source.number_of_pulses = test_numeric_value(
|
|
485
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
486
|
+
)
|
|
487
|
+
elif (
|
|
488
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Number of Frames"
|
|
489
|
+
) and (
|
|
490
|
+
cont.ConceptNameCodeSequence[0].CodingSchemeDesignator
|
|
491
|
+
== "99PHI-IXR-XPER"
|
|
492
|
+
):
|
|
493
|
+
# Philips Allura XPer systems: Private coding scheme designator: 99PHI-IXR-XPER; [number of pulses]
|
|
494
|
+
source.number_of_pulses = test_numeric_value(
|
|
495
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
496
|
+
)
|
|
497
|
+
# should be a derivation thing in here for when the no. pulses is estimated
|
|
498
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Duration":
|
|
499
|
+
source.irradiation_duration = test_numeric_value(
|
|
500
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
501
|
+
)
|
|
502
|
+
elif (
|
|
503
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
504
|
+
== "Average X-Ray Tube Current"
|
|
505
|
+
):
|
|
506
|
+
source.average_xray_tube_current = test_numeric_value(
|
|
507
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
508
|
+
)
|
|
509
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Time":
|
|
510
|
+
source.exposure_time = test_numeric_value(
|
|
511
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
512
|
+
)
|
|
513
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Focal Spot Size":
|
|
514
|
+
source.focal_spot_size = test_numeric_value(
|
|
515
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
516
|
+
)
|
|
517
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Anode Target Material":
|
|
518
|
+
source.anode_target_material = get_or_create_cid(
|
|
519
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
520
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
521
|
+
)
|
|
522
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Collimated Field Area":
|
|
523
|
+
source.collimated_field_area = test_numeric_value(
|
|
524
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
525
|
+
)
|
|
526
|
+
# TODO: xray_grid no longer exists in this table - it is a model on its own...
|
|
527
|
+
# See https://bitbucket.org/openrem/openrem/issue/181
|
|
528
|
+
elif cont.ConceptNameCodeSequence[0].CodeValue == "111635": # 'X-Ray Grid'
|
|
529
|
+
grid = XrayGrid.objects.create(
|
|
530
|
+
irradiation_event_xray_source_data=source
|
|
531
|
+
)
|
|
532
|
+
grid.xray_grid = get_or_create_cid(
|
|
533
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
534
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
535
|
+
)
|
|
536
|
+
grid.save()
|
|
537
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Pulse Width":
|
|
538
|
+
_pulsewidth(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
539
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "KVP":
|
|
540
|
+
_kvptable(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
541
|
+
elif (
|
|
542
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "113734"
|
|
543
|
+
): # 'X-Ray Tube Current':
|
|
544
|
+
_xraytubecurrent(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
545
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure":
|
|
546
|
+
_exposure(cont.MeasuredValueSequence[0].NumericValue, source)
|
|
547
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "X-Ray Filters":
|
|
548
|
+
_xrayfilters(cont.ContentSequence, source)
|
|
549
|
+
# Maybe we have a Philips Xper system and we can use the privately defined information
|
|
550
|
+
elif (
|
|
551
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Wedges and Shutters"
|
|
552
|
+
) and (
|
|
553
|
+
cont.ConceptNameCodeSequence[0].CodingSchemeDesignator
|
|
554
|
+
== "99PHI-IXR-XPER"
|
|
555
|
+
):
|
|
556
|
+
# According to DICOM Conformance statement:
|
|
557
|
+
# http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/
|
|
558
|
+
# 5144772/DICOM_Conformance_Allura_8.2.pdf%3fnodeid%3d10125540%26vernum%3d-2
|
|
559
|
+
# "Actual shutter distance from centerpoint of collimator specified in the plane at 1 meter.
|
|
560
|
+
# Unit: mm. End of run value is used."
|
|
561
|
+
bottom_shutter_pos = None
|
|
562
|
+
left_shutter_pos = None
|
|
563
|
+
right_shutter_pos = None
|
|
564
|
+
top_shutter_pos = None
|
|
565
|
+
try:
|
|
566
|
+
for cont2 in cont.ContentSequence:
|
|
567
|
+
if (
|
|
568
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
569
|
+
== "Bottom Shutter"
|
|
570
|
+
):
|
|
571
|
+
bottom_shutter_pos = test_numeric_value(
|
|
572
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
573
|
+
)
|
|
574
|
+
if (
|
|
575
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
576
|
+
== "Left Shutter"
|
|
577
|
+
):
|
|
578
|
+
left_shutter_pos = test_numeric_value(
|
|
579
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
580
|
+
)
|
|
581
|
+
if (
|
|
582
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
583
|
+
== "Right Shutter"
|
|
584
|
+
):
|
|
585
|
+
right_shutter_pos = test_numeric_value(
|
|
586
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
587
|
+
)
|
|
588
|
+
if (
|
|
589
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
590
|
+
== "Top Shutter"
|
|
591
|
+
):
|
|
592
|
+
top_shutter_pos = test_numeric_value(
|
|
593
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
594
|
+
)
|
|
595
|
+
# Get distance_source_to_detector (Sdd) in meters
|
|
596
|
+
# Philips Allura XPer only notes distance_source_to_detector if it changed
|
|
597
|
+
try:
|
|
598
|
+
Sdd = (
|
|
599
|
+
float(
|
|
600
|
+
event.irradeventxraymechanicaldata_set.get()
|
|
601
|
+
.doserelateddistancemeasurements_set.get()
|
|
602
|
+
.distance_source_to_detector
|
|
603
|
+
)
|
|
604
|
+
/ 1000
|
|
605
|
+
)
|
|
606
|
+
except (ObjectDoesNotExist, TypeError):
|
|
607
|
+
Sdd = None
|
|
608
|
+
if (
|
|
609
|
+
bottom_shutter_pos
|
|
610
|
+
and left_shutter_pos
|
|
611
|
+
and right_shutter_pos
|
|
612
|
+
and top_shutter_pos
|
|
613
|
+
and Sdd
|
|
614
|
+
):
|
|
615
|
+
# calculate collimated field area, collimated Field Height and Collimated Field Width
|
|
616
|
+
# at image receptor (shutter positions are defined at 1 meter)
|
|
617
|
+
private_collimated_field_height = (
|
|
618
|
+
right_shutter_pos + left_shutter_pos
|
|
619
|
+
) * Sdd # in mm
|
|
620
|
+
private_collimated_field_width = (
|
|
621
|
+
bottom_shutter_pos + top_shutter_pos
|
|
622
|
+
) * Sdd # in mm
|
|
623
|
+
private_collimated_field_area = (
|
|
624
|
+
private_collimated_field_height
|
|
625
|
+
* private_collimated_field_width
|
|
626
|
+
) / 1000000 # in m2
|
|
627
|
+
except AttributeError:
|
|
628
|
+
pass
|
|
629
|
+
except IndexError:
|
|
630
|
+
pass
|
|
631
|
+
_deviceparticipant(dataset, "source", source)
|
|
632
|
+
try:
|
|
633
|
+
source.ii_field_size = (
|
|
634
|
+
fromstring(source.irradiation_event_xray_data.comment)
|
|
635
|
+
.find("iiDiameter")
|
|
636
|
+
.get("SRData")
|
|
637
|
+
)
|
|
638
|
+
except (ParseError, AttributeError, TypeError):
|
|
639
|
+
logger.debug(
|
|
640
|
+
"Failed in attempt to get II field size from comment (aimed at Siemens)"
|
|
641
|
+
)
|
|
642
|
+
if (not source.collimated_field_height) and private_collimated_field_height:
|
|
643
|
+
source.collimated_field_height = private_collimated_field_height
|
|
644
|
+
if (not source.collimated_field_width) and private_collimated_field_width:
|
|
645
|
+
source.collimated_field_width = private_collimated_field_width
|
|
646
|
+
if (not source.collimated_field_area) and private_collimated_field_area:
|
|
647
|
+
source.collimated_field_area = private_collimated_field_area
|
|
648
|
+
source.save()
|
|
649
|
+
if not source.exposure_time and source.number_of_pulses:
|
|
650
|
+
try:
|
|
651
|
+
avg_pulse_width = source.pulsewidth_set.all().aggregate(Avg("pulse_width"))[
|
|
652
|
+
"pulse_width__avg"
|
|
653
|
+
]
|
|
654
|
+
if avg_pulse_width:
|
|
655
|
+
source.exposure_time = avg_pulse_width * Decimal(
|
|
656
|
+
source.number_of_pulses
|
|
657
|
+
)
|
|
658
|
+
source.save()
|
|
659
|
+
except ObjectDoesNotExist:
|
|
660
|
+
pass
|
|
661
|
+
if not source.average_xray_tube_current:
|
|
662
|
+
if source.xraytubecurrent_set.all().count() > 0:
|
|
663
|
+
source.average_xray_tube_current = (
|
|
664
|
+
source.xraytubecurrent_set.all().aggregate(Avg("xray_tube_current"))[
|
|
665
|
+
"xray_tube_current__avg"
|
|
666
|
+
]
|
|
667
|
+
)
|
|
668
|
+
source.save()
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _irradiationeventxraydetectordata(dataset, event): # TID 10003a
|
|
672
|
+
|
|
673
|
+
detector = IrradEventXRayDetectorData.objects.create(
|
|
674
|
+
irradiation_event_xray_data=event
|
|
675
|
+
)
|
|
676
|
+
for cont in dataset.ContentSequence:
|
|
677
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Index":
|
|
678
|
+
detector.exposure_index = test_numeric_value(
|
|
679
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
680
|
+
)
|
|
681
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Exposure Index":
|
|
682
|
+
detector.target_exposure_index = test_numeric_value(
|
|
683
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
684
|
+
)
|
|
685
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Deviation Index":
|
|
686
|
+
detector.deviation_index = test_numeric_value(
|
|
687
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
688
|
+
)
|
|
689
|
+
_deviceparticipant(dataset, "detector", detector)
|
|
690
|
+
detector.save()
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def _imageviewmodifier(dataset, event):
|
|
694
|
+
|
|
695
|
+
modifier = ImageViewModifier.objects.create(irradiation_event_xray_data=event)
|
|
696
|
+
for cont in dataset.ContentSequence:
|
|
697
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Image View Modifier":
|
|
698
|
+
modifier.image_view_modifier = get_or_create_cid(
|
|
699
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
700
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
701
|
+
)
|
|
702
|
+
# TODO: Projection Eponymous Name should be in here - needs db change
|
|
703
|
+
modifier.save()
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _get_patient_position_from_xml_string(event, xml_string):
|
|
707
|
+
"""Use XML parser to extract patient position information from Comment value in Siemens RF RDSR
|
|
708
|
+
|
|
709
|
+
:param event: IrradEventXRayData object
|
|
710
|
+
:param xml_string: Comment value
|
|
711
|
+
:return:
|
|
712
|
+
"""
|
|
713
|
+
|
|
714
|
+
if not xml_string:
|
|
715
|
+
return
|
|
716
|
+
try:
|
|
717
|
+
orientation = (
|
|
718
|
+
fromstring(xml_string)
|
|
719
|
+
.find("PatientPosition")
|
|
720
|
+
.find("Position")
|
|
721
|
+
.get("SRData")
|
|
722
|
+
)
|
|
723
|
+
if orientation.strip().lower() == "hfs":
|
|
724
|
+
event.patient_table_relationship_cid = get_or_create_cid(
|
|
725
|
+
"F-10470", "headfirst"
|
|
726
|
+
)
|
|
727
|
+
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
728
|
+
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
729
|
+
"F-10340", "supine"
|
|
730
|
+
)
|
|
731
|
+
elif orientation.strip().lower() == "hfp":
|
|
732
|
+
event.patient_table_relationship_cid = get_or_create_cid(
|
|
733
|
+
"F-10470", "headfirst"
|
|
734
|
+
)
|
|
735
|
+
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
736
|
+
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
737
|
+
"F-10310", "prone"
|
|
738
|
+
)
|
|
739
|
+
elif orientation.strip().lower() == "ffs":
|
|
740
|
+
event.patient_table_relationship_cid = get_or_create_cid(
|
|
741
|
+
"F-10480", "feet-first"
|
|
742
|
+
)
|
|
743
|
+
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
744
|
+
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
745
|
+
"F-10340", "supine"
|
|
746
|
+
)
|
|
747
|
+
elif orientation.strip().lower() == "ffp":
|
|
748
|
+
event.patient_table_relationship_cid = get_or_create_cid(
|
|
749
|
+
"F-10480", "feet-first"
|
|
750
|
+
)
|
|
751
|
+
event.patient_orientation_cid = get_or_create_cid("F-10450", "recumbent")
|
|
752
|
+
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
753
|
+
"F-10310", "prone"
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
event.patient_table_relationship_cid = None
|
|
757
|
+
event.patient_orientation_cid = None
|
|
758
|
+
event.patient_orientation_modifier_cid = None
|
|
759
|
+
event.save()
|
|
760
|
+
except (ParseError, AttributeError, TypeError):
|
|
761
|
+
logger.debug(
|
|
762
|
+
"Failed to extract patient orientation from comment string (aimed at Siemens)"
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def _irradiationeventxraydata(dataset, proj, fulldataset): # TID 10003
|
|
767
|
+
# TODO: review model to convert to cid where appropriate, and add additional fields
|
|
768
|
+
|
|
769
|
+
event = IrradEventXRayData.objects.create(projection_xray_radiation_dose=proj)
|
|
770
|
+
for cont in dataset.ContentSequence:
|
|
771
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Plane":
|
|
772
|
+
event.acquisition_plane = get_or_create_cid(
|
|
773
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
774
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
775
|
+
)
|
|
776
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event UID":
|
|
777
|
+
event.irradiation_event_uid = cont.UID
|
|
778
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event Label":
|
|
779
|
+
event.irradiation_event_label = cont.TextValue
|
|
780
|
+
try:
|
|
781
|
+
for cont2 in cont.ContentSequence:
|
|
782
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Label Type":
|
|
783
|
+
event.label_type = get_or_create_cid(
|
|
784
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
785
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
786
|
+
)
|
|
787
|
+
except AttributeError:
|
|
788
|
+
continue
|
|
789
|
+
elif (
|
|
790
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "111526"
|
|
791
|
+
): # 'DateTime Started'
|
|
792
|
+
event.date_time_started = make_date_time(cont.DateTime)
|
|
793
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event Type":
|
|
794
|
+
event.irradiation_event_type = get_or_create_cid(
|
|
795
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
796
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
797
|
+
)
|
|
798
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Protocol":
|
|
799
|
+
try:
|
|
800
|
+
event.acquisition_protocol = cont.TextValue
|
|
801
|
+
except AttributeError:
|
|
802
|
+
event.acquisition_protocol = None
|
|
803
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Anatomical structure":
|
|
804
|
+
event.anatomical_structure = get_or_create_cid(
|
|
805
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
806
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
807
|
+
)
|
|
808
|
+
try:
|
|
809
|
+
for cont2 in cont.ContentSequence:
|
|
810
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
|
|
811
|
+
event.laterality = get_or_create_cid(
|
|
812
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
813
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
814
|
+
)
|
|
815
|
+
except AttributeError:
|
|
816
|
+
pass
|
|
817
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Image View":
|
|
818
|
+
event.image_view = get_or_create_cid(
|
|
819
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
820
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
821
|
+
)
|
|
822
|
+
try:
|
|
823
|
+
_imageviewmodifier(cont, event)
|
|
824
|
+
except AttributeError:
|
|
825
|
+
pass
|
|
826
|
+
elif (
|
|
827
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Patient Table Relationship"
|
|
828
|
+
):
|
|
829
|
+
event.patient_table_relationship_cid = get_or_create_cid(
|
|
830
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
831
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
832
|
+
)
|
|
833
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Patient Orientation":
|
|
834
|
+
event.patient_orientation_cid = get_or_create_cid(
|
|
835
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
836
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
837
|
+
)
|
|
838
|
+
try:
|
|
839
|
+
for cont2 in cont.ContentSequence:
|
|
840
|
+
if (
|
|
841
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
842
|
+
== "Patient Orientation Modifier"
|
|
843
|
+
):
|
|
844
|
+
event.patient_orientation_modifier_cid = get_or_create_cid(
|
|
845
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
846
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
847
|
+
)
|
|
848
|
+
except AttributeError:
|
|
849
|
+
pass
|
|
850
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Region":
|
|
851
|
+
event.target_region = get_or_create_cid(
|
|
852
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
853
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
854
|
+
)
|
|
855
|
+
try:
|
|
856
|
+
for cont2 in cont.ContentSequence:
|
|
857
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
|
|
858
|
+
event.laterality = get_or_create_cid(
|
|
859
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
860
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
861
|
+
)
|
|
862
|
+
except AttributeError:
|
|
863
|
+
pass
|
|
864
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product":
|
|
865
|
+
try:
|
|
866
|
+
event.dose_area_product = _check_dap_units(
|
|
867
|
+
cont.MeasuredValueSequence[0]
|
|
868
|
+
)
|
|
869
|
+
except LookupError:
|
|
870
|
+
pass # Will occur if measured value sequence is missing
|
|
871
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Half Value Layer":
|
|
872
|
+
event.half_value_layer = test_numeric_value(
|
|
873
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
874
|
+
)
|
|
875
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Entrance Exposure at RP":
|
|
876
|
+
event.entrance_exposure_at_rp = test_numeric_value(
|
|
877
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
878
|
+
)
|
|
879
|
+
elif (
|
|
880
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Reference Point Definition"
|
|
881
|
+
):
|
|
882
|
+
try:
|
|
883
|
+
event.reference_point_definition = get_or_create_cid(
|
|
884
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
885
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
886
|
+
)
|
|
887
|
+
except AttributeError:
|
|
888
|
+
event.reference_point_definition_text = cont.TextValue
|
|
889
|
+
if cont.ValueType == "CONTAINER":
|
|
890
|
+
if (
|
|
891
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
892
|
+
== "Mammography CAD Breast Composition"
|
|
893
|
+
):
|
|
894
|
+
for cont2 in cont.ContentSequence:
|
|
895
|
+
if cont2.ConceptNamesCodes[0].CodeMeaning == "Breast Composition":
|
|
896
|
+
event.breast_composition = cont2.CodeValue
|
|
897
|
+
elif (
|
|
898
|
+
cont2.ConceptNamesCodes[0].CodeMeaning
|
|
899
|
+
== "Percent Fibroglandular Tissue"
|
|
900
|
+
):
|
|
901
|
+
event.percent_fibroglandular_tissue = cont2.NumericValue
|
|
902
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
|
|
903
|
+
event.comment = cont.TextValue
|
|
904
|
+
event.save()
|
|
905
|
+
for cont3 in fulldataset.ContentSequence:
|
|
906
|
+
if cont3.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
|
|
907
|
+
_get_patient_position_from_xml_string(event, cont3.TextValue)
|
|
908
|
+
# needs include for optional multiple person participant
|
|
909
|
+
_irradiationeventxraydetectordata(dataset, event)
|
|
910
|
+
_irradiationeventxraymechanicaldata(dataset, event)
|
|
911
|
+
# in some cases we need mechanical data before x-ray source data
|
|
912
|
+
_irradiationeventxraysourcedata(dataset, event)
|
|
913
|
+
|
|
914
|
+
event.save()
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _calibration(dataset, accum):
|
|
918
|
+
|
|
919
|
+
cal = Calibration.objects.create(accumulated_xray_dose=accum)
|
|
920
|
+
for cont in dataset.ContentSequence:
|
|
921
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Measurement Device":
|
|
922
|
+
cal.dose_measurement_device = get_or_create_cid(
|
|
923
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
924
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
925
|
+
)
|
|
926
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Date":
|
|
927
|
+
try:
|
|
928
|
+
cal.calibration_date = make_date_time(cont.DateTime)
|
|
929
|
+
except (KeyError, AttributeError):
|
|
930
|
+
pass # Mandatory field not always present - see issue #770
|
|
931
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Factor":
|
|
932
|
+
cal.calibration_factor = test_numeric_value(
|
|
933
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
934
|
+
)
|
|
935
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Uncertainty":
|
|
936
|
+
cal.calibration_uncertainty = test_numeric_value(
|
|
937
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
938
|
+
)
|
|
939
|
+
elif (
|
|
940
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
941
|
+
== "Calibration Responsible Party"
|
|
942
|
+
):
|
|
943
|
+
cal.calibration_responsible_party = cont.TextValue
|
|
944
|
+
cal.save()
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
def _accumulatedmammoxraydose(dataset, accum): # TID 10005
|
|
948
|
+
|
|
949
|
+
for cont in dataset.ContentSequence:
|
|
950
|
+
if (
|
|
951
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
952
|
+
== "Accumulated Average Glandular Dose"
|
|
953
|
+
):
|
|
954
|
+
accummammo = AccumMammographyXRayDose.objects.create(
|
|
955
|
+
accumulated_xray_dose=accum
|
|
956
|
+
)
|
|
957
|
+
accummammo.accumulated_average_glandular_dose = test_numeric_value(
|
|
958
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
959
|
+
)
|
|
960
|
+
for cont2 in cont.ContentSequence:
|
|
961
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
|
|
962
|
+
accummammo.laterality = get_or_create_cid(
|
|
963
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
964
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
965
|
+
)
|
|
966
|
+
accummammo.save()
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def _accumulatedfluoroxraydose(dataset, accum): # TID 10004
|
|
970
|
+
# Name in DICOM standard for TID 10004 is Accumulated Fluoroscopy and Acquisition Projection X-Ray Dose
|
|
971
|
+
# See http://dicom.nema.org/medical/Dicom/2017e/output/chtml/part16/sect_TID_10004.html
|
|
972
|
+
|
|
973
|
+
accumproj = AccumProjXRayDose.objects.create(accumulated_xray_dose=accum)
|
|
974
|
+
for cont in dataset.ContentSequence:
|
|
975
|
+
try:
|
|
976
|
+
if (
|
|
977
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
978
|
+
== "Fluoro Dose Area Product Total"
|
|
979
|
+
):
|
|
980
|
+
accumproj.fluoro_dose_area_product_total = _check_dap_units(
|
|
981
|
+
cont.MeasuredValueSequence[0]
|
|
982
|
+
)
|
|
983
|
+
elif (
|
|
984
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "113728"
|
|
985
|
+
): # = 'Fluoro Dose (RP) Total'
|
|
986
|
+
accumproj.fluoro_dose_rp_total = _check_rp_dose_units(
|
|
987
|
+
cont.MeasuredValueSequence[0]
|
|
988
|
+
)
|
|
989
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Total Fluoro Time":
|
|
990
|
+
accumproj.total_fluoro_time = test_numeric_value(
|
|
991
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
992
|
+
)
|
|
993
|
+
elif (
|
|
994
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
995
|
+
== "Acquisition Dose Area Product Total"
|
|
996
|
+
):
|
|
997
|
+
accumproj.acquisition_dose_area_product_total = _check_dap_units(
|
|
998
|
+
cont.MeasuredValueSequence[0]
|
|
999
|
+
)
|
|
1000
|
+
elif (
|
|
1001
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "113729"
|
|
1002
|
+
): # = 'Acquisition Dose (RP) Total'
|
|
1003
|
+
accumproj.acquisition_dose_rp_total = _check_rp_dose_units(
|
|
1004
|
+
cont.MeasuredValueSequence[0]
|
|
1005
|
+
)
|
|
1006
|
+
elif (
|
|
1007
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Total Acquisition Time"
|
|
1008
|
+
):
|
|
1009
|
+
accumproj.total_acquisition_time = test_numeric_value(
|
|
1010
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1011
|
+
)
|
|
1012
|
+
# TODO: Remove the following four items, as they are also imported (correctly) into
|
|
1013
|
+
# _accumulatedtotalprojectionradiographydose
|
|
1014
|
+
elif (
|
|
1015
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product Total"
|
|
1016
|
+
):
|
|
1017
|
+
accumproj.dose_area_product_total = _check_dap_units(
|
|
1018
|
+
cont.MeasuredValueSequence[0]
|
|
1019
|
+
)
|
|
1020
|
+
elif (
|
|
1021
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "113725"
|
|
1022
|
+
): # = 'Dose (RP) Total':
|
|
1023
|
+
accumproj.dose_rp_total = _check_rp_dose_units(
|
|
1024
|
+
cont.MeasuredValueSequence[0]
|
|
1025
|
+
)
|
|
1026
|
+
elif (
|
|
1027
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1028
|
+
== "Total Number of Radiographic Frames"
|
|
1029
|
+
):
|
|
1030
|
+
accumproj.total_number_of_radiographic_frames = test_numeric_value(
|
|
1031
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1032
|
+
)
|
|
1033
|
+
elif (
|
|
1034
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1035
|
+
== "Reference Point Definition"
|
|
1036
|
+
):
|
|
1037
|
+
try:
|
|
1038
|
+
accumproj.reference_point_definition_code = get_or_create_cid(
|
|
1039
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1040
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1041
|
+
)
|
|
1042
|
+
except AttributeError:
|
|
1043
|
+
accumproj.reference_point_definition = cont.TextValue
|
|
1044
|
+
except IndexError:
|
|
1045
|
+
pass
|
|
1046
|
+
if (
|
|
1047
|
+
accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type
|
|
1048
|
+
== "RF,DX"
|
|
1049
|
+
):
|
|
1050
|
+
if accumproj.fluoro_dose_area_product_total or accumproj.total_fluoro_time:
|
|
1051
|
+
accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type = (
|
|
1052
|
+
"RF"
|
|
1053
|
+
)
|
|
1054
|
+
else:
|
|
1055
|
+
accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type = (
|
|
1056
|
+
"DX"
|
|
1057
|
+
)
|
|
1058
|
+
accumproj.save()
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def _accumulatedcassettebasedprojectionradiographydose(dataset, accum): # TID 10006
|
|
1062
|
+
|
|
1063
|
+
accumcass = AccumCassetteBsdProjRadiogDose.objects.create(
|
|
1064
|
+
accumulated_xray_dose=accum
|
|
1065
|
+
)
|
|
1066
|
+
for cont in dataset.ContentSequence:
|
|
1067
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Detector Type":
|
|
1068
|
+
accumcass.detector_type = get_or_create_cid(
|
|
1069
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1070
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1071
|
+
)
|
|
1072
|
+
elif (
|
|
1073
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1074
|
+
== "Total Number of Radiographic Frames"
|
|
1075
|
+
):
|
|
1076
|
+
accumcass.total_number_of_radiographic_frames = test_numeric_value(
|
|
1077
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1078
|
+
)
|
|
1079
|
+
accumcass.save()
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _accumulatedtotalprojectionradiographydose(dataset, accum): # TID 10007
|
|
1083
|
+
# Name in DICOM standard for TID 10007 is Accumulated Total Projection Radiography Dose
|
|
1084
|
+
# See http://dicom.nema.org/medical/Dicom/2017e/output/chtml/part16/sect_TID_10007.html
|
|
1085
|
+
|
|
1086
|
+
accumint = AccumIntegratedProjRadiogDose.objects.create(accumulated_xray_dose=accum)
|
|
1087
|
+
for cont in dataset.ContentSequence:
|
|
1088
|
+
try:
|
|
1089
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product Total":
|
|
1090
|
+
accumint.dose_area_product_total = _check_dap_units(
|
|
1091
|
+
cont.MeasuredValueSequence[0]
|
|
1092
|
+
)
|
|
1093
|
+
elif (
|
|
1094
|
+
cont.ConceptNameCodeSequence[0].CodeValue == "113725"
|
|
1095
|
+
): # = 'Dose (RP) Total':
|
|
1096
|
+
accumint.dose_rp_total = _check_rp_dose_units(
|
|
1097
|
+
cont.MeasuredValueSequence[0]
|
|
1098
|
+
)
|
|
1099
|
+
elif (
|
|
1100
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1101
|
+
== "Total Number of Radiographic Frames"
|
|
1102
|
+
):
|
|
1103
|
+
accumint.total_number_of_radiographic_frames = test_numeric_value(
|
|
1104
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1105
|
+
)
|
|
1106
|
+
elif (
|
|
1107
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1108
|
+
== "Reference Point Definition"
|
|
1109
|
+
):
|
|
1110
|
+
try:
|
|
1111
|
+
accumint.reference_point_definition_code = get_or_create_cid(
|
|
1112
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1113
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1114
|
+
)
|
|
1115
|
+
except AttributeError:
|
|
1116
|
+
accumint.reference_point_definition = cont.TextValue
|
|
1117
|
+
except IndexError:
|
|
1118
|
+
pass
|
|
1119
|
+
accumint.save()
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _accumulatedxraydose(dataset, proj): # TID 10002
|
|
1123
|
+
|
|
1124
|
+
accum = AccumXRayDose.objects.create(projection_xray_radiation_dose=proj)
|
|
1125
|
+
for cont in dataset.ContentSequence:
|
|
1126
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Plane":
|
|
1127
|
+
accum.acquisition_plane = get_or_create_cid(
|
|
1128
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1129
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1130
|
+
)
|
|
1131
|
+
if cont.ValueType == "CONTAINER":
|
|
1132
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration":
|
|
1133
|
+
_calibration(cont, accum)
|
|
1134
|
+
if proj.acquisition_device_type_cid:
|
|
1135
|
+
if (
|
|
1136
|
+
"Fluoroscopy-Guided" in proj.acquisition_device_type_cid.code_meaning
|
|
1137
|
+
or "Azurion"
|
|
1138
|
+
in proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name
|
|
1139
|
+
):
|
|
1140
|
+
_accumulatedfluoroxraydose(dataset, accum)
|
|
1141
|
+
elif proj.procedure_reported and (
|
|
1142
|
+
"Projection X-Ray" in proj.procedure_reported.code_meaning
|
|
1143
|
+
):
|
|
1144
|
+
_accumulatedfluoroxraydose(dataset, accum)
|
|
1145
|
+
if proj.procedure_reported and (
|
|
1146
|
+
proj.procedure_reported.code_meaning == "Mammography"
|
|
1147
|
+
):
|
|
1148
|
+
_accumulatedmammoxraydose(dataset, accum)
|
|
1149
|
+
if proj.acquisition_device_type_cid:
|
|
1150
|
+
if (
|
|
1151
|
+
"Integrated" in proj.acquisition_device_type_cid.code_meaning
|
|
1152
|
+
or "Fluoroscopy-Guided" in proj.acquisition_device_type_cid.code_meaning
|
|
1153
|
+
):
|
|
1154
|
+
_accumulatedtotalprojectionradiographydose(dataset, accum)
|
|
1155
|
+
elif proj.procedure_reported and (
|
|
1156
|
+
"Projection X-Ray" in proj.procedure_reported.code_meaning
|
|
1157
|
+
):
|
|
1158
|
+
_accumulatedtotalprojectionradiographydose(dataset, accum)
|
|
1159
|
+
if proj.acquisition_device_type_cid:
|
|
1160
|
+
if "Cassette-based" in proj.acquisition_device_type_cid.code_meaning:
|
|
1161
|
+
_accumulatedcassettebasedprojectionradiographydose(dataset, accum)
|
|
1162
|
+
accum.save()
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def _scanninglength(dataset, event): # TID 10014
|
|
1166
|
+
|
|
1167
|
+
scanlen = ScanningLength.objects.create(ct_irradiation_event_data=event)
|
|
1168
|
+
try:
|
|
1169
|
+
for cont in dataset.ContentSequence:
|
|
1170
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "scanning length":
|
|
1171
|
+
scanlen.scanning_length = test_numeric_value(
|
|
1172
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1173
|
+
)
|
|
1174
|
+
elif (
|
|
1175
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1176
|
+
== "length of reconstructable volume"
|
|
1177
|
+
):
|
|
1178
|
+
scanlen.length_of_reconstructable_volume = test_numeric_value(
|
|
1179
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1180
|
+
)
|
|
1181
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "exposed range":
|
|
1182
|
+
scanlen.exposed_range = test_numeric_value(
|
|
1183
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1184
|
+
)
|
|
1185
|
+
elif (
|
|
1186
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1187
|
+
== "top z location of reconstructable volume"
|
|
1188
|
+
):
|
|
1189
|
+
scanlen.top_z_location_of_reconstructable_volume = test_numeric_value(
|
|
1190
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1191
|
+
)
|
|
1192
|
+
elif (
|
|
1193
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1194
|
+
== "bottom z location of reconstructable volume"
|
|
1195
|
+
):
|
|
1196
|
+
scanlen.bottom_z_location_of_reconstructable_volume = (
|
|
1197
|
+
test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
|
|
1198
|
+
)
|
|
1199
|
+
elif (
|
|
1200
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1201
|
+
== "top z location of scanning length"
|
|
1202
|
+
):
|
|
1203
|
+
scanlen.top_z_location_of_scanning_length = test_numeric_value(
|
|
1204
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1205
|
+
)
|
|
1206
|
+
elif (
|
|
1207
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1208
|
+
== "bottom z location of scanning length"
|
|
1209
|
+
):
|
|
1210
|
+
scanlen.bottom_z_location_of_scanning_length = test_numeric_value(
|
|
1211
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1212
|
+
)
|
|
1213
|
+
elif (
|
|
1214
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1215
|
+
== "irradiation event uid"
|
|
1216
|
+
):
|
|
1217
|
+
scanlen.irradiation_event_uid = cont.UID
|
|
1218
|
+
scanlen.save()
|
|
1219
|
+
except AttributeError:
|
|
1220
|
+
pass
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def _ctxraysourceparameters(dataset, event):
|
|
1224
|
+
|
|
1225
|
+
param = CtXRaySourceParameters.objects.create(ct_irradiation_event_data=event)
|
|
1226
|
+
for cont in dataset.ContentSequence:
|
|
1227
|
+
if (
|
|
1228
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1229
|
+
== "identification of the x-ray source"
|
|
1230
|
+
or cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1231
|
+
== "identification number of the x-ray source"
|
|
1232
|
+
):
|
|
1233
|
+
param.identification_of_the_xray_source = cont.TextValue
|
|
1234
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "KVP":
|
|
1235
|
+
param.kvp = test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
|
|
1236
|
+
elif (
|
|
1237
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1238
|
+
== "maximum x-ray tube current"
|
|
1239
|
+
):
|
|
1240
|
+
param.maximum_xray_tube_current = test_numeric_value(
|
|
1241
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1242
|
+
)
|
|
1243
|
+
elif (
|
|
1244
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "x-ray tube current"
|
|
1245
|
+
):
|
|
1246
|
+
param.xray_tube_current = test_numeric_value(
|
|
1247
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1248
|
+
)
|
|
1249
|
+
elif cont.ConceptNameCodeSequence[0].CodeValue == "113734":
|
|
1250
|
+
# Additional check as code meaning is wrong for Siemens Intevo see
|
|
1251
|
+
# https://bitbucket.org/openrem/openrem/issues/380/siemens-intevo-rdsr-have-wrong-code
|
|
1252
|
+
param.xray_tube_current = test_numeric_value(
|
|
1253
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1254
|
+
)
|
|
1255
|
+
elif (
|
|
1256
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Time per Rotation"
|
|
1257
|
+
):
|
|
1258
|
+
param.exposure_time_per_rotation = test_numeric_value(
|
|
1259
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1260
|
+
)
|
|
1261
|
+
elif (
|
|
1262
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1263
|
+
== "x-ray filter aluminum equivalent"
|
|
1264
|
+
):
|
|
1265
|
+
param.xray_filter_aluminum_equivalent = test_numeric_value(
|
|
1266
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1267
|
+
)
|
|
1268
|
+
param.save()
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def _ctdosecheckdetails(dataset, dosecheckdetails, isalertdetails): # TID 10015
|
|
1272
|
+
# PARTLY TESTED CODE (no DSR available that has Reason For Proceeding and/or Forward Estimate)
|
|
1273
|
+
|
|
1274
|
+
if isalertdetails:
|
|
1275
|
+
for cont in dataset.ContentSequence:
|
|
1276
|
+
if (
|
|
1277
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1278
|
+
== "DLP Alert Value Configured"
|
|
1279
|
+
):
|
|
1280
|
+
dosecheckdetails.dlp_alert_value_configured = (
|
|
1281
|
+
cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
|
|
1282
|
+
)
|
|
1283
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Alert Value":
|
|
1284
|
+
dosecheckdetails.dlp_alert_value = test_numeric_value(
|
|
1285
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1286
|
+
)
|
|
1287
|
+
if (
|
|
1288
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1289
|
+
== "CTDIvol Alert Value Configured"
|
|
1290
|
+
):
|
|
1291
|
+
dosecheckdetails.ctdivol_alert_value_configured = (
|
|
1292
|
+
cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
|
|
1293
|
+
)
|
|
1294
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "CTDIvol Alert Value":
|
|
1295
|
+
dosecheckdetails.ctdivol_alert_value = test_numeric_value(
|
|
1296
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1297
|
+
)
|
|
1298
|
+
if (
|
|
1299
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1300
|
+
== "Accumulated DLP Forward Estimate"
|
|
1301
|
+
):
|
|
1302
|
+
dosecheckdetails.accumulated_dlp_forward_estimate = test_numeric_value(
|
|
1303
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1304
|
+
)
|
|
1305
|
+
if (
|
|
1306
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1307
|
+
== "Accumulated CTDIvol Forward Estimate"
|
|
1308
|
+
):
|
|
1309
|
+
dosecheckdetails.accumulated_ctdivol_forward_estimate = (
|
|
1310
|
+
test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
|
|
1311
|
+
)
|
|
1312
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Reason For Proceeding":
|
|
1313
|
+
dosecheckdetails.alert_reason_for_proceeding = cont.TextValue
|
|
1314
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Person Name":
|
|
1315
|
+
person_participant(
|
|
1316
|
+
cont, "ct_dose_check_alert", dosecheckdetails, logger
|
|
1317
|
+
)
|
|
1318
|
+
else:
|
|
1319
|
+
for cont in dataset.ContentSequence:
|
|
1320
|
+
if (
|
|
1321
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1322
|
+
== "DLP Notification Value Configured"
|
|
1323
|
+
):
|
|
1324
|
+
dosecheckdetails.dlp_notification_value_configured = (
|
|
1325
|
+
cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
|
|
1326
|
+
)
|
|
1327
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Notification Value":
|
|
1328
|
+
dosecheckdetails.dlp_notification_value = test_numeric_value(
|
|
1329
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1330
|
+
)
|
|
1331
|
+
if (
|
|
1332
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1333
|
+
== "CTDIvol Notification Value Configured"
|
|
1334
|
+
):
|
|
1335
|
+
dosecheckdetails.ctdivol_notification_value_configured = (
|
|
1336
|
+
cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
|
|
1337
|
+
)
|
|
1338
|
+
if (
|
|
1339
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1340
|
+
== "CTDIvol Notification Value"
|
|
1341
|
+
):
|
|
1342
|
+
dosecheckdetails.ctdivol_notification_value = test_numeric_value(
|
|
1343
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1344
|
+
)
|
|
1345
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Forward Estimate":
|
|
1346
|
+
dosecheckdetails.dlp_forward_estimate = test_numeric_value(
|
|
1347
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1348
|
+
)
|
|
1349
|
+
if (
|
|
1350
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1351
|
+
== "CTDIvol Forward Estimate"
|
|
1352
|
+
):
|
|
1353
|
+
dosecheckdetails.ctdivol_forward_estimate = test_numeric_value(
|
|
1354
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1355
|
+
)
|
|
1356
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Reason For Proceeding":
|
|
1357
|
+
dosecheckdetails.notification_reason_for_proceeding = cont.TextValue
|
|
1358
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Person Name":
|
|
1359
|
+
person_participant(
|
|
1360
|
+
cont, "ct_dose_check_notification", dosecheckdetails, logger
|
|
1361
|
+
)
|
|
1362
|
+
dosecheckdetails.save()
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
def _ctirradiationeventdata(dataset, ct): # TID 10013
|
|
1366
|
+
|
|
1367
|
+
event = CtIrradiationEventData.objects.create(ct_radiation_dose=ct)
|
|
1368
|
+
ctdosecheckdetails = None
|
|
1369
|
+
for cont in dataset.ContentSequence:
|
|
1370
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Protocol":
|
|
1371
|
+
event.acquisition_protocol = cont.TextValue
|
|
1372
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Region":
|
|
1373
|
+
try:
|
|
1374
|
+
event.target_region = get_or_create_cid(
|
|
1375
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1376
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1377
|
+
)
|
|
1378
|
+
except (AttributeError, IndexError):
|
|
1379
|
+
logger.info(
|
|
1380
|
+
"Target Region ConceptNameCodeSequence exists, but no content. Study UID {0} from {1}, "
|
|
1381
|
+
"{2}, {3}".format(
|
|
1382
|
+
event.ct_radiation_dose.general_study_module_attributes.study_instance_uid,
|
|
1383
|
+
event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer,
|
|
1384
|
+
event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name,
|
|
1385
|
+
event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().station_name,
|
|
1386
|
+
)
|
|
1387
|
+
)
|
|
1388
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Acquisition Type":
|
|
1389
|
+
event.ct_acquisition_type = get_or_create_cid(
|
|
1390
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1391
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1392
|
+
)
|
|
1393
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Procedure Context":
|
|
1394
|
+
event.procedure_context = get_or_create_cid(
|
|
1395
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1396
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1397
|
+
)
|
|
1398
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event UID":
|
|
1399
|
+
event.irradiation_event_uid = cont.UID
|
|
1400
|
+
event.save()
|
|
1401
|
+
if cont.ValueType == "CONTAINER":
|
|
1402
|
+
if (
|
|
1403
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1404
|
+
== "CT Acquisition Parameters"
|
|
1405
|
+
):
|
|
1406
|
+
_scanninglength(cont, event)
|
|
1407
|
+
try:
|
|
1408
|
+
for cont2 in cont.ContentSequence:
|
|
1409
|
+
if (
|
|
1410
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1411
|
+
== "Exposure Time"
|
|
1412
|
+
):
|
|
1413
|
+
event.exposure_time = test_numeric_value(
|
|
1414
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1415
|
+
)
|
|
1416
|
+
elif (
|
|
1417
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1418
|
+
== "Nominal Single Collimation Width"
|
|
1419
|
+
):
|
|
1420
|
+
event.nominal_single_collimation_width = test_numeric_value(
|
|
1421
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1422
|
+
)
|
|
1423
|
+
elif (
|
|
1424
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1425
|
+
== "Nominal Total Collimation Width"
|
|
1426
|
+
):
|
|
1427
|
+
event.nominal_total_collimation_width = test_numeric_value(
|
|
1428
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1429
|
+
)
|
|
1430
|
+
elif (
|
|
1431
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1432
|
+
== "Pitch Factor"
|
|
1433
|
+
):
|
|
1434
|
+
event.pitch_factor = test_numeric_value(
|
|
1435
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1436
|
+
)
|
|
1437
|
+
elif (
|
|
1438
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1439
|
+
== "number of x-ray sources"
|
|
1440
|
+
):
|
|
1441
|
+
event.number_of_xray_sources = test_numeric_value(
|
|
1442
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1443
|
+
)
|
|
1444
|
+
if cont2.ValueType == "CONTAINER":
|
|
1445
|
+
if (
|
|
1446
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1447
|
+
== "ct x-ray source parameters"
|
|
1448
|
+
):
|
|
1449
|
+
_ctxraysourceparameters(cont2, event)
|
|
1450
|
+
except AttributeError:
|
|
1451
|
+
pass
|
|
1452
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Dose":
|
|
1453
|
+
for cont2 in cont.ContentSequence:
|
|
1454
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Mean CTDIvol":
|
|
1455
|
+
event.mean_ctdivol = test_numeric_value(
|
|
1456
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1457
|
+
)
|
|
1458
|
+
elif (
|
|
1459
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1460
|
+
== "CTDIw Phantom Type"
|
|
1461
|
+
):
|
|
1462
|
+
event.ctdiw_phantom_type = get_or_create_cid(
|
|
1463
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
1464
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
1465
|
+
)
|
|
1466
|
+
elif (
|
|
1467
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1468
|
+
== "CTDIfreeair Calculation Factor"
|
|
1469
|
+
):
|
|
1470
|
+
event.ctdifreeair_calculation_factor = test_numeric_value(
|
|
1471
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1472
|
+
)
|
|
1473
|
+
elif (
|
|
1474
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1475
|
+
== "Mean CTDIfreeair"
|
|
1476
|
+
):
|
|
1477
|
+
event.mean_ctdifreeair = test_numeric_value(
|
|
1478
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1479
|
+
)
|
|
1480
|
+
elif cont2.ConceptNameCodeSequence[0].CodeMeaning == "DLP":
|
|
1481
|
+
event.dlp = test_numeric_value(
|
|
1482
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1483
|
+
)
|
|
1484
|
+
elif (
|
|
1485
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning == "Effective Dose"
|
|
1486
|
+
):
|
|
1487
|
+
event.effective_dose = test_numeric_value(
|
|
1488
|
+
cont2.MeasuredValueSequence[0].NumericValue
|
|
1489
|
+
)
|
|
1490
|
+
## Effective dose measurement method and conversion factor
|
|
1491
|
+
## CT Dose Check Details
|
|
1492
|
+
## Dose Check Alert Details and Notifications Details can appear indepently
|
|
1493
|
+
elif (
|
|
1494
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1495
|
+
== "Dose Check Alert Details"
|
|
1496
|
+
):
|
|
1497
|
+
if ctdosecheckdetails is None:
|
|
1498
|
+
ctdosecheckdetails = CtDoseCheckDetails.objects.create(
|
|
1499
|
+
ct_irradiation_event_data=event
|
|
1500
|
+
)
|
|
1501
|
+
_ctdosecheckdetails(cont2, ctdosecheckdetails, True)
|
|
1502
|
+
elif (
|
|
1503
|
+
cont2.ConceptNameCodeSequence[0].CodeMeaning
|
|
1504
|
+
== "Dose Check Notification Details"
|
|
1505
|
+
):
|
|
1506
|
+
if ctdosecheckdetails is None:
|
|
1507
|
+
ctdosecheckdetails = CtDoseCheckDetails.objects.create(
|
|
1508
|
+
ct_irradiation_event_data=event
|
|
1509
|
+
)
|
|
1510
|
+
_ctdosecheckdetails(cont2, ctdosecheckdetails, False)
|
|
1511
|
+
if (
|
|
1512
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1513
|
+
== "x-ray modulation type"
|
|
1514
|
+
):
|
|
1515
|
+
event.xray_modulation_type = cont.TextValue
|
|
1516
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
|
|
1517
|
+
event.comment = cont.TextValue
|
|
1518
|
+
if not event.xray_modulation_type and event.comment:
|
|
1519
|
+
comments = event.comment.split(",")
|
|
1520
|
+
for comm in comments:
|
|
1521
|
+
if comm.lstrip().startswith("X-ray Modulation Type"):
|
|
1522
|
+
modulationtype = comm[(comm.find("=") + 2) :]
|
|
1523
|
+
event.xray_modulation_type = modulationtype
|
|
1524
|
+
|
|
1525
|
+
## personparticipant here
|
|
1526
|
+
_deviceparticipant(dataset, "ct_event", event)
|
|
1527
|
+
if ctdosecheckdetails is not None:
|
|
1528
|
+
ctdosecheckdetails.save()
|
|
1529
|
+
event.save()
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
def _ctaccumulateddosedata(dataset, ct): # TID 10012
|
|
1533
|
+
|
|
1534
|
+
ctacc = CtAccumulatedDoseData.objects.create(ct_radiation_dose=ct)
|
|
1535
|
+
for cont in dataset.ContentSequence:
|
|
1536
|
+
if (
|
|
1537
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1538
|
+
== "Total Number of Irradiation Events"
|
|
1539
|
+
):
|
|
1540
|
+
ctacc.total_number_of_irradiation_events = test_numeric_value(
|
|
1541
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1542
|
+
)
|
|
1543
|
+
elif (
|
|
1544
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1545
|
+
== "CT Dose Length Product Total"
|
|
1546
|
+
):
|
|
1547
|
+
ctacc.ct_dose_length_product_total = test_numeric_value(
|
|
1548
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1549
|
+
)
|
|
1550
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Effective Dose Total":
|
|
1551
|
+
ctacc.ct_effective_dose_total = test_numeric_value(
|
|
1552
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1553
|
+
)
|
|
1554
|
+
#
|
|
1555
|
+
# Reference authority code or name belongs here, followed by the effective dose details
|
|
1556
|
+
#
|
|
1557
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
|
|
1558
|
+
ctacc.comment = cont.TextValue
|
|
1559
|
+
_deviceparticipant(dataset, "ct_accumulated", ctacc)
|
|
1560
|
+
|
|
1561
|
+
ctacc.save()
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
def _import_varic(dataset, proj):
|
|
1565
|
+
|
|
1566
|
+
for cont in dataset.ContentSequence:
|
|
1567
|
+
if cont.ConceptNameCodeSequence[0].CodeValue == "C-200":
|
|
1568
|
+
accum = AccumXRayDose.objects.create(projection_xray_radiation_dose=proj)
|
|
1569
|
+
accum.acquisition_plane = get_or_create_cid("113622", "Single Plane")
|
|
1570
|
+
accum.save()
|
|
1571
|
+
accumint = AccumIntegratedProjRadiogDose.objects.create(
|
|
1572
|
+
accumulated_xray_dose=accum
|
|
1573
|
+
)
|
|
1574
|
+
try:
|
|
1575
|
+
accumint.total_number_of_radiographic_frames = test_numeric_value(
|
|
1576
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1577
|
+
)
|
|
1578
|
+
except IndexError:
|
|
1579
|
+
pass
|
|
1580
|
+
accumint.save()
|
|
1581
|
+
if cont.ConceptNameCodeSequence[0].CodeValue == "C-202":
|
|
1582
|
+
accumproj = AccumProjXRayDose.objects.create(accumulated_xray_dose=accum)
|
|
1583
|
+
accumproj.save()
|
|
1584
|
+
try:
|
|
1585
|
+
accumproj.total_fluoro_time = test_numeric_value(
|
|
1586
|
+
cont.MeasuredValueSequence[0].NumericValue
|
|
1587
|
+
)
|
|
1588
|
+
except IndexError:
|
|
1589
|
+
pass
|
|
1590
|
+
accumproj.save()
|
|
1591
|
+
if cont.ConceptNameCodeSequence[0].CodeValue == "C-204":
|
|
1592
|
+
try:
|
|
1593
|
+
accumint.dose_area_product_total = (
|
|
1594
|
+
test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
|
|
1595
|
+
/ 10000.0
|
|
1596
|
+
)
|
|
1597
|
+
except (TypeError, IndexError):
|
|
1598
|
+
pass
|
|
1599
|
+
accumint.save()
|
|
1600
|
+
# cumulative air kerma in dose report example is not available ("Measurement not attempted")
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
def projectionxrayradiationdose(dataset, g, reporttype):
|
|
1604
|
+
|
|
1605
|
+
if reporttype == "projection":
|
|
1606
|
+
proj = ProjectionXRayRadiationDose.objects.create(
|
|
1607
|
+
general_study_module_attributes=g
|
|
1608
|
+
)
|
|
1609
|
+
elif reporttype == "ct":
|
|
1610
|
+
proj = CtRadiationDose.objects.create(general_study_module_attributes=g)
|
|
1611
|
+
else:
|
|
1612
|
+
logger.error(
|
|
1613
|
+
"Attempt to create ProjectionXRayRadiationDose failed as report type incorrect"
|
|
1614
|
+
)
|
|
1615
|
+
return
|
|
1616
|
+
equip = GeneralEquipmentModuleAttr.objects.get(general_study_module_attributes=g)
|
|
1617
|
+
proj.general_study_module_attributes.modality_type = (
|
|
1618
|
+
equip.unique_equipment_name.user_defined_modality
|
|
1619
|
+
)
|
|
1620
|
+
if proj.general_study_module_attributes.modality_type == "dual":
|
|
1621
|
+
proj.general_study_module_attributes.modality_type = None
|
|
1622
|
+
|
|
1623
|
+
if (
|
|
1624
|
+
dataset.ConceptNameCodeSequence[0].CodingSchemeDesignator == "99SMS_RADSUM"
|
|
1625
|
+
and dataset.ConceptNameCodeSequence[0].CodeValue == "C-10"
|
|
1626
|
+
):
|
|
1627
|
+
g.modality_type = "RF"
|
|
1628
|
+
g.save()
|
|
1629
|
+
_import_varic(dataset, proj)
|
|
1630
|
+
else:
|
|
1631
|
+
for cont in dataset.ContentSequence:
|
|
1632
|
+
if (
|
|
1633
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1634
|
+
== "procedure reported"
|
|
1635
|
+
):
|
|
1636
|
+
proj.procedure_reported = get_or_create_cid(
|
|
1637
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1638
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1639
|
+
)
|
|
1640
|
+
if (
|
|
1641
|
+
"ContentSequence" in cont
|
|
1642
|
+
): # Extra if statement to allow for non-conformant GE RDSR that don't have this mandatory field.
|
|
1643
|
+
for cont2 in cont.ContentSequence:
|
|
1644
|
+
if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Has Intent":
|
|
1645
|
+
proj.has_intent = get_or_create_cid(
|
|
1646
|
+
cont2.ConceptCodeSequence[0].CodeValue,
|
|
1647
|
+
cont2.ConceptCodeSequence[0].CodeMeaning,
|
|
1648
|
+
)
|
|
1649
|
+
if "Mammography" in proj.procedure_reported.code_meaning:
|
|
1650
|
+
proj.general_study_module_attributes.modality_type = "MG"
|
|
1651
|
+
elif (not proj.general_study_module_attributes.modality_type) and (
|
|
1652
|
+
"Projection X-Ray" in proj.procedure_reported.code_meaning
|
|
1653
|
+
):
|
|
1654
|
+
proj.general_study_module_attributes.modality_type = "RF,DX"
|
|
1655
|
+
elif (
|
|
1656
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1657
|
+
== "acquisition device type"
|
|
1658
|
+
):
|
|
1659
|
+
proj.acquisition_device_type_cid = get_or_create_cid(
|
|
1660
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1661
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1662
|
+
)
|
|
1663
|
+
elif (
|
|
1664
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1665
|
+
== "start of x-ray irradiation"
|
|
1666
|
+
):
|
|
1667
|
+
proj.start_of_xray_irradiation = make_date_time(cont.DateTime)
|
|
1668
|
+
elif (
|
|
1669
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
|
|
1670
|
+
== "end of x-ray irradiation"
|
|
1671
|
+
):
|
|
1672
|
+
proj.end_of_xray_irradiation = make_date_time(cont.DateTime)
|
|
1673
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Scope of Accumulation":
|
|
1674
|
+
proj.scope_of_accumulation = get_or_create_cid(
|
|
1675
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1676
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1677
|
+
)
|
|
1678
|
+
elif (
|
|
1679
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1680
|
+
== "X-Ray Detector Data Available"
|
|
1681
|
+
):
|
|
1682
|
+
proj.xray_detector_data_available = get_or_create_cid(
|
|
1683
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1684
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1685
|
+
)
|
|
1686
|
+
elif (
|
|
1687
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1688
|
+
== "X-Ray Source Data Available"
|
|
1689
|
+
):
|
|
1690
|
+
proj.xray_source_data_available = get_or_create_cid(
|
|
1691
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1692
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1693
|
+
)
|
|
1694
|
+
elif (
|
|
1695
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1696
|
+
== "X-Ray Mechanical Data Available"
|
|
1697
|
+
):
|
|
1698
|
+
proj.xray_mechanical_data_available = get_or_create_cid(
|
|
1699
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1700
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1701
|
+
)
|
|
1702
|
+
elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
|
|
1703
|
+
proj.comment = cont.TextValue
|
|
1704
|
+
elif (
|
|
1705
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1706
|
+
== "Source of Dose Information"
|
|
1707
|
+
):
|
|
1708
|
+
proj.source_of_dose_information = get_or_create_cid(
|
|
1709
|
+
cont.ConceptCodeSequence[0].CodeValue,
|
|
1710
|
+
cont.ConceptCodeSequence[0].CodeMeaning,
|
|
1711
|
+
)
|
|
1712
|
+
if (
|
|
1713
|
+
(not equip.unique_equipment_name.user_defined_modality)
|
|
1714
|
+
and (reporttype == "projection")
|
|
1715
|
+
and proj.acquisition_device_type_cid
|
|
1716
|
+
):
|
|
1717
|
+
if (
|
|
1718
|
+
"Fluoroscopy-Guided"
|
|
1719
|
+
in proj.acquisition_device_type_cid.code_meaning
|
|
1720
|
+
or "Azurion"
|
|
1721
|
+
in proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name
|
|
1722
|
+
):
|
|
1723
|
+
proj.general_study_module_attributes.modality_type = "RF"
|
|
1724
|
+
elif any(
|
|
1725
|
+
x in proj.acquisition_device_type_cid.code_meaning
|
|
1726
|
+
for x in ["Integrated", "Cassette-based"]
|
|
1727
|
+
):
|
|
1728
|
+
proj.general_study_module_attributes.modality_type = "DX"
|
|
1729
|
+
else:
|
|
1730
|
+
logging.error(
|
|
1731
|
+
"Acquisition device type code exists, but the value wasn't matched. Study UID: {0}, "
|
|
1732
|
+
"Station name: {1}, Study date, time: {2}, {3}, device type: {4} ".format(
|
|
1733
|
+
proj.general_study_module_attributes.study_instance_uid,
|
|
1734
|
+
proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().station_name,
|
|
1735
|
+
proj.general_study_module_attributes.study_date,
|
|
1736
|
+
proj.general_study_module_attributes.study_time,
|
|
1737
|
+
proj.acquisition_device_type_cid.code_meaning,
|
|
1738
|
+
)
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
proj.save()
|
|
1742
|
+
|
|
1743
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "Observer Type":
|
|
1744
|
+
if reporttype == "projection":
|
|
1745
|
+
obs = ObserverContext.objects.create(
|
|
1746
|
+
projection_xray_radiation_dose=proj
|
|
1747
|
+
)
|
|
1748
|
+
else:
|
|
1749
|
+
obs = ObserverContext.objects.create(ct_radiation_dose=proj)
|
|
1750
|
+
observercontext(dataset, obs)
|
|
1751
|
+
|
|
1752
|
+
if cont.ValueType == "CONTAINER":
|
|
1753
|
+
if (
|
|
1754
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1755
|
+
== "Accumulated X-Ray Dose Data"
|
|
1756
|
+
):
|
|
1757
|
+
_accumulatedxraydose(cont, proj)
|
|
1758
|
+
if (
|
|
1759
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1760
|
+
== "Irradiation Event X-Ray Data"
|
|
1761
|
+
):
|
|
1762
|
+
_irradiationeventxraydata(cont, proj, dataset)
|
|
1763
|
+
if (
|
|
1764
|
+
cont.ConceptNameCodeSequence[0].CodeMeaning
|
|
1765
|
+
== "CT Accumulated Dose Data"
|
|
1766
|
+
):
|
|
1767
|
+
if proj.general_study_module_attributes.modality_type != "NM":
|
|
1768
|
+
proj.general_study_module_attributes.modality_type = "CT"
|
|
1769
|
+
_ctaccumulateddosedata(cont, proj)
|
|
1770
|
+
if cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Acquisition":
|
|
1771
|
+
_ctirradiationeventdata(cont, proj)
|