Mercurial > hg > orthanc
comparison OrthancServer/Search/LookupResource.cpp @ 1751:fb569ee09a69 db-changes
LookupResource complete
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 27 Oct 2015 16:05:42 +0100 |
parents | 55d52567bebb |
children | faf2ecab3472 |
comparison
equal
deleted
inserted
replaced
1750:55d52567bebb | 1751:fb569ee09a69 |
---|---|
31 | 31 |
32 | 32 |
33 #include "../PrecompiledHeadersServer.h" | 33 #include "../PrecompiledHeadersServer.h" |
34 #include "LookupResource.h" | 34 #include "LookupResource.h" |
35 | 35 |
36 #include "ListConstraint.h" | |
37 #include "RangeConstraint.h" | |
38 #include "ValueConstraint.h" | |
39 #include "WildcardConstraint.h" | |
40 | |
36 #include "../../Core/OrthancException.h" | 41 #include "../../Core/OrthancException.h" |
37 #include "../../Core/FileStorage/StorageAccessor.h" | 42 #include "../../Core/FileStorage/StorageAccessor.h" |
38 #include "../ServerToolbox.h" | 43 #include "../ServerToolbox.h" |
44 #include "../FromDcmtkBridge.h" | |
39 | 45 |
40 | 46 |
41 namespace Orthanc | 47 namespace Orthanc |
42 { | 48 { |
43 LookupResource::Level::Level(ResourceType level) : level_(level) | 49 LookupResource::Level::Level(ResourceType level) : level_(level) |
267 } | 273 } |
268 } | 274 } |
269 | 275 |
270 | 276 |
271 | 277 |
272 void LookupResource::ApplyUnoptimizedConstraints(SetOfResources& candidates, | 278 bool LookupResource::ApplyUnoptimizedConstraints(std::list<int64_t>& result, |
279 const std::list<int64_t>& candidates, | |
280 boost::mutex& databaseMutex, | |
273 IDatabaseWrapper& database, | 281 IDatabaseWrapper& database, |
274 IStorageArea& storageArea) const | 282 IStorageArea& storageArea) const |
275 { | 283 { |
276 if (unoptimizedConstraints_.empty()) | 284 assert(!unoptimizedConstraints_.empty()); |
277 { | |
278 // Nothing to do | |
279 return; | |
280 } | |
281 | |
282 std::list<int64_t> source; | |
283 candidates.Flatten(source); | |
284 candidates.Clear(); | |
285 | 285 |
286 StorageAccessor accessor(storageArea); | 286 StorageAccessor accessor(storageArea); |
287 | 287 |
288 std::list<int64_t> filtered; | 288 for (std::list<int64_t>::const_iterator candidate = candidates.begin(); |
289 for (std::list<int64_t>::const_iterator candidate = source.begin(); | 289 candidate != candidates.end(); ++candidate) |
290 candidate != source.end(); ++candidate) | |
291 { | 290 { |
292 if (maxResults_ != 0 && | 291 if (maxResults_ != 0 && |
293 filtered.size() >= maxResults_) | 292 result.size() >= maxResults_) |
294 { | 293 { |
295 // We have enough results | 294 // We have enough results, not finished |
296 break; | 295 return false; |
297 } | 296 } |
298 | 297 |
299 int64_t instance; | 298 int64_t instance; |
300 FileInfo attachment; | 299 FileInfo attachment; |
301 if (!Toolbox::FindOneChildInstance(instance, database, *candidate, level_) || | 300 |
302 !database.LookupAttachment(attachment, instance, FileContentType_DicomAsJson)) | 301 { |
303 { | 302 boost::mutex::scoped_lock lock(databaseMutex); |
304 continue; | 303 |
304 if (!Toolbox::FindOneChildInstance(instance, database, *candidate, level_) || | |
305 !database.LookupAttachment(attachment, instance, FileContentType_DicomAsJson)) | |
306 { | |
307 continue; | |
308 } | |
305 } | 309 } |
306 | 310 |
307 Json::Value content; | 311 Json::Value content; |
308 accessor.Read(content, attachment); | 312 accessor.Read(content, attachment); |
309 | 313 |
328 } | 332 } |
329 } | 333 } |
330 | 334 |
331 if (match) | 335 if (match) |
332 { | 336 { |
333 filtered.push_back(*candidate); | 337 result.push_back(*candidate); |
334 } | 338 } |
335 } | 339 } |
336 | 340 |
337 candidates.Intersect(filtered); | 341 return true; // Finished |
338 } | 342 } |
339 | 343 |
340 | 344 |
341 void LookupResource::ApplyLevel(SetOfResources& candidates, | 345 void LookupResource::ApplyLevel(SetOfResources& candidates, |
342 ResourceType level, | 346 ResourceType level, |
348 it->second->Apply(candidates, database); | 352 it->second->Apply(candidates, database); |
349 } | 353 } |
350 } | 354 } |
351 | 355 |
352 | 356 |
353 void LookupResource::Apply(std::list<int64_t>& result, | 357 bool LookupResource::Apply(std::list<int64_t>& result, |
358 boost::mutex& databaseMutex, | |
354 IDatabaseWrapper& database, | 359 IDatabaseWrapper& database, |
355 IStorageArea& storageArea) const | 360 IStorageArea& storageArea) const |
356 { | 361 { |
357 SetOfResources candidates(database, level_); | 362 std::list<int64_t> tmp; |
358 | 363 |
359 switch (level_) | 364 { |
360 { | 365 boost::mutex::scoped_lock lock(databaseMutex); |
361 case ResourceType_Patient: | 366 SetOfResources candidates(database, level_); |
362 ApplyLevel(candidates, ResourceType_Patient, database); | 367 |
363 break; | 368 switch (level_) |
364 | 369 { |
365 case ResourceType_Study: | 370 case ResourceType_Patient: |
366 ApplyLevel(candidates, ResourceType_Study, database); | 371 ApplyLevel(candidates, ResourceType_Patient, database); |
367 break; | 372 break; |
368 | 373 |
369 case ResourceType_Series: | 374 case ResourceType_Study: |
370 ApplyLevel(candidates, ResourceType_Study, database); | 375 ApplyLevel(candidates, ResourceType_Study, database); |
371 candidates.GoDown(); | 376 break; |
372 ApplyLevel(candidates, ResourceType_Series, database); | 377 |
373 break; | 378 case ResourceType_Series: |
374 | 379 ApplyLevel(candidates, ResourceType_Study, database); |
375 case ResourceType_Instance: | 380 candidates.GoDown(); |
376 ApplyLevel(candidates, ResourceType_Study, database); | 381 ApplyLevel(candidates, ResourceType_Series, database); |
377 candidates.GoDown(); | 382 break; |
378 ApplyLevel(candidates, ResourceType_Series, database); | 383 |
379 candidates.GoDown(); | 384 case ResourceType_Instance: |
380 ApplyLevel(candidates, ResourceType_Instance, database); | 385 ApplyLevel(candidates, ResourceType_Study, database); |
381 break; | 386 candidates.GoDown(); |
382 | 387 ApplyLevel(candidates, ResourceType_Series, database); |
383 default: | 388 candidates.GoDown(); |
384 throw OrthancException(ErrorCode_InternalError); | 389 ApplyLevel(candidates, ResourceType_Instance, database); |
385 } | 390 break; |
386 | 391 |
387 ApplyUnoptimizedConstraints(candidates, database, storageArea); | 392 default: |
388 candidates.Flatten(result); | 393 throw OrthancException(ErrorCode_InternalError); |
389 } | 394 } |
390 | 395 |
391 | 396 if (unoptimizedConstraints_.empty()) |
392 void LookupResource::Apply(std::list<std::string>& result, | 397 { |
398 return candidates.Flatten(result, maxResults_); | |
399 } | |
400 else | |
401 { | |
402 candidates.Flatten(tmp); | |
403 } | |
404 } | |
405 | |
406 return ApplyUnoptimizedConstraints(result, tmp, databaseMutex, database, storageArea); | |
407 } | |
408 | |
409 | |
410 bool LookupResource::Apply(std::list<std::string>& result, | |
411 boost::mutex& databaseMutex, | |
393 IDatabaseWrapper& database, | 412 IDatabaseWrapper& database, |
394 IStorageArea& storageArea) const | 413 IStorageArea& storageArea) const |
395 { | 414 { |
415 | |
396 std::list<int64_t> tmp; | 416 std::list<int64_t> tmp; |
397 Apply(tmp, database, storageArea); | 417 bool finished = Apply(tmp, databaseMutex, database, storageArea); |
398 | 418 |
399 result.clear(); | 419 result.clear(); |
400 | 420 |
401 for (std::list<int64_t>::const_iterator | 421 { |
402 it = tmp.begin(); it != tmp.end(); ++it) | 422 boost::mutex::scoped_lock lock(databaseMutex); |
403 { | 423 |
404 result.push_back(database.GetPublicId(*it)); | 424 for (std::list<int64_t>::const_iterator |
425 it = tmp.begin(); it != tmp.end(); ++it) | |
426 { | |
427 result.push_back(database.GetPublicId(*it)); | |
428 } | |
429 } | |
430 | |
431 return finished; | |
432 } | |
433 | |
434 | |
435 void LookupResource::Add(const DicomTag& tag, | |
436 const std::string& dicomQuery, | |
437 bool caseSensitivePN) | |
438 { | |
439 ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag); | |
440 | |
441 bool sensitive = true; | |
442 if (vr == ValueRepresentation_PatientName) | |
443 { | |
444 sensitive = caseSensitivePN; | |
445 } | |
446 | |
447 // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained | |
448 // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html | |
449 | |
450 if ((vr == ValueRepresentation_Date || | |
451 vr == ValueRepresentation_DateTime || | |
452 vr == ValueRepresentation_Time) && | |
453 dicomQuery.find('-') != std::string::npos) | |
454 { | |
455 /** | |
456 * Range matching is only defined for TM, DA and DT value | |
457 * representations. This code fixes issues 35 and 37. | |
458 * | |
459 * Reference: "Range matching is not defined for types of | |
460 * Attributes other than dates and times", DICOM PS 3.4, | |
461 * C.2.2.2.5 ("Range Matching"). | |
462 **/ | |
463 size_t separator = dicomQuery.find('-'); | |
464 std::string lower = dicomQuery.substr(0, separator); | |
465 std::string upper = dicomQuery.substr(separator + 1); | |
466 Add(new RangeConstraint(tag, lower, upper, sensitive)); | |
467 } | |
468 else if (dicomQuery.find('\\') != std::string::npos) | |
469 { | |
470 std::auto_ptr<ListConstraint> constraint(new ListConstraint(tag, sensitive)); | |
471 | |
472 std::vector<std::string> items; | |
473 Toolbox::TokenizeString(items, dicomQuery, '\\'); | |
474 | |
475 for (size_t i = 0; i < items.size(); i++) | |
476 { | |
477 constraint->AddAllowedValue(items[i]); | |
478 } | |
479 | |
480 Add(constraint.release()); | |
481 } | |
482 else if (dicomQuery.find('*') != std::string::npos || | |
483 dicomQuery.find('?') != std::string::npos) | |
484 { | |
485 Add(new WildcardConstraint(tag, dicomQuery, sensitive)); | |
486 } | |
487 else | |
488 { | |
489 /** | |
490 * Case-insensitive match for PN value representation (Patient | |
491 * Name). Case-senstive match for all the other value | |
492 * representations. | |
493 * | |
494 * Reference: DICOM PS 3.4 | |
495 * - C.2.2.2.1 ("Single Value Matching") | |
496 * - C.2.2.2.4 ("Wild Card Matching") | |
497 * http://medical.nema.org/Dicom/2011/11_04pu.pdf | |
498 * | |
499 * "Except for Attributes with a PN Value Representation, only | |
500 * entities with values which match exactly the value specified in the | |
501 * request shall match. This matching is case-sensitive, i.e., | |
502 * sensitive to the exact encoding of the key attribute value in | |
503 * character sets where a letter may have multiple encodings (e.g., | |
504 * based on its case, its position in a word, or whether it is | |
505 * accented) | |
506 * | |
507 * For Attributes with a PN Value Representation (e.g., Patient Name | |
508 * (0010,0010)), an application may perform literal matching that is | |
509 * either case-sensitive, or that is insensitive to some or all | |
510 * aspects of case, position, accent, or other character encoding | |
511 * variants." | |
512 * | |
513 * (0008,0018) UI SOPInstanceUID => Case-sensitive | |
514 * (0008,0050) SH AccessionNumber => Case-sensitive | |
515 * (0010,0020) LO PatientID => Case-sensitive | |
516 * (0020,000D) UI StudyInstanceUID => Case-sensitive | |
517 * (0020,000E) UI SeriesInstanceUID => Case-sensitive | |
518 **/ | |
519 | |
520 Add(new ValueConstraint(tag, dicomQuery, sensitive)); | |
405 } | 521 } |
406 } | 522 } |
407 } | 523 } |