diff --git a/docs/source/calibration_docs.rst b/docs/source/calibration_docs.rst index 4500e3c8..924e10f3 100644 --- a/docs/source/calibration_docs.rst +++ b/docs/source/calibration_docs.rst @@ -199,7 +199,16 @@ Equation Definitions * Ktp (Temp/Pressure correction): - .. math:: \frac{273.2+T}{273.2+22} * \frac{101.33}{P} + .. math:: \frac{273.2+T}{273.2+20} * \frac{101.33}{P} + + .. danger:: + + In v3.29 the equation was changed to have an air reference of 20C from 22C. Your absorbed dose values will be different by 0.7%. + This is in line with Table 9 + of the TRS-398 protocol. However, there is a footnote of the table that some countries use 22C. + If you still want to use 22C as the reference temperature you can now pass a new parameter: ``ref_temp``. + The default is 20C per Table 9 but can be changed to 22C or whatever your reference temperature is. + To retain old behavior, pass ``ref_temp=22``. .. warning:: Temperature is in Celsius and pressure is in kPa. Use the helper functions fahrenheit2celsius, mmHg2kPa, and mbar2kPa as needed. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 74224ab6..ee6153a2 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -10,6 +10,26 @@ Legend * :bdg-primary:`Refactor` denotes a code refactor; usually this means an efficiency boost or code cleanup. * :bdg-danger:`Change` denotes a change that may break existing code. +v 3.29.0 +-------- + +TRS-398 +^^^^^^^ + +* :bdg-danger:`Change` The air reference value for ``k_tp`` has been changed to 20C from 22C. Previously, + the reference value was assumed to be the same as AAPM TG-51, but this is incorrect per Table 9. + TRS-398 ``k_tp`` values will be different: 0.7% lower. This results in an absorbed dose increase of ~0.7% at dmax. A user warning has also been added when calling + ``k_tp`` describing this change. + + A new parameter has been added: ``ref_temp`` with a default of 20C. If you are in a country that + uses 22C as the reference temperature you can pass ``ref_temp=22`` to the ``k_tp`` function. + Also, if you want to retain the old behavior, you can pass ``ref_temp=22``. + + .. danger:: + + This change will affect absorbed dose TRS-398 calculations if you rely on the ``k_tp`` function. If you are using TRS-398, please verify that your + results are still accurate. We apologize for this oversight. + v 3.28.0 -------- diff --git a/pylinac/calibration/trs398.py b/pylinac/calibration/trs398.py index 691773ce..069a3777 100644 --- a/pylinac/calibration/trs398.py +++ b/pylinac/calibration/trs398.py @@ -1,3 +1,4 @@ +import warnings import webbrowser from abc import ABC from datetime import datetime @@ -10,7 +11,13 @@ from ..core.typing import NumberOrArray from ..core.utilities import Structure, is_close from . import tg51 as _tg51 -from .tg51 import MAX_PPOL # make available to module +from .tg51 import ( + MAX_PPOL, + MIN_TEMP, + MAX_TEMP, + MIN_PRESSURE, + MAX_PRESSURE, +) # make available to module from .tg51 import ( MAX_PELEC, MAX_PION, @@ -560,12 +567,50 @@ } # Rename common functions from TG-51 -k_tp = _tg51.p_tp k_pol = _tg51.p_pol z_ref = _tg51.d_ref r_50 = _tg51.r_50 +def k_tp(*, temp: float, press: float, ref_temp: float = 20) -> float: + """Calculate the temperature & pressure correction per TRS-398. + + .. note:: + + Per Table 9 the reference air temperature is 20°C. This is difference than AAPM TG-51. + + .. versionchanged:: 3.29 + + The reference air temperature is now 20°C from 22. The ``ref_temp`` parameter default is 20 but + can be changed via ``ref_temp``. + + Parameters + ---------- + temp : float (17-27) + The temperature in degrees Celsius. + press : float (91-111) + The value of pressure in kPa. Can be converted from mmHg and mbar; + see :func:`~pylinac.calibration.tg51.mmHg2kPa` and :func:`~pylinac.calibration.tg51.mbar2kPa`. + ref_temp: float + The reference temperature. Default is 20°C. + """ + warnings.warn( + "In pylinac v3.29 the reference air temperature was changed from 22 to 20°C to match TRS-398 protocol. This changes k_tp values down by 0.7%.", + UserWarning, + ) + argue.verify_bounds( + temp, + bounds=(MIN_TEMP, MAX_TEMP), + message="Temperature {:2.2f} out of range. Did you use Fahrenheit? Consider using the utility function fahrenheit2celsius()", + ) + argue.verify_bounds( + press, + bounds=(MIN_PRESSURE, MAX_PRESSURE), + message="Pressure {:2.2f} out of range. Did you use kPa? Consider using the utility functions mmHg2kPa() or mbar2kPa()", + ) + return ((273.2 + temp) / (273.2 + ref_temp)) * (101.33 / press) + + def k_s( *, voltage_reference: int, diff --git a/tests_basic/test_trs398.py b/tests_basic/test_trs398.py index 3b4ceb7c..e3cbfb4b 100644 --- a/tests_basic/test_trs398.py +++ b/tests_basic/test_trs398.py @@ -2,6 +2,7 @@ from unittest import TestCase from argue import BoundsError +from parameterized import parameterized from pylinac.calibration import trs398 from tests_basic.utils import save_file @@ -58,6 +59,21 @@ def test_kq_electron(self): trs398.kq_electron(chamber=model, r_50=r_50), kq, delta=0.001 ) + @parameterized.expand( + [ + (15, 101.3, 20, 0.983), + (18, 101.3, 20, 0.993), + (22, 101.3, 20, 1.007), + (26, 101.3, 20, 1.020), + (20, 110, 20, 0.921), + (22, 101.3, 22, 1.000), + ] + ) + def test_k_tp(self, temp, press, ref_temp, ktp): + self.assertAlmostEqual( + trs398.k_tp(temp=temp, press=press, ref_temp=ref_temp), ktp, delta=0.001 + ) + class TRS398Base: temp = float @@ -167,8 +183,8 @@ class MDA_TB2_2015_15x(TRS398Photon, TestCase): m_reference = 29.28 m_opposite = -29.33 m_reduced = 29.10 - dose_mu_zref = 0.779 - dose_mu_zmax = 1.007 + dose_mu_zref = 0.785 + dose_mu_zmax = 1.014 clinical_pdd_zref = 77.4 tpr2010 = 0.762 @@ -183,8 +199,8 @@ class MDA_TB1_2015_10x(TRS398Photon, TestCase): m_opposite = 27.784 m_reduced = 27.635 clinical_pdd_zref = 73.5 - dose_mu_zref = 0.734 - dose_mu_zmax = 0.998 + dose_mu_zref = 0.7386 + dose_mu_zmax = 1.005 tpr2010 = (73.42 / 73.7) * trs398.tpr2010_from_pdd2010(pdd2010=46.3 / 73.7) # open_pdf = True # print_data = True @@ -200,16 +216,16 @@ class ACB5_2011_6x(TRS398Photon, TestCase): m_reduced = 24.79 clinical_pdd_zref = 66.8 tissue_correction = 0.99 - dose_mu_zref = 0.673 - dose_mu_zmax = 1.007 + dose_mu_zref = 0.677 + dose_mu_zmax = 1.014 def test_zmax_adjusted(self): self.trs398.m_reference_adjusted = 24.65 - self.assertAlmostEqual(self.trs398.dose_mu_zmax_adjusted, 1.000, delta=0.0005) + self.assertAlmostEqual(self.trs398.dose_mu_zmax_adjusted, 1.007, delta=0.0005) def test_zref_adjusted(self): self.trs398.m_reference_adjusted = 24.65 - self.assertAlmostEqual(self.trs398.dose_mu_zref_adjusted, 0.668, delta=0.0005) + self.assertAlmostEqual(self.trs398.dose_mu_zref_adjusted, 0.6725, delta=0.0005) class ACB5_2012_6X(TRS398Photon, TestCase): @@ -222,8 +238,8 @@ class ACB5_2012_6X(TRS398Photon, TestCase): clinical_pdd_zref = 66.8 tpr2010 = trs398.tpr2010_from_pdd2010(pdd2010=38.4 / 66.8) tissue_correction = 0.99 - dose_mu_zref = 0.679 - dose_mu_zmax = 1.0159 + dose_mu_zref = 0.683 + dose_mu_zmax = 1.023 class ACB5_2012_18X(TRS398Photon, TestCase): @@ -237,8 +253,8 @@ class ACB5_2012_18X(TRS398Photon, TestCase): m_reduced = 30.50 clinical_pdd_zref = 79.7 tissue_correction = 0.99 - dose_mu_zref = 0.807 - dose_mu_zmax = 1.0125 + dose_mu_zref = 0.813 + dose_mu_zmax = 1.0198 class IMMCTB_6FFF(TRS398Photon, TestCase): @@ -253,8 +269,8 @@ class IMMCTB_6FFF(TRS398Photon, TestCase): m_reduced = 11.533 clinical_pdd_zref = 63.5 mu = 100 - dose_mu_zref = 0.638 - dose_mu_zmax = 1.005 + dose_mu_zref = 0.642 + dose_mu_zmax = 1.0116 print_data = True @@ -270,8 +286,8 @@ class IMMCTB_10FFF(TRS398Photon, TestCase): tpr2010 = trs398.tpr2010_from_pdd2010(pdd2010=(43 / 71.2)) clinical_pdd_zref = 71.1 mu = 100 - dose_mu_zref = 0.712 - dose_mu_zmax = 1.0005 + dose_mu_zref = 0.7165 + dose_mu_zmax = 1.0077 # open_pdf = True @@ -286,8 +302,8 @@ class IMMCTB_15X(TRS398Photon, TestCase): clinical_pdd_zref = 76.7 tpr2010 = trs398.tpr2010_from_pdd2010(pdd2010=(49.9 / 76.9)) * (76.79 / 76.9) mu = 100 - dose_mu_zref = 0.770 - dose_mu_zmax = 1.004 + dose_mu_zref = 0.775 + dose_mu_zmax = 1.011 # print_data = True # open_pdf = True @@ -305,5 +321,5 @@ class IMMC_TB_20E(TRS398Electron, TestCase): m_reduced = 19.437 * 0.99354 i_50 = 8.22 clinical_pdd_zref = 96.8 - dose_mu_zref = 0.972 - dose_mu_zmax = 1.004 + dose_mu_zref = 0.979 + dose_mu_zmax = 1.011