rtp-connect 1.6 → 1.7
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 +15 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +29 -14
- data/Gemfile.lock +27 -21
- data/{README.rdoc → README.md} +62 -50
- data/lib/rtp-connect.rb +1 -0
- data/lib/rtp-connect/constants.rb +1 -0
- data/lib/rtp-connect/control_point.rb +142 -100
- data/lib/rtp-connect/dose_tracking.rb +31 -36
- data/lib/rtp-connect/extended_field.rb +15 -51
- data/lib/rtp-connect/extended_plan.rb +133 -0
- data/lib/rtp-connect/field.rb +101 -128
- data/lib/rtp-connect/methods.rb +31 -16
- data/lib/rtp-connect/plan.rb +80 -98
- data/lib/rtp-connect/plan_to_dcm.rb +68 -106
- data/lib/rtp-connect/prescription.rb +18 -56
- data/lib/rtp-connect/record.rb +62 -1
- data/lib/rtp-connect/ruby_extensions.rb +34 -3
- data/lib/rtp-connect/simulation_field.rb +58 -136
- data/lib/rtp-connect/site_setup.rb +51 -62
- data/lib/rtp-connect/version.rb +1 -1
- data/rakefile.rb +0 -1
- data/rtp-connect.gemspec +7 -7
- metadata +51 -41
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZDIyZThkNzk2OGI3OTdlMjY5MDk4MDQ2YmU5ODBhMTk3NTM2NzUwNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MzdjNjVjMTAwYjc2Yzg5YzI5ZTI1YjBiY2ZlNDA0MmIyYjE4MGNiNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzZlMGZmMDY3ODVkNjgyOGE2ZTMzMzZkNWMzMWUxMzU4MTk0NGIzY2UzZTYx
|
10
|
+
Y2RhODM1NDdlNzE0ZDZhN2JkOWRlYmZhZWUzNzI2MmQ5YzIwOTE2ZjYwZmYw
|
11
|
+
ZDZhZmRjZDc3NzAxNTU4ZDRiODdmYmEzMmRiMGYwMDI5MTRmNTY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTYwNjBkOTI2NjNkOGFiNjdmMDJiMjU5NzNjZmNhOTQyYjVlNTFhNWI5NTIy
|
14
|
+
MTFmNWNhMTlmMzFmZjkxYjZlZTI4ZTVkNjA2OTkxYzY3ZTM0OGY2ZjRjZWY4
|
15
|
+
ODRhNDRiYTExODllYzQ1MWIyOTU5MWE0ZDYxYzNjMWJkOWQ2ZWM=
|
@@ -1,6 +1,21 @@
|
|
1
|
-
|
1
|
+
# 1.7
|
2
2
|
|
3
|
-
|
3
|
+
## 17th September 2014
|
4
|
+
|
5
|
+
* Added a repair option for trying to fix records containing invalid CSV format (i.e. unescaped " characters).
|
6
|
+
* Added an option for skipping unknown record types when reading an RTPConnect file (instead of raising an error).
|
7
|
+
* Fixed an issue with writing records containing attributes of mixed encoding.
|
8
|
+
* Added attributes for table top displacements in the Site Setup record (Mosaiq 2.6).
|
9
|
+
* Upgraded test suite for Rspec 3.
|
10
|
+
* Added support for the Extended Plan Definition record (Mosaiq 2.5).
|
11
|
+
* Added option for ignoring invalid checksums.
|
12
|
+
* Switched from RDoc to Markdown format.
|
13
|
+
* Plan#to_dcm improvements.
|
14
|
+
|
15
|
+
|
16
|
+
# 1.6
|
17
|
+
|
18
|
+
## 12th December, 2013
|
4
19
|
|
5
20
|
* Plan#to_dcm improvements:
|
6
21
|
* Added support for VMAT by improving the handling of control point conversion.
|
@@ -17,9 +32,9 @@
|
|
17
32
|
* Make sure that the last cumulative meterset weight exactly equals the final cumulative meterset weight.
|
18
33
|
|
19
34
|
|
20
|
-
|
35
|
+
# 1.5
|
21
36
|
|
22
|
-
|
37
|
+
## 24th October, 2013
|
23
38
|
|
24
39
|
* Added support for the Simulation Field record.
|
25
40
|
* Bumped required Ruby version to 1.9.3.
|
@@ -42,25 +57,25 @@
|
|
42
57
|
* Device Serial Number
|
43
58
|
|
44
59
|
|
45
|
-
|
60
|
+
# 1.4
|
46
61
|
|
47
|
-
|
62
|
+
## 10th April, 2013
|
48
63
|
|
49
64
|
* Support an extended ascii character set (ISO8859-1 encoding) for record values and file read/write.
|
50
65
|
|
51
66
|
|
52
|
-
|
67
|
+
# 1.3
|
53
68
|
|
54
|
-
|
69
|
+
## 12th October, 2012
|
55
70
|
|
56
71
|
* Added support for the updated ExtendedField record values introduced in Mosaiq 2.4.
|
57
72
|
* Simply log a warning instead of raising an exception when reading a record with more values than excpected.
|
58
73
|
* Allow reading (incomplete) records that contain the required values but not all the optional ones (instead of raising an exception).
|
59
74
|
|
60
75
|
|
61
|
-
|
76
|
+
# 1.2
|
62
77
|
|
63
|
-
|
78
|
+
## 13th July, 2012
|
64
79
|
|
65
80
|
* Converted documentation format from RDoc to YARD.
|
66
81
|
* Added support for the Dose Tracking record.
|
@@ -68,9 +83,9 @@
|
|
68
83
|
* Added the to_dcm conversion method for converting from RTP to DICOM.
|
69
84
|
|
70
85
|
|
71
|
-
|
86
|
+
# 1.1
|
72
87
|
|
73
|
-
|
88
|
+
## 18th April, 2012
|
74
89
|
|
75
90
|
* Added to_* methods for all records to complete the implementation of dynamic typing
|
76
91
|
* Added comparison related methods to the record classes: #==, #eql? and #hash
|
@@ -80,9 +95,9 @@
|
|
80
95
|
* Fixed an issue where reading a string with a single digit checksum would fail.
|
81
96
|
|
82
97
|
|
83
|
-
|
98
|
+
# 1.0
|
84
99
|
|
85
|
-
|
100
|
+
## 29th December, 2011
|
86
101
|
|
87
102
|
First public release.
|
88
103
|
The library, although missing support for a number of record types, should be usable
|
data/Gemfile.lock
CHANGED
@@ -1,35 +1,41 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rtp-connect (1.
|
4
|
+
rtp-connect (1.7)
|
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.6)
|
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 (10.3.2)
|
15
|
+
redcarpet (3.1.2)
|
16
|
+
rspec (3.0.0)
|
17
|
+
rspec-core (~> 3.0.0)
|
18
|
+
rspec-expectations (~> 3.0.0)
|
19
|
+
rspec-mocks (~> 3.0.0)
|
20
|
+
rspec-core (3.0.4)
|
21
|
+
rspec-support (~> 3.0.0)
|
22
|
+
rspec-expectations (3.0.4)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.0.0)
|
25
|
+
rspec-mocks (3.0.4)
|
26
|
+
rspec-support (~> 3.0.0)
|
27
|
+
rspec-support (3.0.4)
|
28
|
+
yard (0.8.7.3)
|
24
29
|
|
25
30
|
PLATFORMS
|
26
31
|
x86-mingw32
|
27
32
|
|
28
33
|
DEPENDENCIES
|
29
|
-
bundler (~> 1.
|
30
|
-
dicom (~> 0.9.
|
31
|
-
mocha (~>
|
32
|
-
rake (~>
|
33
|
-
|
34
|
+
bundler (~> 1.6)
|
35
|
+
dicom (~> 0.9, >= 0.9.6)
|
36
|
+
mocha (~> 1.1)
|
37
|
+
rake (~> 10.3)
|
38
|
+
redcarpet (~> 3.1)
|
39
|
+
rspec (~> 3.0)
|
34
40
|
rtp-connect!
|
35
|
-
yard (~> 0.8.
|
41
|
+
yard (~> 0.8, >= 0.8.7)
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# RTPConnect
|
2
2
|
|
3
3
|
The RTPConnect library allows you to read, edit and write RTPConnect files in Ruby.
|
4
4
|
RTPConnect is a file format used in radiotherapy (e.g. Mosaiq) for export & import
|
@@ -6,67 +6,78 @@ of treatment planning data. The library is written entirely in Ruby and has no
|
|
6
6
|
external dependencies.
|
7
7
|
|
8
8
|
|
9
|
-
|
9
|
+
## INSTALLATION
|
10
10
|
|
11
11
|
gem install rtp-connect
|
12
12
|
|
13
13
|
|
14
|
-
|
14
|
+
## REQUIREMENTS
|
15
15
|
|
16
16
|
* Ruby 1.9.3 (or higher)
|
17
17
|
|
18
18
|
|
19
|
-
|
19
|
+
## BASIC USAGE
|
20
20
|
|
21
|
-
|
21
|
+
### Load & Include
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
require 'rtp-connect'
|
24
|
+
include RTP
|
25
25
|
|
26
|
-
|
26
|
+
### Read, modify and write
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
# Read file:
|
29
|
+
rtp = Plan.read('some_file.rtp')
|
30
|
+
# Extract the Patient's Name:
|
31
|
+
name = rtp.patient_last_name
|
32
|
+
# Modify the Patient's Name:
|
33
|
+
rtp.patient_last_name = 'Anonymous'
|
34
|
+
# Write to file:
|
35
|
+
rtp.write('new_file.rtp')
|
36
36
|
|
37
|
-
|
37
|
+
### Create a new Plan Definition Record from scratch
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# Create the instance:
|
40
|
+
rtp = Plan.new
|
41
|
+
# Set the Patient's ID attribute:
|
42
|
+
rtp.patient_id = '12345'
|
43
|
+
# Export the instance to an RTP string (with CRC):
|
44
|
+
output = rtp.to_s
|
45
45
|
|
46
|
-
|
46
|
+
### Fix invalid RTP files:
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
# Read an RTP file containing invalid checksum(s):
|
49
|
+
rtp = Plan.read('invalid_crc.rtp', ignore_crc: true)
|
50
|
+
# Read an RTP file containing unknown record type(s):
|
51
|
+
rtp = Plan.read('custom.rtp', skip_unknown: true)
|
52
|
+
# Read an RTP file containing invalid CSV format:
|
53
|
+
rtp = Plan.read('invalid_csv.rtp', repair: true)
|
54
|
+
# Write a corrected RTP file:
|
55
|
+
rtp.write('valid.rtp')
|
51
56
|
|
52
|
-
|
57
|
+
### Convert an RTP file to DICOM:
|
53
58
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
l = Logger.new('my_logfile.log')
|
58
|
-
RTP.logger = l
|
59
|
-
# Create a logger which ages logfile daily/monthly:
|
60
|
-
RTP.logger = Logger.new('foo.log', 'daily')
|
61
|
-
RTP.logger = Logger.new('foo.log', 'monthly')
|
59
|
+
p = Plan.read('some_file.rtp')
|
60
|
+
dcm = p.to_dcm
|
61
|
+
dcm.write('rtplan.dcm')
|
62
62
|
|
63
|
-
|
63
|
+
### Log settings
|
64
|
+
|
65
|
+
# Change the log level so that only error messages are displayed:
|
66
|
+
RTP.logger.level = Logger::ERROR
|
67
|
+
# Setting up a simple file log:
|
68
|
+
l = Logger.new('my_logfile.log')
|
69
|
+
RTP.logger = l
|
70
|
+
# Create a logger which ages logfile daily/monthly:
|
71
|
+
RTP.logger = Logger.new('foo.log', 'daily')
|
72
|
+
RTP.logger = Logger.new('foo.log', 'monthly')
|
73
|
+
|
74
|
+
### Scripts
|
64
75
|
|
65
76
|
For more comprehensive and useful examples, check out the scripts folder
|
66
77
|
which contains various Ruby scripts that intends to show off real world
|
67
78
|
usage scenarios of the RTPConnect library.
|
68
79
|
|
69
|
-
|
80
|
+
### IRB Tip
|
70
81
|
|
71
82
|
When working with the RTPConnect library in irb, you may be annoyed with all
|
72
83
|
the information that is printed to screen, regardless of your log level.
|
@@ -75,21 +86,23 @@ automatically printed to the screen. A useful hack to avoid this effect is
|
|
75
86
|
to append ";0" after a command.
|
76
87
|
|
77
88
|
Example:
|
78
|
-
|
89
|
+
|
90
|
+
rtp = Plan.read('some_file.rtp') ;0
|
79
91
|
|
80
92
|
|
81
|
-
|
93
|
+
## RESOURCES
|
82
94
|
|
83
|
-
*
|
84
|
-
*
|
85
|
-
*
|
95
|
+
* [Rubygems download](https://rubygems.org/gems/rtp-connect)
|
96
|
+
* [Documentation](http://rubydoc.info/gems/rtp-connect/frames)
|
97
|
+
* [Source code repository](https://github.com/dicom/rtp-connect)
|
86
98
|
|
87
99
|
|
88
|
-
|
100
|
+
## RESTRICTIONS
|
89
101
|
|
90
|
-
|
102
|
+
### Supported records
|
91
103
|
|
92
104
|
* Plan definition [PLAN_DEF]
|
105
|
+
* Extended plan definition [EXTENDED_PLAN_DEF]
|
93
106
|
* Prescription site [RX_DEF]
|
94
107
|
* Site setup [SITE_SETUP_DEF]
|
95
108
|
* Simulation field [SIM_DEF]
|
@@ -98,9 +111,8 @@ Example:
|
|
98
111
|
* Control point record [CONTROL_PT_DEF]
|
99
112
|
* Dose tracking record [DOSE_DEF]
|
100
113
|
|
101
|
-
|
114
|
+
### Unsupported records
|
102
115
|
|
103
|
-
* Extended plan definition [EXTENDED_PLAN_DEF]
|
104
116
|
* Document based treatment field [PDF_FIELD_DEF]
|
105
117
|
* Multileaf collimator [MLC_DEF]
|
106
118
|
* MLC shape [MLC_SHAPE_DEF]
|
@@ -109,9 +121,9 @@ Example:
|
|
109
121
|
If you encounter an RTP file with an unsupported record type, please contact me.
|
110
122
|
|
111
123
|
|
112
|
-
|
124
|
+
## COPYRIGHT
|
113
125
|
|
114
|
-
Copyright 2011-
|
126
|
+
Copyright 2011-2014 Christoffer Lervåg
|
115
127
|
|
116
128
|
This program is free software: you can redistribute it and/or modify
|
117
129
|
it under the terms of the GNU General Public License as published by
|
@@ -127,7 +139,7 @@ You should have received a copy of the GNU General Public License
|
|
127
139
|
along with this program. If not, see http://www.gnu.org/licenses/ .
|
128
140
|
|
129
141
|
|
130
|
-
|
142
|
+
## ABOUT THE AUTHOR
|
131
143
|
|
132
144
|
* Name: Christoffer Lervåg
|
133
145
|
* Location: Norway
|
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'
|
@@ -42,6 +42,7 @@ module RTP
|
|
42
42
|
# Pairs of RTPConnect keywords and parse method names.
|
43
43
|
PARSE_METHOD = {
|
44
44
|
"PLAN_DEF" => :plan_definition,
|
45
|
+
"EXTENDED_PLAN_DEF" => :extended_plan,
|
45
46
|
"RX_DEF" => :prescription_site,
|
46
47
|
"SITE_SETUP_DEF" => :site_setup,
|
47
48
|
"SIM_DEF" => :simulation_field,
|
@@ -56,50 +56,8 @@ module RTP
|
|
56
56
|
# @raise [ArgumentError] if given a string containing an invalid number of elements
|
57
57
|
#
|
58
58
|
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
59
|
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
|
60
|
+
cp.load(string)
|
103
61
|
end
|
104
62
|
|
105
63
|
# Creates a new ControlPoint.
|
@@ -107,14 +65,51 @@ module RTP
|
|
107
65
|
# @param [Record] parent a record which is used to determine the proper parent of this instance
|
108
66
|
#
|
109
67
|
def initialize(parent)
|
68
|
+
super('CONTROL_PT_DEF', 233, 233)
|
110
69
|
# Child:
|
111
70
|
@mlc_shape = nil
|
112
71
|
# Parent relation (may get more than one type of record here):
|
113
72
|
@parent = get_parent(parent.to_record, Field)
|
114
73
|
@parent.add_control_point(self)
|
115
|
-
@keyword = 'CONTROL_PT_DEF'
|
116
74
|
@mlc_lp_a = Array.new(100)
|
117
75
|
@mlc_lp_b = Array.new(100)
|
76
|
+
@attributes = [
|
77
|
+
# Required:
|
78
|
+
:keyword,
|
79
|
+
:field_id,
|
80
|
+
:mlc_type,
|
81
|
+
:mlc_leaves,
|
82
|
+
:total_control_points,
|
83
|
+
:control_pt_number,
|
84
|
+
:mu_convention,
|
85
|
+
:monitor_units,
|
86
|
+
:wedge_position,
|
87
|
+
:energy,
|
88
|
+
:doserate,
|
89
|
+
:ssd,
|
90
|
+
:scale_convention,
|
91
|
+
:gantry_angle,
|
92
|
+
:gantry_dir,
|
93
|
+
:collimator_angle,
|
94
|
+
:collimator_dir,
|
95
|
+
:field_x_mode,
|
96
|
+
:field_x,
|
97
|
+
:collimator_x1,
|
98
|
+
:collimator_x2,
|
99
|
+
:field_y_mode,
|
100
|
+
:field_y,
|
101
|
+
:collimator_y1,
|
102
|
+
:collimator_y2,
|
103
|
+
:couch_vertical,
|
104
|
+
:couch_lateral,
|
105
|
+
:couch_longitudinal,
|
106
|
+
:couch_angle,
|
107
|
+
:couch_dir,
|
108
|
+
:couch_pedestal,
|
109
|
+
:couch_ped_dir,
|
110
|
+
:mlc_lp_a,
|
111
|
+
:mlc_lp_b
|
112
|
+
]
|
118
113
|
end
|
119
114
|
|
120
115
|
# Checks for equality.
|
@@ -145,51 +140,67 @@ module RTP
|
|
145
140
|
|
146
141
|
# Converts the collimator_x1 attribute to proper DICOM format.
|
147
142
|
#
|
143
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from a native readout format to IEC1217 (supported values are :elekta & :varian)
|
148
144
|
# @return [Float] the DICOM-formatted collimator_x1 attribute
|
149
145
|
#
|
150
|
-
def dcm_collimator_x1
|
151
|
-
|
152
|
-
|
153
|
-
|
146
|
+
def dcm_collimator_x1(scale=nil)
|
147
|
+
coeff = 1
|
148
|
+
axis = :x
|
149
|
+
if scale == :elekta
|
150
|
+
axis = :y
|
151
|
+
coeff = -1
|
152
|
+
elsif scale == :varian
|
153
|
+
coeff = -1
|
154
|
+
end
|
155
|
+
dcm_collimator1(axis, coeff)
|
154
156
|
end
|
155
157
|
|
156
158
|
# Converts the collimator_x2 attribute to proper DICOM format.
|
157
159
|
#
|
160
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
158
161
|
# @return [Float] the DICOM-formatted collimator_x2 attribute
|
159
162
|
#
|
160
|
-
def dcm_collimator_x2
|
161
|
-
|
162
|
-
|
163
|
-
target.send(attribute).to_f * 10
|
163
|
+
def dcm_collimator_x2(scale=nil)
|
164
|
+
axis = (scale == :elekta ? :y : :x)
|
165
|
+
dcm_collimator2(axis, coeff=1)
|
164
166
|
end
|
165
167
|
|
166
168
|
# Converts the collimator_y1 attribute to proper DICOM format.
|
167
169
|
#
|
170
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
168
171
|
# @return [Float] the DICOM-formatted collimator_y1 attribute
|
169
172
|
#
|
170
|
-
def dcm_collimator_y1
|
171
|
-
|
172
|
-
|
173
|
-
|
173
|
+
def dcm_collimator_y1(scale=nil)
|
174
|
+
coeff = 1
|
175
|
+
axis = :y
|
176
|
+
if scale == :elekta
|
177
|
+
axis = :x
|
178
|
+
coeff = -1
|
179
|
+
elsif scale == :varian
|
180
|
+
coeff = -1
|
181
|
+
end
|
182
|
+
dcm_collimator1(axis, coeff)
|
174
183
|
end
|
175
184
|
|
176
185
|
# Converts the collimator_y2 attribute to proper DICOM format.
|
177
186
|
#
|
187
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
178
188
|
# @return [Float] the DICOM-formatted collimator_y2 attribute
|
179
189
|
#
|
180
|
-
def dcm_collimator_y2
|
181
|
-
|
182
|
-
|
183
|
-
target.send(attribute).to_f * 10
|
190
|
+
def dcm_collimator_y2(scale=nil)
|
191
|
+
axis = (scale == :elekta ? :x : :y)
|
192
|
+
dcm_collimator2(axis, coeff=1)
|
184
193
|
end
|
185
194
|
|
186
195
|
# Converts the mlc_lp_a & mlc_lp_b attributes to a proper DICOM formatted string.
|
187
196
|
#
|
197
|
+
# @param [Symbol] scale if set, relevant device parameters are converted from native readout format to IEC1217 (supported values are :elekta & :varian)
|
188
198
|
# @return [String] the DICOM-formatted leaf pair positions
|
189
199
|
#
|
190
|
-
def dcm_mlc_positions
|
200
|
+
def dcm_mlc_positions(scale=nil)
|
201
|
+
coeff = (scale == :elekta ? -1 : 1)
|
191
202
|
# 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 *
|
203
|
+
pos_a = @mlc_lp_a.collect{|p| (p.to_f * 10 * coeff).round(1) unless p.empty?}.compact
|
193
204
|
pos_b = @mlc_lp_b.collect{|p| (p.to_f * 10).round(1) unless p.empty?}.compact
|
194
205
|
(pos_a + pos_b).join("\\")
|
195
206
|
end
|
@@ -219,7 +230,7 @@ module RTP
|
|
219
230
|
# @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
|
220
231
|
#
|
221
232
|
def values
|
222
|
-
|
233
|
+
[
|
223
234
|
@keyword,
|
224
235
|
@field_id,
|
225
236
|
@mlc_type,
|
@@ -289,9 +300,7 @@ module RTP
|
|
289
300
|
# @param [Array<nil, #to_s>] array the new attribute values
|
290
301
|
#
|
291
302
|
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}
|
303
|
+
@mlc_lp_a = array.to_a.validate_and_process(100)
|
295
304
|
end
|
296
305
|
|
297
306
|
# Sets the mlc_lp_b attribute.
|
@@ -301,21 +310,7 @@ module RTP
|
|
301
310
|
# @param [Array<nil, #to_s>] array the new attribute values
|
302
311
|
#
|
303
312
|
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
|
313
|
+
@mlc_lp_b = array.to_a.validate_and_process(100)
|
319
314
|
end
|
320
315
|
|
321
316
|
# Sets the field_id attribute.
|
@@ -577,31 +572,78 @@ module RTP
|
|
577
572
|
#
|
578
573
|
alias_method :state, :values
|
579
574
|
|
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).
|
575
|
+
# Converts the collimator attribute to proper DICOM format.
|
586
576
|
#
|
587
|
-
# @
|
577
|
+
# @param [Symbol] axis a representation for the axis of interest (x or y)
|
578
|
+
# @param [Integer] coeff a coeffecient (of -1 or 1) which the attribute is multiplied with
|
579
|
+
# @return [Float] the DICOM-formatted collimator attribute
|
588
580
|
#
|
589
|
-
def
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
581
|
+
def dcm_collimator1(axis, coeff)
|
582
|
+
mode = self.send("field_#{axis}_mode")
|
583
|
+
if mode && !mode.empty?
|
584
|
+
target = self
|
585
|
+
else
|
586
|
+
target = @parent
|
587
|
+
end
|
588
|
+
target.send("collimator_#{axis}1").to_f * 10 * coeff
|
595
589
|
end
|
596
590
|
|
597
|
-
#
|
598
|
-
# 'scale_convention' attribute.
|
591
|
+
# Converts the collimator attribute to proper DICOM format.
|
599
592
|
#
|
600
|
-
# @param [
|
601
|
-
# @
|
593
|
+
# @param [Symbol] axis a representation for the axis of interest (x or y)
|
594
|
+
# @param [Integer] coeff a coeffecient (of -1 or 1) which the attribute is multiplied with
|
595
|
+
# @return [Float] the DICOM-formatted collimator attribute
|
602
596
|
#
|
603
|
-
def
|
604
|
-
|
597
|
+
def dcm_collimator2(axis, coeff)
|
598
|
+
mode = self.send("field_#{axis}_mode")
|
599
|
+
if mode && !mode.empty?
|
600
|
+
target = self
|
601
|
+
else
|
602
|
+
target = @parent
|
603
|
+
end
|
604
|
+
target.send("collimator_#{axis}2").to_f * 10 * coeff
|
605
|
+
end
|
606
|
+
|
607
|
+
# Sets the attributes of the record instance.
|
608
|
+
#
|
609
|
+
# @param [Array<String>] values the record attributes (as parsed from a record string)
|
610
|
+
#
|
611
|
+
def set_attributes(values)
|
612
|
+
self.keyword = values[0]
|
613
|
+
@field_id = values[1]
|
614
|
+
@mlc_type = values[2]
|
615
|
+
@mlc_leaves = values[3]
|
616
|
+
@total_control_points = values[4]
|
617
|
+
@control_pt_number = values[5]
|
618
|
+
@mu_convention = values[6]
|
619
|
+
@monitor_units = values[7]
|
620
|
+
@wedge_position = values[8]
|
621
|
+
@energy = values[9]
|
622
|
+
@doserate = values[10]
|
623
|
+
@ssd = values[11]
|
624
|
+
@scale_convention = values[12]
|
625
|
+
@gantry_angle = values[13]
|
626
|
+
@gantry_dir = values[14]
|
627
|
+
@collimator_angle = values[15]
|
628
|
+
@collimator_dir = values[16]
|
629
|
+
@field_x_mode = values[17]
|
630
|
+
@field_x = values[18]
|
631
|
+
@collimator_x1 = values[19]
|
632
|
+
@collimator_x2 = values[20]
|
633
|
+
@field_y_mode = values[21]
|
634
|
+
@field_y = values[22]
|
635
|
+
@collimator_y1 = values[23]
|
636
|
+
@collimator_y2 = values[24]
|
637
|
+
@couch_vertical = values[25]
|
638
|
+
@couch_lateral = values[26]
|
639
|
+
@couch_longitudinal = values[27]
|
640
|
+
@couch_angle = values[28]
|
641
|
+
@couch_dir = values[29]
|
642
|
+
@couch_pedestal = values[30]
|
643
|
+
@couch_ped_dir = values[31]
|
644
|
+
@mlc_lp_a = [*values[32..131]]
|
645
|
+
@mlc_lp_b = [*values[132..231]]
|
646
|
+
@crc = values[-1]
|
605
647
|
end
|
606
648
|
|
607
649
|
end
|