comparison Core/DicomNetworking/Internals/FindScp.cpp @ 2382:7284093111b0

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents OrthancServer/Internals/FindScp.cpp@b8969010b534
children 878b59270859
comparison
equal deleted inserted replaced
2381:b8969010b534 2382:7284093111b0
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017 Osimis, Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34
35 /*=========================================================================
36
37 This file is based on portions of the following project:
38
39 Program: DCMTK 3.6.0
40 Module: http://dicom.offis.de/dcmtk.php.en
41
42 Copyright (C) 1994-2011, OFFIS e.V.
43 All rights reserved.
44
45 This software and supporting documentation were developed by
46
47 OFFIS e.V.
48 R&D Division Health
49 Escherweg 2
50 26121 Oldenburg, Germany
51
52 Redistribution and use in source and binary forms, with or without
53 modification, are permitted provided that the following conditions
54 are met:
55
56 - Redistributions of source code must retain the above copyright
57 notice, this list of conditions and the following disclaimer.
58
59 - Redistributions in binary form must reproduce the above copyright
60 notice, this list of conditions and the following disclaimer in the
61 documentation and/or other materials provided with the distribution.
62
63 - Neither the name of OFFIS nor the names of its contributors may be
64 used to endorse or promote products derived from this software
65 without specific prior written permission.
66
67 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
68 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
69 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
70 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
71 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
72 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
73 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
74 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
75 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
76 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
77 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
78
79 =========================================================================*/
80
81
82
83 #include "../../PrecompiledHeaders.h"
84 #include "FindScp.h"
85
86 #include "../../DicomParsing/FromDcmtkBridge.h"
87 #include "../../DicomParsing/ToDcmtkBridge.h"
88 #include "../../Logging.h"
89 #include "../../OrthancException.h"
90
91 #include <dcmtk/dcmdata/dcfilefo.h>
92 #include <dcmtk/dcmdata/dcdeftag.h>
93
94
95
96 /**
97 * The function below is extracted from DCMTK 3.6.0, cf. file
98 * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc".
99 **/
100
101 static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset,
102 const DcmTagKey &sequenceTagKey)
103 // Date : May 3, 2005
104 // Author : Thomas Wilkens
105 // Task : This function performs a check on a sequence attribute in the given dataset. At two different places
106 // in the definition of the DICOM worklist management service, a sequence attribute with a return type
107 // of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes
108 // specifies that in case a sequence item is present, then these two attributes must be existent and
109 // must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.)
110 // In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass
111 // and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what
112 // this function does.
113 // Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked.
114 // sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked.
115 // Return Value : none.
116 {
117 DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL;
118
119 // in case the sequence attribute contains exactly one item with an empty
120 // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item
121 if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() &&
122 ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 &&
123 ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() &&
124 referencedSOPClassUIDAttribute->getLength() == 0 &&
125 ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() &&
126 referencedSOPInstanceUIDAttribute->getLength() == 0 )
127 {
128 DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) );
129 delete item;
130 }
131 }
132
133
134
135 namespace Orthanc
136 {
137 namespace
138 {
139 struct FindScpData
140 {
141 DicomServer::IRemoteModalities* modalities_;
142 IFindRequestHandler* findHandler_;
143 IWorklistRequestHandler* worklistHandler_;
144 DicomFindAnswers answers_;
145 DcmDataset* lastRequest_;
146 const std::string* remoteIp_;
147 const std::string* remoteAet_;
148 const std::string* calledAet_;
149
150 FindScpData() : answers_(false)
151 {
152 }
153 };
154
155
156
157 static void FixWorklistQuery(ParsedDicomFile& query)
158 {
159 // TODO: Check out
160 // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()"
161 // in DCMTK 3.6.0
162
163 DcmDataset* dataset = query.GetDcmtkObject().getDataset();
164 HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence);
165 HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence);
166 }
167
168
169
170 void FindScpCallback(
171 /* in */
172 void *callbackData,
173 OFBool cancelled,
174 T_DIMSE_C_FindRQ *request,
175 DcmDataset *requestIdentifiers,
176 int responseCount,
177 /* out */
178 T_DIMSE_C_FindRSP *response,
179 DcmDataset **responseIdentifiers,
180 DcmDataset **statusDetail)
181 {
182 bzero(response, sizeof(T_DIMSE_C_FindRSP));
183 *statusDetail = NULL;
184
185 std::string sopClassUid(request->AffectedSOPClassUID);
186
187 FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
188 if (data.lastRequest_ == NULL)
189 {
190 bool ok = false;
191
192 try
193 {
194 RemoteModalityParameters modality;
195
196 /**
197 * Ensure that the remote modality is known to Orthanc for C-FIND requests.
198 **/
199
200 assert(data.modalities_ != NULL);
201 if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_))
202 {
203 LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_
204 << "\" is not defined in the \"DicomModalities\" configuration option";
205 throw OrthancException(ErrorCode_UnknownModality);
206 }
207
208
209 if (sopClassUid == UID_FINDModalityWorklistInformationModel)
210 {
211 data.answers_.SetWorklist(true);
212
213 if (data.worklistHandler_ != NULL)
214 {
215 ParsedDicomFile query(*requestIdentifiers);
216 FixWorklistQuery(query);
217
218 data.worklistHandler_->Handle(data.answers_, query,
219 *data.remoteIp_, *data.remoteAet_,
220 *data.calledAet_, modality.GetManufacturer());
221 ok = true;
222 }
223 else
224 {
225 LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
226 }
227 }
228 else
229 {
230 data.answers_.SetWorklist(false);
231
232 if (data.findHandler_ != NULL)
233 {
234 std::list<DicomTag> sequencesToReturn;
235
236 for (unsigned long i = 0; i < requestIdentifiers->card(); i++)
237 {
238 DcmElement* element = requestIdentifiers->getElement(i);
239 if (element && !element->isLeaf())
240 {
241 const DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
242
243 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
244 if (sequence.card() != 0)
245 {
246 LOG(WARNING) << "Orthanc only supports sequence matching on worklists, "
247 << "ignoring C-FIND SCU constraint on tag (" << tag.Format()
248 << ") " << FromDcmtkBridge::GetTagName(*element);
249 }
250
251 sequencesToReturn.push_back(tag);
252 }
253 }
254
255 DicomMap input;
256 FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
257
258 data.findHandler_->Handle(data.answers_, input, sequencesToReturn,
259 *data.remoteIp_, *data.remoteAet_,
260 *data.calledAet_, modality.GetManufacturer());
261 ok = true;
262 }
263 else
264 {
265 LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
266 }
267 }
268 }
269 catch (OrthancException& e)
270 {
271 // Internal error!
272 LOG(ERROR) << "C-FIND request handler has failed: " << e.What();
273 }
274
275 if (!ok)
276 {
277 response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
278 *responseIdentifiers = NULL;
279 return;
280 }
281
282 data.lastRequest_ = requestIdentifiers;
283 }
284 else if (data.lastRequest_ != requestIdentifiers)
285 {
286 // Internal error!
287 response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
288 *responseIdentifiers = NULL;
289 return;
290 }
291
292 if (responseCount <= static_cast<int>(data.answers_.GetSize()))
293 {
294 // There are pending results that are still to be sent
295 response->DimseStatus = STATUS_Pending;
296 *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
297 }
298 else if (data.answers_.IsComplete())
299 {
300 // Success: All the results have been sent
301 response->DimseStatus = STATUS_Success;
302 *responseIdentifiers = NULL;
303 }
304 else
305 {
306 // Success, but the results were too numerous and had to be cropped
307 LOG(WARNING) << "Too many results for an incoming C-FIND query";
308 response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
309 *responseIdentifiers = NULL;
310 }
311 }
312 }
313
314
315 OFCondition Internals::findScp(T_ASC_Association * assoc,
316 T_DIMSE_Message * msg,
317 T_ASC_PresentationContextID presID,
318 DicomServer::IRemoteModalities& modalities,
319 IFindRequestHandler* findHandler,
320 IWorklistRequestHandler* worklistHandler,
321 const std::string& remoteIp,
322 const std::string& remoteAet,
323 const std::string& calledAet)
324 {
325 FindScpData data;
326 data.modalities_ = &modalities;
327 data.findHandler_ = findHandler;
328 data.worklistHandler_ = worklistHandler;
329 data.lastRequest_ = NULL;
330 data.remoteIp_ = &remoteIp;
331 data.remoteAet_ = &remoteAet;
332 data.calledAet_ = &calledAet;
333
334 OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ,
335 FindScpCallback, &data,
336 /*opt_blockMode*/ DIMSE_BLOCKING,
337 /*opt_dimse_timeout*/ 0);
338
339 // if some error occured, dump corresponding information and remove the outfile if necessary
340 if (cond.bad())
341 {
342 OFString temp_str;
343 LOG(ERROR) << "Find SCP Failed: " << cond.text();
344 }
345
346 return cond;
347 }
348 }