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 }