rtp-connect 1.6 → 1.11
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.
- checksums.yaml +7 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +137 -90
- data/COPYING +674 -674
- data/Gemfile +2 -2
- data/Gemfile.lock +31 -21
- data/README.md +161 -0
- data/lib/rtp-connect.rb +1 -0
- data/lib/rtp-connect/constants.rb +58 -57
- data/lib/rtp-connect/control_point.rb +158 -118
- data/lib/rtp-connect/dose_tracking.rb +37 -54
- data/lib/rtp-connect/extended_field.rb +36 -69
- data/lib/rtp-connect/extended_plan.rb +127 -0
- data/lib/rtp-connect/field.rb +158 -143
- data/lib/rtp-connect/methods.rb +85 -62
- data/lib/rtp-connect/plan.rb +645 -636
- data/lib/rtp-connect/plan_to_dcm.rb +668 -694
- data/lib/rtp-connect/prescription.rb +57 -74
- data/lib/rtp-connect/record.rb +225 -57
- data/lib/rtp-connect/ruby_extensions.rb +34 -3
- data/lib/rtp-connect/simulation_field.rb +606 -701
- data/lib/rtp-connect/site_setup.rb +112 -80
- data/lib/rtp-connect/version.rb +5 -5
- data/rakefile.rb +0 -1
- data/rtp-connect.gemspec +27 -27
- metadata +67 -58
- data/README.rdoc +0 -136
data/Gemfile
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
source "http://www.rubygems.org"
|
2
|
-
|
1
|
+
source "http://www.rubygems.org"
|
2
|
+
|
3
3
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,35 +1,45 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rtp-connect (1.
|
4
|
+
rtp-connect (1.11)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://www.rubygems.org/
|
8
8
|
specs:
|
9
|
-
dicom (0.9.
|
10
|
-
diff-lcs (1.2.
|
11
|
-
metaclass (0.0.
|
12
|
-
mocha (
|
9
|
+
dicom (0.9.8)
|
10
|
+
diff-lcs (1.2.5)
|
11
|
+
metaclass (0.0.4)
|
12
|
+
mocha (1.1.0)
|
13
13
|
metaclass (~> 0.0.1)
|
14
|
-
rake (
|
15
|
-
|
16
|
-
|
17
|
-
rspec-
|
18
|
-
rspec-
|
19
|
-
|
20
|
-
rspec-
|
21
|
-
|
22
|
-
rspec-
|
23
|
-
|
14
|
+
rake (12.3.1)
|
15
|
+
redcarpet (3.4.0)
|
16
|
+
rspec (3.7.0)
|
17
|
+
rspec-core (~> 3.7.0)
|
18
|
+
rspec-expectations (~> 3.7.0)
|
19
|
+
rspec-mocks (~> 3.7.0)
|
20
|
+
rspec-core (3.7.1)
|
21
|
+
rspec-support (~> 3.7.0)
|
22
|
+
rspec-expectations (3.7.0)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.7.0)
|
25
|
+
rspec-mocks (3.7.0)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.7.0)
|
28
|
+
rspec-support (3.7.1)
|
29
|
+
yard (0.9.12)
|
24
30
|
|
25
31
|
PLATFORMS
|
26
32
|
x86-mingw32
|
27
33
|
|
28
34
|
DEPENDENCIES
|
29
|
-
bundler (~> 1.
|
30
|
-
dicom (~> 0.9.
|
31
|
-
mocha (~>
|
32
|
-
rake (~>
|
33
|
-
|
35
|
+
bundler (~> 1.11)
|
36
|
+
dicom (~> 0.9, >= 0.9.8)
|
37
|
+
mocha (~> 1.1)
|
38
|
+
rake (~> 12.3)
|
39
|
+
redcarpet (~> 3.4)
|
40
|
+
rspec (~> 3.7)
|
34
41
|
rtp-connect!
|
35
|
-
yard (~> 0.
|
42
|
+
yard (~> 0.9, >= 0.9.12)
|
43
|
+
|
44
|
+
BUNDLED WITH
|
45
|
+
1.16.5
|
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
[](http://dx.doi.org/10.5281/zenodo.11752)
|
2
|
+
|
3
|
+
# RTPConnect
|
4
|
+
|
5
|
+
The RTPConnect library allows you to read, edit and write RTPConnect files in Ruby.
|
6
|
+
RTPConnect is a file format used in radiotherapy (e.g. Mosaiq) for export & import
|
7
|
+
of treatment planning data. The library is written entirely in Ruby and has no
|
8
|
+
external dependencies.
|
9
|
+
|
10
|
+
|
11
|
+
## INSTALLATION
|
12
|
+
|
13
|
+
gem install rtp-connect
|
14
|
+
|
15
|
+
|
16
|
+
## REQUIREMENTS
|
17
|
+
|
18
|
+
* Ruby 1.9.3 (or higher)
|
19
|
+
|
20
|
+
|
21
|
+
## BASIC USAGE
|
22
|
+
|
23
|
+
### Load & Include
|
24
|
+
|
25
|
+
require 'rtp-connect'
|
26
|
+
include RTP
|
27
|
+
|
28
|
+
### Read, modify and write
|
29
|
+
|
30
|
+
# Read file:
|
31
|
+
rtp = Plan.read('some_file.rtp')
|
32
|
+
# Extract the Patient's Name:
|
33
|
+
name = rtp.patient_last_name
|
34
|
+
# Modify the Patient's Name:
|
35
|
+
rtp.patient_last_name = 'Anonymous'
|
36
|
+
# Write to file:
|
37
|
+
rtp.write('new_file.rtp')
|
38
|
+
|
39
|
+
### Create a new Plan Definition Record from scratch
|
40
|
+
|
41
|
+
# Create the instance:
|
42
|
+
rtp = Plan.new
|
43
|
+
# Set the Patient's ID attribute:
|
44
|
+
rtp.patient_id = '12345'
|
45
|
+
# Export the instance to an RTP string (with CRC):
|
46
|
+
output = rtp.to_s
|
47
|
+
|
48
|
+
### Fix invalid RTP files:
|
49
|
+
|
50
|
+
# Read an RTP file containing invalid checksum(s):
|
51
|
+
rtp = Plan.read('invalid_crc.rtp', ignore_crc: true)
|
52
|
+
# Read an RTP file containing unknown record type(s):
|
53
|
+
rtp = Plan.read('custom.rtp', skip_unknown: true)
|
54
|
+
# Read an RTP file containing invalid CSV format:
|
55
|
+
rtp = Plan.read('invalid_csv.rtp', repair: true)
|
56
|
+
# Write a corrected RTP file:
|
57
|
+
rtp.write('valid.rtp')
|
58
|
+
|
59
|
+
### Write RTP files for specific Mosaiq versions:
|
60
|
+
|
61
|
+
# Mosaiq 2.4:
|
62
|
+
rtp.write('treatment_plan.rtp', version: 2.4)
|
63
|
+
# Mosaiq 2.5:
|
64
|
+
rtp.write('treatment_plan.rtp', version: 2.5)
|
65
|
+
# Mosaiq 2.6 (and 2.62):
|
66
|
+
rtp.write('treatment_plan.rtp', version: 2.6)
|
67
|
+
# By default files are outputted at the latest supported version (currently 2.64) when omitting the version parameter:
|
68
|
+
rtp.write('treatment_plan.rtp')
|
69
|
+
|
70
|
+
### Convert an RTP file to DICOM:
|
71
|
+
|
72
|
+
p = Plan.read('some_file.rtp')
|
73
|
+
dcm = p.to_dcm
|
74
|
+
dcm.write('rtplan.dcm')
|
75
|
+
|
76
|
+
### Log settings
|
77
|
+
|
78
|
+
# Change the log level so that only error messages are displayed:
|
79
|
+
RTP.logger.level = Logger::ERROR
|
80
|
+
# Setting up a simple file log:
|
81
|
+
l = Logger.new('my_logfile.log')
|
82
|
+
RTP.logger = l
|
83
|
+
# Create a logger which ages logfile daily/monthly:
|
84
|
+
RTP.logger = Logger.new('foo.log', 'daily')
|
85
|
+
RTP.logger = Logger.new('foo.log', 'monthly')
|
86
|
+
|
87
|
+
### Scripts
|
88
|
+
|
89
|
+
For more comprehensive and useful examples, check out the scripts folder
|
90
|
+
which contains various Ruby scripts that intends to show off real world
|
91
|
+
usage scenarios of the RTPConnect library.
|
92
|
+
|
93
|
+
### IRB Tip
|
94
|
+
|
95
|
+
When working with the RTPConnect library in irb, you may be annoyed with all
|
96
|
+
the information that is printed to screen, regardless of your log level.
|
97
|
+
This is because in irb every variable loaded in the program is
|
98
|
+
automatically printed to the screen. A useful hack to avoid this effect is
|
99
|
+
to append ";0" after a command.
|
100
|
+
|
101
|
+
Example:
|
102
|
+
|
103
|
+
rtp = Plan.read('some_file.rtp') ;0
|
104
|
+
|
105
|
+
|
106
|
+
## RESOURCES
|
107
|
+
|
108
|
+
* [Rubygems download](https://rubygems.org/gems/rtp-connect)
|
109
|
+
* [Documentation](http://rubydoc.info/gems/rtp-connect/frames)
|
110
|
+
* [Source code repository](https://github.com/dicom/rtp-connect)
|
111
|
+
|
112
|
+
|
113
|
+
## RESTRICTIONS
|
114
|
+
|
115
|
+
### Supported records
|
116
|
+
|
117
|
+
* Plan definition [PLAN_DEF]
|
118
|
+
* Extended plan definition [EXTENDED_PLAN_DEF]
|
119
|
+
* Prescription site [RX_DEF]
|
120
|
+
* Site setup [SITE_SETUP_DEF]
|
121
|
+
* Simulation field [SIM_DEF]
|
122
|
+
* Treatment field [FIELD_DEF]
|
123
|
+
* Extended treatment field [EXTENDED_FIELD_DEF]
|
124
|
+
* Control point record [CONTROL_PT_DEF]
|
125
|
+
* Dose tracking record [DOSE_DEF]
|
126
|
+
|
127
|
+
### Unsupported records
|
128
|
+
|
129
|
+
* Document based treatment field [PDF_FIELD_DEF]
|
130
|
+
* Multileaf collimator [MLC_DEF]
|
131
|
+
* MLC shape [MLC_SHAPE_DEF]
|
132
|
+
* Dose action points [DOSE_ACTION]
|
133
|
+
|
134
|
+
If you encounter an RTP file with an unsupported record type, please contact me.
|
135
|
+
|
136
|
+
|
137
|
+
## COPYRIGHT
|
138
|
+
|
139
|
+
Copyright 2011-2020 Christoffer Lervåg
|
140
|
+
|
141
|
+
This program is free software: you can redistribute it and/or modify
|
142
|
+
it under the terms of the GNU General Public License as published by
|
143
|
+
the Free Software Foundation, either version 3 of the License, or
|
144
|
+
(at your option) any later version.
|
145
|
+
|
146
|
+
This program is distributed in the hope that it will be useful,
|
147
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
148
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
149
|
+
GNU General Public License for more details.
|
150
|
+
|
151
|
+
You should have received a copy of the GNU General Public License
|
152
|
+
along with this program. If not, see http://www.gnu.org/licenses/ .
|
153
|
+
|
154
|
+
|
155
|
+
## ABOUT THE AUTHOR
|
156
|
+
|
157
|
+
* Name: Christoffer Lervåg
|
158
|
+
* Location: Norway
|
159
|
+
* Email: chris.lervag [@nospam.com] @gmail.com
|
160
|
+
|
161
|
+
Please don't hesitate to email me if you have any feedback related to this project!
|
data/lib/rtp-connect.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative 'rtp-connect/logging'
|
|
6
6
|
require_relative 'rtp-connect/record'
|
7
7
|
# Core library:
|
8
8
|
require_relative 'rtp-connect/plan'
|
9
|
+
require_relative 'rtp-connect/extended_plan'
|
9
10
|
require_relative 'rtp-connect/plan_to_dcm'
|
10
11
|
require_relative 'rtp-connect/prescription'
|
11
12
|
require_relative 'rtp-connect/site_setup'
|
@@ -1,58 +1,59 @@
|
|
1
|
-
module RTP
|
2
|
-
|
3
|
-
# The seed value used in the RTPConnect implementation of the CCITT algorithm.
|
4
|
-
CRC_SEED = 0x0521
|
5
|
-
|
6
|
-
# The table & values used in the RTPConnect implementation of the CCITT algorithm.
|
7
|
-
CRC_TABLE = [
|
8
|
-
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
9
|
-
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
10
|
-
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
11
|
-
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
12
|
-
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
13
|
-
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
14
|
-
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
15
|
-
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
16
|
-
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
17
|
-
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
18
|
-
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
19
|
-
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
20
|
-
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
21
|
-
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
22
|
-
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
23
|
-
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
24
|
-
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
25
|
-
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
26
|
-
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
27
|
-
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
28
|
-
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
29
|
-
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
30
|
-
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
31
|
-
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
32
|
-
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
33
|
-
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
34
|
-
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
35
|
-
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
36
|
-
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
37
|
-
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
38
|
-
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
39
|
-
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
40
|
-
]
|
41
|
-
|
42
|
-
# Pairs of RTPConnect keywords and parse method names.
|
43
|
-
PARSE_METHOD = {
|
44
|
-
"PLAN_DEF" => :plan_definition,
|
45
|
-
"
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
-
"
|
53
|
-
"
|
54
|
-
"
|
55
|
-
"
|
56
|
-
|
57
|
-
|
1
|
+
module RTP
|
2
|
+
|
3
|
+
# The seed value used in the RTPConnect implementation of the CCITT algorithm.
|
4
|
+
CRC_SEED = 0x0521
|
5
|
+
|
6
|
+
# The table & values used in the RTPConnect implementation of the CCITT algorithm.
|
7
|
+
CRC_TABLE = [
|
8
|
+
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
9
|
+
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
10
|
+
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
11
|
+
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
12
|
+
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
13
|
+
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
14
|
+
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
15
|
+
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
16
|
+
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
17
|
+
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
18
|
+
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
19
|
+
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
20
|
+
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
21
|
+
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
22
|
+
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
23
|
+
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
24
|
+
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
25
|
+
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
26
|
+
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
27
|
+
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
28
|
+
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
29
|
+
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
30
|
+
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
31
|
+
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
32
|
+
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
33
|
+
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
34
|
+
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
35
|
+
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
36
|
+
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
37
|
+
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
38
|
+
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
39
|
+
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
40
|
+
]
|
41
|
+
|
42
|
+
# Pairs of RTPConnect keywords and parse method names.
|
43
|
+
PARSE_METHOD = {
|
44
|
+
"PLAN_DEF" => :plan_definition,
|
45
|
+
"EXTENDED_PLAN_DEF" => :extended_plan_def,
|
46
|
+
"RX_DEF" => :prescription_site,
|
47
|
+
"SITE_SETUP_DEF" => :site_setup,
|
48
|
+
"SIM_DEF" => :simulation_field,
|
49
|
+
"FIELD_DEF" => :treatment_field,
|
50
|
+
"EXTENDED_FIELD_DEF" => :extended_treatment_field,
|
51
|
+
"PDF_FIELD_DEF" => :document_based_treatment_field,
|
52
|
+
"MLC_DEF" => :multileaf_collimator,
|
53
|
+
"CONTROL_PT_DEF" => :control_point,
|
54
|
+
"MLC_SHAPE_DEF" => :mlc_shape,
|
55
|
+
"DOSE_DEF" => :dose_tracking,
|
56
|
+
"DOSE_ACTION" => :dose_action,
|
57
|
+
}
|
58
|
+
|
58
59
|
end
|
@@ -8,8 +8,11 @@ module RTP
|
|
8
8
|
#
|
9
9
|
class ControlPoint < Record
|
10
10
|
|
11
|
+
# The number of attributes not having their own variable for this record (200 - 2).
|
12
|
+
NR_SURPLUS_ATTRIBUTES = 198
|
13
|
+
|
11
14
|
# The Record which this instance belongs to.
|
12
|
-
|
15
|
+
attr_accessor :parent
|
13
16
|
# The MLC shape record (if any) that belongs to this ControlPoint.
|
14
17
|
attr_reader :mlc_shape
|
15
18
|
attr_reader :field_id
|
@@ -43,6 +46,9 @@ module RTP
|
|
43
46
|
attr_reader :couch_dir
|
44
47
|
attr_reader :couch_pedestal
|
45
48
|
attr_reader :couch_ped_dir
|
49
|
+
attr_reader :iso_pos_x
|
50
|
+
attr_reader :iso_pos_y
|
51
|
+
attr_reader :iso_pos_z
|
46
52
|
# Note: This attribute contains an array of all MLC LP A values (leaves 1..100).
|
47
53
|
attr_reader :mlc_lp_a
|
48
54
|
# Note: This attribute contains an array of all MLC LP B values (leaves 1..100).
|
@@ -56,50 +62,8 @@ module RTP
|
|
56
62
|
# @raise [ArgumentError] if given a string containing an invalid number of elements
|
57
63
|
#
|
58
64
|
def self.load(string, parent)
|
59
|
-
# Get the quote-less values:
|
60
|
-
values = string.to_s.values
|
61
|
-
low_limit = 233
|
62
|
-
high_limit = 233
|
63
|
-
raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
|
64
|
-
RTP.logger.warn "The number of elements (#{values.length}) for this ControlPoint record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
|
65
65
|
cp = self.new(parent)
|
66
|
-
|
67
|
-
cp.keyword = values[0]
|
68
|
-
cp.field_id = values[1]
|
69
|
-
cp.mlc_type = values[2]
|
70
|
-
cp.mlc_leaves = values[3]
|
71
|
-
cp.total_control_points = values[4]
|
72
|
-
cp.control_pt_number = values[5]
|
73
|
-
cp.mu_convention = values[6]
|
74
|
-
cp.monitor_units = values[7]
|
75
|
-
cp.wedge_position = values[8]
|
76
|
-
cp.energy = values[9]
|
77
|
-
cp.doserate = values[10]
|
78
|
-
cp.ssd = values[11]
|
79
|
-
cp.scale_convention = values[12]
|
80
|
-
cp.gantry_angle = values[13]
|
81
|
-
cp.gantry_dir = values[14]
|
82
|
-
cp.collimator_angle = values[15]
|
83
|
-
cp.collimator_dir = values[16]
|
84
|
-
cp.field_x_mode = values[17]
|
85
|
-
cp.field_x = values[18]
|
86
|
-
cp.collimator_x1 = values[19]
|
87
|
-
cp.collimator_x2 = values[20]
|
88
|
-
cp.field_y_mode = values[21]
|
89
|
-
cp.field_y = values[22]
|
90
|
-
cp.collimator_y1 = values[23]
|
91
|
-
cp.collimator_y2 = values[24]
|
92
|
-
cp.couch_vertical = values[25]
|
93
|
-
cp.couch_lateral = values[26]
|
94
|
-
cp.couch_longitudinal = values[27]
|
95
|
-
cp.couch_angle = values[28]
|
96
|
-
cp.couch_dir = values[29]
|
97
|
-
cp.couch_pedestal = values[30]
|
98
|
-
cp.couch_ped_dir = values[31]
|
99
|
-
cp.mlc_lp_a = [*values[32..131]]
|
100
|
-
cp.mlc_lp_b = [*values[132..231]]
|
101
|
-
cp.crc = values[-1]
|
102
|
-
return cp
|
66
|
+
cp.load(string)
|
103
67
|
end
|
104
68
|
|
105
69
|
# Creates a new ControlPoint.
|
@@ -107,14 +71,54 @@ module RTP
|
|
107
71
|
# @param [Record] parent a record which is used to determine the proper parent of this instance
|
108
72
|
#
|
109
73
|
def initialize(parent)
|
74
|
+
super('CONTROL_PT_DEF', 233, 236)
|
110
75
|
# Child:
|
111
76
|
@mlc_shape = nil
|
112
77
|
# Parent relation (may get more than one type of record here):
|
113
78
|
@parent = get_parent(parent.to_record, Field)
|
114
79
|
@parent.add_control_point(self)
|
115
|
-
@keyword = 'CONTROL_PT_DEF'
|
116
80
|
@mlc_lp_a = Array.new(100)
|
117
81
|
@mlc_lp_b = Array.new(100)
|
82
|
+
@attributes = [
|
83
|
+
# Required:
|
84
|
+
:keyword,
|
85
|
+
:field_id,
|
86
|
+
:mlc_type,
|
87
|
+
:mlc_leaves,
|
88
|
+
:total_control_points,
|
89
|
+
:control_pt_number,
|
90
|
+
:mu_convention,
|
91
|
+
:monitor_units,
|
92
|
+
:wedge_position,
|
93
|
+
:energy,
|
94
|
+
:doserate,
|
95
|
+
:ssd,
|
96
|
+
:scale_convention,
|
97
|
+
:gantry_angle,
|
98
|
+
:gantry_dir,
|
99
|
+
:collimator_angle,
|
100
|
+
:collimator_dir,
|
101
|
+
:field_x_mode,
|
102
|
+
:field_x,
|
103
|
+
:collimator_x1,
|
104
|
+
:collimator_x2,
|
105
|
+
:field_y_mode,
|
106
|
+
:field_y,
|
107
|
+
:collimator_y1,
|
108
|
+
:collimator_y2,
|
109
|
+
:couch_vertical,
|
110
|
+
:couch_lateral,
|
111
|
+
:couch_longitudinal,
|
112
|
+
:couch_angle,
|
113
|
+
:couch_dir,
|
114
|
+
:couch_pedestal,
|
115
|
+
:couch_ped_dir,
|
116
|
+
:iso_pos_x,
|
117
|
+
:iso_pos_y,
|
118
|
+
:iso_pos_z,
|
119
|
+
:mlc_lp_a,
|
120
|
+
:mlc_lp_b
|
121
|
+
]
|
118
122
|
end
|
119
123
|
|
120
124
|
# Checks for equality.
|
@@ -145,51 +149,51 @@ module RTP
|
|
145
149
|
|
146
150
|
# Converts the collimator_x1 attribute to proper DICOM format.
|
147
151
|
#
|
152
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from a native readout format to IEC1217 (supported values are :elekta & :varian)
|
148
153
|
# @return [Float] the DICOM-formatted collimator_x1 attribute
|
149
154
|
#
|
150
|
-
def dcm_collimator_x1
|
151
|
-
|
152
|
-
target = (@field_x_mode && !@field_x_mode.empty? ? self : @parent)
|
153
|
-
target.send(attribute).to_f * 10 * scale_factor
|
155
|
+
def dcm_collimator_x1(scale=nil)
|
156
|
+
dcm_collimator_1(scale, default_axis=:x)
|
154
157
|
end
|
155
158
|
|
156
159
|
# Converts the collimator_x2 attribute to proper DICOM format.
|
157
160
|
#
|
161
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
158
162
|
# @return [Float] the DICOM-formatted collimator_x2 attribute
|
159
163
|
#
|
160
|
-
def dcm_collimator_x2
|
161
|
-
|
162
|
-
|
163
|
-
target.send(attribute).to_f * 10
|
164
|
+
def dcm_collimator_x2(scale=nil)
|
165
|
+
axis = (scale == :elekta ? :y : :x)
|
166
|
+
dcm_collimator(axis, coeff=1, side=2)
|
164
167
|
end
|
165
168
|
|
166
169
|
# Converts the collimator_y1 attribute to proper DICOM format.
|
167
170
|
#
|
171
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
168
172
|
# @return [Float] the DICOM-formatted collimator_y1 attribute
|
169
173
|
#
|
170
|
-
def dcm_collimator_y1
|
171
|
-
|
172
|
-
target = (@field_y_mode && !@field_y_mode.empty? ? self : @parent)
|
173
|
-
target.send(attribute).to_f * 10 * scale_factor
|
174
|
+
def dcm_collimator_y1(scale=nil)
|
175
|
+
dcm_collimator_1(scale, default_axis=:y)
|
174
176
|
end
|
175
177
|
|
176
178
|
# Converts the collimator_y2 attribute to proper DICOM format.
|
177
179
|
#
|
180
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
178
181
|
# @return [Float] the DICOM-formatted collimator_y2 attribute
|
179
182
|
#
|
180
|
-
def dcm_collimator_y2
|
181
|
-
|
182
|
-
|
183
|
-
target.send(attribute).to_f * 10
|
183
|
+
def dcm_collimator_y2(scale=nil)
|
184
|
+
axis = (scale == :elekta ? :x : :y)
|
185
|
+
dcm_collimator(axis, coeff=1, side=2)
|
184
186
|
end
|
185
187
|
|
186
188
|
# Converts the mlc_lp_a & mlc_lp_b attributes to a proper DICOM formatted string.
|
187
189
|
#
|
190
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
188
191
|
# @return [String] the DICOM-formatted leaf pair positions
|
189
192
|
#
|
190
|
-
def dcm_mlc_positions
|
193
|
+
def dcm_mlc_positions(scale=nil)
|
194
|
+
coeff = (scale == :elekta ? -1 : 1)
|
191
195
|
# As with the collimators, the first side (1/a) may need scale invertion:
|
192
|
-
pos_a = @mlc_lp_a.collect{|p| (p.to_f * 10 *
|
196
|
+
pos_a = @mlc_lp_a.collect{|p| (p.to_f * 10 * coeff).round(1) unless p.empty?}.compact
|
193
197
|
pos_b = @mlc_lp_b.collect{|p| (p.to_f * 10).round(1) unless p.empty?}.compact
|
194
198
|
(pos_a + pos_b).join("\\")
|
195
199
|
end
|
@@ -219,7 +223,7 @@ module RTP
|
|
219
223
|
# @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
|
220
224
|
#
|
221
225
|
def values
|
222
|
-
|
226
|
+
[
|
223
227
|
@keyword,
|
224
228
|
@field_id,
|
225
229
|
@mlc_type,
|
@@ -252,6 +256,9 @@ module RTP
|
|
252
256
|
@couch_dir,
|
253
257
|
@couch_pedestal,
|
254
258
|
@couch_ped_dir,
|
259
|
+
@iso_pos_x,
|
260
|
+
@iso_pos_y,
|
261
|
+
@iso_pos_z,
|
255
262
|
*@mlc_lp_a,
|
256
263
|
*@mlc_lp_b
|
257
264
|
]
|
@@ -265,23 +272,6 @@ module RTP
|
|
265
272
|
self
|
266
273
|
end
|
267
274
|
|
268
|
-
# Encodes the ControlPoint object + any hiearchy of child objects,
|
269
|
-
# to a properly formatted RTPConnect ascii string.
|
270
|
-
#
|
271
|
-
# @return [String] an RTP string with a single or multiple lines/records
|
272
|
-
#
|
273
|
-
def to_s
|
274
|
-
str = encode
|
275
|
-
if children
|
276
|
-
children.each do |child|
|
277
|
-
str += child.to_s
|
278
|
-
end
|
279
|
-
end
|
280
|
-
return str
|
281
|
-
end
|
282
|
-
|
283
|
-
alias :to_str :to_s
|
284
|
-
|
285
275
|
# Sets the mlc_lp_a attribute.
|
286
276
|
#
|
287
277
|
# @note As opposed to the ordinary (string) attributes, this attribute
|
@@ -289,9 +279,7 @@ module RTP
|
|
289
279
|
# @param [Array<nil, #to_s>] array the new attribute values
|
290
280
|
#
|
291
281
|
def mlc_lp_a=(array)
|
292
|
-
|
293
|
-
raise ArgumentError, "Invalid argument 'array'. Expected length 100, got #{array.length}." unless array.length == 100
|
294
|
-
@mlc_lp_a = array.collect! {|e| e && e.to_s.strip}
|
282
|
+
@mlc_lp_a = array.to_a.validate_and_process(100)
|
295
283
|
end
|
296
284
|
|
297
285
|
# Sets the mlc_lp_b attribute.
|
@@ -301,21 +289,7 @@ module RTP
|
|
301
289
|
# @param [Array<nil, #to_s>] array the new attribute values
|
302
290
|
#
|
303
291
|
def mlc_lp_b=(array)
|
304
|
-
|
305
|
-
raise ArgumentError, "Invalid argument 'array'. Expected length 100, got #{array.length}." unless array.length == 100
|
306
|
-
@mlc_lp_b = array.collect! {|e| e && e.to_s.strip}
|
307
|
-
end
|
308
|
-
|
309
|
-
# Sets the keyword attribute.
|
310
|
-
#
|
311
|
-
# @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
|
312
|
-
# @param [#to_s] value the new attribute value
|
313
|
-
# @raise [ArgumentError] if given an unexpected keyword
|
314
|
-
#
|
315
|
-
def keyword=(value)
|
316
|
-
value = value.to_s.upcase
|
317
|
-
raise ArgumentError, "Invalid keyword. Expected 'CONTROL_PT_DEF', got #{value}." unless value == "CONTROL_PT_DEF"
|
318
|
-
@keyword = value
|
292
|
+
@mlc_lp_b = array.to_a.validate_and_process(100)
|
319
293
|
end
|
320
294
|
|
321
295
|
# Sets the field_id attribute.
|
@@ -566,6 +540,30 @@ module RTP
|
|
566
540
|
@couch_ped_dir = value && value.to_s
|
567
541
|
end
|
568
542
|
|
543
|
+
# Sets the iso_pos_x attribute.
|
544
|
+
#
|
545
|
+
# @param [nil, #to_s] value the new attribute value
|
546
|
+
#
|
547
|
+
def iso_pos_x=(value)
|
548
|
+
@iso_pos_x = value && value.to_s.strip
|
549
|
+
end
|
550
|
+
|
551
|
+
# Sets the iso_pos_y attribute.
|
552
|
+
#
|
553
|
+
# @param [nil, #to_s] value the new attribute value
|
554
|
+
#
|
555
|
+
def iso_pos_y=(value)
|
556
|
+
@iso_pos_y = value && value.to_s.strip
|
557
|
+
end
|
558
|
+
|
559
|
+
# Sets the iso_pos_z attribute.
|
560
|
+
#
|
561
|
+
# @param [nil, #to_s] value the new attribute value
|
562
|
+
#
|
563
|
+
def iso_pos_z=(value)
|
564
|
+
@iso_pos_z = value && value.to_s.strip
|
565
|
+
end
|
566
|
+
|
569
567
|
|
570
568
|
private
|
571
569
|
|
@@ -577,31 +575,73 @@ module RTP
|
|
577
575
|
#
|
578
576
|
alias_method :state, :values
|
579
577
|
|
580
|
-
#
|
581
|
-
# convertion is to be applied. This convertion entails converting a value
|
582
|
-
# from IEC1217 format to the target machine's native readout format.
|
583
|
-
# Note that the scope of this scale conversion is not precisely known (the
|
584
|
-
# current implementation is based on a few observations made from a single
|
585
|
-
# RTP file).
|
578
|
+
# Converts the collimator attribute to proper DICOM format.
|
586
579
|
#
|
587
|
-
# @
|
580
|
+
# @param [Symbol] axis a representation for the axis of interest (x or y)
|
581
|
+
# @param [Integer] coeff a coeffecient (of -1 or 1) which the attribute is multiplied with
|
582
|
+
# @param [Integer] nr collimator side/index (1 or 2)
|
583
|
+
# @return [Float] the DICOM-formatted collimator attribute
|
588
584
|
#
|
589
|
-
def
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
585
|
+
def dcm_collimator(axis, coeff, nr)
|
586
|
+
mode = self.send("field_#{axis}_mode")
|
587
|
+
if mode && !mode.empty?
|
588
|
+
target = self
|
589
|
+
else
|
590
|
+
target = @parent
|
591
|
+
end
|
592
|
+
target.send("collimator_#{axis}#{nr}").to_f * 10 * coeff
|
595
593
|
end
|
596
594
|
|
597
|
-
#
|
598
|
-
# 'scale_convention' attribute.
|
595
|
+
# Converts the collimator1 attribute to proper DICOM format.
|
599
596
|
#
|
600
|
-
# @param [
|
601
|
-
# @return [
|
597
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from a native readout format to IEC1217 (supported values are :elekta & :varian)
|
598
|
+
# @return [Float] the DICOM-formatted collimator_x1 attribute
|
602
599
|
#
|
603
|
-
def
|
604
|
-
|
600
|
+
def dcm_collimator_1(scale=nil, axis)
|
601
|
+
coeff = 1
|
602
|
+
if scale == :elekta
|
603
|
+
axis = (axis == :x ? :y : :x)
|
604
|
+
coeff = -1
|
605
|
+
elsif scale == :varian
|
606
|
+
coeff = -1
|
607
|
+
end
|
608
|
+
dcm_collimator(axis, coeff, side=1)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Gives an array of indices indicating where the attributes of this record gets its
|
612
|
+
# values from in the comma separated string which the instance is created from.
|
613
|
+
#
|
614
|
+
# @param [Integer] length the number of elements to create in the indices array
|
615
|
+
#
|
616
|
+
def import_indices(length)
|
617
|
+
# Note that this method is defined in the parent Record class, where it is
|
618
|
+
# used for most record types. However, because this record has two attributes
|
619
|
+
# which contain an array of values, we use a custom import_indices method.
|
620
|
+
#
|
621
|
+
# Furthermore, as of Mosaiq version 2.64, the RTP ControlPoint record includes
|
622
|
+
# 3 new attributes: iso_pos_x/y/z. Since these (unfortunately) are not placed
|
623
|
+
# at the end of the record (which is the norm), but rather inserted before the
|
624
|
+
# MLC leaf positions, we have to take special care here to make sure that this
|
625
|
+
# gets right for records where these are included or excluded.
|
626
|
+
#
|
627
|
+
# Override length:
|
628
|
+
applied_length = 235
|
629
|
+
ind = Array.new(applied_length - NR_SURPLUS_ATTRIBUTES) { |i| [i] }
|
630
|
+
# Override indices for mlc_pl_a and mlc_lp_b:
|
631
|
+
# Allocation here is dependent on the RTP file version:
|
632
|
+
# For 2.62 and earlier, where length is 232, we dont have the 3 iso_pos_x/y/z values preceeding the mlc arrays leaf position arrays.
|
633
|
+
# For 2.64 (and later), where length is 235, we have the 3 iso_pos_x/y/z values preceeding the mlc leaf position arrays.
|
634
|
+
if length == 232
|
635
|
+
ind[32] = nil
|
636
|
+
ind[33] = nil
|
637
|
+
ind[34] = nil
|
638
|
+
ind[35] = (32..131).to_a
|
639
|
+
ind[36] = (132..231).to_a
|
640
|
+
else # (length = 235)
|
641
|
+
ind[35] = (35..134).to_a
|
642
|
+
ind[36] = (135..234).to_a
|
643
|
+
end
|
644
|
+
ind
|
605
645
|
end
|
606
646
|
|
607
647
|
end
|