rtp-connect 1.6 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|