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.
Files changed (279) hide show
  1. openrem/locale/de/LC_MESSAGES/django.po +1060 -1059
  2. openrem/locale/django.pot +973 -972
  3. openrem/locale/es_MX/LC_MESSAGES/django.po +1049 -1048
  4. openrem/locale/it/LC_MESSAGES/django.po +1044 -1043
  5. openrem/locale/lt/LC_MESSAGES/django.po +989 -988
  6. openrem/locale/nb_NO/LC_MESSAGES/django.po +985 -984
  7. openrem/locale/pt_BR/LC_MESSAGES/django.po +1003 -1002
  8. openrem/manage.py +10 -10
  9. openrem/openremproject/__init__.py +1 -1
  10. openrem/openremproject/local_settings.py.linux +128 -128
  11. openrem/openremproject/local_settings.py.windows +144 -144
  12. openrem/openremproject/local_settings.py.windows-sqlite3 +129 -129
  13. openrem/openremproject/settings.py +278 -278
  14. openrem/openremproject/urls.py +32 -32
  15. openrem/openremproject/wsgi.py.example +28 -28
  16. openrem/remapp/__init__.py +2 -2
  17. openrem/remapp/admin.py +31 -31
  18. openrem/remapp/exports/ct_export.py +780 -753
  19. openrem/remapp/exports/dx_export.py +817 -805
  20. openrem/remapp/exports/export_common.py +931 -951
  21. openrem/remapp/exports/export_common_pandas.py +2422 -0
  22. openrem/remapp/exports/exportviews.py +815 -860
  23. openrem/remapp/exports/mg_csv_nhsbsp.py +292 -292
  24. openrem/remapp/exports/mg_export.py +673 -510
  25. openrem/remapp/exports/nm_export.py +796 -575
  26. openrem/remapp/exports/rf_export.py +1418 -1431
  27. openrem/remapp/extractors/ct_philips.py +424 -414
  28. openrem/remapp/extractors/ct_toshiba.py +2116 -2108
  29. openrem/remapp/extractors/dx.py +1033 -952
  30. openrem/remapp/extractors/extract_common.py +817 -817
  31. openrem/remapp/extractors/import_views.py +426 -426
  32. openrem/remapp/extractors/mam.py +685 -672
  33. openrem/remapp/extractors/nm_image.py +439 -431
  34. openrem/remapp/extractors/ptsizecsv2db.py +368 -368
  35. openrem/remapp/extractors/rdsr.py +667 -654
  36. openrem/remapp/extractors/rdsr_methods.py +1771 -1768
  37. openrem/remapp/extractors/rrdsr_methods.py +630 -622
  38. openrem/remapp/fixtures/openskin_safelist.json +11 -11
  39. openrem/remapp/forms.py +2286 -2277
  40. openrem/remapp/interface/chart_functions.py +2412 -2393
  41. openrem/remapp/interface/mod_filters.py +1241 -1243
  42. openrem/remapp/migrations/0001_initial.py.1-0-upgrade +1043 -1043
  43. openrem/remapp/models.py +3418 -3407
  44. openrem/remapp/netdicom/dicomviews.py +681 -683
  45. openrem/remapp/netdicom/qrscu.py +2646 -2646
  46. openrem/remapp/netdicom/tools.py +134 -134
  47. openrem/remapp/static/css/bootstrap-theme.css +587 -587
  48. openrem/remapp/static/css/bootstrap-theme.min.css +4 -4
  49. openrem/remapp/static/css/bootstrap.css +6800 -6800
  50. openrem/remapp/static/css/bootstrap.min.css +4 -4
  51. openrem/remapp/static/css/datepicker3.css +790 -790
  52. openrem/remapp/static/css/jquery.qtip.min.css +2 -2
  53. openrem/remapp/static/css/openrem-extra.css +442 -442
  54. openrem/remapp/static/css/openrem.css +96 -96
  55. openrem/remapp/static/css/registration.css +34 -34
  56. openrem/remapp/static/fonts/glyphicons-halflings-regular.svg +287 -287
  57. openrem/remapp/static/js/bootstrap-datepicker.js +1671 -1671
  58. openrem/remapp/static/js/bootstrap.js +2363 -2363
  59. openrem/remapp/static/js/bootstrap.min.js +6 -6
  60. openrem/remapp/static/js/charts/chartCommonFunctions.js +75 -75
  61. openrem/remapp/static/js/charts/chartFullScreen.js +41 -41
  62. openrem/remapp/static/js/charts/ctChartAjax.js +331 -331
  63. openrem/remapp/static/js/charts/dxChartAjax.js +290 -290
  64. openrem/remapp/static/js/charts/mgChartAjax.js +144 -144
  65. openrem/remapp/static/js/charts/nmChartAjax.js +64 -64
  66. openrem/remapp/static/js/charts/plotly-2.35.2.min.js +8 -0
  67. openrem/remapp/static/js/charts/rfChartAjax.js +128 -128
  68. openrem/remapp/static/js/chroma.min.js +32 -32
  69. openrem/remapp/static/js/datepicker.js +5 -5
  70. openrem/remapp/static/js/dicom.js +115 -115
  71. openrem/remapp/static/js/django_reverse/reverse.js +13 -13
  72. openrem/remapp/static/js/formatDate.js +7 -7
  73. openrem/remapp/static/js/html5shiv.min.js +8 -8
  74. openrem/remapp/static/js/jquery-1.11.0.min.js +4 -4
  75. openrem/remapp/static/js/npm.js +12 -12
  76. openrem/remapp/static/js/respond.min.js +4 -4
  77. openrem/remapp/static/js/skin-dose-maps/jquery.qtip.min.js +4 -4
  78. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dHUDObject.js +112 -112
  79. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dObject.js +367 -367
  80. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dPersonObject.js +158 -158
  81. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapColourScaleObject.js +153 -153
  82. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapObject.js +367 -367
  83. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping.js +584 -584
  84. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping3d.js +255 -255
  85. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMappingAjax.js +267 -212
  86. openrem/remapp/static/js/skin-dose-maps/three.min.js +835 -835
  87. openrem/remapp/static/js/sorttable.js +495 -495
  88. openrem/remapp/templates/base.html +253 -253
  89. openrem/remapp/templates/registration/changepassword.html +25 -25
  90. openrem/remapp/templates/registration/changepassworddone.html +12 -12
  91. openrem/remapp/templates/registration/login.html +42 -42
  92. openrem/remapp/templates/remapp/backgroundtaskmaximumrows_form.html +29 -29
  93. openrem/remapp/templates/remapp/base.html +1 -1
  94. openrem/remapp/templates/remapp/ctdetail.html +235 -235
  95. openrem/remapp/templates/remapp/ctfiltered.html +310 -310
  96. openrem/remapp/templates/remapp/dicomdeletesettings_form.html +31 -31
  97. openrem/remapp/templates/remapp/dicomqr.html +147 -147
  98. openrem/remapp/templates/remapp/dicomquerydetails.html +83 -83
  99. openrem/remapp/templates/remapp/dicomqueryimages.html +49 -49
  100. openrem/remapp/templates/remapp/dicomqueryseries.html +109 -109
  101. openrem/remapp/templates/remapp/dicomquerysummary.html +48 -48
  102. openrem/remapp/templates/remapp/dicomremoteqr_confirm_delete.html +60 -60
  103. openrem/remapp/templates/remapp/dicomremoteqr_form.html +32 -32
  104. openrem/remapp/templates/remapp/dicomstorescp_confirm_delete.html +53 -53
  105. openrem/remapp/templates/remapp/dicomstorescp_form.html +48 -48
  106. openrem/remapp/templates/remapp/dicomsummary.html +257 -257
  107. openrem/remapp/templates/remapp/displaychartoptions.html +184 -184
  108. openrem/remapp/templates/remapp/displayhomepageoptions.html +57 -57
  109. openrem/remapp/templates/remapp/displayname-count.html +6 -6
  110. openrem/remapp/templates/remapp/displayname-last-date.html +3 -3
  111. openrem/remapp/templates/remapp/displayname-modality.html +86 -105
  112. openrem/remapp/templates/remapp/displayname-skinmap.html +18 -18
  113. openrem/remapp/templates/remapp/displaynameupdate.html +100 -100
  114. openrem/remapp/templates/remapp/displaynameview.html +222 -219
  115. openrem/remapp/templates/remapp/dxdetail.html +176 -176
  116. openrem/remapp/templates/remapp/dxfiltered.html +324 -324
  117. openrem/remapp/templates/remapp/exports-active.html +25 -25
  118. openrem/remapp/templates/remapp/exports-complete.html +35 -35
  119. openrem/remapp/templates/remapp/exports-error.html +26 -26
  120. openrem/remapp/templates/remapp/exports-queue.html +18 -18
  121. openrem/remapp/templates/remapp/exports.html +191 -191
  122. openrem/remapp/templates/remapp/failed_summary_list.html +27 -27
  123. openrem/remapp/templates/remapp/filteredbase.html +162 -162
  124. openrem/remapp/templates/remapp/highdosemetricalertsettings_form.html +76 -76
  125. openrem/remapp/templates/remapp/home-list-modalities.html +94 -94
  126. openrem/remapp/templates/remapp/home.html +202 -202
  127. openrem/remapp/templates/remapp/list_filters.html +24 -24
  128. openrem/remapp/templates/remapp/mgdetail.html +160 -138
  129. openrem/remapp/templates/remapp/mgfiltered.html +311 -311
  130. openrem/remapp/templates/remapp/nmdetail.html +300 -300
  131. openrem/remapp/templates/remapp/nmfiltered.html +255 -255
  132. openrem/remapp/templates/remapp/notpatient.html +190 -190
  133. openrem/remapp/templates/remapp/notpatientindicators_form_base.html +81 -81
  134. openrem/remapp/templates/remapp/notpatientindicatorsid_confirm_delete.html +54 -54
  135. openrem/remapp/templates/remapp/notpatientindicatorsid_form.html +23 -23
  136. openrem/remapp/templates/remapp/notpatientindicatorsname_confirm_delete.html +54 -54
  137. openrem/remapp/templates/remapp/notpatientindicatorsname_form.html +23 -23
  138. openrem/remapp/templates/remapp/notpatientindicatorsname_form_base.html +85 -85
  139. openrem/remapp/templates/remapp/openskinsafelist_add.html +130 -130
  140. openrem/remapp/templates/remapp/openskinsafelist_confirm_delete.html +100 -100
  141. openrem/remapp/templates/remapp/openskinsafelist_form.html +207 -207
  142. openrem/remapp/templates/remapp/patientidsettings_form.html +83 -83
  143. openrem/remapp/templates/remapp/populate_summary_progress.html +83 -83
  144. openrem/remapp/templates/remapp/populate_summary_progress_error.html +36 -36
  145. openrem/remapp/templates/remapp/review_failed_imports.html +157 -157
  146. openrem/remapp/templates/remapp/review_failed_study.html +41 -41
  147. openrem/remapp/templates/remapp/review_studies_delete_button.html +20 -20
  148. openrem/remapp/templates/remapp/review_study.html +19 -19
  149. openrem/remapp/templates/remapp/review_summary_list.html +245 -245
  150. openrem/remapp/templates/remapp/rf_dose_alert_email_template.html +14 -1
  151. openrem/remapp/templates/remapp/rfalertnotificationsview.html +59 -59
  152. openrem/remapp/templates/remapp/rfdetail.html +547 -543
  153. openrem/remapp/templates/remapp/rfdetailbase.html +18 -18
  154. openrem/remapp/templates/remapp/rffiltered.html +404 -404
  155. openrem/remapp/templates/remapp/sizeimports.html +119 -119
  156. openrem/remapp/templates/remapp/sizeprocess.html +96 -96
  157. openrem/remapp/templates/remapp/sizeupload.html +110 -110
  158. openrem/remapp/templates/remapp/skindosemapcalcsettings_form.html +28 -28
  159. openrem/remapp/templates/remapp/standardname-modality.html +69 -69
  160. openrem/remapp/templates/remapp/standardnames_confirm_delete.html +71 -71
  161. openrem/remapp/templates/remapp/standardnames_form.html +87 -87
  162. openrem/remapp/templates/remapp/standardnamesettings_form.html +41 -41
  163. openrem/remapp/templates/remapp/standardnamesrefreshall.html +92 -92
  164. openrem/remapp/templates/remapp/standardnameview.html +103 -103
  165. openrem/remapp/templates/remapp/study_confirm_delete.html +147 -147
  166. openrem/remapp/templates/remapp/task_admin.html +265 -265
  167. openrem/remapp/templates/remapp/tasks.html +76 -76
  168. openrem/remapp/templatetags/formfilters.py +13 -13
  169. openrem/remapp/templatetags/proper_paginate.py +38 -38
  170. openrem/remapp/templatetags/remappduration.py +36 -36
  171. openrem/remapp/templatetags/sigdig.py +38 -38
  172. openrem/remapp/templatetags/sort_class_property_value.py +15 -15
  173. openrem/remapp/templatetags/update_variable.py +20 -20
  174. openrem/remapp/templatetags/url_replace.py +25 -25
  175. openrem/remapp/tests/test_charts_common.py +202 -202
  176. openrem/remapp/tests/test_charts_ct.py +7111 -7111
  177. openrem/remapp/tests/test_charts_dx.py +3513 -3513
  178. openrem/remapp/tests/test_charts_mg.py +1116 -1115
  179. openrem/remapp/tests/test_dcmdatetime.py +189 -189
  180. openrem/remapp/tests/test_dicom_qr.py +2580 -2580
  181. openrem/remapp/tests/test_display_name.py +274 -274
  182. openrem/remapp/tests/test_export_ct_xlsx.py +272 -248
  183. openrem/remapp/tests/test_export_dx_xlsx.py +137 -134
  184. openrem/remapp/tests/test_export_mammo_csv.py +242 -242
  185. openrem/remapp/tests/test_export_rf_xlsx.py +246 -246
  186. openrem/remapp/tests/test_files/DX-Im-DRGEM.dcm +0 -0
  187. openrem/remapp/tests/test_files/MG-RDSR-GEPristina-2D.dcm +0 -0
  188. openrem/remapp/tests/test_files/MG-RDSR-GEPristina-DBT.dcm +0 -0
  189. openrem/remapp/tests/test_files/MG-RDSR-Giotto-DBT.dcm +0 -0
  190. openrem/remapp/tests/test_files/skin_map_alphenix.py +590 -590
  191. openrem/remapp/tests/test_files/skin_map_zee.py +354 -354
  192. openrem/remapp/tests/test_filters_ct.py +321 -321
  193. openrem/remapp/tests/test_filters_dx.py +92 -92
  194. openrem/remapp/tests/test_filters_mammo.py +183 -183
  195. openrem/remapp/tests/test_filters_rf.py +118 -118
  196. openrem/remapp/tests/test_get_values.py +72 -72
  197. openrem/remapp/tests/test_hash_id.py +65 -65
  198. openrem/remapp/tests/test_import_ct_esr_ge.py +3034 -3034
  199. openrem/remapp/tests/test_import_ct_philips_rdsr.py +42 -42
  200. openrem/remapp/tests/test_import_ct_rdsr_multiple.py +256 -256
  201. openrem/remapp/tests/test_import_ct_rdsr_siemens.py +827 -827
  202. openrem/remapp/tests/test_import_ct_rdsr_spectrumdynamics.py +91 -91
  203. openrem/remapp/tests/test_import_ct_rdsr_toshiba_dosecheck.py +67 -67
  204. openrem/remapp/tests/test_import_ct_rdsr_toshiba_multivaluesd.py +33 -33
  205. openrem/remapp/tests/test_import_ct_rdsr_toshiba_pixelmed.py +118 -118
  206. openrem/remapp/tests/test_import_ct_sc_philips.py +44 -44
  207. openrem/remapp/tests/test_import_dual_rdsr.py +110 -110
  208. openrem/remapp/tests/test_import_dx.py +1267 -1191
  209. openrem/remapp/tests/test_import_dx_rdsr.py +1250 -1253
  210. openrem/remapp/tests/test_import_mam.py +438 -438
  211. openrem/remapp/tests/test_import_mg_im_hol_proj.py +46 -46
  212. openrem/remapp/tests/test_import_mg_rdsr.py +586 -586
  213. openrem/remapp/tests/test_import_nm_image.py +420 -420
  214. openrem/remapp/tests/test_import_nm_siemens_rdsr.py +396 -396
  215. openrem/remapp/tests/test_import_px.py +161 -161
  216. openrem/remapp/tests/test_import_rf_rdsr.py +420 -418
  217. openrem/remapp/tests/test_missing_date.py +42 -42
  218. openrem/remapp/tests/test_not_patient.py +60 -60
  219. openrem/remapp/tests/test_openskin.py +272 -272
  220. openrem/remapp/tests/test_patient_id_settings.py +72 -72
  221. openrem/remapp/tests/test_pt_size_import.py +232 -232
  222. openrem/remapp/tests/test_rf_detail.py +113 -113
  223. openrem/remapp/tests/test_rf_high_dose_alert.py +361 -361
  224. openrem/remapp/tools/background.py +361 -361
  225. openrem/remapp/tools/check_standard_name_status.py +47 -0
  226. openrem/remapp/tools/check_uid.py +70 -70
  227. openrem/remapp/tools/dcmdatetime.py +248 -248
  228. openrem/remapp/tools/default_import.py +44 -47
  229. openrem/remapp/tools/get_values.py +230 -230
  230. openrem/remapp/tools/hash_id.py +58 -58
  231. openrem/remapp/tools/make_skin_map.py +448 -406
  232. openrem/remapp/tools/not_patient_indicators.py +72 -72
  233. openrem/remapp/tools/openskin/calc_exp_map.py +173 -173
  234. openrem/remapp/tools/openskin/geomclass.py +475 -475
  235. openrem/remapp/tools/openskin/geomfunc.py +433 -432
  236. openrem/remapp/tools/openskin/skinmap.py +417 -417
  237. openrem/remapp/tools/populate_summary.py +185 -193
  238. openrem/remapp/tools/save_skin_map_structure.py +73 -73
  239. openrem/remapp/tools/send_high_dose_alert_emails.py +238 -207
  240. openrem/remapp/urls.py +456 -448
  241. openrem/remapp/version.py +11 -11
  242. openrem/remapp/views.py +1147 -1052
  243. openrem/remapp/views_admin.py +3876 -3936
  244. openrem/remapp/views_charts_ct.py +2110 -2058
  245. openrem/remapp/views_charts_dx.py +1906 -1836
  246. openrem/remapp/views_charts_mg.py +1349 -1196
  247. openrem/remapp/views_charts_nm.py +535 -535
  248. openrem/remapp/views_charts_rf.py +1219 -1241
  249. openrem/remapp/views_openskin.py +379 -384
  250. openrem/sample-config/openrem-consumer.service +12 -12
  251. openrem/sample-config/openrem-gunicorn.service +13 -13
  252. openrem/sample-config/openrem-server +14 -13
  253. openrem/sample-config/openrem_orthanc_config_linux.lua +454 -454
  254. openrem/sample-config/openrem_orthanc_config_windows.lua +455 -455
  255. openrem/sample-config/queue-init.bat +73 -73
  256. openrem/scripts/openrem_ctphilips.py +25 -25
  257. openrem/scripts/openrem_cttoshiba.py +28 -28
  258. openrem/scripts/openrem_dx.py +22 -22
  259. openrem/scripts/openrem_mg.py +22 -22
  260. openrem/scripts/openrem_nm.py +22 -22
  261. openrem/scripts/openrem_ptsizecsv.py +17 -17
  262. openrem/scripts/openrem_qr.py +12 -12
  263. openrem/scripts/openrem_rdsr.py +25 -25
  264. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/METADATA +39 -29
  265. openrem-1.0.0b3.dist-info/RECORD +379 -0
  266. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/WHEEL +1 -1
  267. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/COPYING-GPLv3 +674 -674
  268. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/LICENSE +22 -22
  269. OpenREM-1.0.0b2.dist-info/RECORD +0 -373
  270. openrem/remapp/static/js/charts/plotly-2.17.1.min.js +0 -8
  271. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ctphilips.py +0 -0
  272. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_cttoshiba.py +0 -0
  273. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_dx.py +0 -0
  274. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_mg.py +0 -0
  275. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_nm.py +0 -0
  276. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ptsizecsv.py +0 -0
  277. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_qr.py +0 -0
  278. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_rdsr.py +0 -0
  279. {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
- for cont2 in cont.ContentSequence:
781
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Label Type":
782
- event.label_type = get_or_create_cid(
783
- cont2.ConceptCodeSequence[0].CodeValue,
784
- cont2.ConceptCodeSequence[0].CodeMeaning,
785
- )
786
- elif (
787
- cont.ConceptNameCodeSequence[0].CodeValue == "111526"
788
- ): # 'DateTime Started'
789
- event.date_time_started = make_date_time(cont.DateTime)
790
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event Type":
791
- event.irradiation_event_type = get_or_create_cid(
792
- cont.ConceptCodeSequence[0].CodeValue,
793
- cont.ConceptCodeSequence[0].CodeMeaning,
794
- )
795
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Protocol":
796
- try:
797
- event.acquisition_protocol = cont.TextValue
798
- except AttributeError:
799
- event.acquisition_protocol = None
800
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Anatomical structure":
801
- event.anatomical_structure = get_or_create_cid(
802
- cont.ConceptCodeSequence[0].CodeValue,
803
- cont.ConceptCodeSequence[0].CodeMeaning,
804
- )
805
- try:
806
- for cont2 in cont.ContentSequence:
807
- if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
808
- event.laterality = get_or_create_cid(
809
- cont2.ConceptCodeSequence[0].CodeValue,
810
- cont2.ConceptCodeSequence[0].CodeMeaning,
811
- )
812
- except AttributeError:
813
- pass
814
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Image View":
815
- event.image_view = get_or_create_cid(
816
- cont.ConceptCodeSequence[0].CodeValue,
817
- cont.ConceptCodeSequence[0].CodeMeaning,
818
- )
819
- try:
820
- _imageviewmodifier(cont, event)
821
- except AttributeError:
822
- pass
823
- elif (
824
- cont.ConceptNameCodeSequence[0].CodeMeaning == "Patient Table Relationship"
825
- ):
826
- event.patient_table_relationship_cid = get_or_create_cid(
827
- cont.ConceptCodeSequence[0].CodeValue,
828
- cont.ConceptCodeSequence[0].CodeMeaning,
829
- )
830
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Patient Orientation":
831
- event.patient_orientation_cid = get_or_create_cid(
832
- cont.ConceptCodeSequence[0].CodeValue,
833
- cont.ConceptCodeSequence[0].CodeMeaning,
834
- )
835
- try:
836
- for cont2 in cont.ContentSequence:
837
- if (
838
- cont2.ConceptNameCodeSequence[0].CodeMeaning
839
- == "Patient Orientation Modifier"
840
- ):
841
- event.patient_orientation_modifier_cid = get_or_create_cid(
842
- cont2.ConceptCodeSequence[0].CodeValue,
843
- cont2.ConceptCodeSequence[0].CodeMeaning,
844
- )
845
- except AttributeError:
846
- pass
847
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Region":
848
- event.target_region = get_or_create_cid(
849
- cont.ConceptCodeSequence[0].CodeValue,
850
- cont.ConceptCodeSequence[0].CodeMeaning,
851
- )
852
- try:
853
- for cont2 in cont.ContentSequence:
854
- if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
855
- event.laterality = get_or_create_cid(
856
- cont2.ConceptCodeSequence[0].CodeValue,
857
- cont2.ConceptCodeSequence[0].CodeMeaning,
858
- )
859
- except AttributeError:
860
- pass
861
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product":
862
- try:
863
- event.dose_area_product = _check_dap_units(
864
- cont.MeasuredValueSequence[0]
865
- )
866
- except LookupError:
867
- pass # Will occur if measured value sequence is missing
868
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Half Value Layer":
869
- event.half_value_layer = test_numeric_value(
870
- cont.MeasuredValueSequence[0].NumericValue
871
- )
872
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Entrance Exposure at RP":
873
- event.entrance_exposure_at_rp = test_numeric_value(
874
- cont.MeasuredValueSequence[0].NumericValue
875
- )
876
- elif (
877
- cont.ConceptNameCodeSequence[0].CodeMeaning == "Reference Point Definition"
878
- ):
879
- try:
880
- event.reference_point_definition = get_or_create_cid(
881
- cont.ConceptCodeSequence[0].CodeValue,
882
- cont.ConceptCodeSequence[0].CodeMeaning,
883
- )
884
- except AttributeError:
885
- event.reference_point_definition_text = cont.TextValue
886
- if cont.ValueType == "CONTAINER":
887
- if (
888
- cont.ConceptNameCodeSequence[0].CodeMeaning
889
- == "Mammography CAD Breast Composition"
890
- ):
891
- for cont2 in cont.ContentSequence:
892
- if cont2.ConceptNamesCodes[0].CodeMeaning == "Breast Composition":
893
- event.breast_composition = cont2.CodeValue
894
- elif (
895
- cont2.ConceptNamesCodes[0].CodeMeaning
896
- == "Percent Fibroglandular Tissue"
897
- ):
898
- event.percent_fibroglandular_tissue = cont2.NumericValue
899
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
900
- event.comment = cont.TextValue
901
- event.save()
902
- for cont3 in fulldataset.ContentSequence:
903
- if cont3.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
904
- _get_patient_position_from_xml_string(event, cont3.TextValue)
905
- # needs include for optional multiple person participant
906
- _irradiationeventxraydetectordata(dataset, event)
907
- _irradiationeventxraymechanicaldata(dataset, event)
908
- # in some cases we need mechanical data before x-ray source data
909
- _irradiationeventxraysourcedata(dataset, event)
910
-
911
- event.save()
912
-
913
-
914
- def _calibration(dataset, accum):
915
-
916
- cal = Calibration.objects.create(accumulated_xray_dose=accum)
917
- for cont in dataset.ContentSequence:
918
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Measurement Device":
919
- cal.dose_measurement_device = get_or_create_cid(
920
- cont.ConceptCodeSequence[0].CodeValue,
921
- cont.ConceptCodeSequence[0].CodeMeaning,
922
- )
923
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Date":
924
- try:
925
- cal.calibration_date = make_date_time(cont.DateTime)
926
- except (KeyError, AttributeError):
927
- pass # Mandatory field not always present - see issue #770
928
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Factor":
929
- cal.calibration_factor = test_numeric_value(
930
- cont.MeasuredValueSequence[0].NumericValue
931
- )
932
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration Uncertainty":
933
- cal.calibration_uncertainty = test_numeric_value(
934
- cont.MeasuredValueSequence[0].NumericValue
935
- )
936
- elif (
937
- cont.ConceptNameCodeSequence[0].CodeMeaning
938
- == "Calibration Responsible Party"
939
- ):
940
- cal.calibration_responsible_party = cont.TextValue
941
- cal.save()
942
-
943
-
944
- def _accumulatedmammoxraydose(dataset, accum): # TID 10005
945
-
946
- for cont in dataset.ContentSequence:
947
- if (
948
- cont.ConceptNameCodeSequence[0].CodeMeaning
949
- == "Accumulated Average Glandular Dose"
950
- ):
951
- accummammo = AccumMammographyXRayDose.objects.create(
952
- accumulated_xray_dose=accum
953
- )
954
- accummammo.accumulated_average_glandular_dose = test_numeric_value(
955
- cont.MeasuredValueSequence[0].NumericValue
956
- )
957
- for cont2 in cont.ContentSequence:
958
- if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Laterality":
959
- accummammo.laterality = get_or_create_cid(
960
- cont2.ConceptCodeSequence[0].CodeValue,
961
- cont2.ConceptCodeSequence[0].CodeMeaning,
962
- )
963
- accummammo.save()
964
-
965
-
966
- def _accumulatedfluoroxraydose(dataset, accum): # TID 10004
967
- # Name in DICOM standard for TID 10004 is Accumulated Fluoroscopy and Acquisition Projection X-Ray Dose
968
- # See http://dicom.nema.org/medical/Dicom/2017e/output/chtml/part16/sect_TID_10004.html
969
-
970
- accumproj = AccumProjXRayDose.objects.create(accumulated_xray_dose=accum)
971
- for cont in dataset.ContentSequence:
972
- try:
973
- if (
974
- cont.ConceptNameCodeSequence[0].CodeMeaning
975
- == "Fluoro Dose Area Product Total"
976
- ):
977
- accumproj.fluoro_dose_area_product_total = _check_dap_units(
978
- cont.MeasuredValueSequence[0]
979
- )
980
- elif (
981
- cont.ConceptNameCodeSequence[0].CodeValue == "113728"
982
- ): # = 'Fluoro Dose (RP) Total'
983
- accumproj.fluoro_dose_rp_total = _check_rp_dose_units(
984
- cont.MeasuredValueSequence[0]
985
- )
986
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Total Fluoro Time":
987
- accumproj.total_fluoro_time = test_numeric_value(
988
- cont.MeasuredValueSequence[0].NumericValue
989
- )
990
- elif (
991
- cont.ConceptNameCodeSequence[0].CodeMeaning
992
- == "Acquisition Dose Area Product Total"
993
- ):
994
- accumproj.acquisition_dose_area_product_total = _check_dap_units(
995
- cont.MeasuredValueSequence[0]
996
- )
997
- elif (
998
- cont.ConceptNameCodeSequence[0].CodeValue == "113729"
999
- ): # = 'Acquisition Dose (RP) Total'
1000
- accumproj.acquisition_dose_rp_total = _check_rp_dose_units(
1001
- cont.MeasuredValueSequence[0]
1002
- )
1003
- elif (
1004
- cont.ConceptNameCodeSequence[0].CodeMeaning == "Total Acquisition Time"
1005
- ):
1006
- accumproj.total_acquisition_time = test_numeric_value(
1007
- cont.MeasuredValueSequence[0].NumericValue
1008
- )
1009
- # TODO: Remove the following four items, as they are also imported (correctly) into
1010
- # _accumulatedtotalprojectionradiographydose
1011
- elif (
1012
- cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product Total"
1013
- ):
1014
- accumproj.dose_area_product_total = _check_dap_units(
1015
- cont.MeasuredValueSequence[0]
1016
- )
1017
- elif (
1018
- cont.ConceptNameCodeSequence[0].CodeValue == "113725"
1019
- ): # = 'Dose (RP) Total':
1020
- accumproj.dose_rp_total = _check_rp_dose_units(
1021
- cont.MeasuredValueSequence[0]
1022
- )
1023
- elif (
1024
- cont.ConceptNameCodeSequence[0].CodeMeaning
1025
- == "Total Number of Radiographic Frames"
1026
- ):
1027
- accumproj.total_number_of_radiographic_frames = test_numeric_value(
1028
- cont.MeasuredValueSequence[0].NumericValue
1029
- )
1030
- elif (
1031
- cont.ConceptNameCodeSequence[0].CodeMeaning
1032
- == "Reference Point Definition"
1033
- ):
1034
- try:
1035
- accumproj.reference_point_definition_code = get_or_create_cid(
1036
- cont.ConceptCodeSequence[0].CodeValue,
1037
- cont.ConceptCodeSequence[0].CodeMeaning,
1038
- )
1039
- except AttributeError:
1040
- accumproj.reference_point_definition = cont.TextValue
1041
- except IndexError:
1042
- pass
1043
- if (
1044
- accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type
1045
- == "RF,DX"
1046
- ):
1047
- if accumproj.fluoro_dose_area_product_total or accumproj.total_fluoro_time:
1048
- accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type = (
1049
- "RF"
1050
- )
1051
- else:
1052
- accumproj.accumulated_xray_dose.projection_xray_radiation_dose.general_study_module_attributes.modality_type = (
1053
- "DX"
1054
- )
1055
- accumproj.save()
1056
-
1057
-
1058
- def _accumulatedcassettebasedprojectionradiographydose(dataset, accum): # TID 10006
1059
-
1060
- accumcass = AccumCassetteBsdProjRadiogDose.objects.create(
1061
- accumulated_xray_dose=accum
1062
- )
1063
- for cont in dataset.ContentSequence:
1064
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Detector Type":
1065
- accumcass.detector_type = get_or_create_cid(
1066
- cont.ConceptCodeSequence[0].CodeValue,
1067
- cont.ConceptCodeSequence[0].CodeMeaning,
1068
- )
1069
- elif (
1070
- cont.ConceptNameCodeSequence[0].CodeMeaning
1071
- == "Total Number of Radiographic Frames"
1072
- ):
1073
- accumcass.total_number_of_radiographic_frames = test_numeric_value(
1074
- cont.MeasuredValueSequence[0].NumericValue
1075
- )
1076
- accumcass.save()
1077
-
1078
-
1079
- def _accumulatedtotalprojectionradiographydose(dataset, accum): # TID 10007
1080
- # Name in DICOM standard for TID 10007 is Accumulated Total Projection Radiography Dose
1081
- # See http://dicom.nema.org/medical/Dicom/2017e/output/chtml/part16/sect_TID_10007.html
1082
-
1083
- accumint = AccumIntegratedProjRadiogDose.objects.create(accumulated_xray_dose=accum)
1084
- for cont in dataset.ContentSequence:
1085
- try:
1086
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Dose Area Product Total":
1087
- accumint.dose_area_product_total = _check_dap_units(
1088
- cont.MeasuredValueSequence[0]
1089
- )
1090
- elif (
1091
- cont.ConceptNameCodeSequence[0].CodeValue == "113725"
1092
- ): # = 'Dose (RP) Total':
1093
- accumint.dose_rp_total = _check_rp_dose_units(
1094
- cont.MeasuredValueSequence[0]
1095
- )
1096
- elif (
1097
- cont.ConceptNameCodeSequence[0].CodeMeaning
1098
- == "Total Number of Radiographic Frames"
1099
- ):
1100
- accumint.total_number_of_radiographic_frames = test_numeric_value(
1101
- cont.MeasuredValueSequence[0].NumericValue
1102
- )
1103
- elif (
1104
- cont.ConceptNameCodeSequence[0].CodeMeaning
1105
- == "Reference Point Definition"
1106
- ):
1107
- try:
1108
- accumint.reference_point_definition_code = get_or_create_cid(
1109
- cont.ConceptCodeSequence[0].CodeValue,
1110
- cont.ConceptCodeSequence[0].CodeMeaning,
1111
- )
1112
- except AttributeError:
1113
- accumint.reference_point_definition = cont.TextValue
1114
- except IndexError:
1115
- pass
1116
- accumint.save()
1117
-
1118
-
1119
- def _accumulatedxraydose(dataset, proj): # TID 10002
1120
-
1121
- accum = AccumXRayDose.objects.create(projection_xray_radiation_dose=proj)
1122
- for cont in dataset.ContentSequence:
1123
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Plane":
1124
- accum.acquisition_plane = get_or_create_cid(
1125
- cont.ConceptCodeSequence[0].CodeValue,
1126
- cont.ConceptCodeSequence[0].CodeMeaning,
1127
- )
1128
- if cont.ValueType == "CONTAINER":
1129
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Calibration":
1130
- _calibration(cont, accum)
1131
- if proj.acquisition_device_type_cid:
1132
- if (
1133
- "Fluoroscopy-Guided" in proj.acquisition_device_type_cid.code_meaning
1134
- or "Azurion"
1135
- in proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name
1136
- ):
1137
- _accumulatedfluoroxraydose(dataset, accum)
1138
- elif proj.procedure_reported and (
1139
- "Projection X-Ray" in proj.procedure_reported.code_meaning
1140
- ):
1141
- _accumulatedfluoroxraydose(dataset, accum)
1142
- if proj.procedure_reported and (
1143
- proj.procedure_reported.code_meaning == "Mammography"
1144
- ):
1145
- _accumulatedmammoxraydose(dataset, accum)
1146
- if proj.acquisition_device_type_cid:
1147
- if (
1148
- "Integrated" in proj.acquisition_device_type_cid.code_meaning
1149
- or "Fluoroscopy-Guided" in proj.acquisition_device_type_cid.code_meaning
1150
- ):
1151
- _accumulatedtotalprojectionradiographydose(dataset, accum)
1152
- elif proj.procedure_reported and (
1153
- "Projection X-Ray" in proj.procedure_reported.code_meaning
1154
- ):
1155
- _accumulatedtotalprojectionradiographydose(dataset, accum)
1156
- if proj.acquisition_device_type_cid:
1157
- if "Cassette-based" in proj.acquisition_device_type_cid.code_meaning:
1158
- _accumulatedcassettebasedprojectionradiographydose(dataset, accum)
1159
- accum.save()
1160
-
1161
-
1162
- def _scanninglength(dataset, event): # TID 10014
1163
-
1164
- scanlen = ScanningLength.objects.create(ct_irradiation_event_data=event)
1165
- try:
1166
- for cont in dataset.ContentSequence:
1167
- if cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "scanning length":
1168
- scanlen.scanning_length = test_numeric_value(
1169
- cont.MeasuredValueSequence[0].NumericValue
1170
- )
1171
- elif (
1172
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1173
- == "length of reconstructable volume"
1174
- ):
1175
- scanlen.length_of_reconstructable_volume = test_numeric_value(
1176
- cont.MeasuredValueSequence[0].NumericValue
1177
- )
1178
- elif cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "exposed range":
1179
- scanlen.exposed_range = test_numeric_value(
1180
- cont.MeasuredValueSequence[0].NumericValue
1181
- )
1182
- elif (
1183
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1184
- == "top z location of reconstructable volume"
1185
- ):
1186
- scanlen.top_z_location_of_reconstructable_volume = test_numeric_value(
1187
- cont.MeasuredValueSequence[0].NumericValue
1188
- )
1189
- elif (
1190
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1191
- == "bottom z location of reconstructable volume"
1192
- ):
1193
- scanlen.bottom_z_location_of_reconstructable_volume = (
1194
- test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
1195
- )
1196
- elif (
1197
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1198
- == "top z location of scanning length"
1199
- ):
1200
- scanlen.top_z_location_of_scanning_length = test_numeric_value(
1201
- cont.MeasuredValueSequence[0].NumericValue
1202
- )
1203
- elif (
1204
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1205
- == "bottom z location of scanning length"
1206
- ):
1207
- scanlen.bottom_z_location_of_scanning_length = test_numeric_value(
1208
- cont.MeasuredValueSequence[0].NumericValue
1209
- )
1210
- elif (
1211
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1212
- == "irradiation event uid"
1213
- ):
1214
- scanlen.irradiation_event_uid = cont.UID
1215
- scanlen.save()
1216
- except AttributeError:
1217
- pass
1218
-
1219
-
1220
- def _ctxraysourceparameters(dataset, event):
1221
-
1222
- param = CtXRaySourceParameters.objects.create(ct_irradiation_event_data=event)
1223
- for cont in dataset.ContentSequence:
1224
- if (
1225
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1226
- == "identification of the x-ray source"
1227
- or cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1228
- == "identification number of the x-ray source"
1229
- ):
1230
- param.identification_of_the_xray_source = cont.TextValue
1231
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "KVP":
1232
- param.kvp = test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
1233
- elif (
1234
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1235
- == "maximum x-ray tube current"
1236
- ):
1237
- param.maximum_xray_tube_current = test_numeric_value(
1238
- cont.MeasuredValueSequence[0].NumericValue
1239
- )
1240
- elif (
1241
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower() == "x-ray tube current"
1242
- ):
1243
- param.xray_tube_current = test_numeric_value(
1244
- cont.MeasuredValueSequence[0].NumericValue
1245
- )
1246
- elif cont.ConceptNameCodeSequence[0].CodeValue == "113734":
1247
- # Additional check as code meaning is wrong for Siemens Intevo see
1248
- # https://bitbucket.org/openrem/openrem/issues/380/siemens-intevo-rdsr-have-wrong-code
1249
- param.xray_tube_current = test_numeric_value(
1250
- cont.MeasuredValueSequence[0].NumericValue
1251
- )
1252
- elif (
1253
- cont.ConceptNameCodeSequence[0].CodeMeaning == "Exposure Time per Rotation"
1254
- ):
1255
- param.exposure_time_per_rotation = test_numeric_value(
1256
- cont.MeasuredValueSequence[0].NumericValue
1257
- )
1258
- elif (
1259
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1260
- == "x-ray filter aluminum equivalent"
1261
- ):
1262
- param.xray_filter_aluminum_equivalent = test_numeric_value(
1263
- cont.MeasuredValueSequence[0].NumericValue
1264
- )
1265
- param.save()
1266
-
1267
-
1268
- def _ctdosecheckdetails(dataset, dosecheckdetails, isalertdetails): # TID 10015
1269
- # PARTLY TESTED CODE (no DSR available that has Reason For Proceeding and/or Forward Estimate)
1270
-
1271
- if isalertdetails:
1272
- for cont in dataset.ContentSequence:
1273
- if (
1274
- cont.ConceptNameCodeSequence[0].CodeMeaning
1275
- == "DLP Alert Value Configured"
1276
- ):
1277
- dosecheckdetails.dlp_alert_value_configured = (
1278
- cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
1279
- )
1280
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Alert Value":
1281
- dosecheckdetails.dlp_alert_value = test_numeric_value(
1282
- cont.MeasuredValueSequence[0].NumericValue
1283
- )
1284
- if (
1285
- cont.ConceptNameCodeSequence[0].CodeMeaning
1286
- == "CTDIvol Alert Value Configured"
1287
- ):
1288
- dosecheckdetails.ctdivol_alert_value_configured = (
1289
- cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
1290
- )
1291
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "CTDIvol Alert Value":
1292
- dosecheckdetails.ctdivol_alert_value = test_numeric_value(
1293
- cont.MeasuredValueSequence[0].NumericValue
1294
- )
1295
- if (
1296
- cont.ConceptNameCodeSequence[0].CodeMeaning
1297
- == "Accumulated DLP Forward Estimate"
1298
- ):
1299
- dosecheckdetails.accumulated_dlp_forward_estimate = test_numeric_value(
1300
- cont.MeasuredValueSequence[0].NumericValue
1301
- )
1302
- if (
1303
- cont.ConceptNameCodeSequence[0].CodeMeaning
1304
- == "Accumulated CTDIvol Forward Estimate"
1305
- ):
1306
- dosecheckdetails.accumulated_ctdivol_forward_estimate = (
1307
- test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
1308
- )
1309
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Reason For Proceeding":
1310
- dosecheckdetails.alert_reason_for_proceeding = cont.TextValue
1311
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Person Name":
1312
- person_participant(
1313
- cont, "ct_dose_check_alert", dosecheckdetails, logger
1314
- )
1315
- else:
1316
- for cont in dataset.ContentSequence:
1317
- if (
1318
- cont.ConceptNameCodeSequence[0].CodeMeaning
1319
- == "DLP Notification Value Configured"
1320
- ):
1321
- dosecheckdetails.dlp_notification_value_configured = (
1322
- cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
1323
- )
1324
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Notification Value":
1325
- dosecheckdetails.dlp_notification_value = test_numeric_value(
1326
- cont.MeasuredValueSequence[0].NumericValue
1327
- )
1328
- if (
1329
- cont.ConceptNameCodeSequence[0].CodeMeaning
1330
- == "CTDIvol Notification Value Configured"
1331
- ):
1332
- dosecheckdetails.ctdivol_notification_value_configured = (
1333
- cont.ConceptCodeSequence[0].CodeMeaning == "Yes"
1334
- )
1335
- if (
1336
- cont.ConceptNameCodeSequence[0].CodeMeaning
1337
- == "CTDIvol Notification Value"
1338
- ):
1339
- dosecheckdetails.ctdivol_notification_value = test_numeric_value(
1340
- cont.MeasuredValueSequence[0].NumericValue
1341
- )
1342
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "DLP Forward Estimate":
1343
- dosecheckdetails.dlp_forward_estimate = test_numeric_value(
1344
- cont.MeasuredValueSequence[0].NumericValue
1345
- )
1346
- if (
1347
- cont.ConceptNameCodeSequence[0].CodeMeaning
1348
- == "CTDIvol Forward Estimate"
1349
- ):
1350
- dosecheckdetails.ctdivol_forward_estimate = test_numeric_value(
1351
- cont.MeasuredValueSequence[0].NumericValue
1352
- )
1353
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Reason For Proceeding":
1354
- dosecheckdetails.notification_reason_for_proceeding = cont.TextValue
1355
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Person Name":
1356
- person_participant(
1357
- cont, "ct_dose_check_notification", dosecheckdetails, logger
1358
- )
1359
- dosecheckdetails.save()
1360
-
1361
-
1362
- def _ctirradiationeventdata(dataset, ct): # TID 10013
1363
-
1364
- event = CtIrradiationEventData.objects.create(ct_radiation_dose=ct)
1365
- ctdosecheckdetails = None
1366
- for cont in dataset.ContentSequence:
1367
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Acquisition Protocol":
1368
- event.acquisition_protocol = cont.TextValue
1369
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Target Region":
1370
- try:
1371
- event.target_region = get_or_create_cid(
1372
- cont.ConceptCodeSequence[0].CodeValue,
1373
- cont.ConceptCodeSequence[0].CodeMeaning,
1374
- )
1375
- except (AttributeError, IndexError):
1376
- logger.info(
1377
- "Target Region ConceptNameCodeSequence exists, but no content. Study UID {0} from {1}, "
1378
- "{2}, {3}".format(
1379
- event.ct_radiation_dose.general_study_module_attributes.study_instance_uid,
1380
- event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer,
1381
- event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name,
1382
- event.ct_radiation_dose.general_study_module_attributes.generalequipmentmoduleattr_set.get().station_name,
1383
- )
1384
- )
1385
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Acquisition Type":
1386
- event.ct_acquisition_type = get_or_create_cid(
1387
- cont.ConceptCodeSequence[0].CodeValue,
1388
- cont.ConceptCodeSequence[0].CodeMeaning,
1389
- )
1390
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Procedure Context":
1391
- event.procedure_context = get_or_create_cid(
1392
- cont.ConceptCodeSequence[0].CodeValue,
1393
- cont.ConceptCodeSequence[0].CodeMeaning,
1394
- )
1395
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Irradiation Event UID":
1396
- event.irradiation_event_uid = cont.UID
1397
- event.save()
1398
- if cont.ValueType == "CONTAINER":
1399
- if (
1400
- cont.ConceptNameCodeSequence[0].CodeMeaning
1401
- == "CT Acquisition Parameters"
1402
- ):
1403
- _scanninglength(cont, event)
1404
- try:
1405
- for cont2 in cont.ContentSequence:
1406
- if (
1407
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1408
- == "Exposure Time"
1409
- ):
1410
- event.exposure_time = test_numeric_value(
1411
- cont2.MeasuredValueSequence[0].NumericValue
1412
- )
1413
- elif (
1414
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1415
- == "Nominal Single Collimation Width"
1416
- ):
1417
- event.nominal_single_collimation_width = test_numeric_value(
1418
- cont2.MeasuredValueSequence[0].NumericValue
1419
- )
1420
- elif (
1421
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1422
- == "Nominal Total Collimation Width"
1423
- ):
1424
- event.nominal_total_collimation_width = test_numeric_value(
1425
- cont2.MeasuredValueSequence[0].NumericValue
1426
- )
1427
- elif (
1428
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1429
- == "Pitch Factor"
1430
- ):
1431
- event.pitch_factor = test_numeric_value(
1432
- cont2.MeasuredValueSequence[0].NumericValue
1433
- )
1434
- elif (
1435
- cont2.ConceptNameCodeSequence[0].CodeMeaning.lower()
1436
- == "number of x-ray sources"
1437
- ):
1438
- event.number_of_xray_sources = test_numeric_value(
1439
- cont2.MeasuredValueSequence[0].NumericValue
1440
- )
1441
- if cont2.ValueType == "CONTAINER":
1442
- if (
1443
- cont2.ConceptNameCodeSequence[0].CodeMeaning.lower()
1444
- == "ct x-ray source parameters"
1445
- ):
1446
- _ctxraysourceparameters(cont2, event)
1447
- except AttributeError:
1448
- pass
1449
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Dose":
1450
- for cont2 in cont.ContentSequence:
1451
- if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Mean CTDIvol":
1452
- event.mean_ctdivol = test_numeric_value(
1453
- cont2.MeasuredValueSequence[0].NumericValue
1454
- )
1455
- elif (
1456
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1457
- == "CTDIw Phantom Type"
1458
- ):
1459
- event.ctdiw_phantom_type = get_or_create_cid(
1460
- cont2.ConceptCodeSequence[0].CodeValue,
1461
- cont2.ConceptCodeSequence[0].CodeMeaning,
1462
- )
1463
- elif (
1464
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1465
- == "CTDIfreeair Calculation Factor"
1466
- ):
1467
- event.ctdifreeair_calculation_factor = test_numeric_value(
1468
- cont2.MeasuredValueSequence[0].NumericValue
1469
- )
1470
- elif (
1471
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1472
- == "Mean CTDIfreeair"
1473
- ):
1474
- event.mean_ctdifreeair = test_numeric_value(
1475
- cont2.MeasuredValueSequence[0].NumericValue
1476
- )
1477
- elif cont2.ConceptNameCodeSequence[0].CodeMeaning == "DLP":
1478
- event.dlp = test_numeric_value(
1479
- cont2.MeasuredValueSequence[0].NumericValue
1480
- )
1481
- elif (
1482
- cont2.ConceptNameCodeSequence[0].CodeMeaning == "Effective Dose"
1483
- ):
1484
- event.effective_dose = test_numeric_value(
1485
- cont2.MeasuredValueSequence[0].NumericValue
1486
- )
1487
- ## Effective dose measurement method and conversion factor
1488
- ## CT Dose Check Details
1489
- ## Dose Check Alert Details and Notifications Details can appear indepently
1490
- elif (
1491
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1492
- == "Dose Check Alert Details"
1493
- ):
1494
- if ctdosecheckdetails is None:
1495
- ctdosecheckdetails = CtDoseCheckDetails.objects.create(
1496
- ct_irradiation_event_data=event
1497
- )
1498
- _ctdosecheckdetails(cont2, ctdosecheckdetails, True)
1499
- elif (
1500
- cont2.ConceptNameCodeSequence[0].CodeMeaning
1501
- == "Dose Check Notification Details"
1502
- ):
1503
- if ctdosecheckdetails is None:
1504
- ctdosecheckdetails = CtDoseCheckDetails.objects.create(
1505
- ct_irradiation_event_data=event
1506
- )
1507
- _ctdosecheckdetails(cont2, ctdosecheckdetails, False)
1508
- if (
1509
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1510
- == "x-ray modulation type"
1511
- ):
1512
- event.xray_modulation_type = cont.TextValue
1513
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
1514
- event.comment = cont.TextValue
1515
- if not event.xray_modulation_type and event.comment:
1516
- comments = event.comment.split(",")
1517
- for comm in comments:
1518
- if comm.lstrip().startswith("X-ray Modulation Type"):
1519
- modulationtype = comm[(comm.find("=") + 2) :]
1520
- event.xray_modulation_type = modulationtype
1521
-
1522
- ## personparticipant here
1523
- _deviceparticipant(dataset, "ct_event", event)
1524
- if ctdosecheckdetails is not None:
1525
- ctdosecheckdetails.save()
1526
- event.save()
1527
-
1528
-
1529
- def _ctaccumulateddosedata(dataset, ct): # TID 10012
1530
-
1531
- ctacc = CtAccumulatedDoseData.objects.create(ct_radiation_dose=ct)
1532
- for cont in dataset.ContentSequence:
1533
- if (
1534
- cont.ConceptNameCodeSequence[0].CodeMeaning
1535
- == "Total Number of Irradiation Events"
1536
- ):
1537
- ctacc.total_number_of_irradiation_events = test_numeric_value(
1538
- cont.MeasuredValueSequence[0].NumericValue
1539
- )
1540
- elif (
1541
- cont.ConceptNameCodeSequence[0].CodeMeaning
1542
- == "CT Dose Length Product Total"
1543
- ):
1544
- ctacc.ct_dose_length_product_total = test_numeric_value(
1545
- cont.MeasuredValueSequence[0].NumericValue
1546
- )
1547
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Effective Dose Total":
1548
- ctacc.ct_effective_dose_total = test_numeric_value(
1549
- cont.MeasuredValueSequence[0].NumericValue
1550
- )
1551
- #
1552
- # Reference authority code or name belongs here, followed by the effective dose details
1553
- #
1554
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
1555
- ctacc.comment = cont.TextValue
1556
- _deviceparticipant(dataset, "ct_accumulated", ctacc)
1557
-
1558
- ctacc.save()
1559
-
1560
-
1561
- def _import_varic(dataset, proj):
1562
-
1563
- for cont in dataset.ContentSequence:
1564
- if cont.ConceptNameCodeSequence[0].CodeValue == "C-200":
1565
- accum = AccumXRayDose.objects.create(projection_xray_radiation_dose=proj)
1566
- accum.acquisition_plane = get_or_create_cid("113622", "Single Plane")
1567
- accum.save()
1568
- accumint = AccumIntegratedProjRadiogDose.objects.create(
1569
- accumulated_xray_dose=accum
1570
- )
1571
- try:
1572
- accumint.total_number_of_radiographic_frames = test_numeric_value(
1573
- cont.MeasuredValueSequence[0].NumericValue
1574
- )
1575
- except IndexError:
1576
- pass
1577
- accumint.save()
1578
- if cont.ConceptNameCodeSequence[0].CodeValue == "C-202":
1579
- accumproj = AccumProjXRayDose.objects.create(accumulated_xray_dose=accum)
1580
- accumproj.save()
1581
- try:
1582
- accumproj.total_fluoro_time = test_numeric_value(
1583
- cont.MeasuredValueSequence[0].NumericValue
1584
- )
1585
- except IndexError:
1586
- pass
1587
- accumproj.save()
1588
- if cont.ConceptNameCodeSequence[0].CodeValue == "C-204":
1589
- try:
1590
- accumint.dose_area_product_total = (
1591
- test_numeric_value(cont.MeasuredValueSequence[0].NumericValue)
1592
- / 10000.0
1593
- )
1594
- except (TypeError, IndexError):
1595
- pass
1596
- accumint.save()
1597
- # cumulative air kerma in dose report example is not available ("Measurement not attempted")
1598
-
1599
-
1600
- def projectionxrayradiationdose(dataset, g, reporttype):
1601
-
1602
- if reporttype == "projection":
1603
- proj = ProjectionXRayRadiationDose.objects.create(
1604
- general_study_module_attributes=g
1605
- )
1606
- elif reporttype == "ct":
1607
- proj = CtRadiationDose.objects.create(general_study_module_attributes=g)
1608
- else:
1609
- logger.error(
1610
- "Attempt to create ProjectionXRayRadiationDose failed as report type incorrect"
1611
- )
1612
- return
1613
- equip = GeneralEquipmentModuleAttr.objects.get(general_study_module_attributes=g)
1614
- proj.general_study_module_attributes.modality_type = (
1615
- equip.unique_equipment_name.user_defined_modality
1616
- )
1617
- if proj.general_study_module_attributes.modality_type == "dual":
1618
- proj.general_study_module_attributes.modality_type = None
1619
-
1620
- if (
1621
- dataset.ConceptNameCodeSequence[0].CodingSchemeDesignator == "99SMS_RADSUM"
1622
- and dataset.ConceptNameCodeSequence[0].CodeValue == "C-10"
1623
- ):
1624
- g.modality_type = "RF"
1625
- g.save()
1626
- _import_varic(dataset, proj)
1627
- else:
1628
- for cont in dataset.ContentSequence:
1629
- if (
1630
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1631
- == "procedure reported"
1632
- ):
1633
- proj.procedure_reported = get_or_create_cid(
1634
- cont.ConceptCodeSequence[0].CodeValue,
1635
- cont.ConceptCodeSequence[0].CodeMeaning,
1636
- )
1637
- if (
1638
- "ContentSequence" in cont
1639
- ): # Extra if statement to allow for non-conformant GE RDSR that don't have this mandatory field.
1640
- for cont2 in cont.ContentSequence:
1641
- if cont2.ConceptNameCodeSequence[0].CodeMeaning == "Has Intent":
1642
- proj.has_intent = get_or_create_cid(
1643
- cont2.ConceptCodeSequence[0].CodeValue,
1644
- cont2.ConceptCodeSequence[0].CodeMeaning,
1645
- )
1646
- if "Mammography" in proj.procedure_reported.code_meaning:
1647
- proj.general_study_module_attributes.modality_type = "MG"
1648
- elif (not proj.general_study_module_attributes.modality_type) and (
1649
- "Projection X-Ray" in proj.procedure_reported.code_meaning
1650
- ):
1651
- proj.general_study_module_attributes.modality_type = "RF,DX"
1652
- elif (
1653
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1654
- == "acquisition device type"
1655
- ):
1656
- proj.acquisition_device_type_cid = get_or_create_cid(
1657
- cont.ConceptCodeSequence[0].CodeValue,
1658
- cont.ConceptCodeSequence[0].CodeMeaning,
1659
- )
1660
- elif (
1661
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1662
- == "start of x-ray irradiation"
1663
- ):
1664
- proj.start_of_xray_irradiation = make_date_time(cont.DateTime)
1665
- elif (
1666
- cont.ConceptNameCodeSequence[0].CodeMeaning.lower()
1667
- == "end of x-ray irradiation"
1668
- ):
1669
- proj.end_of_xray_irradiation = make_date_time(cont.DateTime)
1670
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Scope of Accumulation":
1671
- proj.scope_of_accumulation = get_or_create_cid(
1672
- cont.ConceptCodeSequence[0].CodeValue,
1673
- cont.ConceptCodeSequence[0].CodeMeaning,
1674
- )
1675
- elif (
1676
- cont.ConceptNameCodeSequence[0].CodeMeaning
1677
- == "X-Ray Detector Data Available"
1678
- ):
1679
- proj.xray_detector_data_available = get_or_create_cid(
1680
- cont.ConceptCodeSequence[0].CodeValue,
1681
- cont.ConceptCodeSequence[0].CodeMeaning,
1682
- )
1683
- elif (
1684
- cont.ConceptNameCodeSequence[0].CodeMeaning
1685
- == "X-Ray Source Data Available"
1686
- ):
1687
- proj.xray_source_data_available = get_or_create_cid(
1688
- cont.ConceptCodeSequence[0].CodeValue,
1689
- cont.ConceptCodeSequence[0].CodeMeaning,
1690
- )
1691
- elif (
1692
- cont.ConceptNameCodeSequence[0].CodeMeaning
1693
- == "X-Ray Mechanical Data Available"
1694
- ):
1695
- proj.xray_mechanical_data_available = get_or_create_cid(
1696
- cont.ConceptCodeSequence[0].CodeValue,
1697
- cont.ConceptCodeSequence[0].CodeMeaning,
1698
- )
1699
- elif cont.ConceptNameCodeSequence[0].CodeMeaning == "Comment":
1700
- proj.comment = cont.TextValue
1701
- elif (
1702
- cont.ConceptNameCodeSequence[0].CodeMeaning
1703
- == "Source of Dose Information"
1704
- ):
1705
- proj.source_of_dose_information = get_or_create_cid(
1706
- cont.ConceptCodeSequence[0].CodeValue,
1707
- cont.ConceptCodeSequence[0].CodeMeaning,
1708
- )
1709
- if (
1710
- (not equip.unique_equipment_name.user_defined_modality)
1711
- and (reporttype == "projection")
1712
- and proj.acquisition_device_type_cid
1713
- ):
1714
- if (
1715
- "Fluoroscopy-Guided"
1716
- in proj.acquisition_device_type_cid.code_meaning
1717
- or "Azurion"
1718
- in proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().manufacturer_model_name
1719
- ):
1720
- proj.general_study_module_attributes.modality_type = "RF"
1721
- elif any(
1722
- x in proj.acquisition_device_type_cid.code_meaning
1723
- for x in ["Integrated", "Cassette-based"]
1724
- ):
1725
- proj.general_study_module_attributes.modality_type = "DX"
1726
- else:
1727
- logging.error(
1728
- "Acquisition device type code exists, but the value wasn't matched. Study UID: {0}, "
1729
- "Station name: {1}, Study date, time: {2}, {3}, device type: {4} ".format(
1730
- proj.general_study_module_attributes.study_instance_uid,
1731
- proj.general_study_module_attributes.generalequipmentmoduleattr_set.get().station_name,
1732
- proj.general_study_module_attributes.study_date,
1733
- proj.general_study_module_attributes.study_time,
1734
- proj.acquisition_device_type_cid.code_meaning,
1735
- )
1736
- )
1737
-
1738
- proj.save()
1739
-
1740
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "Observer Type":
1741
- if reporttype == "projection":
1742
- obs = ObserverContext.objects.create(
1743
- projection_xray_radiation_dose=proj
1744
- )
1745
- else:
1746
- obs = ObserverContext.objects.create(ct_radiation_dose=proj)
1747
- observercontext(dataset, obs)
1748
-
1749
- if cont.ValueType == "CONTAINER":
1750
- if (
1751
- cont.ConceptNameCodeSequence[0].CodeMeaning
1752
- == "Accumulated X-Ray Dose Data"
1753
- ):
1754
- _accumulatedxraydose(cont, proj)
1755
- if (
1756
- cont.ConceptNameCodeSequence[0].CodeMeaning
1757
- == "Irradiation Event X-Ray Data"
1758
- ):
1759
- _irradiationeventxraydata(cont, proj, dataset)
1760
- if (
1761
- cont.ConceptNameCodeSequence[0].CodeMeaning
1762
- == "CT Accumulated Dose Data"
1763
- ):
1764
- if proj.general_study_module_attributes.modality_type != "NM":
1765
- proj.general_study_module_attributes.modality_type = "CT"
1766
- _ctaccumulateddosedata(cont, proj)
1767
- if cont.ConceptNameCodeSequence[0].CodeMeaning == "CT Acquisition":
1768
- _ctirradiationeventdata(cont, proj)
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)