dicom 0.9.3 → 0.9.4

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.
@@ -1,359 +1,329 @@
1
- module DICOM
2
-
3
- # This class contains code for setting up a Service Class Provider (SCP),
4
- # which will act as a simple storage node (a DICOM server that receives images).
5
- #
6
-
7
- class DServer
8
- include Logging
9
-
10
- # Runs the server and takes a block for initializing.
11
- #
12
- # === Parameters
13
- #
14
- # * <tt>port</tt> -- Fixnum. The network port to be used. Defaults to 104.
15
- # * <tt>path</tt> -- String. The path where incoming DICOM files will be stored. Defaults to "./received/".
16
- # * <tt>&block</tt> -- A block of code that will be run on the DServer instance, between creation and the launch of the SCP itself.
17
- #
18
- # === Examples
19
- #
20
- # require 'dicom'
21
- # require 'my_file_handler'
22
- # include DICOM
23
- # DServer.run(104, 'c:/temp/') do |s|
24
- # s.timeout = 100
25
- # s.file_handler = MyFileHandler
26
- # end
27
- #
28
- def self.run(port=104, path='./received/', &block)
29
- server = DServer.new(port)
30
- server.instance_eval(&block)
31
- server.start_scp(path)
32
- end
33
-
34
- # A customized FileHandler class to use instead of the default FileHandler included with Ruby DICOM.
35
- attr_accessor :file_handler
36
- # The hostname that the TCPServer binds to.
37
- attr_accessor :host
38
- # The name of the server (application entity).
39
- attr_accessor :host_ae
40
- # The maximum allowed size of network packages (in bytes).
41
- attr_accessor :max_package_size
42
- # The network port to be used.
43
- attr_accessor :port
44
- # The maximum period the server will wait on an answer from a client before aborting the communication.
45
- attr_accessor :timeout
46
-
47
- # A hash containing the abstract syntaxes that will be accepted.
48
- attr_reader :accepted_abstract_syntaxes
49
- # A hash containing the transfer syntaxes that will be accepted.
50
- attr_reader :accepted_transfer_syntaxes
51
-
52
- # Creates a DServer instance.
53
- #
54
- # === Notes
55
- #
56
- # * To customize logging behaviour, refer to the Logging module documentation.
57
- #
58
- # === Parameters
59
- #
60
- # * <tt>port</tt> -- Fixnum. The network port to be used. Defaults to 104.
61
- # * <tt>options</tt> -- A hash of parameters.
62
- #
63
- # === Options
64
- #
65
- # * <tt>:file_handler</tt> -- A customized FileHandler class to use instead of the default FileHandler.
66
- # * <tt>:host</tt> -- String. The hostname that the TCPServer binds to. Defaults to '127.0.0.1'.
67
- # * <tt>:host_ae</tt> -- String. The name of the server (application entity).
68
- # * <tt>:max_package_size</tt> -- Fixnum. The maximum allowed size of network packages (in bytes).
69
- # * <tt>:timeout</tt> -- Fixnum. The maximum period the server will wait on an answer from a client before aborting the communication.
70
- #
71
- # === Examples
72
- #
73
- # # Create a server using default settings:
74
- # s = DICOM::DServer.new
75
- # # Create a server and specify a host name as well as a custom buildt file handler:
76
- # require 'MyFileHandler'
77
- # server = DICOM::DServer.new(104, :host_ae => "RUBY_SERVER", :file_handler => DICOM::MyFileHandler)
78
- #
79
- def initialize(port=104, options={})
80
- require 'socket'
81
- # Required parameters:
82
- @port = port
83
- # Optional parameters (and default values):
84
- @file_handler = options[:file_handler] || FileHandler
85
- @host = options[:host] || '127.0.0.1'
86
- @host_ae = options[:host_ae] || "RUBY_DICOM"
87
- @max_package_size = options[:max_package_size] || 32768 # 16384
88
- @timeout = options[:timeout] || 10 # seconds
89
- @min_length = 12 # minimum number of bytes to expect in an incoming transmission
90
- # Variables used for monitoring state of transmission:
91
- @connection = nil # TCP connection status
92
- @association = nil # DICOM Association status
93
- @request_approved = nil # Status of our DICOM request
94
- @release = nil # Status of received, valid release response
95
- set_default_accepted_syntaxes
96
- end
97
-
98
- # Adds an abstract syntax to the list of abstract syntaxes that the server will accept.
99
- #
100
- # === Parameters
101
- #
102
- # * <tt>uid</tt> -- An abstract syntax UID string.
103
- #
104
- def add_abstract_syntax(uid)
105
- if uid.is_a?(String)
106
- name = LIBRARY.get_syntax_description(uid) || "Unknown UID"
107
- @accepted_abstract_syntaxes[uid] = name
108
- else
109
- raise "Invalid type of UID. Expected String, got #{uid.class}!"
110
- end
111
- end
112
-
113
- # Adds a transfer syntax to the list of transfer syntaxes that the server will accept.
114
- #
115
- #
116
- # === Parameters
117
- #
118
- # * <tt>uid</tt> -- A transfer syntax UID string.
119
- #
120
- def add_transfer_syntax(uid)
121
- if uid.is_a?(String)
122
- name = LIBRARY.get_syntax_description(uid) || "Unknown UID"
123
- @accepted_transfer_syntaxes[uid] = name
124
- else
125
- raise "Invalid type of UID. Expected String, got #{uid.class}!"
126
- end
127
- end
128
-
129
- # Prints the list of accepted abstract syntaxes to the screen.
130
- #
131
- def print_abstract_syntaxes
132
- # Determine length of longest key to ensure pretty print:
133
- max_uid = @accepted_abstract_syntaxes.keys.collect{|k| k.length}.max
134
- puts "Abstract syntaxes which are accepted by this SCP:"
135
- @accepted_abstract_syntaxes.sort.each do |pair|
136
- puts "#{pair[0]}#{' '*(max_uid-pair[0].length)} #{pair[1]}"
137
- end
138
- end
139
-
140
- # Prints the list of accepted transfer syntaxes to the screen.
141
- #
142
- def print_transfer_syntaxes
143
- # Determine length of longest key to ensure pretty print:
144
- max_uid = @accepted_transfer_syntaxes.keys.collect{|k| k.length}.max
145
- puts "Transfer syntaxes which are accepted by this SCP:"
146
- @accepted_transfer_syntaxes.sort.each do |pair|
147
- puts "#{pair[0]}#{' '*(max_uid-pair[0].length)} #{pair[1]}"
148
- end
149
- end
150
-
151
- # Deletes a specific abstract syntax from the list of abstract syntaxes that the server will accept.
152
- #
153
- #
154
- # === Parameters
155
- #
156
- # * <tt>uid</tt> -- An abstract syntax UID string.
157
- #
158
- def delete_abstract_syntax(uid)
159
- if uid.is_a?(String)
160
- @accepted_abstract_syntaxes.delete(uid)
161
- else
162
- raise "Invalid type of UID. Expected String, got #{uid.class}!"
163
- end
164
- end
165
-
166
- # Deletes a specific transfer syntax from the list of transfer syntaxes that the server will accept.
167
- #
168
- # === Parameters
169
- #
170
- # * <tt>uid</tt> -- A transfer syntax UID string.
171
- #
172
- def delete_transfer_syntax(uid)
173
- if uid.is_a?(String)
174
- @accepted_transfer_syntaxes.delete(uid)
175
- else
176
- raise "Invalid type of UID. Expected String, got #{uid.class}!"
177
- end
178
- end
179
-
180
- # Completely clears the list of abstract syntaxes that the server will accept.
181
- #
182
- # === Notes
183
- #
184
- # * Following such a clearance, the user must ensure to add the specific abstract syntaxes that are to be accepted by the server.
185
- #
186
- def clear_abstract_syntaxes
187
- @accepted_abstract_syntaxes = Hash.new
188
- end
189
-
190
- # Completely clears the list of transfer syntaxes that the server will accept.
191
- #
192
- # === Notes
193
- #
194
- # * Following such a clearance, the user must ensure to add the specific transfer syntaxes that are to be accepted by the server.
195
- #
196
- def clear_transfer_syntaxes
197
- @accepted_transfer_syntaxes = Hash.new
198
- end
199
-
200
- # Starts the Service Class Provider (SCP).
201
- #
202
- # === Notes
203
- #
204
- # * This service acts as a simple storage node, which receives DICOM files and stores them in a specified folder.
205
- # * Customized storage actions can be set my modifying or replacing the FileHandler class.
206
- #
207
- # === Parameters
208
- #
209
- # * <tt>path</tt> -- The path where incoming files are to be saved.
210
- #
211
- def start_scp(path='./received/')
212
- if @accepted_abstract_syntaxes.size > 0 and @accepted_transfer_syntaxes.size > 0
213
- logger.info("Started DICOM SCP server on port #{@port}.")
214
- logger.info("Waiting for incoming transmissions...\n\n")
215
- # Initiate server:
216
- @scp = TCPServer.new(@host, @port)
217
- # Use a loop to listen for incoming messages:
218
- loop do
219
- Thread.start(@scp.accept) do |session|
220
- # Initialize the network package handler for this session:
221
- link = Link.new(:host_ae => @host_ae, :max_package_size => @max_package_size, :timeout => @timeout, :file_handler => @file_handler)
222
- link.set_session(session)
223
- # Note who has contacted us:
224
- logger.info("Connection established with: #{session.peeraddr[2]} (IP: #{session.peeraddr[3]})")
225
- # Receive an incoming message:
226
- segments = link.receive_multiple_transmissions
227
- info = segments.first
228
- # Interpret the received message:
229
- if info[:valid]
230
- association_error = check_association_request(info)
231
- unless association_error
232
- info, approved, rejected = process_syntax_requests(info)
233
- link.handle_association_accept(info)
234
- if approved > 0
235
- if approved == 1
236
- logger.info("Accepted the association request with context: #{LIBRARY.get_syntax_description(info[:pc].first[:abstract_syntax])}")
237
- else
238
- if rejected == 0
239
- logger.info("Accepted all #{approved} proposed contexts in the association request.")
240
- else
241
- logger.warn("Accepted only #{approved} of #{approved+rejected} of the proposed contexts in the association request.")
242
- end
243
- end
244
- # Process the incoming data. This method will also take care of releasing the association:
245
- success, messages = link.handle_incoming_data(path)
246
- # Pass along any messages that has been recorded:
247
- messages.each { |m| logger.public_send(m.first, m.last) } if messages.first
248
- else
249
- # No abstract syntaxes in the incoming request were accepted:
250
- if rejected == 1
251
- logger.warn("Rejected the association request with proposed context: #{LIBRARY.get_syntax_description(info[:pc].first[:abstract_syntax])}")
252
- else
253
- logger.warn("Rejected all #{rejected} proposed contexts in the association request.")
254
- end
255
- # Since the requested abstract syntax was not accepted, the association must be released.
256
- link.await_release
257
- end
258
- else
259
- # The incoming association was not formally correct.
260
- link.handle_rejection
261
- end
262
- else
263
- # The incoming message was not recognised as a valid DICOM message. Abort:
264
- link.handle_abort
265
- end
266
- # Terminate the connection:
267
- link.stop_session
268
- logger.info("Connection closed.\n\n")
269
- end
270
- end
271
- else
272
- raise "Unable to start SCP server as no accepted abstract syntaxes have been set!" if @accepted_abstract_syntaxes.length == 0
273
- raise "Unable to start SCP server as no accepted transfer syntaxes have been set!" if @accepted_transfer_syntaxes.length == 0
274
- end
275
- end
276
-
277
-
278
- # Following methods are private:
279
- private
280
-
281
- # Checks if the association request is formally correct, by matching against an exact application context UID.
282
- # Returns nil if valid, and an error code if it is not approved.
283
- #
284
- # === Notes
285
- #
286
- # Other things can potentially be checked here too, if we want to make the server more strict with regards to what information is received:
287
- # * Application context name, calling AE title, called AE title
288
- # * Description of error codes are given in the DICOM Standard, PS 3.8, Chapter 9.3.4 (Table 9-21).
289
- #
290
- # === Parameters
291
- #
292
- # * <tt>info</tt> -- An information hash from the received association request.
293
- #
294
- def check_association_request(info)
295
- unless info[:application_context] == APPLICATION_CONTEXT
296
- error = 2 # (application context name not supported)
297
- logger.error("The application context in the incoming association request was not recognized: (#{info[:application_context]})")
298
- else
299
- error = nil
300
- end
301
- return error
302
- end
303
-
304
- # Checks if the requested abstract syntax & its transfer syntax(es) are supported by this server instance,
305
- # and inserts a corresponding result code for each presentation context.
306
- # Returns the modified association information hash, as well as the number of abstract syntaxes that were accepted and rejected.
307
- #
308
- # === Notes
309
- #
310
- # * Description of error codes are given in the DICOM Standard, PS 3.8, Chapter 9.3.3.2 (Table 9-18).
311
- #
312
- # === Parameters
313
- #
314
- # * <tt>info</tt> -- An information hash from the received association request.
315
- #
316
- def process_syntax_requests(info)
317
- # A couple of variables used to analyse the properties of the association:
318
- approved = 0
319
- rejected = 0
320
- # Loop through the presentation contexts:
321
- info[:pc].each do |pc|
322
- if @accepted_abstract_syntaxes[pc[:abstract_syntax]]
323
- # Abstract syntax accepted. Proceed to check its transfer syntax(es):
324
- proposed_transfer_syntaxes = pc[:ts].collect{|t| t[:transfer_syntax]}.sort
325
- # Choose the first proposed transfer syntax that exists in our list of accepted transfer syntaxes:
326
- accepted_transfer_syntax = nil
327
- proposed_transfer_syntaxes.each do |proposed_ts|
328
- if @accepted_transfer_syntaxes.include?(proposed_ts)
329
- accepted_transfer_syntax = proposed_ts
330
- break
331
- end
332
- end
333
- if accepted_transfer_syntax
334
- # Both abstract and transfer syntax has been approved:
335
- pc[:result] = ACCEPTANCE
336
- pc[:selected_transfer_syntax] = accepted_transfer_syntax
337
- # Update our status variables:
338
- approved += 1
339
- else
340
- # No transfer syntax was accepted for this particular presentation context:
341
- pc[:result] = TRANSFER_SYNTAX_REJECTED
342
- rejected += 1
343
- end
344
- else
345
- # Abstract syntax rejected:
346
- pc[:result] = ABSTRACT_SYNTAX_REJECTED
347
- end
348
- end
349
- return info, approved, rejected
350
- end
351
-
352
- # Sets the default accepted abstract syntaxes and transfer syntaxes for this SCP.
353
- #
354
- def set_default_accepted_syntaxes
355
- @accepted_transfer_syntaxes, @accepted_abstract_syntaxes = LIBRARY.extract_transfer_syntaxes_and_sop_classes
356
- end
357
-
358
- end
359
- end
1
+ module DICOM
2
+
3
+ # This class contains code for setting up a Service Class Provider (SCP),
4
+ # which will act as a simple storage node (a DICOM server that receives images).
5
+ #
6
+ class DServer
7
+ include Logging
8
+
9
+ # Runs the server and takes a block for initializing.
10
+ #
11
+ # @param [Integer] port the network port to be used (defaults to 104)
12
+ # @param [String] path the directory where incoming DICOM files will be stored (defaults to './received/')
13
+ # @param [&block] block a block of code that will be run on the DServer instance, between creation and the launch of the SCP itself
14
+ #
15
+ # @example Run a server instance with a custom file handler
16
+ # require 'dicom'
17
+ # require 'my_file_handler'
18
+ # include DICOM
19
+ # DServer.run(104, 'c:/temp/') do |s|
20
+ # s.timeout = 100
21
+ # s.file_handler = MyFileHandler
22
+ # end
23
+ #
24
+ def self.run(port=104, path='./received/', &block)
25
+ server = DServer.new(port)
26
+ server.instance_eval(&block)
27
+ server.start_scp(path)
28
+ end
29
+
30
+ # A customized FileHandler class to use instead of the default FileHandler included with ruby-dicom.
31
+ attr_accessor :file_handler
32
+ # The hostname that the TCPServer binds to.
33
+ attr_accessor :host
34
+ # The name of the server (application entity).
35
+ attr_accessor :host_ae
36
+ # The maximum allowed size of network packages (in bytes).
37
+ attr_accessor :max_package_size
38
+ # The network port to be used.
39
+ attr_accessor :port
40
+ # The maximum period the server will wait on an answer from a client before aborting the communication.
41
+ attr_accessor :timeout
42
+
43
+ # A hash containing the abstract syntaxes that will be accepted.
44
+ attr_reader :accepted_abstract_syntaxes
45
+ # A hash containing the transfer syntaxes that will be accepted.
46
+ attr_reader :accepted_transfer_syntaxes
47
+
48
+ # Creates a DServer instance.
49
+ #
50
+ # @note To customize logging behaviour, refer to the Logging module documentation.
51
+ #
52
+ # @param [Integer] port the network port to be used
53
+ # @param [Hash] options the options to use for the DICOM server
54
+ # @option options [String] :file_handler a customized FileHandler class to use instead of the default FileHandler
55
+ # @option options [String] :host the hostname that the TCPServer binds to (defaults to '127.0.0.1')
56
+ # @option options [String] :host_ae the name of the server (application entity)
57
+ # @option options [String] :max_package_size the maximum allowed size of network packages (in bytes)
58
+ # @option options [String] :timeout the number of seconds the server will wait on an answer from a client before aborting the communication
59
+ #
60
+ # @example Create a server using default settings
61
+ # s = DICOM::DServer.new
62
+ # @example Create a server with a specific host name and a custom buildt file handler
63
+ # require 'MyFileHandler'
64
+ # server = DICOM::DServer.new(104, :host_ae => "RUBY_SERVER", :file_handler => DICOM::MyFileHandler)
65
+ #
66
+ def initialize(port=104, options={})
67
+ require 'socket'
68
+ # Required parameters:
69
+ @port = port
70
+ # Optional parameters (and default values):
71
+ @file_handler = options[:file_handler] || FileHandler
72
+ @host = options[:host] || '127.0.0.1'
73
+ @host_ae = options[:host_ae] || "RUBY_DICOM"
74
+ @max_package_size = options[:max_package_size] || 32768 # 16384
75
+ @timeout = options[:timeout] || 10 # seconds
76
+ @min_length = 12 # minimum number of bytes to expect in an incoming transmission
77
+ # Variables used for monitoring state of transmission:
78
+ @connection = nil # TCP connection status
79
+ @association = nil # DICOM Association status
80
+ @request_approved = nil # Status of our DICOM request
81
+ @release = nil # Status of received, valid release response
82
+ set_default_accepted_syntaxes
83
+ end
84
+
85
+ # Adds an abstract syntax to the list of abstract syntaxes that the server will accept.
86
+ #
87
+ # @param [String] uid an abstract syntax UID
88
+ #
89
+ def add_abstract_syntax(uid)
90
+ lib_uid = LIBRARY.uid(uid)
91
+ raise "Invalid/unknown UID: #{uid}" unless lib_uid
92
+ @accepted_abstract_syntaxes[uid] = lib_uid.name
93
+ end
94
+
95
+ # Adds a transfer syntax to the list of transfer syntaxes that the server will accept.
96
+ #
97
+ # @param [String] uid a transfer syntax UID
98
+ #
99
+ def add_transfer_syntax(uid)
100
+ lib_uid = LIBRARY.uid(uid)
101
+ raise "Invalid/unknown UID: #{uid}" unless lib_uid
102
+ @accepted_transfer_syntaxes[uid] = lib_uid.name
103
+ end
104
+
105
+ # Prints the list of accepted abstract syntaxes to the screen.
106
+ #
107
+ def print_abstract_syntaxes
108
+ # Determine length of longest key to ensure pretty print:
109
+ max_uid = @accepted_abstract_syntaxes.keys.collect{|k| k.length}.max
110
+ puts "Abstract syntaxes which are accepted by this SCP:"
111
+ @accepted_abstract_syntaxes.sort.each do |pair|
112
+ puts "#{pair[0]}#{' '*(max_uid-pair[0].length)} #{pair[1]}"
113
+ end
114
+ end
115
+
116
+ # Prints the list of accepted transfer syntaxes to the screen.
117
+ #
118
+ def print_transfer_syntaxes
119
+ # Determine length of longest key to ensure pretty print:
120
+ max_uid = @accepted_transfer_syntaxes.keys.collect{|k| k.length}.max
121
+ puts "Transfer syntaxes which are accepted by this SCP:"
122
+ @accepted_transfer_syntaxes.sort.each do |pair|
123
+ puts "#{pair[0]}#{' '*(max_uid-pair[0].length)} #{pair[1]}"
124
+ end
125
+ end
126
+
127
+ # Deletes a specific abstract syntax from the list of abstract syntaxes
128
+ # that the server will accept.
129
+ #
130
+ # @param [String] uid an abstract syntax UID
131
+ #
132
+ def delete_abstract_syntax(uid)
133
+ if uid.is_a?(String)
134
+ @accepted_abstract_syntaxes.delete(uid)
135
+ else
136
+ raise "Invalid type of UID. Expected String, got #{uid.class}!"
137
+ end
138
+ end
139
+
140
+ # Deletes a specific transfer syntax from the list of transfer syntaxes
141
+ # that the server will accept.
142
+ #
143
+ # @param [String] uid a transfer syntax UID
144
+ #
145
+ def delete_transfer_syntax(uid)
146
+ if uid.is_a?(String)
147
+ @accepted_transfer_syntaxes.delete(uid)
148
+ else
149
+ raise "Invalid type of UID. Expected String, got #{uid.class}!"
150
+ end
151
+ end
152
+
153
+ # Completely clears the list of abstract syntaxes that the server will accept.
154
+ #
155
+ # Following such a clearance, the user must ensure to add the specific
156
+ # abstract syntaxes that are to be accepted by the server.
157
+ #
158
+ def clear_abstract_syntaxes
159
+ @accepted_abstract_syntaxes = Hash.new
160
+ end
161
+
162
+ # Completely clears the list of transfer syntaxes that the server will accept.
163
+ #
164
+ # Following such a clearance, the user must ensure to add the specific
165
+ # transfer syntaxes that are to be accepted by the server.
166
+ #
167
+ def clear_transfer_syntaxes
168
+ @accepted_transfer_syntaxes = Hash.new
169
+ end
170
+
171
+ # Starts the Service Class Provider (SCP).
172
+ #
173
+ # This service acts as a simple storage node, which receives DICOM files
174
+ # and stores them in the specified folder.
175
+ #
176
+ # Customized storage actions can be set my modifying or replacing the FileHandler class.
177
+ #
178
+ # @param [String] path the directory where incoming files are to be saved
179
+ #
180
+ def start_scp(path='./received/')
181
+ if @accepted_abstract_syntaxes.size > 0 and @accepted_transfer_syntaxes.size > 0
182
+ logger.info("Started DICOM SCP server on port #{@port}.")
183
+ logger.info("Waiting for incoming transmissions...\n\n")
184
+ # Initiate server:
185
+ @scp = TCPServer.new(@host, @port)
186
+ # Use a loop to listen for incoming messages:
187
+ loop do
188
+ Thread.start(@scp.accept) do |session|
189
+ # Initialize the network package handler for this session:
190
+ link = Link.new(:host_ae => @host_ae, :max_package_size => @max_package_size, :timeout => @timeout, :file_handler => @file_handler)
191
+ link.set_session(session)
192
+ # Note who has contacted us:
193
+ logger.info("Connection established with: #{session.peeraddr[2]} (IP: #{session.peeraddr[3]})")
194
+ # Receive an incoming message:
195
+ segments = link.receive_multiple_transmissions
196
+ info = segments.first
197
+ # Interpret the received message:
198
+ if info[:valid]
199
+ association_error = check_association_request(info)
200
+ unless association_error
201
+ info, approved, rejected = process_syntax_requests(info)
202
+ link.handle_association_accept(info)
203
+ context = (LIBRARY.uid(info[:pc].first[:abstract_syntax]) ? LIBRARY.uid(info[:pc].first[:abstract_syntax]).name : 'Unknown UID!')
204
+ if approved > 0
205
+ if approved == 1
206
+ logger.info("Accepted the association request with context: #{context}")
207
+ else
208
+ if rejected == 0
209
+ logger.info("Accepted all #{approved} proposed contexts in the association request.")
210
+ else
211
+ logger.warn("Accepted only #{approved} of #{approved+rejected} of the proposed contexts in the association request.")
212
+ end
213
+ end
214
+ # Process the incoming data. This method will also take care of releasing the association:
215
+ success, messages = link.handle_incoming_data(path)
216
+ # Pass along any messages that has been recorded:
217
+ messages.each { |m| logger.public_send(m.first, m.last) } if messages.first
218
+ else
219
+ # No abstract syntaxes in the incoming request were accepted:
220
+ if rejected == 1
221
+ logger.warn("Rejected the association request with proposed context: #{context}")
222
+ else
223
+ logger.warn("Rejected all #{rejected} proposed contexts in the association request.")
224
+ end
225
+ # Since the requested abstract syntax was not accepted, the association must be released.
226
+ link.await_release
227
+ end
228
+ else
229
+ # The incoming association was not formally correct.
230
+ link.handle_rejection
231
+ end
232
+ else
233
+ # The incoming message was not recognised as a valid DICOM message. Abort:
234
+ link.handle_abort
235
+ end
236
+ # Terminate the connection:
237
+ link.stop_session
238
+ logger.info("Connection closed.\n\n")
239
+ end
240
+ end
241
+ else
242
+ raise "Unable to start SCP server as no accepted abstract syntaxes have been set!" if @accepted_abstract_syntaxes.length == 0
243
+ raise "Unable to start SCP server as no accepted transfer syntaxes have been set!" if @accepted_transfer_syntaxes.length == 0
244
+ end
245
+ end
246
+
247
+
248
+ private
249
+
250
+
251
+ # Checks if the association request is formally correct, by matching against an exact application context UID.
252
+ # Returns nil if valid, and an error code if it is not approved.
253
+ #
254
+ # === Notes
255
+ #
256
+ # Other things can potentially be checked here too, if we want to make the server more strict with regards to what information is received:
257
+ # * Application context name, calling AE title, called AE title
258
+ # * Description of error codes are given in the DICOM Standard, PS 3.8, Chapter 9.3.4 (Table 9-21).
259
+ #
260
+ # === Parameters
261
+ #
262
+ # * <tt>info</tt> -- An information hash from the received association request.
263
+ #
264
+ def check_association_request(info)
265
+ unless info[:application_context] == APPLICATION_CONTEXT
266
+ error = 2 # (application context name not supported)
267
+ logger.error("The application context in the incoming association request was not recognized: (#{info[:application_context]})")
268
+ else
269
+ error = nil
270
+ end
271
+ return error
272
+ end
273
+
274
+ # Checks if the requested abstract syntax & its transfer syntax(es) are supported by this server instance,
275
+ # and inserts a corresponding result code for each presentation context.
276
+ # Returns the modified association information hash, as well as the number of abstract syntaxes that were accepted and rejected.
277
+ #
278
+ # === Notes
279
+ #
280
+ # * Description of error codes are given in the DICOM Standard, PS 3.8, Chapter 9.3.3.2 (Table 9-18).
281
+ #
282
+ # === Parameters
283
+ #
284
+ # * <tt>info</tt> -- An information hash from the received association request.
285
+ #
286
+ def process_syntax_requests(info)
287
+ # A couple of variables used to analyse the properties of the association:
288
+ approved = 0
289
+ rejected = 0
290
+ # Loop through the presentation contexts:
291
+ info[:pc].each do |pc|
292
+ if @accepted_abstract_syntaxes[pc[:abstract_syntax]]
293
+ # Abstract syntax accepted. Proceed to check its transfer syntax(es):
294
+ proposed_transfer_syntaxes = pc[:ts].collect{|t| t[:transfer_syntax]}.sort
295
+ # Choose the first proposed transfer syntax that exists in our list of accepted transfer syntaxes:
296
+ accepted_transfer_syntax = nil
297
+ proposed_transfer_syntaxes.each do |proposed_ts|
298
+ if @accepted_transfer_syntaxes.include?(proposed_ts)
299
+ accepted_transfer_syntax = proposed_ts
300
+ break
301
+ end
302
+ end
303
+ if accepted_transfer_syntax
304
+ # Both abstract and transfer syntax has been approved:
305
+ pc[:result] = ACCEPTANCE
306
+ pc[:selected_transfer_syntax] = accepted_transfer_syntax
307
+ # Update our status variables:
308
+ approved += 1
309
+ else
310
+ # No transfer syntax was accepted for this particular presentation context:
311
+ pc[:result] = TRANSFER_SYNTAX_REJECTED
312
+ rejected += 1
313
+ end
314
+ else
315
+ # Abstract syntax rejected:
316
+ pc[:result] = ABSTRACT_SYNTAX_REJECTED
317
+ end
318
+ end
319
+ return info, approved, rejected
320
+ end
321
+
322
+ # Sets the default accepted abstract syntaxes and transfer syntaxes for this SCP.
323
+ #
324
+ def set_default_accepted_syntaxes
325
+ @accepted_transfer_syntaxes, @accepted_abstract_syntaxes = LIBRARY.extract_transfer_syntaxes_and_sop_classes
326
+ end
327
+
328
+ end
329
+ end