comparison OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/DicomNetworking/Internals/FindScp.cpp@58f92b1c8061
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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-2020 Osimis S.A., 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 "../../DicomFormat/DicomArray.h"
87 #include "../../DicomParsing/FromDcmtkBridge.h"
88 #include "../../DicomParsing/ToDcmtkBridge.h"
89 #include "../../Logging.h"
90 #include "../../OrthancException.h"
91
92 #include <dcmtk/dcmdata/dcfilefo.h>
93 #include <dcmtk/dcmdata/dcdeftag.h>
94
95
96
97 /**
98 * The function below is extracted from DCMTK 3.6.0, cf. file
99 * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc".
100 **/
101
102 static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset,
103 const DcmTagKey &sequenceTagKey)
104 // Date : May 3, 2005
105 // Author : Thomas Wilkens
106 // Task : This function performs a check on a sequence attribute in the given dataset. At two different places
107 // in the definition of the DICOM worklist management service, a sequence attribute with a return type
108 // of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes
109 // specifies that in case a sequence item is present, then these two attributes must be existent and
110 // must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.)
111 // In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass
112 // and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what
113 // this function does.
114 // Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked.
115 // sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked.
116 // Return Value : none.
117 {
118 DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL;
119
120 // in case the sequence attribute contains exactly one item with an empty
121 // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item
122 if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() &&
123 ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 &&
124 ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() &&
125 referencedSOPClassUIDAttribute->getLength() == 0 &&
126 ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() &&
127 referencedSOPInstanceUIDAttribute->getLength() == 0 )
128 {
129 DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) );
130 delete item;
131 }
132 }
133
134
135
136 namespace Orthanc
137 {
138 namespace
139 {
140 struct FindScpData
141 {
142 DicomServer::IRemoteModalities* modalities_;
143 IFindRequestHandler* findHandler_;
144 IWorklistRequestHandler* worklistHandler_;
145 DicomFindAnswers answers_;
146 DcmDataset* lastRequest_;
147 const std::string* remoteIp_;
148 const std::string* remoteAet_;
149 const std::string* calledAet_;
150
151 FindScpData() : answers_(false)
152 {
153 }
154 };
155
156
157
158 static void FixWorklistQuery(ParsedDicomFile& query)
159 {
160 // TODO: Check out
161 // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()"
162 // in DCMTK 3.6.0
163
164 DcmDataset* dataset = query.GetDcmtkObject().getDataset();
165 HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence);
166 HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence);
167 }
168
169
170 static void FixFindQuery(DicomMap& target,
171 const DicomMap& source)
172 {
173 // "The definition of a Data Set in PS3.5 specifically excludes
174 // the range of groups below group 0008, and this includes in
175 // particular Meta Information Header elements such as Transfer
176 // Syntax UID (0002,0010)."
177 // http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#sect_C.4.1.1.3
178 // https://groups.google.com/d/msg/orthanc-users/D3kpPuX8yV0/_zgHOzkMEQAJ
179
180 DicomArray a(source);
181
182 for (size_t i = 0; i < a.GetSize(); i++)
183 {
184 if (a.GetElement(i).GetTag().GetGroup() >= 0x0008)
185 {
186 target.SetValue(a.GetElement(i).GetTag(), a.GetElement(i).GetValue());
187 }
188 }
189 }
190
191
192
193 void FindScpCallback(
194 /* in */
195 void *callbackData,
196 OFBool cancelled,
197 T_DIMSE_C_FindRQ *request,
198 DcmDataset *requestIdentifiers,
199 int responseCount,
200 /* out */
201 T_DIMSE_C_FindRSP *response,
202 DcmDataset **responseIdentifiers,
203 DcmDataset **statusDetail)
204 {
205 bzero(response, sizeof(T_DIMSE_C_FindRSP));
206 *statusDetail = NULL;
207
208 std::string sopClassUid(request->AffectedSOPClassUID);
209
210 FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
211 if (data.lastRequest_ == NULL)
212 {
213 bool ok = false;
214
215 try
216 {
217 RemoteModalityParameters modality;
218
219 /**
220 * Ensure that the remote modality is known to Orthanc for C-FIND requests.
221 **/
222
223 assert(data.modalities_ != NULL);
224 if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_))
225 {
226 throw OrthancException(ErrorCode_UnknownModality,
227 "Modality with AET \"" + (*data.remoteAet_) +
228 "\" is not defined in the \"DicomModalities\" configuration option");
229 }
230
231
232 if (sopClassUid == UID_FINDModalityWorklistInformationModel)
233 {
234 data.answers_.SetWorklist(true);
235
236 if (data.worklistHandler_ != NULL)
237 {
238 ParsedDicomFile query(*requestIdentifiers);
239 FixWorklistQuery(query);
240
241 data.worklistHandler_->Handle(data.answers_, query,
242 *data.remoteIp_, *data.remoteAet_,
243 *data.calledAet_, modality.GetManufacturer());
244 ok = true;
245 }
246 else
247 {
248 LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
249 }
250 }
251 else
252 {
253 data.answers_.SetWorklist(false);
254
255 if (data.findHandler_ != NULL)
256 {
257 std::list<DicomTag> sequencesToReturn;
258
259 for (unsigned long i = 0; i < requestIdentifiers->card(); i++)
260 {
261 DcmElement* element = requestIdentifiers->getElement(i);
262 if (element && !element->isLeaf())
263 {
264 const DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
265
266 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
267 if (sequence.card() != 0)
268 {
269 LOG(WARNING) << "Orthanc only supports sequence matching on worklists, "
270 << "ignoring C-FIND SCU constraint on tag (" << tag.Format()
271 << ") " << FromDcmtkBridge::GetTagName(*element);
272 }
273
274 sequencesToReturn.push_back(tag);
275 }
276 }
277
278 DicomMap input;
279 FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
280
281 DicomMap filtered;
282 FixFindQuery(filtered, input);
283
284 data.findHandler_->Handle(data.answers_, filtered, sequencesToReturn,
285 *data.remoteIp_, *data.remoteAet_,
286 *data.calledAet_, modality.GetManufacturer());
287 ok = true;
288 }
289 else
290 {
291 LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
292 }
293 }
294 }
295 catch (OrthancException& e)
296 {
297 // Internal error!
298 LOG(ERROR) << "C-FIND request handler has failed: " << e.What();
299 }
300
301 if (!ok)
302 {
303 response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
304 *responseIdentifiers = NULL;
305 return;
306 }
307
308 data.lastRequest_ = requestIdentifiers;
309 }
310 else if (data.lastRequest_ != requestIdentifiers)
311 {
312 // Internal error!
313 response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
314 *responseIdentifiers = NULL;
315 return;
316 }
317
318 if (responseCount <= static_cast<int>(data.answers_.GetSize()))
319 {
320 // There are pending results that are still to be sent
321 response->DimseStatus = STATUS_Pending;
322 *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
323 }
324 else if (data.answers_.IsComplete())
325 {
326 // Success: All the results have been sent
327 response->DimseStatus = STATUS_Success;
328 *responseIdentifiers = NULL;
329 }
330 else
331 {
332 // Success, but the results were too numerous and had to be cropped
333 LOG(WARNING) << "Too many results for an incoming C-FIND query";
334 response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
335 *responseIdentifiers = NULL;
336 }
337 }
338 }
339
340
341 OFCondition Internals::findScp(T_ASC_Association * assoc,
342 T_DIMSE_Message * msg,
343 T_ASC_PresentationContextID presID,
344 DicomServer::IRemoteModalities& modalities,
345 IFindRequestHandler* findHandler,
346 IWorklistRequestHandler* worklistHandler,
347 const std::string& remoteIp,
348 const std::string& remoteAet,
349 const std::string& calledAet,
350 int timeout)
351 {
352 FindScpData data;
353 data.modalities_ = &modalities;
354 data.findHandler_ = findHandler;
355 data.worklistHandler_ = worklistHandler;
356 data.lastRequest_ = NULL;
357 data.remoteIp_ = &remoteIp;
358 data.remoteAet_ = &remoteAet;
359 data.calledAet_ = &calledAet;
360
361 OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ,
362 FindScpCallback, &data,
363 /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
364 /*opt_dimse_timeout*/ timeout);
365
366 // if some error occured, dump corresponding information and remove the outfile if necessary
367 if (cond.bad())
368 {
369 OFString temp_str;
370 LOG(ERROR) << "Find SCP Failed: " << cond.text();
371 }
372
373 return cond;
374 }
375 }