comparison OrthancServer/DicomFindQuery.cpp @ 1360:0649c5aef34a

DicomFindQuery
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 15 May 2015 15:34:32 +0200
parents
children 94ffb597d297
comparison
equal deleted inserted replaced
1359:4378a6636187 1360:0649c5aef34a
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * In addition, as a special exception, the copyright holders of this
12 * program give permission to link the code of its release with the
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
14 * that use the same license as the "OpenSSL" library), and distribute
15 * the linked executables. You must obey the GNU General Public License
16 * in all respects for all of the code used other than "OpenSSL". If you
17 * modify file(s) with this exception, you may extend this exception to
18 * your version of the file(s), but you are not obligated to do so. If
19 * you do not wish to do so, delete this exception statement from your
20 * version. If you delete this exception statement from all source files
21 * in the program, then also delete it here.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 **/
31
32
33
34 #include "PrecompiledHeadersServer.h"
35 #include "DicomFindQuery.h"
36
37 #include "FromDcmtkBridge.h"
38
39 #include <boost/regex.hpp>
40
41
42 namespace Orthanc
43 {
44 class DicomFindQuery::ValueConstraint : public DicomFindQuery::IConstraint
45 {
46 private:
47 bool isCaseSensitive_;
48 std::string expected_;
49
50 public:
51 ValueConstraint(const std::string& value,
52 bool caseSensitive) :
53 isCaseSensitive_(caseSensitive),
54 expected_(value)
55 {
56 }
57
58 const std::string& GetValue() const
59 {
60 return expected_;
61 }
62
63 virtual bool IsExactConstraint() const
64 {
65 return isCaseSensitive_;
66 }
67
68 virtual bool Apply(const std::string& value) const
69 {
70 if (isCaseSensitive_)
71 {
72 return expected_ == value;
73 }
74 else
75 {
76 std::string v, c;
77 Toolbox::ToLowerCase(v, value);
78 Toolbox::ToLowerCase(c, expected_);
79 return v == c;
80 }
81 }
82 };
83
84
85 class DicomFindQuery::ListConstraint : public DicomFindQuery::IConstraint
86 {
87 private:
88 std::set<std::string> values_;
89
90 public:
91 ListConstraint(const std::string& values)
92 {
93 std::vector<std::string> items;
94 Toolbox::TokenizeString(items, values, '\\');
95
96 for (size_t i = 0; i < items.size(); i++)
97 {
98 std::string lower;
99 Toolbox::ToLowerCase(lower, items[i]);
100 values_.insert(lower);
101 }
102 }
103
104 virtual bool Apply(const std::string& value) const
105 {
106 std::string tmp;
107 Toolbox::ToLowerCase(tmp, value);
108 return values_.find(tmp) != values_.end();
109 }
110 };
111
112
113 class DicomFindQuery::RangeConstraint : public DicomFindQuery::IConstraint
114 {
115 private:
116 std::string lower_;
117 std::string upper_;
118
119 public:
120 RangeConstraint(const std::string& range)
121 {
122 size_t separator = range.find('-');
123 Toolbox::ToLowerCase(lower_, range.substr(0, separator));
124 Toolbox::ToLowerCase(upper_, range.substr(separator + 1));
125 }
126
127 virtual bool Apply(const std::string& value) const
128 {
129 std::string v;
130 Toolbox::ToLowerCase(v, value);
131
132 if (lower_.size() == 0 &&
133 upper_.size() == 0)
134 {
135 return false;
136 }
137
138 if (lower_.size() == 0)
139 {
140 return v <= upper_;
141 }
142
143 if (upper_.size() == 0)
144 {
145 return v >= lower_;
146 }
147
148 return (v >= lower_ && v <= upper_);
149 }
150 };
151
152
153 class DicomFindQuery::WildcardConstraint : public DicomFindQuery::IConstraint
154 {
155 private:
156 boost::regex pattern_;
157
158 public:
159 WildcardConstraint(const std::string& wildcard)
160 {
161 pattern_ = boost::regex(Toolbox::WildcardToRegularExpression(wildcard),
162 boost::regex::icase /* case insensitive search */);
163 }
164
165 virtual bool Apply(const std::string& value) const
166 {
167 return boost::regex_match(value, pattern_);
168 }
169 };
170
171
172 void DicomFindQuery::PrepareMainDicomTags(ResourceType level)
173 {
174 std::set<DicomTag> tags;
175 DicomMap::GetMainDicomTags(tags, level);
176
177 for (std::set<DicomTag>::const_iterator
178 it = tags.begin(); it != tags.end(); ++it)
179 {
180 mainDicomTags_[*it] = level;
181 }
182 }
183
184
185 DicomFindQuery::DicomFindQuery() :
186 level_(ResourceType_Patient),
187 filterJson_(false)
188 {
189 PrepareMainDicomTags(ResourceType_Patient);
190 PrepareMainDicomTags(ResourceType_Study);
191 PrepareMainDicomTags(ResourceType_Series);
192 PrepareMainDicomTags(ResourceType_Instance);
193 }
194
195
196 DicomFindQuery::~DicomFindQuery()
197 {
198 for (Constraints::iterator it = constraints_.begin();
199 it != constraints_.end(); it++)
200 {
201 delete it->second;
202 }
203 }
204
205
206
207
208 void DicomFindQuery::AssignConstraint(const DicomTag& tag,
209 IConstraint* constraint)
210 {
211 Constraints::iterator it = constraints_.find(tag);
212
213 if (it != constraints_.end())
214 {
215 constraints_.erase(it);
216 }
217
218 constraints_[tag] = constraint;
219
220 MainDicomTags::const_iterator tmp = mainDicomTags_.find(tag);
221 if (tmp == mainDicomTags_.end())
222 {
223 // The query depends upon a DICOM tag that is not a main tag
224 // from the point of view of Orthanc, we need to decode the
225 // JSON file on the disk.
226 filterJson_ = true;
227 }
228 else
229 {
230 filteredLevels_.insert(tmp->second);
231 }
232 }
233
234
235 void DicomFindQuery::SetConstraint(const DicomTag& tag,
236 const std::string& constraint)
237 {
238 // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
239 // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html
240
241 if (constraint.find('-') != std::string::npos)
242 {
243 AssignConstraint(tag, new RangeConstraint(constraint));
244 }
245 else if (constraint.find('\\') != std::string::npos)
246 {
247 AssignConstraint(tag, new ListConstraint(constraint));
248 }
249 else if (constraint.find('*') != std::string::npos ||
250 constraint.find('?') != std::string::npos)
251 {
252 AssignConstraint(tag, new WildcardConstraint(constraint));
253 }
254 else
255 {
256 /**
257 * Case-insensitive match for PN value representation (Patient
258 * Name). Case-senstive match for all the other value
259 * representations.
260 *
261 * Reference: DICOM PS 3.4
262 * - C.2.2.2.1 ("Single Value Matching")
263 * - C.2.2.2.4 ("Wild Card Matching")
264 * http://medical.nema.org/Dicom/2011/11_04pu.pdf
265 *
266 * "Except for Attributes with a PN Value Representation, only
267 * entities with values which match exactly the value specified in the
268 * request shall match. This matching is case-sensitive, i.e.,
269 * sensitive to the exact encoding of the key attribute value in
270 * character sets where a letter may have multiple encodings (e.g.,
271 * based on its case, its position in a word, or whether it is
272 * accented)
273 *
274 * For Attributes with a PN Value Representation (e.g., Patient Name
275 * (0010,0010)), an application may perform literal matching that is
276 * either case-sensitive, or that is insensitive to some or all
277 * aspects of case, position, accent, or other character encoding
278 * variants."
279 *
280 * (0008,0018) UI SOPInstanceUID => Case-sensitive
281 * (0008,0050) SH AccessionNumber => Case-sensitive
282 * (0010,0020) LO PatientID => Case-sensitive
283 * (0020,000D) UI StudyInstanceUID => Case-sensitive
284 * (0020,000E) UI SeriesInstanceUID => Case-sensitive
285 **/
286
287 AssignConstraint(tag, new ValueConstraint(constraint, FromDcmtkBridge::IsPNValueRepresentation(tag)));
288 }
289 }
290
291
292 bool DicomFindQuery::RestrictIdentifier(std::string& value,
293 DicomTag identifier) const
294 {
295 Constraints::const_iterator it = constraints_.find(identifier);
296 if (it == constraints_.end() ||
297 !it->second->IsExactConstraint())
298 {
299 return false;
300 }
301 else
302 {
303 value = dynamic_cast<ValueConstraint*>(it->second)->GetValue();
304 return true;
305 }
306 }
307
308 bool DicomFindQuery::HasMainDicomTagsFilter(ResourceType level) const
309 {
310 return filteredLevels_.find(level) != filteredLevels_.end();
311 }
312
313 bool DicomFindQuery::FilterMainDicomTags(const DicomMap& mainTags,
314 ResourceType level) const
315 {
316 std::set<DicomTag> tags;
317 mainTags.GetTags(tags);
318
319 for (std::set<DicomTag>::const_iterator
320 it = tags.begin(); it != tags.end(); ++it)
321 {
322 Constraints::const_iterator constraint = constraints_.find(*it);
323 if (!constraint->second->Apply(mainTags.GetValue(*it).AsString()))
324 {
325 return false;
326 }
327 }
328
329 return true;
330 }
331
332 bool DicomFindQuery::HasInstanceFilter() const
333 {
334 return filterJson_;
335 }
336
337 bool DicomFindQuery::FilterInstance(const std::string& instanceId,
338 const Json::Value& content) const
339 {
340 for (Constraints::const_iterator it = constraints_.begin();
341 it != constraints_.end(); ++it)
342 {
343 std::string tag = it->first.Format();
344 std::string value;
345 if (content.isMember(tag))
346 {
347 value = content.get(tag, Json::arrayValue).get("Value", "").asString();
348 }
349
350 if (!it->second->Apply(value))
351 {
352 return false;
353 }
354 }
355
356 return true;
357 }
358 }