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,1431 +1,1418 @@
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
- .. module:: rf_export.
25
- :synopsis: Module to export RF data from database to single sheet csv and multisheet xlsx.
26
-
27
- .. moduleauthor:: Ed McDonagh
28
-
29
- """
30
-
31
- import datetime
32
- import logging
33
- from openrem.remapp.tools.background import get_or_generate_task_uuid
34
-
35
- from django.core.exceptions import ObjectDoesNotExist
36
- from django.db.models import Avg, Max, Min
37
-
38
- from remapp.models import (
39
- GeneralStudyModuleAttr,
40
- IrradEventXRayData,
41
- StandardNameSettings,
42
- )
43
-
44
- from ..exports.export_common import (
45
- text_and_date_formats,
46
- common_headers,
47
- generate_sheets,
48
- sheet_name,
49
- get_common_data,
50
- get_xray_filter_info,
51
- create_xlsx,
52
- create_csv,
53
- write_export,
54
- create_summary_sheet,
55
- get_pulse_data,
56
- abort_if_zero_studies,
57
- create_export_task,
58
- get_patient_study_data,
59
- )
60
- from ..interface.mod_filters import (
61
- RFSummaryListFilter,
62
- RFFilterPlusPid,
63
- RFFilterPlusStdNames,
64
- RFFilterPlusPidPlusStdNames,
65
- )
66
- from ..tools.get_values import return_for_export
67
-
68
- logger = logging.getLogger(__name__)
69
-
70
-
71
- def _get_accumulated_data(accumXrayDose):
72
- """Extract all the summary level data
73
-
74
- :param accumXrayDose: Accumulated x-ray radiation dose object
75
- :return: dict of summary level data
76
- """
77
- accum = {}
78
- accum["plane"] = accumXrayDose.acquisition_plane.code_meaning
79
- try:
80
- accumulated_integrated_projection_dose = (
81
- accumXrayDose.accumintegratedprojradiogdose_set.get()
82
- )
83
- accum[
84
- "dose_area_product_total"
85
- ] = accumulated_integrated_projection_dose.dose_area_product_total
86
- accum["dose_rp_total"] = accumulated_integrated_projection_dose.dose_rp_total
87
- accum[
88
- "reference_point_definition"
89
- ] = accumulated_integrated_projection_dose.reference_point_definition_code
90
- if not accum["reference_point_definition"]:
91
- accum[
92
- "reference_point_definition"
93
- ] = accumulated_integrated_projection_dose.reference_point_definition
94
- except ObjectDoesNotExist:
95
- accum["dose_area_product_total"] = None
96
- accum["dose_rp_total"] = None
97
- accum["reference_point_definition_code"] = None
98
- try:
99
- accumulated_projection_dose = accumXrayDose.accumprojxraydose_set.get()
100
- accum[
101
- "fluoro_dose_area_product_total"
102
- ] = accumulated_projection_dose.fluoro_dose_area_product_total
103
- accum["fluoro_dose_rp_total"] = accumulated_projection_dose.fluoro_dose_rp_total
104
- accum["total_fluoro_time"] = accumulated_projection_dose.total_fluoro_time
105
- accum[
106
- "acquisition_dose_area_product_total"
107
- ] = accumulated_projection_dose.acquisition_dose_area_product_total
108
- accum[
109
- "acquisition_dose_rp_total"
110
- ] = accumulated_projection_dose.acquisition_dose_rp_total
111
- accum[
112
- "total_acquisition_time"
113
- ] = accumulated_projection_dose.total_acquisition_time
114
- except ObjectDoesNotExist:
115
- accum["fluoro_dose_area_product_total"] = None
116
- accum["fluoro_dose_rp_total"] = None
117
- accum["total_fluoro_time"] = None
118
- accum["acquisition_dose_area_product_total"] = None
119
- accum["acquisition_dose_rp_total"] = None
120
- accum["total_acquisition_time"] = None
121
-
122
- try:
123
- accum["eventcount"] = int(
124
- accumXrayDose.projection_xray_radiation_dose.irradeventxraydata_set.filter(
125
- acquisition_plane__code_meaning__exact=accum["plane"]
126
- ).count()
127
- )
128
- except ObjectDoesNotExist:
129
- accum["eventcount"] = None
130
-
131
- return accum
132
-
133
-
134
- def _add_plane_summary_data(exam):
135
- """Add plane level accumulated data to examdata
136
-
137
- :param exam: exam to export
138
- :return: list of summary data at plane level
139
- """
140
- exam_data = []
141
- for plane in (
142
- exam.projectionxrayradiationdose_set.get()
143
- .accumxraydose_set.all()
144
- .order_by("acquisition_plane__code_value")
145
- ):
146
- accum = _get_accumulated_data(plane)
147
- exam_data += [
148
- accum["dose_area_product_total"],
149
- accum["dose_rp_total"],
150
- accum["fluoro_dose_area_product_total"],
151
- accum["fluoro_dose_rp_total"],
152
- accum["total_fluoro_time"],
153
- accum["acquisition_dose_area_product_total"],
154
- accum["acquisition_dose_rp_total"],
155
- accum["total_acquisition_time"],
156
- accum["eventcount"],
157
- ]
158
- if "Single" in accum["plane"]:
159
- exam_data += ["", "", "", "", "", "", "", "", ""]
160
-
161
- return exam_data
162
-
163
-
164
- def _get_series_data(event, filter_data):
165
- """Return series level data for protocol sheets
166
-
167
- :param event: event in question
168
- :return: list of data
169
- """
170
-
171
- # Obtain the system-level enable_standard_names setting
172
- try:
173
- StandardNameSettings.objects.get()
174
- except ObjectDoesNotExist:
175
- StandardNameSettings.objects.create()
176
- enable_standard_names = StandardNameSettings.objects.values_list(
177
- "enable_standard_names", flat=True
178
- )[0]
179
-
180
- try:
181
- source_data = event.irradeventxraysourcedata_set.get()
182
- pulse_rate = source_data.pulse_rate
183
- ii_field_size = source_data.ii_field_size
184
- exposure_time = source_data.exposure_time
185
- dose_rp = source_data.dose_rp
186
- number_of_pulses = source_data.number_of_pulses
187
- irradiation_duration = source_data.irradiation_duration
188
- pulse_data = get_pulse_data(source_data=source_data, modality="RF")
189
- kVp = pulse_data["kvp"]
190
- xray_tube_current = pulse_data["xray_tube_current"]
191
- pulse_width = pulse_data["pulse_width"]
192
- except ObjectDoesNotExist:
193
- pulse_rate = None
194
- ii_field_size = None
195
- exposure_time = None
196
- dose_rp = None
197
- number_of_pulses = None
198
- irradiation_duration = None
199
- kVp = None
200
- xray_tube_current = None
201
- pulse_width = None
202
- try:
203
- mechanical_data = event.irradeventxraymechanicaldata_set.get()
204
- pos_primary_angle = mechanical_data.positioner_primary_angle
205
- pos_secondary_angle = mechanical_data.positioner_secondary_angle
206
- except ObjectDoesNotExist:
207
- pos_primary_angle = None
208
- pos_secondary_angle = None
209
-
210
- series_data = [
211
- str(event.date_time_started),
212
- event.irradiation_event_type.code_meaning,
213
- event.acquisition_protocol,
214
- ]
215
-
216
- if enable_standard_names:
217
- try:
218
- standard_protocol = event.standard_protocols.first().standard_name
219
- except AttributeError:
220
- standard_protocol = ""
221
-
222
- if standard_protocol:
223
- series_data += [standard_protocol]
224
- else:
225
- series_data += [""]
226
-
227
- series_data = series_data + [
228
- event.acquisition_plane.code_meaning,
229
- ii_field_size,
230
- filter_data["filter_material"],
231
- filter_data["filter_thick"],
232
- kVp,
233
- xray_tube_current,
234
- pulse_width,
235
- pulse_rate,
236
- number_of_pulses,
237
- exposure_time,
238
- irradiation_duration,
239
- event.convert_gym2_to_cgycm2(),
240
- dose_rp,
241
- pos_primary_angle,
242
- pos_secondary_angle,
243
- ]
244
-
245
- return series_data
246
-
247
-
248
- def _all_data_headers(pid=False, name=None, patid=None):
249
- """Compile list of column headers
250
-
251
- :param pid: does the user have patient identifiable data permission
252
- :param name: has patient name been selected for export
253
- :param patid: has patient ID been selected for export
254
- :return: list of headers for all_data sheet and csv sheet
255
- """
256
- all_data_headers = common_headers(
257
- modality="RF", pid=pid, name=name, patid=patid
258
- ) + [
259
- "A DAP total (Gy.m^2)",
260
- "A Dose RP total (Gy)",
261
- "A Fluoro DAP total (Gy.m^2)",
262
- "A Fluoro dose RP total (Gy)",
263
- "A Fluoro duration total (s)",
264
- "A Acq. DAP total (Gy.m^2)",
265
- "A Acq. dose RP total (Gy)",
266
- "A Acq. duration total (s)",
267
- "A Number of events",
268
- "B DAP total (Gy.m^2)",
269
- "B Dose RP total (Gy)",
270
- "B Fluoro DAP total (Gy.m^2)",
271
- "B Fluoro dose RP total (Gy)",
272
- "B Fluoro duration total (s)",
273
- "B Acq. DAP total (Gy.m^2)",
274
- "B Acq. dose RP total (Gy)",
275
- "B Acq. duration total (s)",
276
- "B Number of events",
277
- ]
278
- return all_data_headers
279
-
280
-
281
- def rfxlsx(filterdict, pid=False, name=None, patid=None, user=None):
282
- """Export filtered RF database data to multi-sheet Microsoft XSLX files.
283
-
284
- :param filterdict: Queryset of studies to export
285
- :param pid: does the user have patient identifiable data permission
286
- :param name: has patient name been selected for export
287
- :param patid: has patient ID been selected for export
288
- :param user: User that has started the export
289
- :return: Saves xlsx file into Media directory for user to download
290
- """
291
-
292
- # Obtain the system-level enable_standard_names setting
293
- try:
294
- StandardNameSettings.objects.get()
295
- except ObjectDoesNotExist:
296
- StandardNameSettings.objects.create()
297
- enable_standard_names = StandardNameSettings.objects.values_list(
298
- "enable_standard_names", flat=True
299
- )[0]
300
-
301
- datestamp = datetime.datetime.now()
302
- task_id = get_or_generate_task_uuid()
303
- tsk = create_export_task(
304
- task_id=task_id,
305
- modality="RF",
306
- export_type="XLSX export",
307
- date_stamp=datestamp,
308
- pid=bool(pid and (name or patid)),
309
- user=user,
310
- filters_dict=filterdict,
311
- )
312
-
313
- tmpxlsx, book = create_xlsx(tsk)
314
- if not tmpxlsx:
315
- exit()
316
-
317
- # Get the data
318
- if pid:
319
- if enable_standard_names:
320
- df_filtered_qs = RFFilterPlusPidPlusStdNames(
321
- filterdict,
322
- queryset=GeneralStudyModuleAttr.objects.filter(
323
- modality_type__exact="RF"
324
- ).distinct(),
325
- )
326
- else:
327
- df_filtered_qs = RFFilterPlusPid(
328
- filterdict,
329
- queryset=GeneralStudyModuleAttr.objects.filter(
330
- modality_type__exact="RF"
331
- ).distinct(),
332
- )
333
- else:
334
- if enable_standard_names:
335
- df_filtered_qs = RFFilterPlusStdNames(
336
- filterdict,
337
- queryset=GeneralStudyModuleAttr.objects.filter(
338
- modality_type__exact="RF"
339
- ).distinct(),
340
- )
341
- else:
342
- df_filtered_qs = RFSummaryListFilter(
343
- filterdict,
344
- queryset=GeneralStudyModuleAttr.objects.filter(
345
- modality_type__exact="RF"
346
- ).distinct(),
347
- )
348
-
349
- e = df_filtered_qs.qs
350
-
351
- tsk.num_records = e.count()
352
- if abort_if_zero_studies(tsk.num_records, tsk):
353
- return
354
-
355
- # Add summary sheet and all data sheet
356
- summarysheet = book.add_worksheet("Summary")
357
- wsalldata = book.add_worksheet("All data")
358
-
359
- book = text_and_date_formats(
360
- book, wsalldata, pid=pid, name=name, patid=patid, modality="RF"
361
- )
362
- tsk.progress = "Creating an Excel safe version of protocol names and creating a worksheet for each..."
363
- tsk.save()
364
-
365
- all_data_headers = _all_data_headers(pid=pid, name=name, patid=patid)
366
-
367
- sheet_headers = list(all_data_headers)
368
- protocolheaders = sheet_headers + [
369
- "Time",
370
- "Type",
371
- "Protocol",
372
- ]
373
-
374
- if enable_standard_names:
375
- protocolheaders += [
376
- "Standard acquisition name",
377
- ]
378
-
379
- protocolheaders += [
380
- "Plane",
381
- "Field size",
382
- "Filter material",
383
- "Mean filter thickness (mm)",
384
- "kVp",
385
- "mA",
386
- "Pulse width (ms)",
387
- "Pulse rate",
388
- "Number of pulses",
389
- "Exposure time (ms)",
390
- "Exposure duration (s)",
391
- "DAP (cGy.cm^2)",
392
- "Ref point dose (Gy)",
393
- "Primary angle",
394
- "Secondary angle",
395
- ]
396
-
397
- book, sheetlist = generate_sheets(
398
- e, book, protocolheaders, modality="RF", pid=pid, name=name, patid=patid
399
- )
400
-
401
- ##################
402
- # All data sheet
403
-
404
- num_groups_max = 0
405
- for row, exams in enumerate(e):
406
-
407
- tsk.progress = f"Writing study {row + 1} of {e.count()}"
408
- tsk.save()
409
-
410
- try:
411
- examdata = get_common_data("RF", exams, pid=pid, name=name, patid=patid)
412
- examdata += _add_plane_summary_data(exams)
413
- common_exam_data = list(examdata)
414
-
415
- angle_range = 5.0 # plus or minus range considered to be the same position
416
-
417
- # TODO: Check if generation of inst could be more efficient, ie start with exams?
418
- inst = IrradEventXRayData.objects.filter(
419
- projection_xray_radiation_dose__general_study_module_attributes__id__exact=exams.id
420
- )
421
-
422
- num_groups_this_exam = 0
423
- while (
424
- inst
425
- ): # ie while there are events still left that haven't been matched into a group
426
- tsk.progress = "Writing study {0} of {1}; {2} events remaining.".format(
427
- row + 1, e.count(), inst.count()
428
- )
429
- tsk.save()
430
- num_groups_this_exam += 1
431
- plane = inst[0].acquisition_plane.code_meaning
432
- try:
433
- mechanical_data = inst[0].irradeventxraymechanicaldata_set.get()
434
- anglei = mechanical_data.positioner_primary_angle
435
- angleii = mechanical_data.positioner_secondary_angle
436
- except ObjectDoesNotExist:
437
- anglei = None
438
- angleii = None
439
- try:
440
- source_data = inst[0].irradeventxraysourcedata_set.get()
441
- pulse_rate = source_data.pulse_rate
442
- fieldsize = source_data.ii_field_size
443
- try:
444
- filter_material, filter_thick = get_xray_filter_info(
445
- source_data
446
- )
447
- except ObjectDoesNotExist:
448
- filter_material = None
449
- filter_thick = None
450
- except ObjectDoesNotExist:
451
- pulse_rate = None
452
- fieldsize = None
453
- filter_material = None
454
- filter_thick = None
455
-
456
- protocol = inst[0].acquisition_protocol
457
-
458
- standard_protocol = ""
459
- if enable_standard_names:
460
- try:
461
- standard_protocol = (
462
- inst[0].standard_protocols.first().standard_name
463
- )
464
- except AttributeError:
465
- standard_protocol = ""
466
-
467
- event_type = inst[0].irradiation_event_type.code_meaning
468
-
469
- similarexposures = inst
470
- if plane:
471
- similarexposures = similarexposures.filter(
472
- acquisition_plane__code_meaning__exact=plane
473
- )
474
- if protocol:
475
- similarexposures = similarexposures.filter(
476
- acquisition_protocol__exact=protocol
477
- )
478
- if fieldsize:
479
- similarexposures = similarexposures.filter(
480
- irradeventxraysourcedata__ii_field_size__exact=fieldsize
481
- )
482
- if pulse_rate:
483
- similarexposures = similarexposures.filter(
484
- irradeventxraysourcedata__pulse_rate__exact=pulse_rate
485
- )
486
- if filter_material:
487
- for xray_filter in (
488
- inst[0].irradeventxraysourcedata_set.get().xrayfilters_set.all()
489
- ):
490
- similarexposures = similarexposures.filter(
491
- irradeventxraysourcedata__xrayfilters__xray_filter_material__code_meaning__exact=xray_filter.xray_filter_material.code_meaning
492
- )
493
- similarexposures = similarexposures.filter(
494
- irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__exact=xray_filter.xray_filter_thickness_maximum
495
- )
496
- if anglei:
497
- similarexposures = similarexposures.filter(
498
- irradeventxraymechanicaldata__positioner_primary_angle__range=(
499
- float(anglei) - angle_range,
500
- float(anglei) + angle_range,
501
- )
502
- )
503
- if angleii:
504
- similarexposures = similarexposures.filter(
505
- irradeventxraymechanicaldata__positioner_secondary_angle__range=(
506
- float(angleii) - angle_range,
507
- float(angleii) + angle_range,
508
- )
509
- )
510
- if event_type:
511
- similarexposures = similarexposures.filter(
512
- irradiation_event_type__code_meaning__exact=event_type
513
- )
514
-
515
- # Remove exposures included in this group from inst
516
- exposures_to_exclude = [
517
- o.irradiation_event_uid for o in similarexposures
518
- ]
519
- inst = inst.exclude(irradiation_event_uid__in=exposures_to_exclude)
520
-
521
- angle1 = similarexposures.all().aggregate(
522
- Min("irradeventxraymechanicaldata__positioner_primary_angle"),
523
- Max("irradeventxraymechanicaldata__positioner_primary_angle"),
524
- Avg("irradeventxraymechanicaldata__positioner_primary_angle"),
525
- )
526
- angle2 = similarexposures.all().aggregate(
527
- Min("irradeventxraymechanicaldata__positioner_secondary_angle"),
528
- Max("irradeventxraymechanicaldata__positioner_secondary_angle"),
529
- Avg("irradeventxraymechanicaldata__positioner_secondary_angle"),
530
- )
531
- dap = similarexposures.all().aggregate(
532
- Min("dose_area_product"),
533
- Max("dose_area_product"),
534
- Avg("dose_area_product"),
535
- )
536
- dose_rp = similarexposures.all().aggregate(
537
- Min("irradeventxraysourcedata__dose_rp"),
538
- Max("irradeventxraysourcedata__dose_rp"),
539
- Avg("irradeventxraysourcedata__dose_rp"),
540
- )
541
- kvp = similarexposures.all().aggregate(
542
- Min("irradeventxraysourcedata__kvp__kvp"),
543
- Max("irradeventxraysourcedata__kvp__kvp"),
544
- Avg("irradeventxraysourcedata__kvp__kvp"),
545
- )
546
- tube_current = similarexposures.all().aggregate(
547
- Min("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
548
- Max("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
549
- Avg("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
550
- )
551
- exp_time = similarexposures.all().aggregate(
552
- Min("irradeventxraysourcedata__exposure_time"),
553
- Max("irradeventxraysourcedata__exposure_time"),
554
- Avg("irradeventxraysourcedata__exposure_time"),
555
- )
556
- pulse_width = similarexposures.all().aggregate(
557
- Min("irradeventxraysourcedata__pulsewidth__pulse_width"),
558
- Max("irradeventxraysourcedata__pulsewidth__pulse_width"),
559
- Avg("irradeventxraysourcedata__pulsewidth__pulse_width"),
560
- )
561
-
562
- examdata += [
563
- event_type,
564
- protocol,
565
- ]
566
-
567
- if enable_standard_names:
568
- if standard_protocol:
569
- examdata += [standard_protocol]
570
- else:
571
- examdata += [""]
572
-
573
- examdata += [
574
- similarexposures.count(),
575
- plane,
576
- pulse_rate,
577
- fieldsize,
578
- filter_material,
579
- filter_thick,
580
- kvp["irradeventxraysourcedata__kvp__kvp__min"],
581
- kvp["irradeventxraysourcedata__kvp__kvp__max"],
582
- kvp["irradeventxraysourcedata__kvp__kvp__avg"],
583
- tube_current[
584
- "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__min"
585
- ],
586
- tube_current[
587
- "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__max"
588
- ],
589
- tube_current[
590
- "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__avg"
591
- ],
592
- pulse_width[
593
- "irradeventxraysourcedata__pulsewidth__pulse_width__min"
594
- ],
595
- pulse_width[
596
- "irradeventxraysourcedata__pulsewidth__pulse_width__max"
597
- ],
598
- pulse_width[
599
- "irradeventxraysourcedata__pulsewidth__pulse_width__avg"
600
- ],
601
- exp_time["irradeventxraysourcedata__exposure_time__min"],
602
- exp_time["irradeventxraysourcedata__exposure_time__max"],
603
- exp_time["irradeventxraysourcedata__exposure_time__avg"],
604
- dap["dose_area_product__min"],
605
- dap["dose_area_product__max"],
606
- dap["dose_area_product__avg"],
607
- dose_rp["irradeventxraysourcedata__dose_rp__min"],
608
- dose_rp["irradeventxraysourcedata__dose_rp__max"],
609
- dose_rp["irradeventxraysourcedata__dose_rp__avg"],
610
- angle1[
611
- "irradeventxraymechanicaldata__positioner_primary_angle__min"
612
- ],
613
- angle1[
614
- "irradeventxraymechanicaldata__positioner_primary_angle__max"
615
- ],
616
- angle1[
617
- "irradeventxraymechanicaldata__positioner_primary_angle__avg"
618
- ],
619
- angle2[
620
- "irradeventxraymechanicaldata__positioner_secondary_angle__min"
621
- ],
622
- angle2[
623
- "irradeventxraymechanicaldata__positioner_secondary_angle__max"
624
- ],
625
- angle2[
626
- "irradeventxraymechanicaldata__positioner_secondary_angle__avg"
627
- ],
628
- ]
629
-
630
- if not protocol:
631
- protocol = "Unknown"
632
- tab_text = sheet_name(protocol)
633
- filter_data = {
634
- "filter_material": filter_material,
635
- "filter_thick": filter_thick,
636
- }
637
- for exposure in similarexposures.order_by("pk"):
638
- series_data = _get_series_data(exposure, filter_data)
639
- sheetlist[tab_text]["count"] += 1
640
- sheetlist[tab_text]["sheet"].write_row(
641
- sheetlist[tab_text]["count"], 0, common_exam_data + series_data
642
- )
643
-
644
- if enable_standard_names:
645
- if standard_protocol:
646
- tab_text = sheet_name("[standard] " + standard_protocol)
647
- filter_data = {
648
- "filter_material": filter_material,
649
- "filter_thick": filter_thick,
650
- }
651
- for exposure in similarexposures.order_by("pk"):
652
- series_data = _get_series_data(exposure, filter_data)
653
- sheetlist[tab_text]["count"] += 1
654
- sheetlist[tab_text]["sheet"].write_row(
655
- sheetlist[tab_text]["count"],
656
- 0,
657
- common_exam_data + series_data,
658
- )
659
-
660
- if num_groups_this_exam > num_groups_max:
661
- num_groups_max = num_groups_this_exam
662
-
663
- wsalldata.write_row(row + 1, 0, examdata)
664
-
665
- except ObjectDoesNotExist:
666
- error_message = (
667
- "DoesNotExist error whilst exporting study {0} of {1}, study UID {2}, accession number"
668
- " {3} - maybe database entry was deleted as part of importing later version of same"
669
- " study?".format(
670
- row + 1,
671
- tsk.num_records,
672
- exams.study_instance_uid,
673
- exams.accession_number,
674
- )
675
- )
676
- logger.error(error_message)
677
- wsalldata.write(row + 1, 0, error_message)
678
-
679
- tsk.progress = "Generating headers for the all data sheet..."
680
- tsk.save()
681
-
682
- for h in range(num_groups_max):
683
- all_data_headers += [
684
- "G" + str(h + 1) + " Type",
685
- "G" + str(h + 1) + " Protocol",
686
- ]
687
-
688
- if enable_standard_names:
689
- all_data_headers += ["G" + str(h + 1) + " Standard acquisition name"]
690
-
691
- all_data_headers += [
692
- "G" + str(h + 1) + " No. exposures",
693
- "G" + str(h + 1) + " Plane",
694
- "G" + str(h + 1) + " Pulse rate",
695
- "G" + str(h + 1) + " Field size",
696
- "G" + str(h + 1) + " Filter material",
697
- "G" + str(h + 1) + " Mean filter thickness (mm)",
698
- "G" + str(h + 1) + " kVp min",
699
- "G" + str(h + 1) + " kVp max",
700
- "G" + str(h + 1) + " kVp mean",
701
- "G" + str(h + 1) + " mA min",
702
- "G" + str(h + 1) + " mA max",
703
- "G" + str(h + 1) + " mA mean",
704
- "G" + str(h + 1) + " pulse width min (ms)",
705
- "G" + str(h + 1) + " pulse width max (ms)",
706
- "G" + str(h + 1) + " pulse width mean (ms)",
707
- "G" + str(h + 1) + " Exp time min (ms)",
708
- "G" + str(h + 1) + " Exp time max (ms)",
709
- "G" + str(h + 1) + " Exp time mean (ms)",
710
- "G" + str(h + 1) + " DAP min (Gy.m^2)",
711
- "G" + str(h + 1) + " DAP max (Gy.m^2)",
712
- "G" + str(h + 1) + " DAP mean (Gy.m^2)",
713
- "G" + str(h + 1) + " Ref point dose min (Gy)",
714
- "G" + str(h + 1) + " Ref point dose max (Gy)",
715
- "G" + str(h + 1) + " Ref point dose mean (Gy)",
716
- "G" + str(h + 1) + " Primary angle min",
717
- "G" + str(h + 1) + " Primary angle max",
718
- "G" + str(h + 1) + " Primary angle mean",
719
- "G" + str(h + 1) + " Secondary angle min",
720
- "G" + str(h + 1) + " Secondary angle max",
721
- "G" + str(h + 1) + " Secondary angle mean",
722
- ]
723
- wsalldata.write_row("A1", all_data_headers)
724
- num_rows = e.count()
725
- wsalldata.autofilter(0, 0, num_rows, len(all_data_headers) - 1)
726
-
727
- create_summary_sheet(tsk, e, book, summarysheet, sheetlist)
728
-
729
- book.close()
730
- tsk.progress = "XLSX book written."
731
- tsk.save()
732
-
733
- xlsxfilename = "rfexport{0}.xlsx".format(datestamp.strftime("%Y%m%d-%H%M%S%f"))
734
-
735
- write_export(tsk, xlsxfilename, tmpxlsx, datestamp)
736
-
737
-
738
- def exportFL2excel(filterdict, pid=False, name=None, patid=None, user=None):
739
- """Export filtered fluoro database data to a single-sheet CSV file.
740
-
741
- :param filterdict: Queryset of studies to export
742
- :param pid: does the user have patient identifiable data permission
743
- :param name: has patient name been selected for export
744
- :param patid: has patient ID been selected for export
745
- :param user: User that has started the export
746
- :return: Saves csv file into Media directory for user to download
747
- """
748
-
749
- # Obtain the system-level enable_standard_names setting
750
- try:
751
- StandardNameSettings.objects.get()
752
- except ObjectDoesNotExist:
753
- StandardNameSettings.objects.create()
754
- enable_standard_names = StandardNameSettings.objects.values_list(
755
- "enable_standard_names", flat=True
756
- )[0]
757
-
758
- datestamp = datetime.datetime.now()
759
- task_id = get_or_generate_task_uuid()
760
- tsk = create_export_task(
761
- task_id=task_id,
762
- modality="RF",
763
- export_type="CSV export",
764
- date_stamp=datestamp,
765
- pid=bool(pid and (name or patid)),
766
- user=user,
767
- filters_dict=filterdict,
768
- )
769
-
770
- tmpfile, writer = create_csv(tsk)
771
- if not tmpfile:
772
- exit()
773
-
774
- # Get the data!
775
- if pid:
776
- if enable_standard_names:
777
- df_filtered_qs = RFFilterPlusPidPlusStdNames(
778
- filterdict,
779
- queryset=GeneralStudyModuleAttr.objects.filter(
780
- modality_type__exact="RF"
781
- ).distinct(),
782
- )
783
- else:
784
- df_filtered_qs = RFFilterPlusPid(
785
- filterdict,
786
- queryset=GeneralStudyModuleAttr.objects.filter(
787
- modality_type__exact="RF"
788
- ).distinct(),
789
- )
790
- else:
791
- if enable_standard_names:
792
- df_filtered_qs = RFFilterPlusStdNames(
793
- filterdict,
794
- queryset=GeneralStudyModuleAttr.objects.filter(
795
- modality_type__exact="RF"
796
- ).distinct(),
797
- )
798
- else:
799
- df_filtered_qs = RFSummaryListFilter(
800
- filterdict,
801
- queryset=GeneralStudyModuleAttr.objects.filter(
802
- modality_type__exact="RF"
803
- ).distinct(),
804
- )
805
-
806
- e = df_filtered_qs.qs
807
-
808
- tsk.num_records = e.count()
809
- if abort_if_zero_studies(tsk.num_records, tsk):
810
- return
811
-
812
- headings = _all_data_headers(pid=pid, name=name, patid=patid)
813
- writer.writerow(headings)
814
- for i, exams in enumerate(e):
815
-
816
- tsk.progress = "{0} of {1}".format(i + 1, tsk.num_records)
817
- tsk.save()
818
-
819
- try:
820
- exam_data = get_common_data("RF", exams, pid=pid, name=name, patid=patid)
821
-
822
- for (
823
- plane
824
- ) in exams.projectionxrayradiationdose_set.get().accumxraydose_set.all():
825
- accum = _get_accumulated_data(plane)
826
- exam_data += [
827
- accum["dose_area_product_total"],
828
- accum["dose_rp_total"],
829
- accum["fluoro_dose_area_product_total"],
830
- accum["fluoro_dose_rp_total"],
831
- accum["total_fluoro_time"],
832
- accum["acquisition_dose_area_product_total"],
833
- accum["acquisition_dose_rp_total"],
834
- accum["total_acquisition_time"],
835
- accum["eventcount"],
836
- ]
837
- # Clear out any commas
838
- for index, item in enumerate(exam_data):
839
- if item is None:
840
- exam_data[index] = ""
841
- if isinstance(item, str) and "," in item:
842
- exam_data[index] = item.replace(",", ";")
843
- writer.writerow([str(data_string) for data_string in exam_data])
844
- except ObjectDoesNotExist:
845
- error_message = (
846
- "DoesNotExist error whilst exporting study {0} of {1}, study UID {2}, accession number"
847
- " {3} - maybe database entry was deleted as part of importing later version of same"
848
- " study?".format(
849
- i + 1,
850
- tsk.num_records,
851
- exams.study_instance_uid,
852
- exams.accession_number,
853
- )
854
- )
855
- logger.error(error_message)
856
- writer.writerow([error_message])
857
-
858
- tsk.progress = "All study data written."
859
- tsk.save()
860
-
861
- tmpfile.close()
862
- tsk.status = "COMPLETE"
863
- tsk.processtime = (datetime.datetime.now() - datestamp).total_seconds()
864
- tsk.save()
865
-
866
-
867
- def rfopenskin(studyid):
868
- """Export single RF study data to OpenSkin RF csv sheet.
869
-
870
- :param studyid: RF study database ID.
871
- :type studyid: int
872
-
873
- """
874
-
875
- datestamp = datetime.datetime.now()
876
- task_id = get_or_generate_task_uuid()
877
- tsk = create_export_task(
878
- task_id=task_id,
879
- modality="RF-OpenSkin",
880
- export_type="OpenSkin RF csv export",
881
- date_stamp=datestamp,
882
- pid=False,
883
- user=None,
884
- filters_dict={"study_id": studyid},
885
- )
886
-
887
- tmpfile, writer = create_csv(tsk)
888
- if not tmpfile:
889
- exit()
890
-
891
- # Get the data
892
- study = GeneralStudyModuleAttr.objects.get(pk=studyid)
893
- numevents = (
894
- study.projectionxrayradiationdose_set.get().irradeventxraydata_set.count()
895
- )
896
- tsk.num_records = numevents
897
- tsk.save()
898
-
899
- for i, event in enumerate(
900
- study.projectionxrayradiationdose_set.get().irradeventxraydata_set.all()
901
- ):
902
- try:
903
- study.patientmoduleattr_set.get()
904
- except ObjectDoesNotExist:
905
- patient_sex = ""
906
- else:
907
- patient_sex = study.patientmoduleattr_set.get().patient_sex
908
-
909
- try:
910
- event.irradeventxraysourcedata_set.get()
911
- except ObjectDoesNotExist:
912
- reference_point_definition = ""
913
- dose_rp = ""
914
- fluoro_mode = ""
915
- pulse_rate = ""
916
- number_of_pulses = ""
917
- exposure_time = ""
918
- focal_spot_size = ""
919
- irradiation_duration = ""
920
- average_xray_tube_current = ""
921
- else:
922
- reference_point_definition = (
923
- event.irradeventxraysourcedata_set.get().reference_point_definition
924
- )
925
- dose_rp = event.irradeventxraysourcedata_set.get().dose_rp
926
- fluoro_mode = event.irradeventxraysourcedata_set.get().fluoro_mode
927
- pulse_rate = event.irradeventxraysourcedata_set.get().pulse_rate
928
- number_of_pulses = event.irradeventxraysourcedata_set.get().number_of_pulses
929
- exposure_time = event.irradeventxraysourcedata_set.get().exposure_time
930
- focal_spot_size = event.irradeventxraysourcedata_set.get().focal_spot_size
931
- irradiation_duration = (
932
- event.irradeventxraysourcedata_set.get().irradiation_duration
933
- )
934
- average_xray_tube_current = (
935
- event.irradeventxraysourcedata_set.get().average_xray_tube_current
936
- )
937
-
938
- try:
939
- event.irradeventxraymechanicaldata_set.get()
940
- except ObjectDoesNotExist:
941
- positioner_primary_angle = ""
942
- positioner_secondary_angle = ""
943
- positioner_primary_end_angle = ""
944
- positioner_secondary_end_angle = ""
945
- column_angulation = ""
946
- else:
947
- positioner_primary_angle = (
948
- event.irradeventxraymechanicaldata_set.get().positioner_primary_angle
949
- )
950
- positioner_secondary_angle = (
951
- event.irradeventxraymechanicaldata_set.get().positioner_secondary_angle
952
- )
953
- positioner_primary_end_angle = (
954
- event.irradeventxraymechanicaldata_set.get().positioner_primary_end_angle
955
- )
956
- positioner_secondary_end_angle = (
957
- event.irradeventxraymechanicaldata_set.get().positioner_secondary_end_angle
958
- )
959
- column_angulation = (
960
- event.irradeventxraymechanicaldata_set.get().column_angulation
961
- )
962
-
963
- xray_filter_type = ""
964
- xray_filter_material = ""
965
- xray_filter_thickness_minimum = ""
966
- xray_filter_thickness_maximum = ""
967
- try:
968
- for (
969
- filters
970
- ) in event.irradeventxraysourcedata_set.get().xrayfilters_set.all():
971
- try:
972
- if "Copper" in filters.xray_filter_material.code_meaning:
973
- xray_filter_type = filters.xray_filter_type
974
- xray_filter_material = filters.xray_filter_material
975
- xray_filter_thickness_minimum = (
976
- filters.xray_filter_thickness_minimum
977
- )
978
- xray_filter_thickness_maximum = (
979
- filters.xray_filter_thickness_maximum
980
- )
981
- except AttributeError:
982
- pass
983
- except ObjectDoesNotExist:
984
- pass
985
-
986
- try:
987
- event.irradeventxraysourcedata_set.get().kvp_set.get()
988
- except ObjectDoesNotExist:
989
- kvp = ""
990
- else:
991
- kvp = event.irradeventxraysourcedata_set.get().kvp_set.get().kvp
992
-
993
- try:
994
- event.irradeventxraysourcedata_set.get().xraytubecurrent_set.get()
995
- except ObjectDoesNotExist:
996
- xray_tube_current = ""
997
- else:
998
- xray_tube_current = (
999
- event.irradeventxraysourcedata_set.get()
1000
- .xraytubecurrent_set.get()
1001
- .xray_tube_current
1002
- )
1003
-
1004
- try:
1005
- event.irradeventxraysourcedata_set.get().pulsewidth_set.get()
1006
- except ObjectDoesNotExist:
1007
- pulse_width = ""
1008
- else:
1009
- pulse_width = (
1010
- event.irradeventxraysourcedata_set.get()
1011
- .pulsewidth_set.get()
1012
- .pulse_width
1013
- )
1014
-
1015
- try:
1016
- event.irradeventxraysourcedata_set.get().exposure_set.get()
1017
- except ObjectDoesNotExist:
1018
- exposure = ""
1019
- else:
1020
- exposure = (
1021
- event.irradeventxraysourcedata_set.get().exposure_set.get().exposure
1022
- )
1023
-
1024
- try:
1025
- event.irradeventxraymechanicaldata_set.get().doserelateddistancemeasurements_set.get()
1026
- except ObjectDoesNotExist:
1027
- distance_source_to_detector = ""
1028
- distance_source_to_isocenter = ""
1029
- table_longitudinal_position = ""
1030
- table_lateral_position = ""
1031
- table_height_position = ""
1032
- else:
1033
- distance_source_to_detector = (
1034
- event.irradeventxraymechanicaldata_set.get()
1035
- .doserelateddistancemeasurements_set.get()
1036
- .distance_source_to_detector
1037
- )
1038
- distance_source_to_isocenter = (
1039
- event.irradeventxraymechanicaldata_set.get()
1040
- .doserelateddistancemeasurements_set.get()
1041
- .distance_source_to_isocenter
1042
- )
1043
- table_longitudinal_position = (
1044
- event.irradeventxraymechanicaldata_set.get()
1045
- .doserelateddistancemeasurements_set.get()
1046
- .table_longitudinal_position
1047
- )
1048
- table_lateral_position = (
1049
- event.irradeventxraymechanicaldata_set.get()
1050
- .doserelateddistancemeasurements_set.get()
1051
- .table_lateral_position
1052
- )
1053
- table_height_position = (
1054
- event.irradeventxraymechanicaldata_set.get()
1055
- .doserelateddistancemeasurements_set.get()
1056
- .table_height_position
1057
- )
1058
-
1059
- acquisition_protocol = return_for_export(event, "acquisition_protocol")
1060
- if isinstance(acquisition_protocol, str) and "," in acquisition_protocol:
1061
- acquisition_protocol = acquisition_protocol.replace(",", ";")
1062
- comment = event.comment
1063
- if isinstance(comment, str) and "," in comment:
1064
- comment = comment.replace(",", ";")
1065
-
1066
- data = [
1067
- "Anon",
1068
- patient_sex,
1069
- study.study_instance_uid,
1070
- "",
1071
- event.acquisition_plane,
1072
- event.date_time_started,
1073
- event.irradiation_event_type,
1074
- acquisition_protocol,
1075
- reference_point_definition,
1076
- event.irradiation_event_uid,
1077
- event.dose_area_product,
1078
- dose_rp,
1079
- positioner_primary_angle,
1080
- positioner_secondary_angle,
1081
- positioner_primary_end_angle,
1082
- positioner_secondary_end_angle,
1083
- column_angulation,
1084
- xray_filter_type,
1085
- xray_filter_material,
1086
- xray_filter_thickness_minimum,
1087
- xray_filter_thickness_maximum,
1088
- fluoro_mode,
1089
- pulse_rate,
1090
- number_of_pulses,
1091
- kvp,
1092
- xray_tube_current,
1093
- exposure_time,
1094
- pulse_width,
1095
- exposure,
1096
- focal_spot_size,
1097
- irradiation_duration,
1098
- average_xray_tube_current,
1099
- distance_source_to_detector,
1100
- distance_source_to_isocenter,
1101
- table_longitudinal_position,
1102
- table_lateral_position,
1103
- table_height_position,
1104
- event.target_region,
1105
- comment,
1106
- ]
1107
- writer.writerow(data)
1108
- tsk.progress = "{0} of {1}".format(i, numevents)
1109
- tsk.save()
1110
- tsk.progress = "All study data written."
1111
- tsk.save()
1112
-
1113
- tmpfile.close()
1114
- tsk.status = "COMPLETE"
1115
- tsk.processtime = (datetime.datetime.now() - datestamp).total_seconds()
1116
- tsk.save()
1117
-
1118
-
1119
- def rf_phe_2019(filterdict, user=None):
1120
- """Export filtered RF database data in the format for the 2019 Public Health England IR/fluoro dose survey
1121
-
1122
- :param filterdict: Queryset of studies to export
1123
- :param user: User that has started the export
1124
- :return: Saves Excel file into media directory for user to download
1125
- """
1126
-
1127
- datestamp = datetime.datetime.now()
1128
- task_id = get_or_generate_task_uuid()
1129
- tsk = create_export_task(
1130
- task_id=task_id,
1131
- modality="RF",
1132
- export_type="PHE RF 2019 export",
1133
- date_stamp=datestamp,
1134
- pid=False,
1135
- user=user,
1136
- filters_dict=filterdict,
1137
- )
1138
-
1139
- tmp_xlsx, book = create_xlsx(tsk)
1140
- if not tmp_xlsx:
1141
- exit()
1142
- sheet = book.add_worksheet("PHE IR-Fluoro")
1143
-
1144
- exams = RFSummaryListFilter(
1145
- filterdict,
1146
- queryset=GeneralStudyModuleAttr.objects.filter(modality_type__exact="RF"),
1147
- ).qs
1148
- tsk.num_records = exams.count()
1149
- if abort_if_zero_studies(tsk.num_records, tsk):
1150
- return
1151
-
1152
- tsk.progress = "{0} studies in query.".format(tsk.num_records)
1153
- tsk.save()
1154
-
1155
- row_4 = ["", "", "", "", "", "Gy·m²", "", "seconds", "Gy"]
1156
- sheet.write_row(3, 0, row_4)
1157
-
1158
- num_rows = exams.count()
1159
- for row, exam in enumerate(exams):
1160
- tsk.progress = "Writing study {0} of {1}".format(row + 1, num_rows)
1161
- tsk.save()
1162
-
1163
- row_data = ["", row + 1, exam.pk, exam.study_date, exam.total_dap]
1164
- accum_data = []
1165
- for plane in exam.projectionxrayradiationdose_set.get().accumxraydose_set.all():
1166
- accum_data.append(_get_accumulated_data(plane))
1167
- if len(accum_data) == 2:
1168
- accum_data[0]["fluoro_dose_area_product_total"] += accum_data[1][
1169
- "fluoro_dose_area_product_total"
1170
- ]
1171
- accum_data[0]["fluoro_dose_rp_total"] += accum_data[1][
1172
- "fluoro_dose_rp_total"
1173
- ]
1174
- accum_data[0]["total_fluoro_time"] += accum_data[1]["total_fluoro_time"]
1175
- accum_data[0]["acquisition_dose_area_product_total"] += accum_data[1][
1176
- "acquisition_dose_area_product_total"
1177
- ]
1178
- accum_data[0]["acquisition_dose_rp_total"] += accum_data[1][
1179
- "acquisition_dose_rp_total"
1180
- ]
1181
- accum_data[0]["total_acquisition_time"] += accum_data[1][
1182
- "total_acquisition_time"
1183
- ]
1184
- accum_data[0]["eventcount"] += accum_data[1]["eventcount"]
1185
- row_data += [
1186
- accum_data[0]["fluoro_dose_area_product_total"],
1187
- accum_data[0]["acquisition_dose_area_product_total"],
1188
- accum_data[0]["total_fluoro_time"],
1189
- ]
1190
-
1191
- try:
1192
- total_rp_dose = exam.total_rp_dose_a + exam.total_rp_dose_b
1193
- except TypeError:
1194
- if exam.total_rp_dose_a is not None:
1195
- total_rp_dose = exam.total_rp_dose_a
1196
- elif exam.total_rp_dose_b is not None:
1197
- total_rp_dose = exam.total_rp_dose_b
1198
- else:
1199
- total_rp_dose = 0
1200
- row_data += [
1201
- total_rp_dose,
1202
- accum_data[0]["fluoro_dose_rp_total"],
1203
- accum_data[0]["acquisition_dose_rp_total"],
1204
- "{0} | {1} | {2}".format(
1205
- exam.procedure_code_meaning,
1206
- exam.requested_procedure_code_meaning,
1207
- exam.study_description,
1208
- ),
1209
- ]
1210
- patient_study_data = get_patient_study_data(exam)
1211
- patient_sex = None
1212
- try:
1213
- patient_sex = exam.patientmoduleattr_set.get().patient_sex
1214
- except ObjectDoesNotExist:
1215
- logger.debug(
1216
- "Export {0}; patientmoduleattr_set object does not exist. AccNum {1}, Date {2}".format(
1217
- "PHE 2019 RF", exams.accession_number, exams.study_date
1218
- )
1219
- )
1220
- row_data += [
1221
- patient_study_data["patient_weight"],
1222
- "",
1223
- patient_study_data["patient_age_decimal"],
1224
- patient_sex,
1225
- patient_study_data["patient_size"],
1226
- ]
1227
-
1228
- events = IrradEventXRayData.objects.filter(
1229
- projection_xray_radiation_dose__general_study_module_attributes__pk__exact=exam.pk
1230
- )
1231
- fluoro_events = events.exclude(
1232
- irradiation_event_type__code_value__contains="11361"
1233
- ) # acq events are 113611, 113612, 113613
1234
- acquisition_events = events.filter(
1235
- irradiation_event_type__code_value__contains="11361"
1236
- )
1237
- try:
1238
- row_data += [
1239
- " | ".join(
1240
- fluoro_events.order_by()
1241
- .values_list("acquisition_protocol", flat=True)
1242
- .distinct()
1243
- )
1244
- ]
1245
- except TypeError:
1246
- row_data += [""]
1247
- try:
1248
- row_data += [
1249
- " | ".join(
1250
- fluoro_events.order_by()
1251
- .values_list(
1252
- "irradeventxraysourcedata__fluoro_mode__code_meaning", flat=True
1253
- )
1254
- .distinct()
1255
- )
1256
- ]
1257
- except TypeError:
1258
- row_data += [""]
1259
- fluoro_frame_rates = (
1260
- fluoro_events.order_by()
1261
- .values_list("irradeventxraysourcedata__pulse_rate", flat=True)
1262
- .distinct()
1263
- )
1264
- column_aq = ""
1265
- if len(fluoro_frame_rates) > 1:
1266
- column_aq += "Fluoro: "
1267
- column_aq += " | ".join(
1268
- format(x, "1.1f") for x in fluoro_frame_rates if x is not None
1269
- )
1270
- column_aq += " fps. "
1271
- row_data += ["Multiple rates"]
1272
- else:
1273
- try:
1274
- row_data += [fluoro_frame_rates[0]]
1275
- except IndexError:
1276
- row_data += [""]
1277
- acquisition_frame_rates = (
1278
- acquisition_events.order_by()
1279
- .values_list("irradeventxraysourcedata__pulse_rate", flat=True)
1280
- .distinct()
1281
- )
1282
- add_single = False
1283
- if None in acquisition_frame_rates:
1284
- if len(acquisition_frame_rates) == 1:
1285
- acquisition_frame_rates = ["Single shot"]
1286
- else:
1287
- acquisition_frame_rates = acquisition_frame_rates[1:]
1288
- add_single = True
1289
- if len(acquisition_frame_rates) > 1:
1290
- row_data += ["Multiple rates"]
1291
- column_aq += "Acquisition: "
1292
- if add_single:
1293
- column_aq += "Single shot | "
1294
- column_aq += " | ".join(format(x, "1.1f") for x in acquisition_frame_rates)
1295
- column_aq += " fps. "
1296
- else:
1297
- try:
1298
- row_data += [acquisition_frame_rates[0]]
1299
- except IndexError:
1300
- row_data += [""]
1301
- row_data += [acquisition_events.count()]
1302
- try:
1303
- grid_types = (
1304
- events.order_by()
1305
- .values_list(
1306
- "irradeventxraysourcedata__xraygrid__xray_grid__code_meaning",
1307
- flat=True,
1308
- )
1309
- .distinct()
1310
- )
1311
- if None in grid_types:
1312
- grid_types = grid_types[1:]
1313
- except ObjectDoesNotExist:
1314
- grid_types = [""]
1315
- row_data += [" | ".join(grid_types), ""] # AEC used - not recorded in RDSR
1316
- patient_position = (
1317
- events.order_by()
1318
- .values_list(
1319
- "patient_table_relationship_cid__code_meaning",
1320
- "patient_orientation_cid__code_meaning",
1321
- "patient_orientation_modifier_cid__code_meaning",
1322
- )
1323
- .distinct()
1324
- )
1325
- patient_position_str = ""
1326
- for position_set in patient_position:
1327
- for element in (i for i in position_set if i):
1328
- patient_position_str += "{0}, ".format(element)
1329
- row_data += [
1330
- patient_position_str,
1331
- "", # digital subtraction
1332
- "", # circular field of view
1333
- ]
1334
- field_dimensions = events.aggregate(
1335
- Min("irradeventxraysourcedata__collimated_field_area"),
1336
- Max("irradeventxraysourcedata__collimated_field_area"),
1337
- Min("irradeventxraysourcedata__collimated_field_width"),
1338
- Max("irradeventxraysourcedata__collimated_field_width"),
1339
- Min("irradeventxraysourcedata__collimated_field_height"),
1340
- Max("irradeventxraysourcedata__collimated_field_height"),
1341
- )
1342
- rectangular_fov = ""
1343
- if field_dimensions["irradeventxraysourcedata__collimated_field_area__min"]:
1344
- rectangular_fov += "Area {0:.4f} to {1:.4f} m², ".format(
1345
- field_dimensions[
1346
- "irradeventxraysourcedata__collimated_field_area__min"
1347
- ],
1348
- field_dimensions[
1349
- "irradeventxraysourcedata__collimated_field_area__max"
1350
- ],
1351
- )
1352
- if field_dimensions["irradeventxraysourcedata__collimated_field_width__min"]:
1353
- rectangular_fov += "Width {0:.4f} to {1:.4f} m, ".format(
1354
- field_dimensions[
1355
- "irradeventxraysourcedata__collimated_field_width__min"
1356
- ],
1357
- field_dimensions[
1358
- "irradeventxraysourcedata__collimated_field_width__max"
1359
- ],
1360
- )
1361
- if field_dimensions["irradeventxraysourcedata__collimated_field_height__min"]:
1362
- rectangular_fov += "Height {0:.4f} to {1:.4f} m, ".format(
1363
- field_dimensions[
1364
- "irradeventxraysourcedata__collimated_field_height__min"
1365
- ],
1366
- field_dimensions[
1367
- "irradeventxraysourcedata__collimated_field_height__max"
1368
- ],
1369
- )
1370
- field_sizes = (
1371
- events.order_by()
1372
- .values_list("irradeventxraysourcedata__ii_field_size", flat=True)
1373
- .distinct()
1374
- )
1375
- diagonal_fov = ""
1376
- for fov in field_sizes:
1377
- if fov:
1378
- diagonal_fov += "{0}, ".format(fov)
1379
- if diagonal_fov:
1380
- diagonal_fov += " mm"
1381
- row_data += [rectangular_fov, diagonal_fov]
1382
- filters_al = events.filter(
1383
- irradeventxraysourcedata__xrayfilters__xray_filter_material__code_value__exact="C-120F9"
1384
- )
1385
- filters_cu = events.filter(
1386
- irradeventxraysourcedata__xrayfilters__xray_filter_material__code_value__exact="C-127F9"
1387
- )
1388
- filters_al_thick = filters_al.aggregate(
1389
- Min("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1390
- Max("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1391
- )
1392
- filters_cu_thick = filters_cu.aggregate(
1393
- Min("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1394
- Max("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1395
- )
1396
- filters_al_str = ""
1397
- filters_cu_str = ""
1398
- if filters_al_thick[
1399
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1400
- ]:
1401
- filters_al_str = "{0:.2} - {1:.2} mm".format(
1402
- filters_al_thick[
1403
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1404
- ],
1405
- filters_al_thick[
1406
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__max"
1407
- ],
1408
- )
1409
- if filters_cu_thick[
1410
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1411
- ]:
1412
- filters_cu_str = "{0:.2} - {1:.2} mm".format(
1413
- filters_cu_thick[
1414
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1415
- ],
1416
- filters_cu_thick[
1417
- "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__max"
1418
- ],
1419
- )
1420
- row_data += ["", filters_cu_str, filters_al_str] # filtration automated?
1421
- row_data += ["", "", "", "", "", "", "", "", "", ""]
1422
- row_data += [column_aq]
1423
- sheet.write_row(row + 6, 0, row_data)
1424
-
1425
- book.close()
1426
- tsk.progress = "PHE IR/Fluoro 2019 export complete"
1427
- tsk.save()
1428
-
1429
- xlsxfilename = "PHE_RF_2019_{0}.xlsx".format(datestamp.strftime("%Y%m%d-%H%M%S%f"))
1430
-
1431
- write_export(tsk, xlsxfilename, tmp_xlsx, datestamp)
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
+ .. module:: rf_export.
25
+ :synopsis: Module to export RF data from database to single sheet csv and multisheet xlsx.
26
+
27
+ .. moduleauthor:: Ed McDonagh
28
+
29
+ """
30
+
31
+ import datetime
32
+ import logging
33
+ from openrem.remapp.tools.background import get_or_generate_task_uuid
34
+
35
+ from django.core.exceptions import ObjectDoesNotExist
36
+ from django.db.models import Avg, Max, Min
37
+
38
+ from ..models import (
39
+ GeneralStudyModuleAttr,
40
+ IrradEventXRayData,
41
+ )
42
+
43
+ from ..exports.export_common import (
44
+ text_and_date_formats,
45
+ common_headers,
46
+ generate_sheets,
47
+ sheet_name,
48
+ get_common_data,
49
+ get_xray_filter_info,
50
+ create_xlsx,
51
+ create_csv,
52
+ write_export,
53
+ create_summary_sheet,
54
+ get_pulse_data,
55
+ abort_if_zero_studies,
56
+ create_export_task,
57
+ get_patient_study_data,
58
+ )
59
+ from ..interface.mod_filters import (
60
+ RFSummaryListFilter,
61
+ RFFilterPlusPid,
62
+ RFFilterPlusStdNames,
63
+ RFFilterPlusPidPlusStdNames,
64
+ )
65
+ from ..tools.get_values import return_for_export
66
+
67
+ from ..tools.check_standard_name_status import are_standard_names_enabled
68
+
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+
73
+ def _get_accumulated_data(accumXrayDose):
74
+ """Extract all the summary level data
75
+
76
+ :param accumXrayDose: Accumulated x-ray radiation dose object
77
+ :return: dict of summary level data
78
+ """
79
+ accum = {}
80
+ accum["plane"] = accumXrayDose.acquisition_plane.code_meaning
81
+ try:
82
+ accumulated_integrated_projection_dose = (
83
+ accumXrayDose.accumintegratedprojradiogdose_set.get()
84
+ )
85
+ accum["dose_area_product_total"] = (
86
+ accumulated_integrated_projection_dose.dose_area_product_total
87
+ )
88
+ accum["dose_rp_total"] = accumulated_integrated_projection_dose.dose_rp_total
89
+ accum["reference_point_definition"] = (
90
+ accumulated_integrated_projection_dose.reference_point_definition_code
91
+ )
92
+ if not accum["reference_point_definition"]:
93
+ accum["reference_point_definition"] = (
94
+ accumulated_integrated_projection_dose.reference_point_definition
95
+ )
96
+ except ObjectDoesNotExist:
97
+ accum["dose_area_product_total"] = None
98
+ accum["dose_rp_total"] = None
99
+ accum["reference_point_definition_code"] = None
100
+ try:
101
+ accumulated_projection_dose = accumXrayDose.accumprojxraydose_set.get()
102
+ accum["fluoro_dose_area_product_total"] = (
103
+ accumulated_projection_dose.fluoro_dose_area_product_total
104
+ )
105
+ accum["fluoro_dose_rp_total"] = accumulated_projection_dose.fluoro_dose_rp_total
106
+ accum["total_fluoro_time"] = accumulated_projection_dose.total_fluoro_time
107
+ accum["acquisition_dose_area_product_total"] = (
108
+ accumulated_projection_dose.acquisition_dose_area_product_total
109
+ )
110
+ accum["acquisition_dose_rp_total"] = (
111
+ accumulated_projection_dose.acquisition_dose_rp_total
112
+ )
113
+ accum["total_acquisition_time"] = (
114
+ accumulated_projection_dose.total_acquisition_time
115
+ )
116
+ except ObjectDoesNotExist:
117
+ accum["fluoro_dose_area_product_total"] = None
118
+ accum["fluoro_dose_rp_total"] = None
119
+ accum["total_fluoro_time"] = None
120
+ accum["acquisition_dose_area_product_total"] = None
121
+ accum["acquisition_dose_rp_total"] = None
122
+ accum["total_acquisition_time"] = None
123
+
124
+ try:
125
+ accum["eventcount"] = int(
126
+ accumXrayDose.projection_xray_radiation_dose.irradeventxraydata_set.filter(
127
+ acquisition_plane__code_meaning__exact=accum["plane"]
128
+ ).count()
129
+ )
130
+ except ObjectDoesNotExist:
131
+ accum["eventcount"] = None
132
+
133
+ return accum
134
+
135
+
136
+ def _add_plane_summary_data(exam):
137
+ """Add plane level accumulated data to examdata
138
+
139
+ :param exam: exam to export
140
+ :return: list of summary data at plane level
141
+ """
142
+ exam_data = []
143
+ for plane in (
144
+ exam.projectionxrayradiationdose_set.get()
145
+ .accumxraydose_set.all()
146
+ .order_by("acquisition_plane__code_value")
147
+ ):
148
+ accum = _get_accumulated_data(plane)
149
+ exam_data += [
150
+ accum["dose_area_product_total"],
151
+ accum["dose_rp_total"],
152
+ accum["fluoro_dose_area_product_total"],
153
+ accum["fluoro_dose_rp_total"],
154
+ accum["total_fluoro_time"],
155
+ accum["acquisition_dose_area_product_total"],
156
+ accum["acquisition_dose_rp_total"],
157
+ accum["total_acquisition_time"],
158
+ accum["eventcount"],
159
+ ]
160
+ if "Single" in accum["plane"]:
161
+ exam_data += ["", "", "", "", "", "", "", "", ""]
162
+
163
+ return exam_data
164
+
165
+
166
+ def _get_series_data(event, filter_data):
167
+ """Return series level data for protocol sheets
168
+
169
+ :param event: event in question
170
+ :return: list of data
171
+ """
172
+
173
+ # Obtain the system-level enable_standard_names setting
174
+ enable_standard_names = are_standard_names_enabled()
175
+
176
+ try:
177
+ source_data = event.irradeventxraysourcedata_set.get()
178
+ pulse_rate = source_data.pulse_rate
179
+ ii_field_size = source_data.ii_field_size
180
+ exposure_time = source_data.exposure_time
181
+ dose_rp = source_data.dose_rp
182
+ number_of_pulses = source_data.number_of_pulses
183
+ irradiation_duration = source_data.irradiation_duration
184
+ pulse_data = get_pulse_data(source_data=source_data, modality="RF")
185
+ kVp = pulse_data["kvp"]
186
+ xray_tube_current = pulse_data["xray_tube_current"]
187
+ pulse_width = pulse_data["pulse_width"]
188
+ except ObjectDoesNotExist:
189
+ pulse_rate = None
190
+ ii_field_size = None
191
+ exposure_time = None
192
+ dose_rp = None
193
+ number_of_pulses = None
194
+ irradiation_duration = None
195
+ kVp = None
196
+ xray_tube_current = None
197
+ pulse_width = None
198
+ try:
199
+ mechanical_data = event.irradeventxraymechanicaldata_set.get()
200
+ pos_primary_angle = mechanical_data.positioner_primary_angle
201
+ pos_secondary_angle = mechanical_data.positioner_secondary_angle
202
+ except ObjectDoesNotExist:
203
+ pos_primary_angle = None
204
+ pos_secondary_angle = None
205
+
206
+ series_data = [
207
+ str(event.date_time_started),
208
+ event.irradiation_event_type.code_meaning,
209
+ event.acquisition_protocol,
210
+ ]
211
+
212
+ if enable_standard_names:
213
+ try:
214
+ standard_protocol = event.standard_protocols.first().standard_name
215
+ except AttributeError:
216
+ standard_protocol = ""
217
+
218
+ if standard_protocol:
219
+ series_data += [standard_protocol]
220
+ else:
221
+ series_data += [""]
222
+
223
+ series_data = series_data + [
224
+ event.acquisition_plane.code_meaning,
225
+ ii_field_size,
226
+ filter_data["filter_material"],
227
+ filter_data["filter_thick"],
228
+ kVp,
229
+ xray_tube_current,
230
+ pulse_width,
231
+ pulse_rate,
232
+ number_of_pulses,
233
+ exposure_time,
234
+ irradiation_duration,
235
+ event.convert_gym2_to_cgycm2(),
236
+ dose_rp,
237
+ pos_primary_angle,
238
+ pos_secondary_angle,
239
+ ]
240
+
241
+ return series_data
242
+
243
+
244
+ def _all_data_headers(pid=False, name=None, patid=None):
245
+ """Compile list of column headers
246
+
247
+ :param pid: does the user have patient identifiable data permission
248
+ :param name: has patient name been selected for export
249
+ :param patid: has patient ID been selected for export
250
+ :return: list of headers for all_data sheet and csv sheet
251
+ """
252
+ all_data_headers = common_headers(
253
+ modality="RF", pid=pid, name=name, patid=patid
254
+ ) + [
255
+ "A DAP total (Gy.m^2)",
256
+ "A Dose RP total (Gy)",
257
+ "A Fluoro DAP total (Gy.m^2)",
258
+ "A Fluoro dose RP total (Gy)",
259
+ "A Fluoro duration total (s)",
260
+ "A Acq. DAP total (Gy.m^2)",
261
+ "A Acq. dose RP total (Gy)",
262
+ "A Acq. duration total (s)",
263
+ "A Number of events",
264
+ "B DAP total (Gy.m^2)",
265
+ "B Dose RP total (Gy)",
266
+ "B Fluoro DAP total (Gy.m^2)",
267
+ "B Fluoro dose RP total (Gy)",
268
+ "B Fluoro duration total (s)",
269
+ "B Acq. DAP total (Gy.m^2)",
270
+ "B Acq. dose RP total (Gy)",
271
+ "B Acq. duration total (s)",
272
+ "B Number of events",
273
+ ]
274
+ return all_data_headers
275
+
276
+
277
+ def rfxlsx(filterdict, pid=False, name=None, patid=None, user=None):
278
+ """Export filtered RF database data to multi-sheet Microsoft XSLX files.
279
+
280
+ :param filterdict: Queryset of studies to export
281
+ :param pid: does the user have patient identifiable data permission
282
+ :param name: has patient name been selected for export
283
+ :param patid: has patient ID been selected for export
284
+ :param user: User that has started the export
285
+ :return: Saves xlsx file into Media directory for user to download
286
+ """
287
+
288
+ # Obtain the system-level enable_standard_names setting
289
+ enable_standard_names = are_standard_names_enabled()
290
+
291
+ datestamp = datetime.datetime.now()
292
+ task_id = get_or_generate_task_uuid()
293
+ tsk = create_export_task(
294
+ task_id=task_id,
295
+ modality="RF",
296
+ export_type="XLSX export",
297
+ date_stamp=datestamp,
298
+ pid=bool(pid and (name or patid)),
299
+ user=user,
300
+ filters_dict=filterdict,
301
+ )
302
+
303
+ tmpxlsx, book = create_xlsx(tsk)
304
+ if not tmpxlsx:
305
+ exit()
306
+
307
+ # Get the data
308
+ if pid:
309
+ if enable_standard_names:
310
+ df_filtered_qs = RFFilterPlusPidPlusStdNames(
311
+ filterdict,
312
+ queryset=GeneralStudyModuleAttr.objects.filter(
313
+ modality_type__exact="RF"
314
+ ).distinct(),
315
+ )
316
+ else:
317
+ df_filtered_qs = RFFilterPlusPid(
318
+ filterdict,
319
+ queryset=GeneralStudyModuleAttr.objects.filter(
320
+ modality_type__exact="RF"
321
+ ).distinct(),
322
+ )
323
+ else:
324
+ if enable_standard_names:
325
+ df_filtered_qs = RFFilterPlusStdNames(
326
+ filterdict,
327
+ queryset=GeneralStudyModuleAttr.objects.filter(
328
+ modality_type__exact="RF"
329
+ ).distinct(),
330
+ )
331
+ else:
332
+ df_filtered_qs = RFSummaryListFilter(
333
+ filterdict,
334
+ queryset=GeneralStudyModuleAttr.objects.filter(
335
+ modality_type__exact="RF"
336
+ ).distinct(),
337
+ )
338
+
339
+ e = df_filtered_qs.qs
340
+
341
+ tsk.num_records = e.count()
342
+ if abort_if_zero_studies(tsk.num_records, tsk):
343
+ return
344
+
345
+ # Add summary sheet and all data sheet
346
+ summarysheet = book.add_worksheet("Summary")
347
+ wsalldata = book.add_worksheet("All data")
348
+
349
+ book = text_and_date_formats(
350
+ book, wsalldata, pid=pid, name=name, patid=patid, modality="RF"
351
+ )
352
+ tsk.progress = "Creating an Excel safe version of protocol names and creating a worksheet for each..."
353
+ tsk.save()
354
+
355
+ all_data_headers = _all_data_headers(pid=pid, name=name, patid=patid)
356
+
357
+ sheet_headers = list(all_data_headers)
358
+ protocolheaders = sheet_headers + [
359
+ "Time",
360
+ "Type",
361
+ "Protocol",
362
+ ]
363
+
364
+ if enable_standard_names:
365
+ protocolheaders += [
366
+ "Standard acquisition name",
367
+ ]
368
+
369
+ protocolheaders += [
370
+ "Plane",
371
+ "Field size",
372
+ "Filter material",
373
+ "Mean filter thickness (mm)",
374
+ "kVp",
375
+ "mA",
376
+ "Pulse width (ms)",
377
+ "Pulse rate",
378
+ "Number of pulses",
379
+ "Exposure time (ms)",
380
+ "Exposure duration (s)",
381
+ "DAP (cGy.cm^2)",
382
+ "Ref point dose (Gy)",
383
+ "Primary angle",
384
+ "Secondary angle",
385
+ ]
386
+
387
+ book, sheetlist = generate_sheets(
388
+ e, book, protocolheaders, modality="RF", pid=pid, name=name, patid=patid
389
+ )
390
+
391
+ ##################
392
+ # All data sheet
393
+
394
+ num_groups_max = 0
395
+ for row, exams in enumerate(e):
396
+
397
+ tsk.progress = f"Writing study {row + 1} of {e.count()}"
398
+ tsk.save()
399
+
400
+ try:
401
+ examdata = get_common_data("RF", exams, pid=pid, name=name, patid=patid)
402
+ examdata += _add_plane_summary_data(exams)
403
+ common_exam_data = list(examdata)
404
+
405
+ angle_range = 5.0 # plus or minus range considered to be the same position
406
+
407
+ # TODO: Check if generation of inst could be more efficient, ie start with exams?
408
+ inst = IrradEventXRayData.objects.filter(
409
+ projection_xray_radiation_dose__general_study_module_attributes__id__exact=exams.id
410
+ )
411
+
412
+ num_groups_this_exam = 0
413
+ while (
414
+ inst
415
+ ): # ie while there are events still left that haven't been matched into a group
416
+ tsk.progress = "Writing study {0} of {1}; {2} events remaining.".format(
417
+ row + 1, e.count(), inst.count()
418
+ )
419
+ tsk.save()
420
+ num_groups_this_exam += 1
421
+ plane = inst[0].acquisition_plane.code_meaning
422
+ try:
423
+ mechanical_data = inst[0].irradeventxraymechanicaldata_set.get()
424
+ anglei = mechanical_data.positioner_primary_angle
425
+ angleii = mechanical_data.positioner_secondary_angle
426
+ except ObjectDoesNotExist:
427
+ anglei = None
428
+ angleii = None
429
+ try:
430
+ source_data = inst[0].irradeventxraysourcedata_set.get()
431
+ pulse_rate = source_data.pulse_rate
432
+ fieldsize = source_data.ii_field_size
433
+ try:
434
+ filter_material, filter_thick = get_xray_filter_info(
435
+ source_data
436
+ )
437
+ except ObjectDoesNotExist:
438
+ filter_material = None
439
+ filter_thick = None
440
+ except ObjectDoesNotExist:
441
+ pulse_rate = None
442
+ fieldsize = None
443
+ filter_material = None
444
+ filter_thick = None
445
+
446
+ protocol = inst[0].acquisition_protocol
447
+
448
+ standard_protocol = ""
449
+ if enable_standard_names:
450
+ try:
451
+ standard_protocol = (
452
+ inst[0].standard_protocols.first().standard_name
453
+ )
454
+ except AttributeError:
455
+ standard_protocol = ""
456
+
457
+ event_type = inst[0].irradiation_event_type.code_meaning
458
+
459
+ similarexposures = inst
460
+ if plane:
461
+ similarexposures = similarexposures.filter(
462
+ acquisition_plane__code_meaning__exact=plane
463
+ )
464
+ if protocol:
465
+ similarexposures = similarexposures.filter(
466
+ acquisition_protocol__exact=protocol
467
+ )
468
+ if fieldsize:
469
+ similarexposures = similarexposures.filter(
470
+ irradeventxraysourcedata__ii_field_size__exact=fieldsize
471
+ )
472
+ if pulse_rate:
473
+ similarexposures = similarexposures.filter(
474
+ irradeventxraysourcedata__pulse_rate__exact=pulse_rate
475
+ )
476
+ if filter_material:
477
+ for xray_filter in (
478
+ inst[0].irradeventxraysourcedata_set.get().xrayfilters_set.all()
479
+ ):
480
+ similarexposures = similarexposures.filter(
481
+ irradeventxraysourcedata__xrayfilters__xray_filter_material__code_meaning__exact=xray_filter.xray_filter_material.code_meaning
482
+ )
483
+ similarexposures = similarexposures.filter(
484
+ irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__exact=xray_filter.xray_filter_thickness_maximum
485
+ )
486
+ if anglei:
487
+ similarexposures = similarexposures.filter(
488
+ irradeventxraymechanicaldata__positioner_primary_angle__range=(
489
+ float(anglei) - angle_range,
490
+ float(anglei) + angle_range,
491
+ )
492
+ )
493
+ if angleii:
494
+ similarexposures = similarexposures.filter(
495
+ irradeventxraymechanicaldata__positioner_secondary_angle__range=(
496
+ float(angleii) - angle_range,
497
+ float(angleii) + angle_range,
498
+ )
499
+ )
500
+ if event_type:
501
+ similarexposures = similarexposures.filter(
502
+ irradiation_event_type__code_meaning__exact=event_type
503
+ )
504
+
505
+ # Remove exposures included in this group from inst
506
+ exposures_to_exclude = [
507
+ o.irradiation_event_uid for o in similarexposures
508
+ ]
509
+ inst = inst.exclude(irradiation_event_uid__in=exposures_to_exclude)
510
+
511
+ angle1 = similarexposures.all().aggregate(
512
+ Min("irradeventxraymechanicaldata__positioner_primary_angle"),
513
+ Max("irradeventxraymechanicaldata__positioner_primary_angle"),
514
+ Avg("irradeventxraymechanicaldata__positioner_primary_angle"),
515
+ )
516
+ angle2 = similarexposures.all().aggregate(
517
+ Min("irradeventxraymechanicaldata__positioner_secondary_angle"),
518
+ Max("irradeventxraymechanicaldata__positioner_secondary_angle"),
519
+ Avg("irradeventxraymechanicaldata__positioner_secondary_angle"),
520
+ )
521
+ dap = similarexposures.all().aggregate(
522
+ Min("dose_area_product"),
523
+ Max("dose_area_product"),
524
+ Avg("dose_area_product"),
525
+ )
526
+ dose_rp = similarexposures.all().aggregate(
527
+ Min("irradeventxraysourcedata__dose_rp"),
528
+ Max("irradeventxraysourcedata__dose_rp"),
529
+ Avg("irradeventxraysourcedata__dose_rp"),
530
+ )
531
+ kvp = similarexposures.all().aggregate(
532
+ Min("irradeventxraysourcedata__kvp__kvp"),
533
+ Max("irradeventxraysourcedata__kvp__kvp"),
534
+ Avg("irradeventxraysourcedata__kvp__kvp"),
535
+ )
536
+ tube_current = similarexposures.all().aggregate(
537
+ Min("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
538
+ Max("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
539
+ Avg("irradeventxraysourcedata__xraytubecurrent__xray_tube_current"),
540
+ )
541
+ exp_time = similarexposures.all().aggregate(
542
+ Min("irradeventxraysourcedata__exposure_time"),
543
+ Max("irradeventxraysourcedata__exposure_time"),
544
+ Avg("irradeventxraysourcedata__exposure_time"),
545
+ )
546
+ pulse_width = similarexposures.all().aggregate(
547
+ Min("irradeventxraysourcedata__pulsewidth__pulse_width"),
548
+ Max("irradeventxraysourcedata__pulsewidth__pulse_width"),
549
+ Avg("irradeventxraysourcedata__pulsewidth__pulse_width"),
550
+ )
551
+
552
+ examdata += [
553
+ event_type,
554
+ protocol,
555
+ ]
556
+
557
+ if enable_standard_names:
558
+ if standard_protocol:
559
+ examdata += [standard_protocol]
560
+ else:
561
+ examdata += [""]
562
+
563
+ examdata += [
564
+ similarexposures.count(),
565
+ plane,
566
+ pulse_rate,
567
+ fieldsize,
568
+ filter_material,
569
+ filter_thick,
570
+ kvp["irradeventxraysourcedata__kvp__kvp__min"],
571
+ kvp["irradeventxraysourcedata__kvp__kvp__max"],
572
+ kvp["irradeventxraysourcedata__kvp__kvp__avg"],
573
+ tube_current[
574
+ "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__min"
575
+ ],
576
+ tube_current[
577
+ "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__max"
578
+ ],
579
+ tube_current[
580
+ "irradeventxraysourcedata__xraytubecurrent__xray_tube_current__avg"
581
+ ],
582
+ pulse_width[
583
+ "irradeventxraysourcedata__pulsewidth__pulse_width__min"
584
+ ],
585
+ pulse_width[
586
+ "irradeventxraysourcedata__pulsewidth__pulse_width__max"
587
+ ],
588
+ pulse_width[
589
+ "irradeventxraysourcedata__pulsewidth__pulse_width__avg"
590
+ ],
591
+ exp_time["irradeventxraysourcedata__exposure_time__min"],
592
+ exp_time["irradeventxraysourcedata__exposure_time__max"],
593
+ exp_time["irradeventxraysourcedata__exposure_time__avg"],
594
+ dap["dose_area_product__min"],
595
+ dap["dose_area_product__max"],
596
+ dap["dose_area_product__avg"],
597
+ dose_rp["irradeventxraysourcedata__dose_rp__min"],
598
+ dose_rp["irradeventxraysourcedata__dose_rp__max"],
599
+ dose_rp["irradeventxraysourcedata__dose_rp__avg"],
600
+ angle1[
601
+ "irradeventxraymechanicaldata__positioner_primary_angle__min"
602
+ ],
603
+ angle1[
604
+ "irradeventxraymechanicaldata__positioner_primary_angle__max"
605
+ ],
606
+ angle1[
607
+ "irradeventxraymechanicaldata__positioner_primary_angle__avg"
608
+ ],
609
+ angle2[
610
+ "irradeventxraymechanicaldata__positioner_secondary_angle__min"
611
+ ],
612
+ angle2[
613
+ "irradeventxraymechanicaldata__positioner_secondary_angle__max"
614
+ ],
615
+ angle2[
616
+ "irradeventxraymechanicaldata__positioner_secondary_angle__avg"
617
+ ],
618
+ ]
619
+
620
+ if not protocol:
621
+ protocol = "Unknown"
622
+ tab_text = sheet_name(protocol)
623
+ filter_data = {
624
+ "filter_material": filter_material,
625
+ "filter_thick": filter_thick,
626
+ }
627
+ for exposure in similarexposures.order_by("pk"):
628
+ series_data = _get_series_data(exposure, filter_data)
629
+ sheetlist[tab_text]["count"] += 1
630
+ sheetlist[tab_text]["sheet"].write_row(
631
+ sheetlist[tab_text]["count"], 0, common_exam_data + series_data
632
+ )
633
+
634
+ if enable_standard_names:
635
+ if standard_protocol:
636
+ tab_text = sheet_name("[standard] " + standard_protocol)
637
+ filter_data = {
638
+ "filter_material": filter_material,
639
+ "filter_thick": filter_thick,
640
+ }
641
+ for exposure in similarexposures.order_by("pk"):
642
+ series_data = _get_series_data(exposure, filter_data)
643
+ sheetlist[tab_text]["count"] += 1
644
+ sheetlist[tab_text]["sheet"].write_row(
645
+ sheetlist[tab_text]["count"],
646
+ 0,
647
+ common_exam_data + series_data,
648
+ )
649
+
650
+ if num_groups_this_exam > num_groups_max:
651
+ num_groups_max = num_groups_this_exam
652
+
653
+ wsalldata.write_row(row + 1, 0, examdata)
654
+
655
+ except ObjectDoesNotExist:
656
+ error_message = (
657
+ "DoesNotExist error whilst exporting study {0} of {1}, study UID {2}, accession number"
658
+ " {3} - maybe database entry was deleted as part of importing later version of same"
659
+ " study?".format(
660
+ row + 1,
661
+ tsk.num_records,
662
+ exams.study_instance_uid,
663
+ exams.accession_number,
664
+ )
665
+ )
666
+ logger.error(error_message)
667
+ wsalldata.write(row + 1, 0, error_message)
668
+
669
+ tsk.progress = "Generating headers for the all data sheet..."
670
+ tsk.save()
671
+
672
+ for h in range(num_groups_max):
673
+ all_data_headers += [
674
+ "G" + str(h + 1) + " Type",
675
+ "G" + str(h + 1) + " Protocol",
676
+ ]
677
+
678
+ if enable_standard_names:
679
+ all_data_headers += ["G" + str(h + 1) + " Standard acquisition name"]
680
+
681
+ all_data_headers += [
682
+ "G" + str(h + 1) + " No. exposures",
683
+ "G" + str(h + 1) + " Plane",
684
+ "G" + str(h + 1) + " Pulse rate",
685
+ "G" + str(h + 1) + " Field size",
686
+ "G" + str(h + 1) + " Filter material",
687
+ "G" + str(h + 1) + " Mean filter thickness (mm)",
688
+ "G" + str(h + 1) + " kVp min",
689
+ "G" + str(h + 1) + " kVp max",
690
+ "G" + str(h + 1) + " kVp mean",
691
+ "G" + str(h + 1) + " mA min",
692
+ "G" + str(h + 1) + " mA max",
693
+ "G" + str(h + 1) + " mA mean",
694
+ "G" + str(h + 1) + " pulse width min (ms)",
695
+ "G" + str(h + 1) + " pulse width max (ms)",
696
+ "G" + str(h + 1) + " pulse width mean (ms)",
697
+ "G" + str(h + 1) + " Exp time min (ms)",
698
+ "G" + str(h + 1) + " Exp time max (ms)",
699
+ "G" + str(h + 1) + " Exp time mean (ms)",
700
+ "G" + str(h + 1) + " DAP min (Gy.m^2)",
701
+ "G" + str(h + 1) + " DAP max (Gy.m^2)",
702
+ "G" + str(h + 1) + " DAP mean (Gy.m^2)",
703
+ "G" + str(h + 1) + " Ref point dose min (Gy)",
704
+ "G" + str(h + 1) + " Ref point dose max (Gy)",
705
+ "G" + str(h + 1) + " Ref point dose mean (Gy)",
706
+ "G" + str(h + 1) + " Primary angle min",
707
+ "G" + str(h + 1) + " Primary angle max",
708
+ "G" + str(h + 1) + " Primary angle mean",
709
+ "G" + str(h + 1) + " Secondary angle min",
710
+ "G" + str(h + 1) + " Secondary angle max",
711
+ "G" + str(h + 1) + " Secondary angle mean",
712
+ ]
713
+ wsalldata.write_row("A1", all_data_headers)
714
+ num_rows = e.count()
715
+ wsalldata.autofilter(0, 0, num_rows, len(all_data_headers) - 1)
716
+
717
+ create_summary_sheet(tsk, e, book, summarysheet, sheetlist)
718
+
719
+ book.close()
720
+ tsk.progress = "XLSX book written."
721
+ tsk.save()
722
+
723
+ xlsxfilename = "rfexport{0}.xlsx".format(datestamp.strftime("%Y%m%d-%H%M%S%f"))
724
+
725
+ write_export(tsk, xlsxfilename, tmpxlsx, datestamp)
726
+
727
+
728
+ def exportFL2excel(filterdict, pid=False, name=None, patid=None, user=None):
729
+ """Export filtered fluoro database data to a single-sheet CSV file.
730
+
731
+ :param filterdict: Queryset of studies to export
732
+ :param pid: does the user have patient identifiable data permission
733
+ :param name: has patient name been selected for export
734
+ :param patid: has patient ID been selected for export
735
+ :param user: User that has started the export
736
+ :return: Saves csv file into Media directory for user to download
737
+ """
738
+
739
+ # Obtain the system-level enable_standard_names setting
740
+ enable_standard_names = are_standard_names_enabled()
741
+
742
+ datestamp = datetime.datetime.now()
743
+ task_id = get_or_generate_task_uuid()
744
+ tsk = create_export_task(
745
+ task_id=task_id,
746
+ modality="RF",
747
+ export_type="CSV export",
748
+ date_stamp=datestamp,
749
+ pid=bool(pid and (name or patid)),
750
+ user=user,
751
+ filters_dict=filterdict,
752
+ )
753
+
754
+ tmpfile, writer = create_csv(tsk)
755
+ if not tmpfile:
756
+ exit()
757
+
758
+ # Get the data!
759
+ if pid:
760
+ if enable_standard_names:
761
+ df_filtered_qs = RFFilterPlusPidPlusStdNames(
762
+ filterdict,
763
+ queryset=GeneralStudyModuleAttr.objects.filter(
764
+ modality_type__exact="RF"
765
+ ).distinct(),
766
+ )
767
+ else:
768
+ df_filtered_qs = RFFilterPlusPid(
769
+ filterdict,
770
+ queryset=GeneralStudyModuleAttr.objects.filter(
771
+ modality_type__exact="RF"
772
+ ).distinct(),
773
+ )
774
+ else:
775
+ if enable_standard_names:
776
+ df_filtered_qs = RFFilterPlusStdNames(
777
+ filterdict,
778
+ queryset=GeneralStudyModuleAttr.objects.filter(
779
+ modality_type__exact="RF"
780
+ ).distinct(),
781
+ )
782
+ else:
783
+ df_filtered_qs = RFSummaryListFilter(
784
+ filterdict,
785
+ queryset=GeneralStudyModuleAttr.objects.filter(
786
+ modality_type__exact="RF"
787
+ ).distinct(),
788
+ )
789
+
790
+ e = df_filtered_qs.qs
791
+
792
+ tsk.num_records = e.count()
793
+ if abort_if_zero_studies(tsk.num_records, tsk):
794
+ return
795
+
796
+ headings = _all_data_headers(pid=pid, name=name, patid=patid)
797
+ writer.writerow(headings)
798
+ for i, exams in enumerate(e):
799
+
800
+ tsk.progress = "{0} of {1}".format(i + 1, tsk.num_records)
801
+ tsk.save()
802
+
803
+ try:
804
+ exam_data = get_common_data("RF", exams, pid=pid, name=name, patid=patid)
805
+
806
+ for (
807
+ plane
808
+ ) in exams.projectionxrayradiationdose_set.get().accumxraydose_set.all():
809
+ accum = _get_accumulated_data(plane)
810
+ exam_data += [
811
+ accum["dose_area_product_total"],
812
+ accum["dose_rp_total"],
813
+ accum["fluoro_dose_area_product_total"],
814
+ accum["fluoro_dose_rp_total"],
815
+ accum["total_fluoro_time"],
816
+ accum["acquisition_dose_area_product_total"],
817
+ accum["acquisition_dose_rp_total"],
818
+ accum["total_acquisition_time"],
819
+ accum["eventcount"],
820
+ ]
821
+ # Clear out any commas
822
+ for index, item in enumerate(exam_data):
823
+ if item is None:
824
+ exam_data[index] = ""
825
+ if isinstance(item, str) and "," in item:
826
+ exam_data[index] = item.replace(",", ";")
827
+ writer.writerow([str(data_string) for data_string in exam_data])
828
+ except ObjectDoesNotExist:
829
+ error_message = (
830
+ "DoesNotExist error whilst exporting study {0} of {1}, study UID {2}, accession number"
831
+ " {3} - maybe database entry was deleted as part of importing later version of same"
832
+ " study?".format(
833
+ i + 1,
834
+ tsk.num_records,
835
+ exams.study_instance_uid,
836
+ exams.accession_number,
837
+ )
838
+ )
839
+ logger.error(error_message)
840
+ writer.writerow([error_message])
841
+
842
+ tsk.progress = "All study data written."
843
+ tsk.save()
844
+
845
+ tmpfile.close()
846
+ tsk.status = "COMPLETE"
847
+ tsk.processtime = (datetime.datetime.now() - datestamp).total_seconds()
848
+ tsk.save()
849
+
850
+
851
+ def rfopenskin_csv(studyid):
852
+ # pylint: disable=too-many-locals
853
+ # pylint: disable=too-many-branches
854
+ # pylint: disable=too-many-statements
855
+ """Export single RF study data to OpenSkin RF csv sheet.
856
+
857
+ :param studyid: RF study database ID.
858
+ :type studyid: int
859
+
860
+ """
861
+
862
+ datestamp = datetime.datetime.now()
863
+ task_id = get_or_generate_task_uuid()
864
+ tsk = create_export_task(
865
+ task_id=task_id,
866
+ modality="RF-OpenSkin",
867
+ export_type="OpenSkin RF csv export",
868
+ date_stamp=datestamp,
869
+ pid=False,
870
+ user=None,
871
+ filters_dict={"study_id": studyid},
872
+ )
873
+
874
+ tmpfile, writer = create_csv(tsk)
875
+ if not tmpfile:
876
+ exit()
877
+
878
+ # Get the data
879
+ study = GeneralStudyModuleAttr.objects.get(pk=studyid)
880
+ numevents = (
881
+ study.projectionxrayradiationdose_set.get().irradeventxraydata_set.count()
882
+ )
883
+ tsk.num_records = numevents
884
+ tsk.save()
885
+
886
+ for i, event in enumerate(
887
+ study.projectionxrayradiationdose_set.get().irradeventxraydata_set.all()
888
+ ):
889
+ try:
890
+ study.patientmoduleattr_set.get()
891
+ except ObjectDoesNotExist:
892
+ patient_sex = ""
893
+ else:
894
+ patient_sex = study.patientmoduleattr_set.get().patient_sex
895
+
896
+ try:
897
+ event.irradeventxraysourcedata_set.get()
898
+ except ObjectDoesNotExist:
899
+ reference_point_definition = ""
900
+ dose_rp = ""
901
+ fluoro_mode = ""
902
+ pulse_rate = ""
903
+ number_of_pulses = ""
904
+ exposure_time = ""
905
+ focal_spot_size = ""
906
+ irradiation_duration = ""
907
+ average_xray_tube_current = ""
908
+ else:
909
+ reference_point_definition = (
910
+ event.irradeventxraysourcedata_set.get().reference_point_definition
911
+ )
912
+ dose_rp = event.irradeventxraysourcedata_set.get().dose_rp
913
+ fluoro_mode = event.irradeventxraysourcedata_set.get().fluoro_mode
914
+ pulse_rate = event.irradeventxraysourcedata_set.get().pulse_rate
915
+ number_of_pulses = event.irradeventxraysourcedata_set.get().number_of_pulses
916
+ exposure_time = event.irradeventxraysourcedata_set.get().exposure_time
917
+ focal_spot_size = event.irradeventxraysourcedata_set.get().focal_spot_size
918
+ irradiation_duration = (
919
+ event.irradeventxraysourcedata_set.get().irradiation_duration
920
+ )
921
+ average_xray_tube_current = (
922
+ event.irradeventxraysourcedata_set.get().average_xray_tube_current
923
+ )
924
+
925
+ try:
926
+ event.irradeventxraymechanicaldata_set.get()
927
+ except ObjectDoesNotExist:
928
+ positioner_primary_angle = ""
929
+ positioner_secondary_angle = ""
930
+ positioner_primary_end_angle = ""
931
+ positioner_secondary_end_angle = ""
932
+ column_angulation = ""
933
+ else:
934
+ positioner_primary_angle = (
935
+ event.irradeventxraymechanicaldata_set.get().positioner_primary_angle
936
+ )
937
+ positioner_secondary_angle = (
938
+ event.irradeventxraymechanicaldata_set.get().positioner_secondary_angle
939
+ )
940
+ positioner_primary_end_angle = (
941
+ event.irradeventxraymechanicaldata_set.get().positioner_primary_end_angle
942
+ )
943
+ positioner_secondary_end_angle = (
944
+ event.irradeventxraymechanicaldata_set.get().positioner_secondary_end_angle
945
+ )
946
+ column_angulation = (
947
+ event.irradeventxraymechanicaldata_set.get().column_angulation
948
+ )
949
+
950
+ xray_filter_type = ""
951
+ xray_filter_material = ""
952
+ xray_filter_thickness_minimum = ""
953
+ xray_filter_thickness_maximum = ""
954
+ try:
955
+ for (
956
+ filters
957
+ ) in event.irradeventxraysourcedata_set.get().xrayfilters_set.all():
958
+ try:
959
+ if "Copper" in filters.xray_filter_material.code_meaning:
960
+ xray_filter_type = filters.xray_filter_type
961
+ xray_filter_material = filters.xray_filter_material
962
+ xray_filter_thickness_minimum = (
963
+ filters.xray_filter_thickness_minimum
964
+ )
965
+ xray_filter_thickness_maximum = (
966
+ filters.xray_filter_thickness_maximum
967
+ )
968
+ except AttributeError:
969
+ pass
970
+ except ObjectDoesNotExist:
971
+ pass
972
+
973
+ try:
974
+ event.irradeventxraysourcedata_set.get().kvp_set.get()
975
+ except ObjectDoesNotExist:
976
+ kvp = ""
977
+ else:
978
+ kvp = event.irradeventxraysourcedata_set.get().kvp_set.get().kvp
979
+
980
+ try:
981
+ event.irradeventxraysourcedata_set.get().xraytubecurrent_set.get()
982
+ except ObjectDoesNotExist:
983
+ xray_tube_current = ""
984
+ else:
985
+ xray_tube_current = (
986
+ event.irradeventxraysourcedata_set.get()
987
+ .xraytubecurrent_set.get()
988
+ .xray_tube_current
989
+ )
990
+
991
+ try:
992
+ event.irradeventxraysourcedata_set.get().pulsewidth_set.get()
993
+ except ObjectDoesNotExist:
994
+ pulse_width = ""
995
+ else:
996
+ pulse_width = (
997
+ event.irradeventxraysourcedata_set.get()
998
+ .pulsewidth_set.get()
999
+ .pulse_width
1000
+ )
1001
+
1002
+ try:
1003
+ event.irradeventxraysourcedata_set.get().exposure_set.get()
1004
+ except ObjectDoesNotExist:
1005
+ exposure = ""
1006
+ else:
1007
+ exposure = (
1008
+ event.irradeventxraysourcedata_set.get().exposure_set.get().exposure
1009
+ )
1010
+
1011
+ try:
1012
+ event.irradeventxraymechanicaldata_set.get().doserelateddistancemeasurements_set.get()
1013
+ except ObjectDoesNotExist:
1014
+ distance_source_to_detector = ""
1015
+ distance_source_to_isocenter = ""
1016
+ table_longitudinal_position = ""
1017
+ table_lateral_position = ""
1018
+ table_height_position = ""
1019
+ else:
1020
+ distance_source_to_detector = (
1021
+ event.irradeventxraymechanicaldata_set.get()
1022
+ .doserelateddistancemeasurements_set.get()
1023
+ .distance_source_to_detector
1024
+ )
1025
+ distance_source_to_isocenter = (
1026
+ event.irradeventxraymechanicaldata_set.get()
1027
+ .doserelateddistancemeasurements_set.get()
1028
+ .distance_source_to_isocenter
1029
+ )
1030
+ table_longitudinal_position = (
1031
+ event.irradeventxraymechanicaldata_set.get()
1032
+ .doserelateddistancemeasurements_set.get()
1033
+ .table_longitudinal_position
1034
+ )
1035
+ table_lateral_position = (
1036
+ event.irradeventxraymechanicaldata_set.get()
1037
+ .doserelateddistancemeasurements_set.get()
1038
+ .table_lateral_position
1039
+ )
1040
+ table_height_position = (
1041
+ event.irradeventxraymechanicaldata_set.get()
1042
+ .doserelateddistancemeasurements_set.get()
1043
+ .table_height_position
1044
+ )
1045
+
1046
+ acquisition_protocol = return_for_export(event, "acquisition_protocol")
1047
+ if isinstance(acquisition_protocol, str) and "," in acquisition_protocol:
1048
+ acquisition_protocol = acquisition_protocol.replace(",", ";")
1049
+ comment = event.comment
1050
+ if isinstance(comment, str) and "," in comment:
1051
+ comment = comment.replace(",", ";")
1052
+
1053
+ data = [
1054
+ "Anon",
1055
+ patient_sex,
1056
+ study.study_instance_uid,
1057
+ "",
1058
+ event.acquisition_plane,
1059
+ event.date_time_started,
1060
+ event.irradiation_event_type,
1061
+ acquisition_protocol,
1062
+ reference_point_definition,
1063
+ event.irradiation_event_uid,
1064
+ event.dose_area_product,
1065
+ dose_rp,
1066
+ positioner_primary_angle,
1067
+ positioner_secondary_angle,
1068
+ positioner_primary_end_angle,
1069
+ positioner_secondary_end_angle,
1070
+ column_angulation,
1071
+ xray_filter_type,
1072
+ xray_filter_material,
1073
+ xray_filter_thickness_minimum,
1074
+ xray_filter_thickness_maximum,
1075
+ fluoro_mode,
1076
+ pulse_rate,
1077
+ number_of_pulses,
1078
+ kvp,
1079
+ xray_tube_current,
1080
+ exposure_time,
1081
+ pulse_width,
1082
+ exposure,
1083
+ focal_spot_size,
1084
+ irradiation_duration,
1085
+ average_xray_tube_current,
1086
+ distance_source_to_detector,
1087
+ distance_source_to_isocenter,
1088
+ table_longitudinal_position,
1089
+ table_lateral_position,
1090
+ table_height_position,
1091
+ event.target_region,
1092
+ comment,
1093
+ ]
1094
+ writer.writerow(data)
1095
+ tsk.progress = "{0} of {1}".format(i, numevents)
1096
+ tsk.save()
1097
+ tsk.progress = "All study data written."
1098
+ tsk.save()
1099
+
1100
+ tmpfile.close()
1101
+ tsk.status = "COMPLETE"
1102
+ tsk.processtime = (datetime.datetime.now() - datestamp).total_seconds()
1103
+ tsk.save()
1104
+
1105
+
1106
+ def rf_phe_2019(filterdict, user=None):
1107
+ """Export filtered RF database data in the format for the 2019 Public Health England IR/fluoro dose survey
1108
+
1109
+ :param filterdict: Queryset of studies to export
1110
+ :param user: User that has started the export
1111
+ :return: Saves Excel file into media directory for user to download
1112
+ """
1113
+
1114
+ datestamp = datetime.datetime.now()
1115
+ task_id = get_or_generate_task_uuid()
1116
+ tsk = create_export_task(
1117
+ task_id=task_id,
1118
+ modality="RF",
1119
+ export_type="PHE RF 2019 export",
1120
+ date_stamp=datestamp,
1121
+ pid=False,
1122
+ user=user,
1123
+ filters_dict=filterdict,
1124
+ )
1125
+
1126
+ tmp_xlsx, book = create_xlsx(tsk)
1127
+ if not tmp_xlsx:
1128
+ exit()
1129
+ sheet = book.add_worksheet("PHE IR-Fluoro")
1130
+
1131
+ exams = RFSummaryListFilter(
1132
+ filterdict,
1133
+ queryset=GeneralStudyModuleAttr.objects.filter(modality_type__exact="RF"),
1134
+ ).qs
1135
+ tsk.num_records = exams.count()
1136
+ if abort_if_zero_studies(tsk.num_records, tsk):
1137
+ return
1138
+
1139
+ tsk.progress = "{0} studies in query.".format(tsk.num_records)
1140
+ tsk.save()
1141
+
1142
+ row_4 = ["", "", "", "", "", "Gy·m²", "", "seconds", "Gy"]
1143
+ sheet.write_row(3, 0, row_4)
1144
+
1145
+ num_rows = exams.count()
1146
+ for row, exam in enumerate(exams):
1147
+ tsk.progress = "Writing study {0} of {1}".format(row + 1, num_rows)
1148
+ tsk.save()
1149
+
1150
+ row_data = ["", row + 1, exam.pk, exam.study_date, exam.total_dap]
1151
+ accum_data = []
1152
+ for plane in exam.projectionxrayradiationdose_set.get().accumxraydose_set.all():
1153
+ accum_data.append(_get_accumulated_data(plane))
1154
+ if len(accum_data) == 2:
1155
+ accum_data[0]["fluoro_dose_area_product_total"] += accum_data[1][
1156
+ "fluoro_dose_area_product_total"
1157
+ ]
1158
+ accum_data[0]["fluoro_dose_rp_total"] += accum_data[1][
1159
+ "fluoro_dose_rp_total"
1160
+ ]
1161
+ accum_data[0]["total_fluoro_time"] += accum_data[1]["total_fluoro_time"]
1162
+ accum_data[0]["acquisition_dose_area_product_total"] += accum_data[1][
1163
+ "acquisition_dose_area_product_total"
1164
+ ]
1165
+ accum_data[0]["acquisition_dose_rp_total"] += accum_data[1][
1166
+ "acquisition_dose_rp_total"
1167
+ ]
1168
+ accum_data[0]["total_acquisition_time"] += accum_data[1][
1169
+ "total_acquisition_time"
1170
+ ]
1171
+ accum_data[0]["eventcount"] += accum_data[1]["eventcount"]
1172
+ row_data += [
1173
+ accum_data[0]["fluoro_dose_area_product_total"],
1174
+ accum_data[0]["acquisition_dose_area_product_total"],
1175
+ accum_data[0]["total_fluoro_time"],
1176
+ ]
1177
+
1178
+ try:
1179
+ total_rp_dose = exam.total_rp_dose_a + exam.total_rp_dose_b
1180
+ except TypeError:
1181
+ if exam.total_rp_dose_a is not None:
1182
+ total_rp_dose = exam.total_rp_dose_a
1183
+ elif exam.total_rp_dose_b is not None:
1184
+ total_rp_dose = exam.total_rp_dose_b
1185
+ else:
1186
+ total_rp_dose = 0
1187
+ row_data += [
1188
+ total_rp_dose,
1189
+ accum_data[0]["fluoro_dose_rp_total"],
1190
+ accum_data[0]["acquisition_dose_rp_total"],
1191
+ "{0} | {1} | {2}".format(
1192
+ exam.procedure_code_meaning,
1193
+ exam.requested_procedure_code_meaning,
1194
+ exam.study_description,
1195
+ ),
1196
+ ]
1197
+ patient_study_data = get_patient_study_data(exam)
1198
+ patient_sex = None
1199
+ try:
1200
+ patient_sex = exam.patientmoduleattr_set.get().patient_sex
1201
+ except ObjectDoesNotExist:
1202
+ logger.debug(
1203
+ "Export {0}; patientmoduleattr_set object does not exist. AccNum {1}, Date {2}".format(
1204
+ "PHE 2019 RF", exams.accession_number, exams.study_date
1205
+ )
1206
+ )
1207
+ row_data += [
1208
+ patient_study_data["patient_weight"],
1209
+ "",
1210
+ patient_study_data["patient_age_decimal"],
1211
+ patient_sex,
1212
+ patient_study_data["patient_size"],
1213
+ ]
1214
+
1215
+ events = IrradEventXRayData.objects.filter(
1216
+ projection_xray_radiation_dose__general_study_module_attributes__pk__exact=exam.pk
1217
+ )
1218
+ fluoro_events = events.exclude(
1219
+ irradiation_event_type__code_value__contains="11361"
1220
+ ) # acq events are 113611, 113612, 113613
1221
+ acquisition_events = events.filter(
1222
+ irradiation_event_type__code_value__contains="11361"
1223
+ )
1224
+ try:
1225
+ row_data += [
1226
+ " | ".join(
1227
+ fluoro_events.order_by()
1228
+ .values_list("acquisition_protocol", flat=True)
1229
+ .distinct()
1230
+ )
1231
+ ]
1232
+ except TypeError:
1233
+ row_data += [""]
1234
+ try:
1235
+ row_data += [
1236
+ " | ".join(
1237
+ fluoro_events.order_by()
1238
+ .values_list(
1239
+ "irradeventxraysourcedata__fluoro_mode__code_meaning", flat=True
1240
+ )
1241
+ .distinct()
1242
+ )
1243
+ ]
1244
+ except TypeError:
1245
+ row_data += [""]
1246
+ fluoro_frame_rates = (
1247
+ fluoro_events.order_by()
1248
+ .values_list("irradeventxraysourcedata__pulse_rate", flat=True)
1249
+ .distinct()
1250
+ )
1251
+ column_aq = ""
1252
+ if len(fluoro_frame_rates) > 1:
1253
+ column_aq += "Fluoro: "
1254
+ column_aq += " | ".join(
1255
+ format(x, "1.1f") for x in fluoro_frame_rates if x is not None
1256
+ )
1257
+ column_aq += " fps. "
1258
+ row_data += ["Multiple rates"]
1259
+ else:
1260
+ try:
1261
+ row_data += [fluoro_frame_rates[0]]
1262
+ except IndexError:
1263
+ row_data += [""]
1264
+ acquisition_frame_rates = (
1265
+ acquisition_events.order_by()
1266
+ .values_list("irradeventxraysourcedata__pulse_rate", flat=True)
1267
+ .distinct()
1268
+ )
1269
+ add_single = False
1270
+ if None in acquisition_frame_rates:
1271
+ if len(acquisition_frame_rates) == 1:
1272
+ acquisition_frame_rates = ["Single shot"]
1273
+ else:
1274
+ acquisition_frame_rates = acquisition_frame_rates[1:]
1275
+ add_single = True
1276
+ if len(acquisition_frame_rates) > 1:
1277
+ row_data += ["Multiple rates"]
1278
+ column_aq += "Acquisition: "
1279
+ if add_single:
1280
+ column_aq += "Single shot | "
1281
+ column_aq += " | ".join(format(x, "1.1f") for x in acquisition_frame_rates)
1282
+ column_aq += " fps. "
1283
+ else:
1284
+ try:
1285
+ row_data += [acquisition_frame_rates[0]]
1286
+ except IndexError:
1287
+ row_data += [""]
1288
+ row_data += [acquisition_events.count()]
1289
+ try:
1290
+ grid_types = (
1291
+ events.order_by()
1292
+ .values_list(
1293
+ "irradeventxraysourcedata__xraygrid__xray_grid__code_meaning",
1294
+ flat=True,
1295
+ )
1296
+ .distinct()
1297
+ )
1298
+ if None in grid_types:
1299
+ grid_types = grid_types[1:]
1300
+ except ObjectDoesNotExist:
1301
+ grid_types = [""]
1302
+ row_data += [" | ".join(grid_types), ""] # AEC used - not recorded in RDSR
1303
+ patient_position = (
1304
+ events.order_by()
1305
+ .values_list(
1306
+ "patient_table_relationship_cid__code_meaning",
1307
+ "patient_orientation_cid__code_meaning",
1308
+ "patient_orientation_modifier_cid__code_meaning",
1309
+ )
1310
+ .distinct()
1311
+ )
1312
+ patient_position_str = ""
1313
+ for position_set in patient_position:
1314
+ for element in (i for i in position_set if i):
1315
+ patient_position_str += "{0}, ".format(element)
1316
+ row_data += [
1317
+ patient_position_str,
1318
+ "", # digital subtraction
1319
+ "", # circular field of view
1320
+ ]
1321
+ field_dimensions = events.aggregate(
1322
+ Min("irradeventxraysourcedata__collimated_field_area"),
1323
+ Max("irradeventxraysourcedata__collimated_field_area"),
1324
+ Min("irradeventxraysourcedata__collimated_field_width"),
1325
+ Max("irradeventxraysourcedata__collimated_field_width"),
1326
+ Min("irradeventxraysourcedata__collimated_field_height"),
1327
+ Max("irradeventxraysourcedata__collimated_field_height"),
1328
+ )
1329
+ rectangular_fov = ""
1330
+ if field_dimensions["irradeventxraysourcedata__collimated_field_area__min"]:
1331
+ rectangular_fov += "Area {0:.4f} to {1:.4f} m², ".format(
1332
+ field_dimensions[
1333
+ "irradeventxraysourcedata__collimated_field_area__min"
1334
+ ],
1335
+ field_dimensions[
1336
+ "irradeventxraysourcedata__collimated_field_area__max"
1337
+ ],
1338
+ )
1339
+ if field_dimensions["irradeventxraysourcedata__collimated_field_width__min"]:
1340
+ rectangular_fov += "Width {0:.4f} to {1:.4f} m, ".format(
1341
+ field_dimensions[
1342
+ "irradeventxraysourcedata__collimated_field_width__min"
1343
+ ],
1344
+ field_dimensions[
1345
+ "irradeventxraysourcedata__collimated_field_width__max"
1346
+ ],
1347
+ )
1348
+ if field_dimensions["irradeventxraysourcedata__collimated_field_height__min"]:
1349
+ rectangular_fov += "Height {0:.4f} to {1:.4f} m, ".format(
1350
+ field_dimensions[
1351
+ "irradeventxraysourcedata__collimated_field_height__min"
1352
+ ],
1353
+ field_dimensions[
1354
+ "irradeventxraysourcedata__collimated_field_height__max"
1355
+ ],
1356
+ )
1357
+ field_sizes = (
1358
+ events.order_by()
1359
+ .values_list("irradeventxraysourcedata__ii_field_size", flat=True)
1360
+ .distinct()
1361
+ )
1362
+ diagonal_fov = ""
1363
+ for fov in field_sizes:
1364
+ if fov:
1365
+ diagonal_fov += "{0}, ".format(fov)
1366
+ if diagonal_fov:
1367
+ diagonal_fov += " mm"
1368
+ row_data += [rectangular_fov, diagonal_fov]
1369
+ filters_al = events.filter(
1370
+ irradeventxraysourcedata__xrayfilters__xray_filter_material__code_value__exact="C-120F9"
1371
+ )
1372
+ filters_cu = events.filter(
1373
+ irradeventxraysourcedata__xrayfilters__xray_filter_material__code_value__exact="C-127F9"
1374
+ )
1375
+ filters_al_thick = filters_al.aggregate(
1376
+ Min("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1377
+ Max("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1378
+ )
1379
+ filters_cu_thick = filters_cu.aggregate(
1380
+ Min("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1381
+ Max("irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum"),
1382
+ )
1383
+ filters_al_str = ""
1384
+ filters_cu_str = ""
1385
+ if filters_al_thick[
1386
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1387
+ ]:
1388
+ filters_al_str = "{0:.2} - {1:.2} mm".format(
1389
+ filters_al_thick[
1390
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1391
+ ],
1392
+ filters_al_thick[
1393
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__max"
1394
+ ],
1395
+ )
1396
+ if filters_cu_thick[
1397
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1398
+ ]:
1399
+ filters_cu_str = "{0:.2} - {1:.2} mm".format(
1400
+ filters_cu_thick[
1401
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__min"
1402
+ ],
1403
+ filters_cu_thick[
1404
+ "irradeventxraysourcedata__xrayfilters__xray_filter_thickness_maximum__max"
1405
+ ],
1406
+ )
1407
+ row_data += ["", filters_cu_str, filters_al_str] # filtration automated?
1408
+ row_data += ["", "", "", "", "", "", "", "", "", ""]
1409
+ row_data += [column_aq]
1410
+ sheet.write_row(row + 6, 0, row_data)
1411
+
1412
+ book.close()
1413
+ tsk.progress = "PHE IR/Fluoro 2019 export complete"
1414
+ tsk.save()
1415
+
1416
+ xlsxfilename = "PHE_RF_2019_{0}.xlsx".format(datestamp.strftime("%Y%m%d-%H%M%S%f"))
1417
+
1418
+ write_export(tsk, xlsxfilename, tmp_xlsx, datestamp)