comparison OrthancServer/Sources/Search/DatabaseLookup.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 OrthancServer/Search/DatabaseLookup.cpp@2a170a8f1faf
children 05b8fd21089c
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 #include "../PrecompiledHeadersServer.h"
35 #include "DatabaseLookup.h"
36
37 #include "../ServerToolbox.h"
38 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
39 #include "../../Core/DicomParsing/ToDcmtkBridge.h"
40 #include "../../Core/OrthancException.h"
41 #include "../../Core/Toolbox.h"
42
43 namespace Orthanc
44 {
45 DatabaseLookup::~DatabaseLookup()
46 {
47 for (size_t i = 0; i < constraints_.size(); i++)
48 {
49 assert(constraints_[i] != NULL);
50 delete constraints_[i];
51 }
52 }
53
54
55 const DicomTagConstraint& DatabaseLookup::GetConstraint(size_t index) const
56 {
57 if (index >= constraints_.size())
58 {
59 throw OrthancException(ErrorCode_ParameterOutOfRange);
60 }
61 else
62 {
63 assert(constraints_[index] != NULL);
64 return *constraints_[index];
65 }
66 }
67
68
69 void DatabaseLookup::AddConstraint(DicomTagConstraint* constraint)
70 {
71 if (constraint == NULL)
72 {
73 throw OrthancException(ErrorCode_NullPointer);
74 }
75 else
76 {
77 constraints_.push_back(constraint);
78 }
79 }
80
81
82 bool DatabaseLookup::IsMatch(const DicomMap& value) const
83 {
84 for (size_t i = 0; i < constraints_.size(); i++)
85 {
86 assert(constraints_[i] != NULL);
87 if (!constraints_[i]->IsMatch(value))
88 {
89 return false;
90 }
91 }
92
93 return true;
94 }
95
96
97 bool DatabaseLookup::IsMatch(DcmItem& item,
98 Encoding encoding,
99 bool hasCodeExtensions) const
100 {
101 for (size_t i = 0; i < constraints_.size(); i++)
102 {
103 assert(constraints_[i] != NULL);
104
105 const bool isOptionalConstraint = !constraints_[i]->IsMandatory();
106 const DcmTagKey tag = ToDcmtkBridge::Convert(constraints_[i]->GetTag());
107
108 DcmElement* element = NULL;
109 if (!item.findAndGetElement(tag, element).good())
110 {
111 return isOptionalConstraint;
112 }
113
114 if (element == NULL)
115 {
116 return false;
117 }
118
119 std::set<DicomTag> ignoreTagLength;
120 std::unique_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
121 (*element, DicomToJsonFlags_None,
122 0, encoding, hasCodeExtensions, ignoreTagLength));
123
124 // WARNING: Also modify "HierarchicalMatcher::Setup()" if modifying this code
125 if (value.get() == NULL ||
126 value->IsNull())
127 {
128 return isOptionalConstraint;
129 }
130 else if (value->IsBinary() ||
131 !constraints_[i]->IsMatch(value->GetContent()))
132 {
133 return false;
134 }
135 }
136
137 return true;
138 }
139
140
141 void DatabaseLookup::AddDicomConstraintInternal(const DicomTag& tag,
142 ValueRepresentation vr,
143 const std::string& dicomQuery,
144 bool caseSensitive,
145 bool mandatoryTag)
146 {
147 if ((vr == ValueRepresentation_Date ||
148 vr == ValueRepresentation_DateTime ||
149 vr == ValueRepresentation_Time) &&
150 dicomQuery.find('-') != std::string::npos)
151 {
152 /**
153 * Range matching is only defined for TM, DA and DT value
154 * representations. This code fixes issues 35 and 37.
155 *
156 * Reference: "Range matching is not defined for types of
157 * Attributes other than dates and times", DICOM PS 3.4,
158 * C.2.2.2.5 ("Range Matching").
159 **/
160 size_t separator = dicomQuery.find('-');
161 std::string lower = dicomQuery.substr(0, separator);
162 std::string upper = dicomQuery.substr(separator + 1);
163
164 if (!lower.empty())
165 {
166 AddConstraint(new DicomTagConstraint
167 (tag, ConstraintType_GreaterOrEqual, lower, caseSensitive, mandatoryTag));
168 }
169
170 if (!upper.empty())
171 {
172 AddConstraint(new DicomTagConstraint
173 (tag, ConstraintType_SmallerOrEqual, upper, caseSensitive, mandatoryTag));
174 }
175 }
176 else if (tag == DICOM_TAG_MODALITIES_IN_STUDY ||
177 dicomQuery.find('\\') != std::string::npos)
178 {
179 DicomTag fixedTag(tag);
180
181 if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
182 {
183 // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
184 // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html
185 fixedTag = DICOM_TAG_MODALITY;
186 }
187
188 std::unique_ptr<DicomTagConstraint> constraint
189 (new DicomTagConstraint(fixedTag, ConstraintType_List, caseSensitive, mandatoryTag));
190
191 std::vector<std::string> items;
192 Toolbox::TokenizeString(items, dicomQuery, '\\');
193
194 for (size_t i = 0; i < items.size(); i++)
195 {
196 constraint->AddValue(items[i]);
197 }
198
199 AddConstraint(constraint.release());
200 }
201 else if (
202 /**
203 * New test in Orthanc 1.6.0: Wild card matching is only allowed
204 * for a subset of value representations: AE, CS, LO, LT, PN,
205 * SH, ST, UC, UR, UT.
206 * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.4.html
207 **/
208 (vr == ValueRepresentation_ApplicationEntity || // AE
209 vr == ValueRepresentation_CodeString || // CS
210 vr == ValueRepresentation_LongString || // LO
211 vr == ValueRepresentation_LongText || // LT
212 vr == ValueRepresentation_PersonName || // PN
213 vr == ValueRepresentation_ShortString || // SH
214 vr == ValueRepresentation_ShortText || // ST
215 vr == ValueRepresentation_UnlimitedCharacters || // UC
216 vr == ValueRepresentation_UniversalResource || // UR
217 vr == ValueRepresentation_UnlimitedText // UT
218 ) &&
219 (dicomQuery.find('*') != std::string::npos ||
220 dicomQuery.find('?') != std::string::npos))
221 {
222 AddConstraint(new DicomTagConstraint
223 (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag));
224 }
225 else
226 {
227 AddConstraint(new DicomTagConstraint
228 (tag, ConstraintType_Equal, dicomQuery, caseSensitive, mandatoryTag));
229 }
230 }
231
232
233 void DatabaseLookup::AddDicomConstraint(const DicomTag& tag,
234 const std::string& dicomQuery,
235 bool caseSensitivePN,
236 bool mandatoryTag)
237 {
238 ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag);
239
240 if (vr == ValueRepresentation_Sequence)
241 {
242 throw OrthancException(ErrorCode_ParameterOutOfRange);
243 }
244
245 /**
246 * DICOM specifies that searches must always be case sensitive,
247 * except for tags with a PN value representation. For PN, Orthanc
248 * uses the configuration option "CaseSensitivePN" to decide
249 * whether matching is case-sensitive or case-insensitive.
250 *
251 * Reference: DICOM PS 3.4
252 * - C.2.2.2.1 ("Single Value Matching")
253 * - C.2.2.2.4 ("Wild Card Matching")
254 * http://medical.nema.org/Dicom/2011/11_04pu.pdf
255 *
256 * "Except for Attributes with a PN Value Representation, only
257 * entities with values which match exactly the value specified in the
258 * request shall match. This matching is case-sensitive, i.e.,
259 * sensitive to the exact encoding of the key attribute value in
260 * character sets where a letter may have multiple encodings (e.g.,
261 * based on its case, its position in a word, or whether it is
262 * accented)
263 *
264 * For Attributes with a PN Value Representation (e.g., Patient Name
265 * (0010,0010)), an application may perform literal matching that is
266 * either case-sensitive, or that is insensitive to some or all
267 * aspects of case, position, accent, or other character encoding
268 * variants."
269 *
270 * (0008,0018) UI SOPInstanceUID => Case-sensitive
271 * (0008,0050) SH AccessionNumber => Case-sensitive
272 * (0010,0020) LO PatientID => Case-sensitive
273 * (0020,000D) UI StudyInstanceUID => Case-sensitive
274 * (0020,000E) UI SeriesInstanceUID => Case-sensitive
275 **/
276
277 if (vr == ValueRepresentation_PersonName)
278 {
279 AddDicomConstraintInternal(tag, vr, dicomQuery, caseSensitivePN, mandatoryTag);
280 }
281 else
282 {
283 AddDicomConstraintInternal(tag, vr, dicomQuery, true /* case sensitive */, mandatoryTag);
284 }
285 }
286
287
288 void DatabaseLookup::AddRestConstraint(const DicomTag& tag,
289 const std::string& dicomQuery,
290 bool caseSensitive,
291 bool mandatoryTag)
292 {
293 AddDicomConstraintInternal(tag, FromDcmtkBridge::LookupValueRepresentation(tag),
294 dicomQuery, caseSensitive, mandatoryTag);
295 }
296
297
298 bool DatabaseLookup::HasOnlyMainDicomTags() const
299 {
300 std::set<DicomTag> mainTags;
301 DicomMap::GetMainDicomTags(mainTags);
302
303 for (size_t i = 0; i < constraints_.size(); i++)
304 {
305 assert(constraints_[i] != NULL);
306
307 if (mainTags.find(constraints_[i]->GetTag()) == mainTags.end())
308 {
309 // This is not a main DICOM tag
310 return false;
311 }
312 }
313
314 return true;
315 }
316 }