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,1196 +1,1349 @@
1
- # pylint: disable=too-many-lines
2
- import logging
3
- from datetime import datetime
4
- from django.conf import settings
5
- from django.contrib.auth.decorators import login_required
6
- from django.core.exceptions import ObjectDoesNotExist
7
- from django.http import JsonResponse
8
- from remapp.forms import (
9
- MGChartOptionsForm,
10
- MGChartOptionsFormIncStandard,
11
- )
12
- from remapp.interface.mod_filters import (
13
- MGSummaryListFilter,
14
- MGFilterPlusPid,
15
- MGFilterPlusStdNames,
16
- MGFilterPlusPidPlusStdNames,
17
- )
18
- from remapp.models import (
19
- GeneralStudyModuleAttr,
20
- create_user_profile,
21
- StandardNameSettings,
22
- )
23
- from remapp.views_admin import (
24
- required_average_choices,
25
- initialise_mg_form_data,
26
- set_average_chart_options,
27
- set_mg_chart_options,
28
- set_common_chart_options,
29
- )
30
- from .interface.chart_functions import (
31
- create_dataframe,
32
- create_dataframe_weekdays,
33
- plotly_barchart_weekdays,
34
- plotly_binned_statistic_barchart,
35
- plotly_boxplot,
36
- plotly_barchart,
37
- plotly_histogram_barchart,
38
- plotly_scatter,
39
- plotly_frequency_barchart,
40
- construct_over_time_charts,
41
- plotly_set_default_theme,
42
- create_dataframe_aggregates,
43
- generate_average_chart_group,
44
- )
45
-
46
- logger = logging.getLogger(__name__)
47
-
48
-
49
- def generate_required_mg_charts_list(profile):
50
- # pylint: disable=too-many-branches
51
- # pylint: disable=too-many-statements
52
- """Obtain a list of dictionaries containing the title string and base
53
- variable name for each required chart"""
54
-
55
- # Obtain the system-level enable_standard_names setting
56
- try:
57
- StandardNameSettings.objects.get()
58
- except ObjectDoesNotExist:
59
- StandardNameSettings.objects.create()
60
- enable_standard_names = StandardNameSettings.objects.values_list(
61
- "enable_standard_names", flat=True
62
- )[0]
63
-
64
- required_charts = []
65
-
66
- charts_of_interest = [
67
- profile.plotMGAcquisitionAGDOverTime,
68
- ]
69
- if enable_standard_names:
70
- charts_of_interest.append(profile.plotMGStandardAcquisitionAGDOverTime)
71
-
72
- if any(charts_of_interest):
73
- keys = list(dict(profile.TIME_PERIOD).keys())
74
- values = list(dict(profile.TIME_PERIOD).values())
75
- time_period = (values[keys.index(profile.plotMGOverTimePeriod)]).lower()
76
-
77
- if profile.plotMGacquisitionFreq:
78
- required_charts.append(
79
- {
80
- "title": "Chart of acquisition protocol frequency",
81
- "var_name": "acquisitionFrequency",
82
- }
83
- )
84
-
85
- if profile.plotMGaverageAGD:
86
- if profile.plotMean:
87
- required_charts.append(
88
- {
89
- "title": "Chart of acquisition protocol mean AGD",
90
- "var_name": "acquisitionMeanAGD",
91
- }
92
- )
93
- if profile.plotMedian:
94
- required_charts.append(
95
- {
96
- "title": "Chart of acquisition protocol median AGD",
97
- "var_name": "acquisitionMedianAGD",
98
- }
99
- )
100
- if profile.plotBoxplots:
101
- required_charts.append(
102
- {
103
- "title": "Boxplot of acquisition protocol AGD",
104
- "var_name": "acquisitionBoxplotAGD",
105
- }
106
- )
107
- if profile.plotHistograms:
108
- required_charts.append(
109
- {
110
- "title": "Histogram of acquisition protocol AGD",
111
- "var_name": "acquisitionHistogramAGD",
112
- }
113
- )
114
-
115
- if profile.plotMGAGDvsThickness:
116
- required_charts.append(
117
- {
118
- "title": "Chart of acquisition protocol AGD vs compressed breast thickness",
119
- "var_name": "acquisitionScatterAGDvsThick",
120
- }
121
- )
122
-
123
- if profile.plotMGaverageAGDvsThickness:
124
- if profile.plotMean:
125
- required_charts.append(
126
- {
127
- "title": "Chart of acquisition protocol mean AGD vs compressed breast thickness",
128
- "var_name": "acquisitionMeanAGDvsThick",
129
- }
130
- )
131
- if profile.plotMedian:
132
- required_charts.append(
133
- {
134
- "title": "Chart of acquisition protocol median AGD vs compressed breast thickness",
135
- "var_name": "acquisitionMedianAGDvsThick",
136
- }
137
- )
138
-
139
- if profile.plotMGAcquisitionAGDOverTime:
140
- if profile.plotMean:
141
- required_charts.append(
142
- {
143
- "title": "Chart of acquisition protocol mean AGD over time ("
144
- + time_period
145
- + ")",
146
- "var_name": "acquisitionMeanAGDOverTime",
147
- }
148
- )
149
- if profile.plotMedian:
150
- required_charts.append(
151
- {
152
- "title": "Chart of acquisition protocol median AGD over time ("
153
- + time_period
154
- + ")",
155
- "var_name": "acquisitionMedianAGDOverTime",
156
- }
157
- )
158
-
159
- if profile.plotMGkVpvsThickness:
160
- required_charts.append(
161
- {
162
- "title": "Chart of acquisition protocol kVp vs compressed breast thickness",
163
- "var_name": "acquisitionScatterkVpvsThick",
164
- }
165
- )
166
-
167
- if profile.plotMGmAsvsThickness:
168
- required_charts.append(
169
- {
170
- "title": "Chart of acquisition protocol mAs vs compressed breast thickness",
171
- "var_name": "acquisitionScattermAsvsThick",
172
- }
173
- )
174
-
175
- if profile.plotMGStudyPerDayAndHour:
176
- required_charts.append(
177
- {
178
- "title": "Chart of study description workload",
179
- "var_name": "studyWorkload",
180
- }
181
- )
182
-
183
- if enable_standard_names:
184
- if profile.plotMGStandardAcquisitionFreq:
185
- required_charts.append(
186
- {
187
- "title": "Chart of standard acquisition name frequency",
188
- "var_name": "standardAcquisitionFrequency",
189
- }
190
- )
191
-
192
- if profile.plotMGStandardAverageAGD:
193
- if profile.plotMean:
194
- required_charts.append(
195
- {
196
- "title": "Chart of standard acquisition name mean AGD",
197
- "var_name": "standardAcquisitionMeanAGD",
198
- }
199
- )
200
- if profile.plotMedian:
201
- required_charts.append(
202
- {
203
- "title": "Chart of standard acquisition name median AGD",
204
- "var_name": "standardAcquisitionMedianAGD",
205
- }
206
- )
207
- if profile.plotBoxplots:
208
- required_charts.append(
209
- {
210
- "title": "Boxplot of standard acquisition name AGD",
211
- "var_name": "standardAcquisitionBoxplotAGD",
212
- }
213
- )
214
- if profile.plotHistograms:
215
- required_charts.append(
216
- {
217
- "title": "Histogram of standard acquisition name AGD",
218
- "var_name": "standardAcquisitionHistogramAGD",
219
- }
220
- )
221
-
222
- if profile.plotMGStandardAGDvsThickness:
223
- required_charts.append(
224
- {
225
- "title": "Chart of standard acquisition name AGD vs compressed breast thickness",
226
- "var_name": "standardAcquisitionScatterAGDvsThick",
227
- }
228
- )
229
-
230
- if profile.plotMGStandardAverageAGDvsThickness:
231
- if profile.plotMean:
232
- required_charts.append(
233
- {
234
- "title": "Chart of standard acquisition name mean AGD vs compressed breast thickness",
235
- "var_name": "standardAcquisitionMeanAGDvsThick",
236
- }
237
- )
238
- if profile.plotMedian:
239
- required_charts.append(
240
- {
241
- "title": "Chart of standard acquisition name median AGD vs compressed breast thickness",
242
- "var_name": "standardAcquisitionMedianAGDvsThick",
243
- }
244
- )
245
-
246
- if profile.plotMGStandardAcquisitionAGDOverTime:
247
- if profile.plotMean:
248
- required_charts.append(
249
- {
250
- "title": "Chart of standard acquisition name mean AGD over time ("
251
- + time_period
252
- + ")",
253
- "var_name": "standardAcquisitionMeanAGDOverTime",
254
- }
255
- )
256
- if profile.plotMedian:
257
- required_charts.append(
258
- {
259
- "title": "Chart of standard acquisition name median AGD over time ("
260
- + time_period
261
- + ")",
262
- "var_name": "standardAcquisitionMedianAGDOverTime",
263
- }
264
- )
265
-
266
- if profile.plotMGStandardkVpvsThickness:
267
- required_charts.append(
268
- {
269
- "title": "Chart of standard acquisition name kVp vs compressed breast thickness",
270
- "var_name": "standardAcquisitionScatterkVpvsThick",
271
- }
272
- )
273
-
274
- if profile.plotMGStandardmAsvsThickness:
275
- required_charts.append(
276
- {
277
- "title": "Chart of standard acquisition name mAs vs compressed breast thickness",
278
- "var_name": "standardAcquisitionScattermAsvsThick",
279
- }
280
- )
281
-
282
- if profile.plotMGStandardStudyPerDayAndHour:
283
- required_charts.append(
284
- {
285
- "title": "Chart of standard study name workload",
286
- "var_name": "standardStudyWorkload",
287
- }
288
- )
289
-
290
- return required_charts
291
-
292
-
293
- @login_required
294
- def mg_summary_chart_data(request):
295
- """Obtain data for mammography chart data Ajax view"""
296
-
297
- # Obtain the system-level enable_standard_names setting
298
- try:
299
- StandardNameSettings.objects.get()
300
- except ObjectDoesNotExist:
301
- StandardNameSettings.objects.create()
302
- enable_standard_names = StandardNameSettings.objects.values_list(
303
- "enable_standard_names", flat=True
304
- )[0]
305
-
306
- if request.user.groups.filter(name="pidgroup"):
307
- if enable_standard_names:
308
- f = MGFilterPlusPidPlusStdNames(
309
- request.GET,
310
- queryset=GeneralStudyModuleAttr.objects.filter(
311
- modality_type__exact="MG"
312
- )
313
- .order_by()
314
- .distinct(),
315
- )
316
- else:
317
- f = MGFilterPlusPid(
318
- request.GET,
319
- queryset=GeneralStudyModuleAttr.objects.filter(
320
- modality_type__exact="MG"
321
- )
322
- .order_by()
323
- .distinct(),
324
- )
325
- else:
326
- if enable_standard_names:
327
- f = MGFilterPlusStdNames(
328
- request.GET,
329
- queryset=GeneralStudyModuleAttr.objects.filter(
330
- modality_type__exact="MG"
331
- )
332
- .order_by()
333
- .distinct(),
334
- )
335
- else:
336
- f = MGSummaryListFilter(
337
- request.GET,
338
- queryset=GeneralStudyModuleAttr.objects.filter(
339
- modality_type__exact="MG"
340
- )
341
- .order_by()
342
- .distinct(),
343
- )
344
- try:
345
- # See if the user has plot settings in userprofile
346
- user_profile = request.user.userprofile
347
- except ObjectDoesNotExist:
348
- # Create a default userprofile for the user if one doesn't exist
349
- create_user_profile(sender=request.user, instance=request.user, created=True)
350
- user_profile = request.user.userprofile
351
-
352
- if settings.DEBUG:
353
- start_time = datetime.now()
354
-
355
- return_structure = mg_plot_calculations(f, user_profile)
356
-
357
- if settings.DEBUG:
358
- logger.debug(f"Elapsed time is {datetime.now() - start_time}")
359
-
360
- return JsonResponse(return_structure, safe=False)
361
-
362
-
363
- def mg_plot_calculations(f, user_profile, return_as_dict=False):
364
- # pylint: disable=too-many-locals
365
- # pylint: disable=too-many-branches
366
- # pylint: disable=too-many-statements
367
- """Calculations for mammography charts"""
368
- # Return an empty structure if the queryset is empty
369
- if not f.qs.exists():
370
- return {}
371
-
372
- # Obtain the system-level enable_standard_names setting
373
- try:
374
- StandardNameSettings.objects.get()
375
- except ObjectDoesNotExist:
376
- StandardNameSettings.objects.create()
377
- enable_standard_names = StandardNameSettings.objects.values_list(
378
- "enable_standard_names", flat=True
379
- )[0]
380
-
381
- # Set the Plotly chart theme
382
- plotly_set_default_theme(user_profile.plotThemeChoice)
383
-
384
- return_structure = {}
385
-
386
- average_choices = []
387
- if user_profile.plotMean:
388
- average_choices.append("mean")
389
- if user_profile.plotMedian:
390
- average_choices.append("median")
391
-
392
- charts_of_interest = [user_profile.plotMGAcquisitionAGDOverTime]
393
- if enable_standard_names:
394
- charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
395
- if any(charts_of_interest):
396
- plot_timeunit_period = user_profile.plotMGOverTimePeriod
397
-
398
- #######################################################################
399
- # Prepare acquisition-level Pandas DataFrame to use for charts
400
- charts_of_interest = [
401
- user_profile.plotMGAGDvsThickness,
402
- user_profile.plotMGkVpvsThickness,
403
- user_profile.plotMGmAsvsThickness,
404
- user_profile.plotMGaverageAGDvsThickness,
405
- user_profile.plotMGaverageAGD,
406
- user_profile.plotMGacquisitionFreq,
407
- user_profile.plotMGAcquisitionAGDOverTime,
408
- ]
409
- if enable_standard_names:
410
- charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
411
- charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
412
- charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
413
- charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
414
- charts_of_interest.append(user_profile.plotMGStandardAverageAGD)
415
- charts_of_interest.append(user_profile.plotMGStandardAcquisitionFreq)
416
- charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
417
-
418
- if any(charts_of_interest): # pylint: disable=too-many-nested-blocks
419
-
420
- name_fields = []
421
- charts_of_interest = [
422
- user_profile.plotMGAGDvsThickness,
423
- user_profile.plotMGkVpvsThickness,
424
- user_profile.plotMGmAsvsThickness,
425
- user_profile.plotMGaverageAGDvsThickness,
426
- user_profile.plotMGaverageAGD,
427
- user_profile.plotMGacquisitionFreq,
428
- user_profile.plotMGAcquisitionAGDOverTime,
429
- ]
430
- if any(charts_of_interest):
431
- name_fields.append(
432
- "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
433
- )
434
-
435
- if enable_standard_names:
436
- charts_of_interest = [
437
- user_profile.plotMGStandardAGDvsThickness,
438
- user_profile.plotMGStandardkVpvsThickness,
439
- user_profile.plotMGStandardmAsvsThickness,
440
- user_profile.plotMGStandardAverageAGDvsThickness,
441
- user_profile.plotMGStandardAverageAGD,
442
- user_profile.plotMGStandardAcquisitionFreq,
443
- user_profile.plotMGStandardAcquisitionAGDOverTime,
444
- ]
445
- if any(charts_of_interest):
446
- name_fields.append(
447
- "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
448
- )
449
-
450
- value_fields = []
451
- value_multipliers = []
452
- charts_of_interest = [
453
- user_profile.plotMGAGDvsThickness,
454
- user_profile.plotMGaverageAGDvsThickness,
455
- user_profile.plotMGaverageAGD,
456
- user_profile.plotMGAcquisitionAGDOverTime,
457
- ]
458
- if enable_standard_names:
459
- charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
460
- charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
461
- charts_of_interest.append(user_profile.plotMGStandardAverageAGD)
462
- charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
463
- if any(charts_of_interest):
464
- value_fields.append(
465
- "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose"
466
- )
467
- value_multipliers.append(1)
468
-
469
- charts_of_interest = [user_profile.plotMGkVpvsThickness]
470
- if enable_standard_names:
471
- charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
472
- if any(charts_of_interest):
473
- value_fields.append(
474
- "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp"
475
- )
476
- value_multipliers.append(1)
477
-
478
- charts_of_interest = [user_profile.plotMGmAsvsThickness]
479
- if enable_standard_names:
480
- charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
481
- if any(charts_of_interest):
482
- value_fields.append(
483
- "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure"
484
- )
485
- value_multipliers.append(0.001)
486
-
487
- charts_of_interest = [
488
- user_profile.plotMGAGDvsThickness,
489
- user_profile.plotMGkVpvsThickness,
490
- user_profile.plotMGmAsvsThickness,
491
- user_profile.plotMGaverageAGDvsThickness,
492
- ]
493
- if enable_standard_names:
494
- charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
495
- charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
496
- charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
497
- charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
498
- if any(charts_of_interest):
499
- value_fields.append(
500
- "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness"
501
- )
502
- value_multipliers.append(1)
503
-
504
- date_fields = []
505
- charts_of_interest = [user_profile.plotMGAcquisitionAGDOverTime]
506
- if enable_standard_names:
507
- charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
508
- if any(charts_of_interest):
509
- date_fields.append("study_date")
510
-
511
- time_fields = []
512
-
513
- system_field = []
514
- if user_profile.plotSeriesPerSystem:
515
- system_field.append(
516
- "generalequipmentmoduleattr__unique_equipment_name_id__display_name"
517
- )
518
-
519
- fields = {
520
- "names": name_fields,
521
- "values": value_fields,
522
- "dates": date_fields,
523
- "times": time_fields,
524
- "system": system_field,
525
- }
526
-
527
- df = create_dataframe(
528
- f.qs,
529
- fields,
530
- data_point_name_lowercase=user_profile.plotCaseInsensitiveCategories,
531
- data_point_name_remove_whitespace_padding=user_profile.plotRemoveCategoryWhitespacePadding,
532
- data_point_value_multipliers=value_multipliers,
533
- char_wrap=user_profile.plotLabelCharWrap,
534
- uid="projectionxrayradiationdose__irradeventxraydata__pk",
535
- )
536
-
537
- if user_profile.plotMGaverageAGDvsThickness or user_profile.plotMGaverageAGD:
538
-
539
- if user_profile.plotBoxplots and "median" not in average_choices:
540
- average_choices = average_choices + ["median"]
541
-
542
- name_field = (
543
- "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
544
- )
545
- value_field = "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose" # pylint: disable=line-too-long
546
-
547
- if user_profile.plotMGaverageAGDvsThickness:
548
- category_names_col = name_field
549
- group_by_col = "x_ray_system_name"
550
- legend_title = "Acquisition protocol"
551
-
552
- if user_profile.plotGroupingChoice == "series":
553
- category_names_col = "x_ray_system_name"
554
- group_by_col = name_field
555
- legend_title = "System"
556
-
557
- parameter_dict = { # pylint: disable=line-too-long
558
- "df_x_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
559
- "df_y_value_col": value_field, # pylint: disable=line-too-long
560
- "x_axis_title": "Compressed breast thickness (mm)",
561
- "y_axis_title": "AGD (mGy)",
562
- "df_category_col": category_names_col,
563
- "df_facet_col": group_by_col,
564
- "facet_title": legend_title,
565
- "user_bins": [20, 30, 40, 50, 60, 70, 80, 90],
566
- "colourmap": user_profile.plotColourMapChoice,
567
- "filename": "OpenREM CT acquisition protocol average AGD vs thickness",
568
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
569
- "sorting_choice": [
570
- user_profile.plotInitialSortingDirection,
571
- user_profile.plotMGInitialSortingChoice,
572
- ],
573
- "return_as_dict": return_as_dict,
574
- }
575
- if user_profile.plotMean:
576
- parameter_dict["stat_name"] = "mean"
577
- return_structure[
578
- "meanAGDvsThickness"
579
- ] = plotly_binned_statistic_barchart(
580
- df,
581
- parameter_dict,
582
- )
583
-
584
- if user_profile.plotMedian:
585
- parameter_dict["stat_name"] = "median"
586
- return_structure[
587
- "medianAGDvsThickness"
588
- ] = plotly_binned_statistic_barchart(
589
- df,
590
- parameter_dict,
591
- )
592
-
593
- if user_profile.plotMGaverageAGD:
594
- value_text = "AGD"
595
- units_text = "(mGy)"
596
- name_text = "Acquisition protocol"
597
- variable_name_start = "acquisition"
598
- variable_value_name = "AGD"
599
- modality_text = "CT"
600
- chart_message = ""
601
-
602
- new_charts = generate_average_chart_group(
603
- average_choices,
604
- chart_message,
605
- df,
606
- modality_text,
607
- name_field,
608
- name_text,
609
- return_as_dict,
610
- return_structure,
611
- units_text,
612
- user_profile,
613
- value_field,
614
- value_text,
615
- variable_name_start,
616
- variable_value_name,
617
- user_profile.plotMGInitialSortingChoice,
618
- )
619
-
620
- return_structure = {**return_structure, **new_charts}
621
-
622
- if user_profile.plotMGAGDvsThickness:
623
- parameter_dict = { # pylint: disable=line-too-long
624
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
625
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
626
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
627
- "sorting_choice": [
628
- user_profile.plotInitialSortingDirection,
629
- user_profile.plotMGInitialSortingChoice,
630
- ],
631
- "grouping_choice": user_profile.plotGroupingChoice,
632
- "legend_title": "Acquisition protocol",
633
- "colourmap": user_profile.plotColourMapChoice,
634
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
635
- "x_axis_title": "Compressed breast thickness (mm)",
636
- "y_axis_title": "AGD (mGy)",
637
- "filename": "OpenREM CT acquisition protocol AGD vs thickness",
638
- "return_as_dict": return_as_dict,
639
- }
640
- return_structure[
641
- "AGDvsThickness"
642
- ] = plotly_scatter( # pylint: disable=line-too-long
643
- df,
644
- parameter_dict,
645
- )
646
-
647
- if user_profile.plotMGkVpvsThickness:
648
- parameter_dict = { # pylint: disable=line-too-long
649
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
650
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
651
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp", # pylint: disable=line-too-long
652
- "sorting_choice": [
653
- user_profile.plotInitialSortingDirection,
654
- user_profile.plotMGInitialSortingChoice,
655
- ],
656
- "grouping_choice": user_profile.plotGroupingChoice,
657
- "legend_title": "Acquisition protocol",
658
- "colourmap": user_profile.plotColourMapChoice,
659
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
660
- "x_axis_title": "Compressed breast thickness (mm)",
661
- "y_axis_title": "kVp",
662
- "filename": "OpenREM CT acquisition protocol kVp vs thickness",
663
- "return_as_dict": return_as_dict,
664
- }
665
- return_structure[
666
- "kVpvsThickness"
667
- ] = plotly_scatter( # pylint: disable=line-too-long
668
- df,
669
- parameter_dict,
670
- )
671
-
672
- if user_profile.plotMGmAsvsThickness:
673
- parameter_dict = { # pylint: disable=line-too-long
674
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
675
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
676
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure", # pylint: disable=line-too-long
677
- "sorting_choice": [
678
- user_profile.plotInitialSortingDirection,
679
- user_profile.plotMGInitialSortingChoice,
680
- ],
681
- "grouping_choice": user_profile.plotGroupingChoice,
682
- "legend_title": "Acquisition protocol",
683
- "colourmap": user_profile.plotColourMapChoice,
684
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
685
- "x_axis_title": "Compressed breast thickness (mm)",
686
- "y_axis_title": "mAs",
687
- "filename": "OpenREM CT acquisition protocol mAs vs thickness",
688
- "return_as_dict": return_as_dict,
689
- }
690
- return_structure[
691
- "mAsvsThickness"
692
- ] = plotly_scatter( # pylint: disable=line-too-long
693
- df,
694
- parameter_dict,
695
- )
696
-
697
- if user_profile.plotMGacquisitionFreq:
698
- parameter_dict = {
699
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
700
- "sorting_choice": [
701
- user_profile.plotInitialSortingDirection,
702
- user_profile.plotMGInitialSortingChoice,
703
- ],
704
- "legend_title": "Acquisition",
705
- "df_x_axis_col": "x_ray_system_name",
706
- "x_axis_title": "System",
707
- "grouping_choice": user_profile.plotGroupingChoice,
708
- "colourmap": user_profile.plotColourMapChoice,
709
- "filename": "OpenREM MG acquisition protocol frequency",
710
- "groupby_cols": None,
711
- "facet_col": None,
712
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
713
- "return_as_dict": return_as_dict,
714
- }
715
- (
716
- return_structure["acquisitionFrequencyData"],
717
- return_structure["acquisitionFrequencyDataCSV"],
718
- ) = plotly_frequency_barchart( # pylint: disable=line-too-long
719
- df, parameter_dict, csv_name="acquisitionFrequencyData.csv"
720
- )
721
-
722
- if user_profile.plotMGAcquisitionAGDOverTime:
723
- facet_title = "System"
724
-
725
- if user_profile.plotGroupingChoice == "series":
726
- facet_title = "Acquisition protocol"
727
-
728
- parameter_dict = { # pylint: disable=line-too-long
729
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
730
- "df_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
731
- "df_date_col": "study_date",
732
- "name_title": "Acquisition protocol",
733
- "value_title": "AGD (mGy)",
734
- "date_title": "Study date",
735
- "facet_title": facet_title,
736
- "sorting_choice": [
737
- user_profile.plotInitialSortingDirection,
738
- user_profile.plotMGInitialSortingChoice,
739
- ],
740
- "time_period": plot_timeunit_period,
741
- "average_choices": average_choices + ["count"],
742
- "grouping_choice": user_profile.plotGroupingChoice,
743
- "colourmap": user_profile.plotColourMapChoice,
744
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
745
- "filename": "OpenREM MG acquisition protocol AGD over time",
746
- "return_as_dict": return_as_dict,
747
- }
748
- result = construct_over_time_charts(
749
- df,
750
- parameter_dict,
751
- )
752
-
753
- if user_profile.plotMean:
754
- return_structure["acquisitionMeanAGDOverTime"] = result["mean"]
755
- if user_profile.plotMedian:
756
- return_structure["acquisitionMedianAGDOverTime"] = result["median"]
757
-
758
- if enable_standard_names:
759
- charts_of_interest = [
760
- user_profile.plotMGStandardAGDvsThickness,
761
- user_profile.plotMGStandardkVpvsThickness,
762
- user_profile.plotMGStandardmAsvsThickness,
763
- user_profile.plotMGStandardAverageAGDvsThickness,
764
- user_profile.plotMGStandardAverageAGD,
765
- user_profile.plotMGStandardAcquisitionFreq,
766
- user_profile.plotMGStandardAcquisitionAGDOverTime,
767
- ]
768
-
769
- if any(charts_of_interest):
770
- # Exclude "Blank" and "blank" standard_acquisition_name data
771
- df_without_blanks = df[
772
- (
773
- df[
774
- "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
775
- ]
776
- != "blank"
777
- )
778
- & (
779
- df[
780
- "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
781
- ]
782
- != "Blank"
783
- )
784
- ].copy()
785
- # Remove any unused categories (this will include "Blank" or "blank")
786
- df_without_blanks[
787
- "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
788
- ] = df_without_blanks[
789
- "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
790
- ].cat.remove_unused_categories()
791
-
792
- if (
793
- user_profile.plotMGStandardAverageAGDvsThickness
794
- or user_profile.plotMGStandardAverageAGD
795
- ):
796
-
797
- if user_profile.plotBoxplots and "median" not in average_choices:
798
- average_choices = average_choices + ["median"]
799
-
800
- name_field = "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
801
- value_field = "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose" # pylint: disable=line-too-long
802
-
803
- if user_profile.plotMGStandardAverageAGDvsThickness:
804
- category_names_col = name_field
805
- group_by_col = "x_ray_system_name"
806
- legend_title = "Standard acquisition name"
807
-
808
- if user_profile.plotGroupingChoice == "series":
809
- category_names_col = "x_ray_system_name"
810
- group_by_col = name_field
811
- legend_title = "System"
812
-
813
- parameter_dict = { # pylint: disable=line-too-long
814
- "df_x_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
815
- "df_y_value_col": value_field, # pylint: disable=line-too-long
816
- "x_axis_title": "Compressed breast thickness (mm)",
817
- "y_axis_title": "AGD (mGy)",
818
- "df_category_col": category_names_col,
819
- "df_facet_col": group_by_col,
820
- "facet_title": legend_title,
821
- "user_bins": [20, 30, 40, 50, 60, 70, 80, 90],
822
- "colourmap": user_profile.plotColourMapChoice,
823
- "filename": "OpenREM CT standard acquisition name average AGD vs thickness",
824
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
825
- "sorting_choice": [
826
- user_profile.plotInitialSortingDirection,
827
- user_profile.plotMGInitialSortingChoice,
828
- ],
829
- "return_as_dict": return_as_dict,
830
- }
831
- if user_profile.plotMean:
832
- parameter_dict["stat_name"] = "mean"
833
- return_structure[
834
- "standardMeanAGDvsThickness"
835
- ] = plotly_binned_statistic_barchart(
836
- df_without_blanks,
837
- parameter_dict,
838
- )
839
-
840
- if user_profile.plotMedian:
841
- parameter_dict["stat_name"] = "median"
842
- return_structure[
843
- "standardMedianAGDvsThickness"
844
- ] = plotly_binned_statistic_barchart(
845
- df_without_blanks,
846
- parameter_dict,
847
- )
848
-
849
- if user_profile.plotMGStandardAverageAGD:
850
- value_text = "AGD"
851
- units_text = "(mGy)"
852
- name_text = "Standard acquisition name"
853
- variable_name_start = "standardAcquisition"
854
- variable_value_name = "AGD"
855
- modality_text = "CT"
856
- chart_message = ""
857
-
858
- new_charts = generate_average_chart_group(
859
- average_choices,
860
- chart_message,
861
- df_without_blanks,
862
- modality_text,
863
- name_field,
864
- name_text,
865
- return_as_dict,
866
- return_structure,
867
- units_text,
868
- user_profile,
869
- value_field,
870
- value_text,
871
- variable_name_start,
872
- variable_value_name,
873
- user_profile.plotMGInitialSortingChoice,
874
- )
875
-
876
- return_structure = {**return_structure, **new_charts}
877
-
878
- if user_profile.plotMGStandardAGDvsThickness:
879
- parameter_dict = { # pylint: disable=line-too-long
880
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
881
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
882
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
883
- "sorting_choice": [
884
- user_profile.plotInitialSortingDirection,
885
- user_profile.plotMGInitialSortingChoice,
886
- ],
887
- "grouping_choice": user_profile.plotGroupingChoice,
888
- "legend_title": "Standard acquisition name",
889
- "colourmap": user_profile.plotColourMapChoice,
890
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
891
- "x_axis_title": "Compressed breast thickness (mm)",
892
- "y_axis_title": "AGD (mGy)",
893
- "filename": "OpenREM CT standard acquisition name AGD vs thickness",
894
- "return_as_dict": return_as_dict,
895
- }
896
- return_structure[
897
- "standardAGDvsThickness"
898
- ] = plotly_scatter( # pylint: disable=line-too-long
899
- df_without_blanks,
900
- parameter_dict,
901
- )
902
-
903
- if user_profile.plotMGStandardkVpvsThickness:
904
- parameter_dict = { # pylint: disable=line-too-long
905
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
906
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
907
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp", # pylint: disable=line-too-long
908
- "sorting_choice": [
909
- user_profile.plotInitialSortingDirection,
910
- user_profile.plotMGInitialSortingChoice,
911
- ],
912
- "grouping_choice": user_profile.plotGroupingChoice,
913
- "legend_title": "Standard acquisition name",
914
- "colourmap": user_profile.plotColourMapChoice,
915
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
916
- "x_axis_title": "Compressed breast thickness (mm)",
917
- "y_axis_title": "kVp",
918
- "filename": "OpenREM CT standard acquisition name kVp vs thickness",
919
- "return_as_dict": return_as_dict,
920
- }
921
- return_structure[
922
- "standardkVpvsThickness"
923
- ] = plotly_scatter( # pylint: disable=line-too-long
924
- df_without_blanks,
925
- parameter_dict,
926
- )
927
-
928
- if user_profile.plotMGStandardmAsvsThickness:
929
- parameter_dict = { # pylint: disable=line-too-long
930
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
931
- "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
932
- "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure", # pylint: disable=line-too-long
933
- "sorting_choice": [
934
- user_profile.plotInitialSortingDirection,
935
- user_profile.plotMGInitialSortingChoice,
936
- ],
937
- "grouping_choice": user_profile.plotGroupingChoice,
938
- "legend_title": "Standard acquisition name",
939
- "colourmap": user_profile.plotColourMapChoice,
940
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
941
- "x_axis_title": "Compressed breast thickness (mm)",
942
- "y_axis_title": "mAs",
943
- "filename": "OpenREM CT standard acquisition name mAs vs thickness",
944
- "return_as_dict": return_as_dict,
945
- }
946
- return_structure[
947
- "standardmAsvsThickness"
948
- ] = plotly_scatter( # pylint: disable=line-too-long
949
- df_without_blanks,
950
- parameter_dict,
951
- )
952
-
953
- if user_profile.plotMGStandardAcquisitionFreq:
954
- parameter_dict = {
955
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
956
- "sorting_choice": [
957
- user_profile.plotInitialSortingDirection,
958
- user_profile.plotMGInitialSortingChoice,
959
- ],
960
- "legend_title": "Acquisition",
961
- "df_x_axis_col": "x_ray_system_name",
962
- "x_axis_title": "System",
963
- "grouping_choice": user_profile.plotGroupingChoice,
964
- "colourmap": user_profile.plotColourMapChoice,
965
- "filename": "OpenREM MG standard acquisition name frequency",
966
- "groupby_cols": None,
967
- "facet_col": None,
968
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
969
- "return_as_dict": return_as_dict,
970
- }
971
- (
972
- return_structure["standardAcquisitionFrequencyData"],
973
- return_structure["standardAcquisitionFrequencyDataCSV"],
974
- ) = plotly_frequency_barchart( # pylint: disable=line-too-long
975
- df_without_blanks,
976
- parameter_dict,
977
- csv_name="standardAcquisitionFrequencyData.csv",
978
- )
979
-
980
- if user_profile.plotMGStandardAcquisitionAGDOverTime:
981
- facet_title = "System"
982
-
983
- if user_profile.plotGroupingChoice == "series":
984
- facet_title = "Standard acquisition name"
985
-
986
- parameter_dict = { # pylint: disable=line-too-long
987
- "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
988
- "df_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
989
- "df_date_col": "study_date",
990
- "name_title": "Standard acquisition name",
991
- "value_title": "AGD (mGy)",
992
- "date_title": "Study date",
993
- "facet_title": facet_title,
994
- "sorting_choice": [
995
- user_profile.plotInitialSortingDirection,
996
- user_profile.plotMGInitialSortingChoice,
997
- ],
998
- "time_period": plot_timeunit_period,
999
- "average_choices": average_choices + ["count"],
1000
- "grouping_choice": user_profile.plotGroupingChoice,
1001
- "colourmap": user_profile.plotColourMapChoice,
1002
- "facet_col_wrap": user_profile.plotFacetColWrapVal,
1003
- "filename": "OpenREM MG standard acquisition name AGD over time",
1004
- "return_as_dict": return_as_dict,
1005
- }
1006
- result = construct_over_time_charts(
1007
- df_without_blanks,
1008
- parameter_dict,
1009
- )
1010
-
1011
- if user_profile.plotMean:
1012
- return_structure["standardAcquisitionMeanAGDOverTime"] = result[
1013
- "mean"
1014
- ]
1015
- if user_profile.plotMedian:
1016
- return_structure["standardAcquisitionMedianAGDOverTime"] = result[
1017
- "median"
1018
- ]
1019
-
1020
- #######################################################################
1021
- # Prepare study- and request-level Pandas DataFrame to use for charts
1022
- charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1023
- if enable_standard_names:
1024
- charts_of_interest.append(user_profile.plotMGStandardStudyPerDayAndHour)
1025
-
1026
- if any(charts_of_interest):
1027
-
1028
- name_fields = []
1029
- charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1030
- if any(charts_of_interest):
1031
- name_fields.append("study_description")
1032
-
1033
- if enable_standard_names:
1034
- charts_of_interest = [user_profile.plotMGStandardStudyPerDayAndHour]
1035
- if any(charts_of_interest):
1036
- name_fields.append("standard_names__standard_name")
1037
-
1038
- value_fields = []
1039
-
1040
- date_fields = []
1041
- time_fields = []
1042
- charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1043
- if enable_standard_names:
1044
- charts_of_interest.append(user_profile.plotMGStandardStudyPerDayAndHour)
1045
- if any(charts_of_interest):
1046
- date_fields.append("study_date")
1047
- time_fields.append("study_time")
1048
-
1049
- system_field = []
1050
- if user_profile.plotSeriesPerSystem:
1051
- system_field.append(
1052
- "generalequipmentmoduleattr__unique_equipment_name_id__display_name"
1053
- )
1054
-
1055
- fields = {
1056
- "names": name_fields,
1057
- "values": value_fields,
1058
- "dates": date_fields,
1059
- "times": time_fields,
1060
- "system": system_field,
1061
- }
1062
-
1063
- # If only standard_names__standard_name is required then exclude all entries where these are None as these are
1064
- # not required for standard name charts.
1065
- queryset = f.qs
1066
- if name_fields == ["standard_names__standard_name"]:
1067
- queryset = queryset.exclude(standard_names__standard_name__isnull=True)
1068
-
1069
- df = create_dataframe(
1070
- queryset,
1071
- fields,
1072
- data_point_name_lowercase=user_profile.plotCaseInsensitiveCategories,
1073
- data_point_name_remove_whitespace_padding=user_profile.plotRemoveCategoryWhitespacePadding,
1074
- uid="pk",
1075
- )
1076
-
1077
- if user_profile.plotMGStudyPerDayAndHour:
1078
- df_time_series_per_weekday = create_dataframe_weekdays(
1079
- df, "study_description", df_date_col="study_date"
1080
- )
1081
-
1082
- return_structure["studyWorkloadData"] = plotly_barchart_weekdays(
1083
- df_time_series_per_weekday,
1084
- "weekday",
1085
- "study_description",
1086
- name_axis_title="Weekday",
1087
- value_axis_title="Frequency",
1088
- colourmap=user_profile.plotColourMapChoice,
1089
- filename="OpenREM CT study description workload",
1090
- facet_col_wrap=user_profile.plotFacetColWrapVal,
1091
- sorting_choice=[
1092
- user_profile.plotInitialSortingDirection,
1093
- user_profile.plotMGInitialSortingChoice,
1094
- ],
1095
- return_as_dict=return_as_dict,
1096
- )
1097
-
1098
- if enable_standard_names:
1099
- charts_of_interest = [user_profile.plotMGStandardStudyPerDayAndHour]
1100
-
1101
- if any(charts_of_interest):
1102
-
1103
- # Create a standard name data frame - remove any blank standard names
1104
- standard_name_df = df[
1105
- (df["standard_names__standard_name"] != "blank")
1106
- & (df["standard_names__standard_name"] != "Blank")
1107
- ].copy()
1108
- # Remove any unused categories (this will include "Blank" or "blank")
1109
- standard_name_df["standard_names__standard_name"] = standard_name_df[
1110
- "standard_names__standard_name"
1111
- ].cat.remove_unused_categories()
1112
-
1113
- if user_profile.plotMGStandardStudyPerDayAndHour:
1114
- df_time_series_per_weekday = create_dataframe_weekdays(
1115
- standard_name_df,
1116
- "standard_names__standard_name",
1117
- df_date_col="study_date",
1118
- )
1119
-
1120
- return_structure[
1121
- "standardStudyWorkloadData"
1122
- ] = plotly_barchart_weekdays(
1123
- df_time_series_per_weekday,
1124
- "weekday",
1125
- "standard_names__standard_name",
1126
- name_axis_title="Weekday",
1127
- value_axis_title="Frequency",
1128
- colourmap=user_profile.plotColourMapChoice,
1129
- filename="OpenREM CT standard study name workload",
1130
- facet_col_wrap=user_profile.plotFacetColWrapVal,
1131
- sorting_choice=[
1132
- user_profile.plotInitialSortingDirection,
1133
- user_profile.plotMGInitialSortingChoice,
1134
- ],
1135
- return_as_dict=return_as_dict,
1136
- )
1137
-
1138
- return return_structure
1139
-
1140
-
1141
- def mg_chart_form_processing(request, user_profile):
1142
- # pylint: disable=too-many-statements
1143
-
1144
- # Obtain the system-level enable_standard_names setting
1145
- try:
1146
- StandardNameSettings.objects.get()
1147
- except ObjectDoesNotExist:
1148
- StandardNameSettings.objects.create()
1149
- enable_standard_names = StandardNameSettings.objects.values_list(
1150
- "enable_standard_names", flat=True
1151
- )[0]
1152
-
1153
- # Obtain the chart options from the request
1154
- chart_options_form = None
1155
- if enable_standard_names:
1156
- chart_options_form = MGChartOptionsFormIncStandard(request.GET)
1157
- else:
1158
- chart_options_form = MGChartOptionsForm(request.GET)
1159
-
1160
- # Check whether the form data is valid
1161
- if chart_options_form.is_valid():
1162
- # Use the form data if the user clicked on the submit button
1163
- if "submit" in request.GET:
1164
- # process the data in form.cleaned_data as required
1165
-
1166
- set_common_chart_options(chart_options_form, user_profile)
1167
-
1168
- set_average_chart_options(chart_options_form, user_profile)
1169
-
1170
- set_mg_chart_options(chart_options_form, user_profile)
1171
-
1172
- user_profile.save()
1173
-
1174
- else:
1175
- average_choices = required_average_choices(user_profile)
1176
-
1177
- mg_form_data = initialise_mg_form_data(user_profile)
1178
-
1179
- form_data = {
1180
- "plotCharts": user_profile.plotCharts,
1181
- "plotGrouping": user_profile.plotGroupingChoice,
1182
- "plotSeriesPerSystem": user_profile.plotSeriesPerSystem,
1183
- "plotHistograms": user_profile.plotHistograms,
1184
- "plotInitialSortingDirection": user_profile.plotInitialSortingDirection,
1185
- "plotAverageChoice": average_choices,
1186
- }
1187
-
1188
- form_data = {**form_data, **mg_form_data}
1189
-
1190
- chart_options_form = None
1191
- if enable_standard_names:
1192
- chart_options_form = MGChartOptionsFormIncStandard(form_data)
1193
- else:
1194
- chart_options_form = MGChartOptionsForm(form_data)
1195
-
1196
- return chart_options_form
1
+ # pylint: disable=too-many-lines
2
+ import logging
3
+ from datetime import datetime
4
+ from django.conf import settings
5
+ from django.contrib.auth.decorators import login_required
6
+ from django.core.exceptions import ObjectDoesNotExist
7
+ from django.http import JsonResponse
8
+ from remapp.forms import (
9
+ MGChartOptionsForm,
10
+ MGChartOptionsFormIncStandard,
11
+ )
12
+ from remapp.interface.mod_filters import (
13
+ MGSummaryListFilter,
14
+ MGFilterPlusPid,
15
+ MGFilterPlusStdNames,
16
+ MGFilterPlusPidPlusStdNames,
17
+ )
18
+ from remapp.models import (
19
+ GeneralStudyModuleAttr,
20
+ create_user_profile,
21
+ StandardNameSettings,
22
+ )
23
+ from remapp.views_admin import (
24
+ required_average_choices,
25
+ initialise_mg_form_data,
26
+ set_average_chart_options,
27
+ set_mg_chart_options,
28
+ set_common_chart_options,
29
+ )
30
+ from .interface.chart_functions import (
31
+ create_dataframe,
32
+ create_dataframe_weekdays,
33
+ plotly_barchart_weekdays,
34
+ plotly_binned_statistic_barchart,
35
+ plotly_boxplot,
36
+ plotly_barchart,
37
+ plotly_histogram_barchart,
38
+ plotly_scatter,
39
+ plotly_frequency_barchart,
40
+ construct_over_time_charts,
41
+ plotly_set_default_theme,
42
+ create_dataframe_aggregates,
43
+ generate_average_chart_group,
44
+ )
45
+
46
+ from .tools.check_standard_name_status import are_standard_names_enabled
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ def generate_required_mg_charts_list(profile):
52
+ # pylint: disable=too-many-branches
53
+ # pylint: disable=too-many-statements
54
+ """Obtain a list of dictionaries containing the title string and base
55
+ variable name for each required chart"""
56
+
57
+ # Obtain the system-level enable_standard_names setting
58
+ enable_standard_names = are_standard_names_enabled()
59
+
60
+ required_charts = []
61
+
62
+ charts_of_interest = [
63
+ profile.plotMGAcquisitionAGDOverTime,
64
+ ]
65
+ if enable_standard_names:
66
+ charts_of_interest.append(profile.plotMGStandardAcquisitionAGDOverTime)
67
+
68
+ if any(charts_of_interest):
69
+ keys = list(dict(profile.TIME_PERIOD).keys())
70
+ values = list(dict(profile.TIME_PERIOD).values())
71
+ time_period = (values[keys.index(profile.plotMGOverTimePeriod)]).lower()
72
+
73
+ if profile.plotMGacquisitionFreq:
74
+ required_charts.append(
75
+ {
76
+ "title": "Chart of acquisition protocol frequency",
77
+ "var_name": "acquisitionFrequency",
78
+ }
79
+ )
80
+
81
+ if profile.plotMGaverageAGD:
82
+ if profile.plotMean:
83
+ required_charts.append(
84
+ {
85
+ "title": "Chart of acquisition protocol mean AGD",
86
+ "var_name": "acquisitionMeanAGD",
87
+ }
88
+ )
89
+ if profile.plotMedian:
90
+ required_charts.append(
91
+ {
92
+ "title": "Chart of acquisition protocol median AGD",
93
+ "var_name": "acquisitionMedianAGD",
94
+ }
95
+ )
96
+ if profile.plotBoxplots:
97
+ required_charts.append(
98
+ {
99
+ "title": "Boxplot of acquisition protocol AGD",
100
+ "var_name": "acquisitionBoxplotAGD",
101
+ }
102
+ )
103
+ if profile.plotHistograms:
104
+ required_charts.append(
105
+ {
106
+ "title": "Histogram of acquisition protocol AGD",
107
+ "var_name": "acquisitionHistogramAGD",
108
+ }
109
+ )
110
+
111
+ if profile.plotMGAGDvsThickness:
112
+ required_charts.append(
113
+ {
114
+ "title": "Chart of acquisition protocol AGD vs compressed breast thickness",
115
+ "var_name": "acquisitionScatterAGDvsThick",
116
+ }
117
+ )
118
+
119
+ if profile.plotMGaverageAGDvsThickness:
120
+ if profile.plotMean:
121
+ required_charts.append(
122
+ {
123
+ "title": "Chart of acquisition protocol mean AGD vs compressed breast thickness",
124
+ "var_name": "acquisitionMeanAGDvsThick",
125
+ }
126
+ )
127
+ if profile.plotMedian:
128
+ required_charts.append(
129
+ {
130
+ "title": "Chart of acquisition protocol median AGD vs compressed breast thickness",
131
+ "var_name": "acquisitionMedianAGDvsThick",
132
+ }
133
+ )
134
+
135
+ if profile.plotMGAcquisitionAGDOverTime:
136
+ if profile.plotMean:
137
+ required_charts.append(
138
+ {
139
+ "title": "Chart of acquisition protocol mean AGD over time ("
140
+ + time_period
141
+ + ")",
142
+ "var_name": "acquisitionMeanAGDOverTime",
143
+ }
144
+ )
145
+ if profile.plotMedian:
146
+ required_charts.append(
147
+ {
148
+ "title": "Chart of acquisition protocol median AGD over time ("
149
+ + time_period
150
+ + ")",
151
+ "var_name": "acquisitionMedianAGDOverTime",
152
+ }
153
+ )
154
+
155
+ if profile.plotMGkVpvsThickness:
156
+ required_charts.append(
157
+ {
158
+ "title": "Chart of acquisition protocol kVp vs compressed breast thickness",
159
+ "var_name": "acquisitionScatterkVpvsThick",
160
+ }
161
+ )
162
+
163
+ if profile.plotMGmAsvsThickness:
164
+ required_charts.append(
165
+ {
166
+ "title": "Chart of acquisition protocol mAs vs compressed breast thickness",
167
+ "var_name": "acquisitionScattermAsvsThick",
168
+ }
169
+ )
170
+
171
+ if profile.plotMGStudyPerDayAndHour:
172
+ required_charts.append(
173
+ {
174
+ "title": "Chart of study description workload",
175
+ "var_name": "studyWorkload",
176
+ }
177
+ )
178
+
179
+ if enable_standard_names:
180
+ if profile.plotMGStandardAcquisitionFreq:
181
+ required_charts.append(
182
+ {
183
+ "title": "Chart of standard acquisition name frequency",
184
+ "var_name": "standardAcquisitionFrequency",
185
+ }
186
+ )
187
+
188
+ if profile.plotMGStandardAverageAGD:
189
+ if profile.plotMean:
190
+ required_charts.append(
191
+ {
192
+ "title": "Chart of standard acquisition name mean AGD",
193
+ "var_name": "standardAcquisitionMeanAGD",
194
+ }
195
+ )
196
+ if profile.plotMedian:
197
+ required_charts.append(
198
+ {
199
+ "title": "Chart of standard acquisition name median AGD",
200
+ "var_name": "standardAcquisitionMedianAGD",
201
+ }
202
+ )
203
+ if profile.plotBoxplots:
204
+ required_charts.append(
205
+ {
206
+ "title": "Boxplot of standard acquisition name AGD",
207
+ "var_name": "standardAcquisitionBoxplotAGD",
208
+ }
209
+ )
210
+ if profile.plotHistograms:
211
+ required_charts.append(
212
+ {
213
+ "title": "Histogram of standard acquisition name AGD",
214
+ "var_name": "standardAcquisitionHistogramAGD",
215
+ }
216
+ )
217
+
218
+ if profile.plotMGStandardAGDvsThickness:
219
+ required_charts.append(
220
+ {
221
+ "title": "Chart of standard acquisition name AGD vs compressed breast thickness",
222
+ "var_name": "standardAcquisitionScatterAGDvsThick",
223
+ }
224
+ )
225
+
226
+ if profile.plotMGStandardAverageAGDvsThickness:
227
+ if profile.plotMean:
228
+ required_charts.append(
229
+ {
230
+ "title": "Chart of standard acquisition name mean AGD vs compressed breast thickness",
231
+ "var_name": "standardAcquisitionMeanAGDvsThick",
232
+ }
233
+ )
234
+ if profile.plotMedian:
235
+ required_charts.append(
236
+ {
237
+ "title": "Chart of standard acquisition name median AGD vs compressed breast thickness",
238
+ "var_name": "standardAcquisitionMedianAGDvsThick",
239
+ }
240
+ )
241
+
242
+ if profile.plotMGStandardAcquisitionAGDOverTime:
243
+ if profile.plotMean:
244
+ required_charts.append(
245
+ {
246
+ "title": "Chart of standard acquisition name mean AGD over time ("
247
+ + time_period
248
+ + ")",
249
+ "var_name": "standardAcquisitionMeanAGDOverTime",
250
+ }
251
+ )
252
+ if profile.plotMedian:
253
+ required_charts.append(
254
+ {
255
+ "title": "Chart of standard acquisition name median AGD over time ("
256
+ + time_period
257
+ + ")",
258
+ "var_name": "standardAcquisitionMedianAGDOverTime",
259
+ }
260
+ )
261
+
262
+ if profile.plotMGStandardkVpvsThickness:
263
+ required_charts.append(
264
+ {
265
+ "title": "Chart of standard acquisition name kVp vs compressed breast thickness",
266
+ "var_name": "standardAcquisitionScatterkVpvsThick",
267
+ }
268
+ )
269
+
270
+ if profile.plotMGStandardmAsvsThickness:
271
+ required_charts.append(
272
+ {
273
+ "title": "Chart of standard acquisition name mAs vs compressed breast thickness",
274
+ "var_name": "standardAcquisitionScattermAsvsThick",
275
+ }
276
+ )
277
+
278
+ if profile.plotMGStandardStudyPerDayAndHour:
279
+ required_charts.append(
280
+ {
281
+ "title": "Chart of standard study name workload",
282
+ "var_name": "standardStudyWorkload",
283
+ }
284
+ )
285
+
286
+ return required_charts
287
+
288
+
289
+ @login_required
290
+ def mg_summary_chart_data(request):
291
+ """Obtain data for mammography chart data Ajax view"""
292
+
293
+ # Obtain the system-level enable_standard_names setting
294
+ enable_standard_names = are_standard_names_enabled()
295
+
296
+ if request.user.groups.filter(name="pidgroup"):
297
+ if enable_standard_names:
298
+ f = MGFilterPlusPidPlusStdNames(
299
+ request.GET,
300
+ queryset=GeneralStudyModuleAttr.objects.filter(
301
+ modality_type__exact="MG"
302
+ )
303
+ .order_by()
304
+ .distinct(),
305
+ )
306
+ else:
307
+ f = MGFilterPlusPid(
308
+ request.GET,
309
+ queryset=GeneralStudyModuleAttr.objects.filter(
310
+ modality_type__exact="MG"
311
+ )
312
+ .order_by()
313
+ .distinct(),
314
+ )
315
+ else:
316
+ if enable_standard_names:
317
+ f = MGFilterPlusStdNames(
318
+ request.GET,
319
+ queryset=GeneralStudyModuleAttr.objects.filter(
320
+ modality_type__exact="MG"
321
+ )
322
+ .order_by()
323
+ .distinct(),
324
+ )
325
+ else:
326
+ f = MGSummaryListFilter(
327
+ request.GET,
328
+ queryset=GeneralStudyModuleAttr.objects.filter(
329
+ modality_type__exact="MG"
330
+ )
331
+ .order_by()
332
+ .distinct(),
333
+ )
334
+ try:
335
+ # See if the user has plot settings in userprofile
336
+ user_profile = request.user.userprofile
337
+ except ObjectDoesNotExist:
338
+ # Create a default userprofile for the user if one doesn't exist
339
+ create_user_profile(sender=request.user, instance=request.user, created=True)
340
+ user_profile = request.user.userprofile
341
+
342
+ if settings.DEBUG:
343
+ start_time = datetime.now()
344
+
345
+ return_structure = mg_plot_calculations(f, user_profile)
346
+
347
+ if settings.DEBUG:
348
+ logger.debug(f"Elapsed time is {datetime.now() - start_time}")
349
+
350
+ return JsonResponse(return_structure, safe=False)
351
+
352
+
353
+ def mg_plot_calculations(f, user_profile, return_as_dict=False):
354
+ # pylint: disable=too-many-locals
355
+ # pylint: disable=too-many-branches
356
+ # pylint: disable=too-many-statements
357
+ """Calculations for mammography charts"""
358
+ # Return an empty structure if the queryset is empty
359
+ if not f.qs.exists():
360
+ return {}
361
+
362
+ # Obtain the system-level enable_standard_names setting
363
+ enable_standard_names = are_standard_names_enabled()
364
+
365
+ # Set the Plotly chart theme
366
+ plotly_set_default_theme(user_profile.plotThemeChoice)
367
+
368
+ return_structure = {}
369
+
370
+ average_choices = []
371
+ if user_profile.plotMean:
372
+ average_choices.append("mean")
373
+ if user_profile.plotMedian:
374
+ average_choices.append("median")
375
+
376
+ charts_of_interest = [user_profile.plotMGAcquisitionAGDOverTime]
377
+ if enable_standard_names:
378
+ charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
379
+ if any(charts_of_interest):
380
+ plot_timeunit_period = user_profile.plotMGOverTimePeriod
381
+
382
+ #######################################################################
383
+ # Prepare acquisition-level Pandas DataFrame to use for charts
384
+ charts_of_interest = [
385
+ user_profile.plotMGAGDvsThickness,
386
+ user_profile.plotMGkVpvsThickness,
387
+ user_profile.plotMGmAsvsThickness,
388
+ user_profile.plotMGaverageAGDvsThickness,
389
+ user_profile.plotMGaverageAGD,
390
+ user_profile.plotMGacquisitionFreq,
391
+ user_profile.plotMGAcquisitionAGDOverTime,
392
+ ]
393
+ if enable_standard_names:
394
+ charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
395
+ charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
396
+ charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
397
+ charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
398
+ charts_of_interest.append(user_profile.plotMGStandardAverageAGD)
399
+ charts_of_interest.append(user_profile.plotMGStandardAcquisitionFreq)
400
+ charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
401
+
402
+ if any(charts_of_interest): # pylint: disable=too-many-nested-blocks
403
+
404
+ name_fields = []
405
+ charts_of_interest = [
406
+ user_profile.plotMGAGDvsThickness,
407
+ user_profile.plotMGkVpvsThickness,
408
+ user_profile.plotMGmAsvsThickness,
409
+ user_profile.plotMGaverageAGDvsThickness,
410
+ user_profile.plotMGaverageAGD,
411
+ user_profile.plotMGacquisitionFreq,
412
+ user_profile.plotMGAcquisitionAGDOverTime,
413
+ ]
414
+ if any(charts_of_interest):
415
+ name_fields.append(
416
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
417
+ )
418
+ name_fields.append(
419
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
420
+ )
421
+ name_fields.append(
422
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
423
+ )
424
+
425
+ if enable_standard_names:
426
+ charts_of_interest = [
427
+ user_profile.plotMGStandardAGDvsThickness,
428
+ user_profile.plotMGStandardkVpvsThickness,
429
+ user_profile.plotMGStandardmAsvsThickness,
430
+ user_profile.plotMGStandardAverageAGDvsThickness,
431
+ user_profile.plotMGStandardAverageAGD,
432
+ user_profile.plotMGStandardAcquisitionFreq,
433
+ user_profile.plotMGStandardAcquisitionAGDOverTime,
434
+ ]
435
+ if any(charts_of_interest):
436
+ name_fields.append(
437
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
438
+ )
439
+
440
+ value_fields = []
441
+ value_multipliers = []
442
+ charts_of_interest = [
443
+ user_profile.plotMGAGDvsThickness,
444
+ user_profile.plotMGaverageAGDvsThickness,
445
+ user_profile.plotMGaverageAGD,
446
+ user_profile.plotMGAcquisitionAGDOverTime,
447
+ ]
448
+ if enable_standard_names:
449
+ charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
450
+ charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
451
+ charts_of_interest.append(user_profile.plotMGStandardAverageAGD)
452
+ charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
453
+ if any(charts_of_interest):
454
+ value_fields.append(
455
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose"
456
+ )
457
+ value_multipliers.append(1)
458
+
459
+ charts_of_interest = [user_profile.plotMGkVpvsThickness]
460
+ if enable_standard_names:
461
+ charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
462
+ if any(charts_of_interest):
463
+ value_fields.append(
464
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp"
465
+ )
466
+ value_multipliers.append(1)
467
+
468
+ charts_of_interest = [user_profile.plotMGmAsvsThickness]
469
+ if enable_standard_names:
470
+ charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
471
+ if any(charts_of_interest):
472
+ value_fields.append(
473
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure"
474
+ )
475
+ value_multipliers.append(0.001)
476
+
477
+ charts_of_interest = [
478
+ user_profile.plotMGAGDvsThickness,
479
+ user_profile.plotMGkVpvsThickness,
480
+ user_profile.plotMGmAsvsThickness,
481
+ user_profile.plotMGaverageAGDvsThickness,
482
+ ]
483
+ if enable_standard_names:
484
+ charts_of_interest.append(user_profile.plotMGStandardAGDvsThickness)
485
+ charts_of_interest.append(user_profile.plotMGStandardkVpvsThickness)
486
+ charts_of_interest.append(user_profile.plotMGStandardmAsvsThickness)
487
+ charts_of_interest.append(user_profile.plotMGStandardAverageAGDvsThickness)
488
+ if any(charts_of_interest):
489
+ value_fields.append(
490
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness"
491
+ )
492
+ value_multipliers.append(1)
493
+
494
+ date_fields = []
495
+ charts_of_interest = [user_profile.plotMGAcquisitionAGDOverTime]
496
+ if enable_standard_names:
497
+ charts_of_interest.append(user_profile.plotMGStandardAcquisitionAGDOverTime)
498
+ if any(charts_of_interest):
499
+ date_fields.append("study_date")
500
+
501
+ time_fields = []
502
+
503
+ system_field = []
504
+ if user_profile.plotSeriesPerSystem:
505
+ system_field.append(
506
+ "generalequipmentmoduleattr__unique_equipment_name_id__display_name"
507
+ )
508
+
509
+ fields = {
510
+ "names": name_fields,
511
+ "values": value_fields,
512
+ "dates": date_fields,
513
+ "times": time_fields,
514
+ "system": system_field,
515
+ }
516
+
517
+ df = create_dataframe(
518
+ f.qs,
519
+ fields,
520
+ data_point_name_lowercase=user_profile.plotCaseInsensitiveCategories,
521
+ data_point_name_remove_whitespace_padding=user_profile.plotRemoveCategoryWhitespacePadding,
522
+ data_point_value_multipliers=value_multipliers,
523
+ char_wrap=user_profile.plotLabelCharWrap,
524
+ uid="projectionxrayradiationdose__irradeventxraydata__pk",
525
+ )
526
+
527
+ #######################################################################
528
+ # Addressing issue 1050 (https://bitbucket.org/openrem/openrem/issues/1050)
529
+ # Filter data frame using acquisition protocol name, min / max compression thickness,
530
+ # standard acquisition name, image view code, and exposure control mode filters.
531
+ if (
532
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_min"
533
+ in f.data
534
+ ):
535
+ if f.data[
536
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_min"
537
+ ]:
538
+ df = df[
539
+ df[
540
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness"
541
+ ]
542
+ >= float(
543
+ f.data[
544
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_min"
545
+ ]
546
+ )
547
+ ]
548
+
549
+ if (
550
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_max"
551
+ in f.data
552
+ ):
553
+ if f.data[
554
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_max"
555
+ ]:
556
+ df = df[
557
+ df[
558
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness"
559
+ ]
560
+ <= float(
561
+ f.data[
562
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness__range_max"
563
+ ]
564
+ )
565
+ ]
566
+
567
+ if (
568
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
569
+ in f.data
570
+ ):
571
+ if f.data[
572
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
573
+ ]:
574
+ df[
575
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
576
+ ] = df[
577
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
578
+ ].astype(
579
+ "object"
580
+ )
581
+ df = df[
582
+ df[
583
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
584
+ ].str.contains(
585
+ f.data[
586
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
587
+ ],
588
+ case=False,
589
+ )
590
+ ]
591
+ df[
592
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
593
+ ] = df[
594
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
595
+ ].astype(
596
+ "category"
597
+ )
598
+
599
+ if (
600
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
601
+ in f.data
602
+ ):
603
+ if f.data[
604
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
605
+ ]:
606
+ df[
607
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
608
+ ] = df[
609
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
610
+ ].astype(
611
+ "object"
612
+ )
613
+ df = df[
614
+ df[
615
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
616
+ ].str.contains(
617
+ f.data[
618
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
619
+ ],
620
+ case=False,
621
+ )
622
+ ]
623
+ df[
624
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
625
+ ] = df[
626
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
627
+ ].astype(
628
+ "category"
629
+ )
630
+
631
+ if (
632
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
633
+ in f.data
634
+ ):
635
+ if f.data[
636
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
637
+ ]:
638
+ df[
639
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
640
+ ] = df[
641
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
642
+ ].astype(
643
+ "object"
644
+ )
645
+ df = df[
646
+ df[
647
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
648
+ ].str.contains(
649
+ f.data[
650
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
651
+ ],
652
+ case=False,
653
+ )
654
+ ]
655
+ df[
656
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
657
+ ] = df[
658
+ "projectionxrayradiationdose__irradeventxraydata__image_view__code_meaning"
659
+ ].astype(
660
+ "category"
661
+ )
662
+
663
+ if (
664
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
665
+ in f.data
666
+ ):
667
+ if f.data[
668
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
669
+ ]:
670
+ df[
671
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
672
+ ] = df[
673
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
674
+ ].astype(
675
+ "object"
676
+ )
677
+ df = df[
678
+ df[
679
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
680
+ ].str.contains(
681
+ f.data[
682
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
683
+ ],
684
+ case=False,
685
+ )
686
+ ]
687
+ df[
688
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
689
+ ] = df[
690
+ "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure_control_mode"
691
+ ].astype(
692
+ "category"
693
+ )
694
+ #######################################################################
695
+
696
+ if user_profile.plotMGaverageAGDvsThickness or user_profile.plotMGaverageAGD:
697
+
698
+ if user_profile.plotBoxplots and "median" not in average_choices:
699
+ average_choices = average_choices + ["median"]
700
+
701
+ name_field = (
702
+ "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol"
703
+ )
704
+ value_field = "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose" # pylint: disable=line-too-long
705
+
706
+ if user_profile.plotMGaverageAGDvsThickness:
707
+ category_names_col = name_field
708
+ group_by_col = "x_ray_system_name"
709
+ legend_title = "Acquisition protocol"
710
+
711
+ if user_profile.plotGroupingChoice == "series":
712
+ category_names_col = "x_ray_system_name"
713
+ group_by_col = name_field
714
+ legend_title = "System"
715
+
716
+ parameter_dict = { # pylint: disable=line-too-long
717
+ "df_x_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
718
+ "df_y_value_col": value_field, # pylint: disable=line-too-long
719
+ "x_axis_title": "Compressed breast thickness (mm)",
720
+ "y_axis_title": "AGD (mGy)",
721
+ "df_category_col": category_names_col,
722
+ "df_facet_col": group_by_col,
723
+ "facet_title": legend_title,
724
+ "user_bins": [20, 30, 40, 50, 60, 70, 80, 90],
725
+ "colourmap": user_profile.plotColourMapChoice,
726
+ "filename": "OpenREM CT acquisition protocol average AGD vs thickness",
727
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
728
+ "sorting_choice": [
729
+ user_profile.plotInitialSortingDirection,
730
+ user_profile.plotMGInitialSortingChoice,
731
+ ],
732
+ "return_as_dict": return_as_dict,
733
+ }
734
+ if user_profile.plotMean:
735
+ parameter_dict["stat_name"] = "mean"
736
+ return_structure["meanAGDvsThickness"] = (
737
+ plotly_binned_statistic_barchart(
738
+ df,
739
+ parameter_dict,
740
+ )
741
+ )
742
+
743
+ if user_profile.plotMedian:
744
+ parameter_dict["stat_name"] = "median"
745
+ return_structure["medianAGDvsThickness"] = (
746
+ plotly_binned_statistic_barchart(
747
+ df,
748
+ parameter_dict,
749
+ )
750
+ )
751
+
752
+ if user_profile.plotMGaverageAGD:
753
+ value_text = "AGD"
754
+ units_text = "(mGy)"
755
+ name_text = "Acquisition protocol"
756
+ variable_name_start = "acquisition"
757
+ variable_value_name = "AGD"
758
+ modality_text = "CT"
759
+ chart_message = ""
760
+
761
+ new_charts = generate_average_chart_group(
762
+ average_choices,
763
+ chart_message,
764
+ df,
765
+ modality_text,
766
+ name_field,
767
+ name_text,
768
+ return_as_dict,
769
+ return_structure,
770
+ units_text,
771
+ user_profile,
772
+ value_field,
773
+ value_text,
774
+ variable_name_start,
775
+ variable_value_name,
776
+ user_profile.plotMGInitialSortingChoice,
777
+ )
778
+
779
+ return_structure = {**return_structure, **new_charts}
780
+
781
+ if user_profile.plotMGAGDvsThickness:
782
+ parameter_dict = { # pylint: disable=line-too-long
783
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
784
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
785
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
786
+ "sorting_choice": [
787
+ user_profile.plotInitialSortingDirection,
788
+ user_profile.plotMGInitialSortingChoice,
789
+ ],
790
+ "grouping_choice": user_profile.plotGroupingChoice,
791
+ "legend_title": "Acquisition protocol",
792
+ "colourmap": user_profile.plotColourMapChoice,
793
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
794
+ "x_axis_title": "Compressed breast thickness (mm)",
795
+ "y_axis_title": "AGD (mGy)",
796
+ "filename": "OpenREM CT acquisition protocol AGD vs thickness",
797
+ "return_as_dict": return_as_dict,
798
+ }
799
+ return_structure["AGDvsThickness"] = (
800
+ plotly_scatter( # pylint: disable=line-too-long
801
+ df,
802
+ parameter_dict,
803
+ )
804
+ )
805
+
806
+ if user_profile.plotMGkVpvsThickness:
807
+ parameter_dict = { # pylint: disable=line-too-long
808
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
809
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
810
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp", # pylint: disable=line-too-long
811
+ "sorting_choice": [
812
+ user_profile.plotInitialSortingDirection,
813
+ user_profile.plotMGInitialSortingChoice,
814
+ ],
815
+ "grouping_choice": user_profile.plotGroupingChoice,
816
+ "legend_title": "Acquisition protocol",
817
+ "colourmap": user_profile.plotColourMapChoice,
818
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
819
+ "x_axis_title": "Compressed breast thickness (mm)",
820
+ "y_axis_title": "kVp",
821
+ "filename": "OpenREM CT acquisition protocol kVp vs thickness",
822
+ "return_as_dict": return_as_dict,
823
+ }
824
+ return_structure["kVpvsThickness"] = (
825
+ plotly_scatter( # pylint: disable=line-too-long
826
+ df,
827
+ parameter_dict,
828
+ )
829
+ )
830
+
831
+ if user_profile.plotMGmAsvsThickness:
832
+ parameter_dict = { # pylint: disable=line-too-long
833
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
834
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
835
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure", # pylint: disable=line-too-long
836
+ "sorting_choice": [
837
+ user_profile.plotInitialSortingDirection,
838
+ user_profile.plotMGInitialSortingChoice,
839
+ ],
840
+ "grouping_choice": user_profile.plotGroupingChoice,
841
+ "legend_title": "Acquisition protocol",
842
+ "colourmap": user_profile.plotColourMapChoice,
843
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
844
+ "x_axis_title": "Compressed breast thickness (mm)",
845
+ "y_axis_title": "mAs",
846
+ "filename": "OpenREM CT acquisition protocol mAs vs thickness",
847
+ "return_as_dict": return_as_dict,
848
+ }
849
+ return_structure["mAsvsThickness"] = (
850
+ plotly_scatter( # pylint: disable=line-too-long
851
+ df,
852
+ parameter_dict,
853
+ )
854
+ )
855
+
856
+ if user_profile.plotMGacquisitionFreq:
857
+ parameter_dict = {
858
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
859
+ "sorting_choice": [
860
+ user_profile.plotInitialSortingDirection,
861
+ user_profile.plotMGInitialSortingChoice,
862
+ ],
863
+ "legend_title": "Acquisition",
864
+ "df_x_axis_col": "x_ray_system_name",
865
+ "x_axis_title": "System",
866
+ "grouping_choice": user_profile.plotGroupingChoice,
867
+ "colourmap": user_profile.plotColourMapChoice,
868
+ "filename": "OpenREM MG acquisition protocol frequency",
869
+ "groupby_cols": None,
870
+ "facet_col": None,
871
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
872
+ "return_as_dict": return_as_dict,
873
+ }
874
+ (
875
+ return_structure["acquisitionFrequencyData"],
876
+ return_structure["acquisitionFrequencyDataCSV"],
877
+ ) = plotly_frequency_barchart( # pylint: disable=line-too-long
878
+ df, parameter_dict, csv_name="acquisitionFrequencyData.csv"
879
+ )
880
+
881
+ if user_profile.plotMGAcquisitionAGDOverTime:
882
+ facet_title = "System"
883
+
884
+ if user_profile.plotGroupingChoice == "series":
885
+ facet_title = "Acquisition protocol"
886
+
887
+ parameter_dict = { # pylint: disable=line-too-long
888
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__acquisition_protocol",
889
+ "df_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
890
+ "df_date_col": "study_date",
891
+ "name_title": "Acquisition protocol",
892
+ "value_title": "AGD (mGy)",
893
+ "date_title": "Study date",
894
+ "facet_title": facet_title,
895
+ "sorting_choice": [
896
+ user_profile.plotInitialSortingDirection,
897
+ user_profile.plotMGInitialSortingChoice,
898
+ ],
899
+ "time_period": plot_timeunit_period,
900
+ "average_choices": average_choices + ["count"],
901
+ "grouping_choice": user_profile.plotGroupingChoice,
902
+ "colourmap": user_profile.plotColourMapChoice,
903
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
904
+ "filename": "OpenREM MG acquisition protocol AGD over time",
905
+ "return_as_dict": return_as_dict,
906
+ }
907
+ result = construct_over_time_charts(
908
+ df,
909
+ parameter_dict,
910
+ )
911
+
912
+ if user_profile.plotMean:
913
+ return_structure["acquisitionMeanAGDOverTime"] = result["mean"]
914
+ if user_profile.plotMedian:
915
+ return_structure["acquisitionMedianAGDOverTime"] = result["median"]
916
+
917
+ if enable_standard_names:
918
+ charts_of_interest = [
919
+ user_profile.plotMGStandardAGDvsThickness,
920
+ user_profile.plotMGStandardkVpvsThickness,
921
+ user_profile.plotMGStandardmAsvsThickness,
922
+ user_profile.plotMGStandardAverageAGDvsThickness,
923
+ user_profile.plotMGStandardAverageAGD,
924
+ user_profile.plotMGStandardAcquisitionFreq,
925
+ user_profile.plotMGStandardAcquisitionAGDOverTime,
926
+ ]
927
+
928
+ if any(charts_of_interest):
929
+ # Exclude "Blank" and "blank" standard_acquisition_name data
930
+ df_without_blanks = df[
931
+ (
932
+ df[
933
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
934
+ ]
935
+ != "blank"
936
+ )
937
+ & (
938
+ df[
939
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
940
+ ]
941
+ != "Blank"
942
+ )
943
+ ].copy()
944
+ # Remove any unused categories (this will include "Blank" or "blank")
945
+ df_without_blanks[
946
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
947
+ ] = df_without_blanks[
948
+ "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
949
+ ].cat.remove_unused_categories()
950
+
951
+ if (
952
+ user_profile.plotMGStandardAverageAGDvsThickness
953
+ or user_profile.plotMGStandardAverageAGD
954
+ ):
955
+
956
+ if user_profile.plotBoxplots and "median" not in average_choices:
957
+ average_choices = average_choices + ["median"]
958
+
959
+ name_field = "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name"
960
+ value_field = "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose" # pylint: disable=line-too-long
961
+
962
+ if user_profile.plotMGStandardAverageAGDvsThickness:
963
+ category_names_col = name_field
964
+ group_by_col = "x_ray_system_name"
965
+ legend_title = "Standard acquisition name"
966
+
967
+ if user_profile.plotGroupingChoice == "series":
968
+ category_names_col = "x_ray_system_name"
969
+ group_by_col = name_field
970
+ legend_title = "System"
971
+
972
+ parameter_dict = { # pylint: disable=line-too-long
973
+ "df_x_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
974
+ "df_y_value_col": value_field, # pylint: disable=line-too-long
975
+ "x_axis_title": "Compressed breast thickness (mm)",
976
+ "y_axis_title": "AGD (mGy)",
977
+ "df_category_col": category_names_col,
978
+ "df_facet_col": group_by_col,
979
+ "facet_title": legend_title,
980
+ "user_bins": [20, 30, 40, 50, 60, 70, 80, 90],
981
+ "colourmap": user_profile.plotColourMapChoice,
982
+ "filename": "OpenREM CT standard acquisition name average AGD vs thickness",
983
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
984
+ "sorting_choice": [
985
+ user_profile.plotInitialSortingDirection,
986
+ user_profile.plotMGInitialSortingChoice,
987
+ ],
988
+ "return_as_dict": return_as_dict,
989
+ }
990
+ if user_profile.plotMean:
991
+ parameter_dict["stat_name"] = "mean"
992
+ return_structure["standardMeanAGDvsThickness"] = (
993
+ plotly_binned_statistic_barchart(
994
+ df_without_blanks,
995
+ parameter_dict,
996
+ )
997
+ )
998
+
999
+ if user_profile.plotMedian:
1000
+ parameter_dict["stat_name"] = "median"
1001
+ return_structure["standardMedianAGDvsThickness"] = (
1002
+ plotly_binned_statistic_barchart(
1003
+ df_without_blanks,
1004
+ parameter_dict,
1005
+ )
1006
+ )
1007
+
1008
+ if user_profile.plotMGStandardAverageAGD:
1009
+ value_text = "AGD"
1010
+ units_text = "(mGy)"
1011
+ name_text = "Standard acquisition name"
1012
+ variable_name_start = "standardAcquisition"
1013
+ variable_value_name = "AGD"
1014
+ modality_text = "CT"
1015
+ chart_message = ""
1016
+
1017
+ new_charts = generate_average_chart_group(
1018
+ average_choices,
1019
+ chart_message,
1020
+ df_without_blanks,
1021
+ modality_text,
1022
+ name_field,
1023
+ name_text,
1024
+ return_as_dict,
1025
+ return_structure,
1026
+ units_text,
1027
+ user_profile,
1028
+ value_field,
1029
+ value_text,
1030
+ variable_name_start,
1031
+ variable_value_name,
1032
+ user_profile.plotMGInitialSortingChoice,
1033
+ )
1034
+
1035
+ return_structure = {**return_structure, **new_charts}
1036
+
1037
+ if user_profile.plotMGStandardAGDvsThickness:
1038
+ parameter_dict = { # pylint: disable=line-too-long
1039
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
1040
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
1041
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
1042
+ "sorting_choice": [
1043
+ user_profile.plotInitialSortingDirection,
1044
+ user_profile.plotMGInitialSortingChoice,
1045
+ ],
1046
+ "grouping_choice": user_profile.plotGroupingChoice,
1047
+ "legend_title": "Standard acquisition name",
1048
+ "colourmap": user_profile.plotColourMapChoice,
1049
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
1050
+ "x_axis_title": "Compressed breast thickness (mm)",
1051
+ "y_axis_title": "AGD (mGy)",
1052
+ "filename": "OpenREM CT standard acquisition name AGD vs thickness",
1053
+ "return_as_dict": return_as_dict,
1054
+ }
1055
+ return_structure["standardAGDvsThickness"] = (
1056
+ plotly_scatter( # pylint: disable=line-too-long
1057
+ df_without_blanks,
1058
+ parameter_dict,
1059
+ )
1060
+ )
1061
+
1062
+ if user_profile.plotMGStandardkVpvsThickness:
1063
+ parameter_dict = { # pylint: disable=line-too-long
1064
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
1065
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
1066
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__kvp__kvp", # pylint: disable=line-too-long
1067
+ "sorting_choice": [
1068
+ user_profile.plotInitialSortingDirection,
1069
+ user_profile.plotMGInitialSortingChoice,
1070
+ ],
1071
+ "grouping_choice": user_profile.plotGroupingChoice,
1072
+ "legend_title": "Standard acquisition name",
1073
+ "colourmap": user_profile.plotColourMapChoice,
1074
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
1075
+ "x_axis_title": "Compressed breast thickness (mm)",
1076
+ "y_axis_title": "kVp",
1077
+ "filename": "OpenREM CT standard acquisition name kVp vs thickness",
1078
+ "return_as_dict": return_as_dict,
1079
+ }
1080
+ return_structure["standardkVpvsThickness"] = (
1081
+ plotly_scatter( # pylint: disable=line-too-long
1082
+ df_without_blanks,
1083
+ parameter_dict,
1084
+ )
1085
+ )
1086
+
1087
+ if user_profile.plotMGStandardmAsvsThickness:
1088
+ parameter_dict = { # pylint: disable=line-too-long
1089
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
1090
+ "df_x_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraymechanicaldata__compression_thickness", # pylint: disable=line-too-long
1091
+ "df_y_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__exposure__exposure", # pylint: disable=line-too-long
1092
+ "sorting_choice": [
1093
+ user_profile.plotInitialSortingDirection,
1094
+ user_profile.plotMGInitialSortingChoice,
1095
+ ],
1096
+ "grouping_choice": user_profile.plotGroupingChoice,
1097
+ "legend_title": "Standard acquisition name",
1098
+ "colourmap": user_profile.plotColourMapChoice,
1099
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
1100
+ "x_axis_title": "Compressed breast thickness (mm)",
1101
+ "y_axis_title": "mAs",
1102
+ "filename": "OpenREM CT standard acquisition name mAs vs thickness",
1103
+ "return_as_dict": return_as_dict,
1104
+ }
1105
+ return_structure["standardmAsvsThickness"] = (
1106
+ plotly_scatter( # pylint: disable=line-too-long
1107
+ df_without_blanks,
1108
+ parameter_dict,
1109
+ )
1110
+ )
1111
+
1112
+ if user_profile.plotMGStandardAcquisitionFreq:
1113
+ parameter_dict = {
1114
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
1115
+ "sorting_choice": [
1116
+ user_profile.plotInitialSortingDirection,
1117
+ user_profile.plotMGInitialSortingChoice,
1118
+ ],
1119
+ "legend_title": "Acquisition",
1120
+ "df_x_axis_col": "x_ray_system_name",
1121
+ "x_axis_title": "System",
1122
+ "grouping_choice": user_profile.plotGroupingChoice,
1123
+ "colourmap": user_profile.plotColourMapChoice,
1124
+ "filename": "OpenREM MG standard acquisition name frequency",
1125
+ "groupby_cols": None,
1126
+ "facet_col": None,
1127
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
1128
+ "return_as_dict": return_as_dict,
1129
+ }
1130
+ (
1131
+ return_structure["standardAcquisitionFrequencyData"],
1132
+ return_structure["standardAcquisitionFrequencyDataCSV"],
1133
+ ) = plotly_frequency_barchart( # pylint: disable=line-too-long
1134
+ df_without_blanks,
1135
+ parameter_dict,
1136
+ csv_name="standardAcquisitionFrequencyData.csv",
1137
+ )
1138
+
1139
+ if user_profile.plotMGStandardAcquisitionAGDOverTime:
1140
+ facet_title = "System"
1141
+
1142
+ if user_profile.plotGroupingChoice == "series":
1143
+ facet_title = "Standard acquisition name"
1144
+
1145
+ parameter_dict = { # pylint: disable=line-too-long
1146
+ "df_name_col": "projectionxrayradiationdose__irradeventxraydata__standard_protocols__standard_name",
1147
+ "df_value_col": "projectionxrayradiationdose__irradeventxraydata__irradeventxraysourcedata__average_glandular_dose", # pylint: disable=line-too-long
1148
+ "df_date_col": "study_date",
1149
+ "name_title": "Standard acquisition name",
1150
+ "value_title": "AGD (mGy)",
1151
+ "date_title": "Study date",
1152
+ "facet_title": facet_title,
1153
+ "sorting_choice": [
1154
+ user_profile.plotInitialSortingDirection,
1155
+ user_profile.plotMGInitialSortingChoice,
1156
+ ],
1157
+ "time_period": plot_timeunit_period,
1158
+ "average_choices": average_choices + ["count"],
1159
+ "grouping_choice": user_profile.plotGroupingChoice,
1160
+ "colourmap": user_profile.plotColourMapChoice,
1161
+ "facet_col_wrap": user_profile.plotFacetColWrapVal,
1162
+ "filename": "OpenREM MG standard acquisition name AGD over time",
1163
+ "return_as_dict": return_as_dict,
1164
+ }
1165
+ result = construct_over_time_charts(
1166
+ df_without_blanks,
1167
+ parameter_dict,
1168
+ )
1169
+
1170
+ if user_profile.plotMean:
1171
+ return_structure["standardAcquisitionMeanAGDOverTime"] = result[
1172
+ "mean"
1173
+ ]
1174
+ if user_profile.plotMedian:
1175
+ return_structure["standardAcquisitionMedianAGDOverTime"] = result[
1176
+ "median"
1177
+ ]
1178
+
1179
+ #######################################################################
1180
+ # Prepare study- and request-level Pandas DataFrame to use for charts
1181
+ charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1182
+ if enable_standard_names:
1183
+ charts_of_interest.append(user_profile.plotMGStandardStudyPerDayAndHour)
1184
+
1185
+ if any(charts_of_interest):
1186
+
1187
+ name_fields = []
1188
+ charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1189
+ if any(charts_of_interest):
1190
+ name_fields.append("study_description")
1191
+
1192
+ if enable_standard_names:
1193
+ charts_of_interest = [user_profile.plotMGStandardStudyPerDayAndHour]
1194
+ if any(charts_of_interest):
1195
+ name_fields.append("standard_names__standard_name")
1196
+
1197
+ value_fields = []
1198
+
1199
+ date_fields = []
1200
+ time_fields = []
1201
+ charts_of_interest = [user_profile.plotMGStudyPerDayAndHour]
1202
+ if enable_standard_names:
1203
+ charts_of_interest.append(user_profile.plotMGStandardStudyPerDayAndHour)
1204
+ if any(charts_of_interest):
1205
+ date_fields.append("study_date")
1206
+ time_fields.append("study_time")
1207
+
1208
+ system_field = []
1209
+ if user_profile.plotSeriesPerSystem:
1210
+ system_field.append(
1211
+ "generalequipmentmoduleattr__unique_equipment_name_id__display_name"
1212
+ )
1213
+
1214
+ fields = {
1215
+ "names": name_fields,
1216
+ "values": value_fields,
1217
+ "dates": date_fields,
1218
+ "times": time_fields,
1219
+ "system": system_field,
1220
+ }
1221
+
1222
+ # If only standard_names__standard_name is required then exclude all entries where these are None as these are
1223
+ # not required for standard name charts.
1224
+ queryset = f.qs
1225
+ if name_fields == ["standard_names__standard_name"]:
1226
+ queryset = queryset.exclude(standard_names__standard_name__isnull=True)
1227
+
1228
+ df = create_dataframe(
1229
+ queryset,
1230
+ fields,
1231
+ data_point_name_lowercase=user_profile.plotCaseInsensitiveCategories,
1232
+ data_point_name_remove_whitespace_padding=user_profile.plotRemoveCategoryWhitespacePadding,
1233
+ uid="pk",
1234
+ )
1235
+
1236
+ if user_profile.plotMGStudyPerDayAndHour:
1237
+ df_time_series_per_weekday = create_dataframe_weekdays(
1238
+ df, "study_description", df_date_col="study_date"
1239
+ )
1240
+
1241
+ return_structure["studyWorkloadData"] = plotly_barchart_weekdays(
1242
+ df_time_series_per_weekday,
1243
+ "weekday",
1244
+ "study_description",
1245
+ name_axis_title="Weekday",
1246
+ value_axis_title="Frequency",
1247
+ colourmap=user_profile.plotColourMapChoice,
1248
+ filename="OpenREM CT study description workload",
1249
+ facet_col_wrap=user_profile.plotFacetColWrapVal,
1250
+ sorting_choice=[
1251
+ user_profile.plotInitialSortingDirection,
1252
+ user_profile.plotMGInitialSortingChoice,
1253
+ ],
1254
+ return_as_dict=return_as_dict,
1255
+ )
1256
+
1257
+ if enable_standard_names:
1258
+ charts_of_interest = [user_profile.plotMGStandardStudyPerDayAndHour]
1259
+
1260
+ if any(charts_of_interest):
1261
+
1262
+ # Create a standard name data frame - remove any blank standard names
1263
+ standard_name_df = df[
1264
+ (df["standard_names__standard_name"] != "blank")
1265
+ & (df["standard_names__standard_name"] != "Blank")
1266
+ ].copy()
1267
+ # Remove any unused categories (this will include "Blank" or "blank")
1268
+ standard_name_df["standard_names__standard_name"] = standard_name_df[
1269
+ "standard_names__standard_name"
1270
+ ].cat.remove_unused_categories()
1271
+
1272
+ if user_profile.plotMGStandardStudyPerDayAndHour:
1273
+ df_time_series_per_weekday = create_dataframe_weekdays(
1274
+ standard_name_df,
1275
+ "standard_names__standard_name",
1276
+ df_date_col="study_date",
1277
+ )
1278
+
1279
+ return_structure["standardStudyWorkloadData"] = (
1280
+ plotly_barchart_weekdays(
1281
+ df_time_series_per_weekday,
1282
+ "weekday",
1283
+ "standard_names__standard_name",
1284
+ name_axis_title="Weekday",
1285
+ value_axis_title="Frequency",
1286
+ colourmap=user_profile.plotColourMapChoice,
1287
+ filename="OpenREM CT standard study name workload",
1288
+ facet_col_wrap=user_profile.plotFacetColWrapVal,
1289
+ sorting_choice=[
1290
+ user_profile.plotInitialSortingDirection,
1291
+ user_profile.plotMGInitialSortingChoice,
1292
+ ],
1293
+ return_as_dict=return_as_dict,
1294
+ )
1295
+ )
1296
+
1297
+ return return_structure
1298
+
1299
+
1300
+ def mg_chart_form_processing(request, user_profile):
1301
+ # pylint: disable=too-many-statements
1302
+
1303
+ # Obtain the system-level enable_standard_names setting
1304
+ enable_standard_names = are_standard_names_enabled()
1305
+
1306
+ # Obtain the chart options from the request
1307
+ chart_options_form = None
1308
+ if enable_standard_names:
1309
+ chart_options_form = MGChartOptionsFormIncStandard(request.GET)
1310
+ else:
1311
+ chart_options_form = MGChartOptionsForm(request.GET)
1312
+
1313
+ # Check whether the form data is valid
1314
+ if chart_options_form.is_valid():
1315
+ # Use the form data if the user clicked on the submit button
1316
+ if "submit" in request.GET:
1317
+ # process the data in form.cleaned_data as required
1318
+
1319
+ set_common_chart_options(chart_options_form, user_profile)
1320
+
1321
+ set_average_chart_options(chart_options_form, user_profile)
1322
+
1323
+ set_mg_chart_options(chart_options_form, user_profile)
1324
+
1325
+ user_profile.save()
1326
+
1327
+ else:
1328
+ average_choices = required_average_choices(user_profile)
1329
+
1330
+ mg_form_data = initialise_mg_form_data(user_profile)
1331
+
1332
+ form_data = {
1333
+ "plotCharts": user_profile.plotCharts,
1334
+ "plotGrouping": user_profile.plotGroupingChoice,
1335
+ "plotSeriesPerSystem": user_profile.plotSeriesPerSystem,
1336
+ "plotHistograms": user_profile.plotHistograms,
1337
+ "plotInitialSortingDirection": user_profile.plotInitialSortingDirection,
1338
+ "plotAverageChoice": average_choices,
1339
+ }
1340
+
1341
+ form_data = {**form_data, **mg_form_data}
1342
+
1343
+ chart_options_form = None
1344
+ if enable_standard_names:
1345
+ chart_options_form = MGChartOptionsFormIncStandard(form_data)
1346
+ else:
1347
+ chart_options_form = MGChartOptionsForm(form_data)
1348
+
1349
+ return chart_options_form