accord-rt 0.2__tar.gz

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.
accord_rt-0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Nelson Sandoval Puente
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
accord_rt-0.2/PKG-INFO ADDED
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: accord-rt
3
+ Version: 0.2
4
+ Summary: accord: Calculator for TRS-398 calibration.
5
+ Author: jonjon-el
6
+ Requires-Python: >=3.8
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Healthcare Industry
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ License-File: LICENSE
16
+ Requires-Dist: click>=8.0
17
+ Requires-Dist: pylinac>=3.43.2,<4.0.0
18
+ Requires-Dist: numpy
19
+ Requires-Dist: scipy
20
+ Project-URL: Homepage, https://github.com/jonjon-el/accord
21
+ Project-URL: Issues, https://github.com/jonjon-el/accord/issues
22
+ Project-URL: Source, https://github.com/jonjon-el/accord
@@ -0,0 +1,2 @@
1
+ """accord: Calculator for TRS-398 calibration."""
2
+ __version__ = "0.2"
@@ -0,0 +1,7 @@
1
+ import accord.interfaces.cli_click.commands
2
+
3
+ def main():
4
+ """
5
+ CLI entry point.
6
+ """
7
+ accord.interfaces.cli_click.commands.cli()
File without changes
@@ -0,0 +1,82 @@
1
+ from pylinac import ct
2
+ from scipy import constants
3
+ import numpy as np
4
+
5
+ def calc_c_T(P_expected_value: float, T_ref: float, P_ref: float) -> float:
6
+ """
7
+ Calculate the sensitivity of temperature.
8
+
9
+ Args:
10
+ P_expected_value (float): The expected value of the measurand. (e.g., the expected pressure)
11
+ T_ref (float): The reference temperature.
12
+ P_ref (float): The reference pressure.
13
+ Returns:
14
+ float: The sensitivity of temperature.
15
+ """
16
+
17
+ # Check values
18
+ if P_expected_value <= 0:
19
+ raise ValueError("Expected value must be greater than zero.")
20
+ if P_ref <= 0:
21
+ raise ValueError("Reference pressure must be greater than zero.")
22
+ if T_ref <= -constants.zero_Celsius:
23
+ raise ValueError("Reference temperature must be greater than absolute zero.")
24
+
25
+ # Calculate the sensitivity of temperature
26
+ c_T = abs( P_ref / ((constants.zero_Celsius + T_ref)*P_expected_value))
27
+ return c_T
28
+
29
+ def calc_c_P(L_expected_value: float, T_expected_value: float, P_expected_value: float, T_ref: float, P_ref: float) -> float:
30
+ """
31
+ Calculate the sensitivity of pressure.
32
+
33
+ Args:
34
+ L_expected_value (float): The expected value of the measurand. (e.g., the expected crude lecture)
35
+ T_expected_value (float): The expected value of the measurand. (e.g., the expected temperature)
36
+ P_expected_value (float): The expected value of the measurand. (e.g., the expected pressure)
37
+ T_ref (float): The reference temperature.
38
+ P_ref (float): The reference pressure.
39
+ Returns:
40
+ float: The sensitivity of pressure.
41
+ """
42
+
43
+ # Check values
44
+ if T_expected_value <= -constants.zero_Celsius:
45
+ raise ValueError("Expected temperature must be greater than absolute zero.")
46
+ if P_expected_value <= 0:
47
+ raise ValueError("Expected value must be greater than zero.")
48
+ if P_ref <= 0:
49
+ raise ValueError("Reference pressure must be greater than zero.")
50
+ if T_ref <= -constants.zero_Celsius:
51
+ raise ValueError("Reference temperature must be greater than absolute zero.")
52
+
53
+ # Calculate the sensitivity of pressure
54
+ c_P = abs(((constants.zero_Celsius + T_expected_value)*P_ref*L_expected_value) / ((constants.zero_Celsius + T_ref)*np.power(P_expected_value, 2)))
55
+ return c_P
56
+
57
+ def calc_c_L(T_expected_value: float, P_expected_value: float, T_ref: float, P_ref: float) -> float:
58
+ """
59
+ Calculate the sensitivity of crude lecture.
60
+
61
+ Args:
62
+ T_expected_value (float): The expected value of the measurand. (e.g., the expected temperature)
63
+ P_expected_value (float): The expected value of the measurand. (e.g., the expected pressure)
64
+ T_ref (float): The reference temperature.
65
+ P_ref (float): The reference pressure.
66
+ Returns:
67
+ float: The sensitivity of crude lecture.
68
+ """
69
+
70
+ # Check values
71
+ if T_expected_value <= -constants.zero_Celsius:
72
+ raise ValueError("Expected temperature must be greater than absolute zero.")
73
+ if P_expected_value <= 0:
74
+ raise ValueError("Expected value must be greater than zero.")
75
+ if P_ref <= 0:
76
+ raise ValueError("Reference pressure must be greater than zero.")
77
+ if T_ref <= -constants.zero_Celsius:
78
+ raise ValueError("Reference temperature must be greater than absolute zero.")
79
+
80
+ # Calculate the sensitivity of crude lecture
81
+ c_L = abs(((constants.zero_Celsius + T_expected_value)*P_ref) / ((constants.zero_Celsius + T_ref)*P_expected_value))
82
+ return c_L
@@ -0,0 +1,12 @@
1
+ from pylinac.core.image_generator.simulators import Simulator
2
+
3
+ class iViewGTImage(Simulator):
4
+ pixel_size = 0.40
5
+ shape = (1024, 1024)
6
+
7
+ def test():
8
+ obj0 = iViewGTImage()
9
+
10
+ if __name__=="__main__":
11
+ print("Running as main...")
12
+ test()
@@ -0,0 +1,73 @@
1
+ import numpy as np
2
+ import functools
3
+
4
+ def calc_U (u: float, k: float) -> float:
5
+ """
6
+ Calculate the combined uncertainty.
7
+
8
+ Args:
9
+ u (float): The combined standard uncertainty.
10
+ k (float): The coverage factor.
11
+
12
+ Returns:
13
+ float: The combined uncertainty.
14
+ """
15
+ return u * k
16
+
17
+ def calc_u_c (*u_i) -> float:
18
+ """
19
+ Calculate the combined standard uncertainty.
20
+
21
+ Args:
22
+ *u_i (float): Variable number of standard uncertainties.
23
+ Returns:
24
+ float: The combined standard uncertainty.
25
+ """
26
+ return functools.reduce(np.hypot, u_i)
27
+
28
+ def calc_u_A (*measurand_values) -> float:
29
+ """
30
+ Calculate Type A standard uncertainty.
31
+
32
+ Args:
33
+ *measurand_values (float): Variable number of the mean values for each series.
34
+ Returns:
35
+ float: The Type A uncertainty.
36
+ """
37
+
38
+ n = len(measurand_values)
39
+
40
+ # Calculate the sample standard deviation. (n-1 Bessel's correction)
41
+ std_dev = np.std(measurand_values, ddof=1)
42
+
43
+ # Calculate the standard uncertainty of the mean
44
+ u_A = std_dev / np.sqrt(n)
45
+
46
+ return u_A
47
+
48
+ def calc_u_B(error_value: float, distribution: str, k: float|None = None) -> float:
49
+ """
50
+ Calculate Type B standard uncertainty.
51
+
52
+ Args:
53
+ error_value (float): The value of the error.
54
+ distribution (str): The type of uncertainty. ("rectangular" , "triangular", "normal" distributions are supported)
55
+ k (float): The coverage factor for normal distribution. (ignored for other distributions)
56
+ Returns:
57
+ float: The standard uncertainty.
58
+ """
59
+
60
+ if distribution == "rectangular":
61
+ u_B = error_value / np.sqrt(3)
62
+ elif distribution == "triangular":
63
+ u_B = error_value / np.sqrt(6)
64
+ elif distribution == "normal":
65
+ if k is None:
66
+ raise ValueError("Coverage factor 'k' is required for normal distribution.")
67
+ if k <= 0:
68
+ raise ValueError("Coverage factor 'k' must be a positive number.")
69
+ u_B = error_value / k
70
+ else:
71
+ raise ValueError("Unsupported distribution type. Supported types are: 'rectangular', 'triangular', 'normal'.")
72
+
73
+ return u_B
@@ -0,0 +1,201 @@
1
+ import importlib.resources
2
+ import pathlib
3
+ import shutil
4
+
5
+ import pylinac.calibration.trs398
6
+ import tomllib
7
+
8
+ # This file contains auxiliary functions for the NEL calibration process.
9
+
10
+ # Copy sample files.
11
+ def copy_sample_files(path: pathlib.Path, file_class: str):
12
+ configTraversable_list = list()
13
+ if file_class == "config":
14
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/config.toml"))
15
+ elif file_class == "calibration":
16
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/calibration.toml"))
17
+ # TODO: make file_class a list of paths to work with the three preliminary.csv original files.
18
+ elif file_class == "preliminary":
19
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/preliminary_0.csv"))
20
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/preliminary_1.csv"))
21
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/preliminary_2.csv"))
22
+ elif file_class == "devices":
23
+ configTraversable_list.append(importlib.resources.files("accord").joinpath("sampleFiles/devices.toml"))
24
+ else:
25
+ raise ValueError("Invalid file type. Please choose 'config', 'calibration', 'preliminary', or 'devices'.")
26
+
27
+ try:
28
+ for configTraversable in configTraversable_list:
29
+ with importlib.resources.as_file(configTraversable) as configPath:
30
+ shutil.copy(configPath, path)
31
+ print(f"File copied.")
32
+ except FileNotFoundError:
33
+ raise ValueError(f"Sample file not found in {configPath}.")
34
+ except PermissionError:
35
+ raise ValueError(f"Permission denied when copying file.")
36
+ except shutil.SameFileError:
37
+ raise ValueError(f"Source and destination represents the same file.")
38
+ except OSError as e:
39
+ raise ValueError(f"Error copying file: {e.strerror}.")
40
+
41
+ # Load a TOML file
42
+ def load_toml_file(path: pathlib.Path) -> dict[str, object]:
43
+ with open(path, "rb") as f:
44
+ return tomllib.load(f)
45
+
46
+ # Get a nested value in a dictionary
47
+ def get_nested(dictObj, *keys, default=None):
48
+ for key in keys:
49
+ if isinstance(dictObj, dict):
50
+ dictObj = dictObj.get(key)
51
+ else:
52
+ return default
53
+ return dictObj if dictObj is not None else default
54
+
55
+ # Validate option helper.
56
+ def resolve_option2(cli_value, config, key_path):
57
+ keys = key_path.split(".")
58
+ value = cli_value or get_nested(config, *keys)
59
+
60
+ # if value is None:
61
+ # raise ValueError(f"Missing required option: --{keys[-1]} or '{key_path}' in config.")
62
+ return value
63
+
64
+ # Example calibration data structure used in the calibration file.
65
+ calibration_data = {
66
+ "chamber":"30013",
67
+ "clinical_pdd_zref": 66.5,
68
+ "energy": 6,
69
+ "fff": False,
70
+ "institution": "Your institution here",
71
+ "k_elec": 1.000,
72
+ "m_opposite": [25.64, 25.65, 25.65],
73
+ "m_reference": [25.65, 25.66, 25.65],
74
+ "m_reduced": [25.64, 25.63, 25.63],
75
+ "measurement_date": "2025-05-16",
76
+ "mu": 200,
77
+ "n_dw": 5.443,
78
+ "physicist": "Person realizing the calibration",
79
+ "press": 100.65839,
80
+ "setup": "SSD",
81
+ "temp": 22.1,
82
+ "tissue_correction": 1.0,
83
+ "tpr2010": 0.573573574,
84
+ "unit": "Unit employed",
85
+ "voltage_reduced": -150,
86
+ "voltage_reference": -300,
87
+ "notes": "Sample note."
88
+ }
89
+
90
+ def GetBaseTypes(config_quantities: dict) -> dict:
91
+ """
92
+ Get the base types of the quantities from the config file.
93
+ """
94
+ baseTypes = dict()
95
+ for key in config_quantities:
96
+ baseTypes[key] = config_quantities[key]["baseType"]
97
+ return baseTypes
98
+
99
+ def Row2Measurement(row: dict, baseTypes: dict) -> dict:
100
+ """
101
+ Convert a row from the CSV file into a measurement dictionary.
102
+ """
103
+ measurement = dict()
104
+ for key in row:
105
+ if baseTypes[key] == "int":
106
+ measurement[key] = int(row[key])
107
+ elif baseTypes[key] == "float":
108
+ measurement[key] = float(row[key])
109
+
110
+ return measurement
111
+
112
+ def Row2Measurement2(row: dict, quantities: dict) -> dict:
113
+ """
114
+ Convert a dictionary containing a row from the CSV file to a dictionary containing the same values but with the correct base types.
115
+ Base types are obtained from the quantities dictionary in the json file.
116
+ """
117
+ measurement = dict()
118
+ for key in row:
119
+ if quantities[key]["baseType"] == "int":
120
+ measurement[key] = int(row[key])
121
+ elif quantities[key]["baseType"] == "float":
122
+ measurement[key] = float(row[key])
123
+ elif quantities[key]["baseType"] == "str":
124
+ measurement[key] = row[key]
125
+ elif quantities[key]["baseType"] == "bool":
126
+ measurement[key] = row[key].lower() in ("true")
127
+
128
+ return measurement
129
+
130
+ def ConvertMeasurement(rawMeasurement: dict, oldUnits: dict, newUnits: dict) -> dict:
131
+ """
132
+ Converts raw measurement in old units to new units.
133
+ """
134
+ measurement = rawMeasurement.copy()
135
+ for key in rawMeasurement:
136
+ if oldUnits[key] != newUnits[key]:
137
+ if key == "T":
138
+ # measurement[key] = trs398.convert_temperature(rawMeasurement[key], oldUnits[key], newUnits[key])
139
+ if oldUnits[key] == "°F":
140
+ measurement[key] = pylinac.calibration.trs398.fahrenheit2celsius(rawMeasurement[key])
141
+ elif oldUnits[key] == "K":
142
+ measurement[key] = rawMeasurement[key] + 273
143
+ elif key == "P":
144
+ # measurement[key] = trs398.convert_pressure(rawMeasurement[key], oldUnits[key], newUnits[key])
145
+ if oldUnits[key] == "mbar":
146
+ measurement[key] = pylinac.calibration.trs398.mbar2kPa(rawMeasurement[key])
147
+ elif oldUnits[key] == "mmHg":
148
+ measurement[key] = pylinac.calibration.trs398.mmHg2kPa(rawMeasurement[key])
149
+ # elif key == "m":
150
+ # measurement[key] = trs398.convert_charge(rawMeasurement[key], oldUnits[key], newUnits[key])
151
+ # elif key == "k_TP":
152
+ # measurement[key] = trs398.convert_temperature_pressure_correction_factor(rawMeasurement["T"], rawMeasurement["P"], oldUnits["T"], oldUnits["P"], newUnits["T"], newUnits["P"])
153
+ # elif key == "m_corrected":
154
+ # measurement[key] = trs398.convert_charge(rawMeasurement[key], oldUnits[key], newUnits[key])
155
+ return measurement
156
+
157
+ def Convert_measurement_to_pylinac_units(measurement: dict, oldUnits: dict) -> dict:
158
+ converted_measurement = measurement.copy()
159
+ for key in converted_measurement:
160
+ if key == "T":
161
+ if oldUnits[key] == "°F":
162
+ converted_measurement[key] = pylinac.calibration.trs398.fahrenheit2celsius(converted_measurement[key])
163
+ elif oldUnits[key] == "°C":
164
+ pass
165
+ else:
166
+ raise ValueError(f"Invalid temperature unit: {oldUnits[key]}")
167
+ elif key == "P":
168
+ if oldUnits[key] == "mbar":
169
+ converted_measurement[key] = pylinac.calibration.trs398.mbar2kPa(converted_measurement[key])
170
+ elif oldUnits[key] == "mmHg":
171
+ converted_measurement[key] = pylinac.calibration.trs398.mmHg2kPa(converted_measurement[key])
172
+ elif oldUnits[key] == "kPa":
173
+ pass
174
+ else:
175
+ raise ValueError(f"Invalid pressure unit: {oldUnits[key]}")
176
+
177
+ return converted_measurement
178
+
179
+ def FindAverage(numberList: list) -> float:
180
+ acum = 0
181
+ for number in numberList:
182
+ acum = acum + number
183
+ # DEBUG: FAIL. With empty value .csv input files.
184
+ return acum / len(numberList)
185
+
186
+ def FindStdDev(numberList: list) -> float:
187
+ average = FindAverage(numberList)
188
+ acum = 0
189
+ for number in numberList:
190
+ acum = acum + (number - average) ** 2
191
+ return (acum / (len(numberList)-1)) ** 0.5
192
+
193
+ def FindExpectedValue(numberList: list) -> float:
194
+ return FindAverage(numberList)
195
+
196
+ def main():
197
+ return 0
198
+
199
+ if __name__ == "__main__":
200
+ errorCode = main()
201
+ print(f"Program terminated with errorCode: {errorCode}")
@@ -0,0 +1,67 @@
1
+ {
2
+ "input_preliminary": {
3
+ "type:": "csv",
4
+ "description": "Preliminary input file with raw readings of the electrometer and the temperature and pressure values.",
5
+ "column_delimiter": ";",
6
+ "columns": {
7
+ "index": {
8
+ "name": "Index",
9
+ "unit": "",
10
+ "description": "Index"
11
+ },
12
+ "T": {
13
+ "name": "T",
14
+ "unit": "°C",
15
+ "description": "Temperature"
16
+ },
17
+ "P": {
18
+ "name": "P",
19
+ "unit": "kPa",
20
+ "description": "Pressure"
21
+ },
22
+ "m": {
23
+ "name": "m",
24
+ "unit": "nC",
25
+ "description": "Raw reading of the electrometer"
26
+ }
27
+ }
28
+ }
29
+ ,
30
+ "output_preliminary": {
31
+ "type:": "csv",
32
+ "description": "Preliminary output file with corrected readings of the electrometer and the correction factor for temperature and pressure.",
33
+ "column_delimiter": ";",
34
+ "columns": {
35
+ "index": {
36
+ "name": "Index",
37
+ "unit": "",
38
+ "description": "Index"
39
+ },
40
+ "T": {
41
+ "name": "T",
42
+ "unit": "°C",
43
+ "description": "Temperature"
44
+ },
45
+ "P": {
46
+ "name": "P",
47
+ "unit": "kPa",
48
+ "description": "Pressure"
49
+ },
50
+ "m": {
51
+ "name": "m",
52
+ "unit": "nC",
53
+ "description": "Raw reading of the electrometer"
54
+ },
55
+ "k_TP": {
56
+ "name": "k_TP",
57
+ "unit": "",
58
+ "description": "Correction factor for temperature and pressure"
59
+ },
60
+ "m_corrected": {
61
+ "name": "m_corrected",
62
+ "unit": "nC",
63
+ "description": "Corrected reading of electrometer"
64
+ }
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "index": {
3
+ "description": "Index of the quantity",
4
+ "unit": null,
5
+ "baseType": "int"
6
+ },
7
+ "T": {
8
+ "description": "Temperature",
9
+ "unit": "K",
10
+ "baseType": "float"
11
+ },
12
+ "P": {
13
+ "description": "Pressure",
14
+ "unit": "Pa",
15
+ "baseType": "float"
16
+ },
17
+ "m": {
18
+ "description": "Electrometer reading",
19
+ "unit": "nC",
20
+ "baseType": "float"
21
+ }
22
+ }
File without changes