comparison OrthancServer/OrthancFindRequestHandler.cpp @ 714:6a1dbba0cca7

new implementation of C-Find handler
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Feb 2014 11:26:12 +0100
parents 2e67366aab83
children cf6e761e7da5
comparison
equal deleted inserted replaced
713:9d1973813d8b 714:6a1dbba0cca7
284 284
285 return true; 285 return true;
286 } 286 }
287 287
288 288
289 static bool LookupCandidateResourcesInternal(/* out */ std::list<std::string>& resources, 289 namespace
290 /* in */ ServerIndex& index, 290 {
291 /* in */ ResourceType level, 291 class CandidateResources
292 /* in */ const DicomMap& query, 292 {
293 /* in */ DicomTag tag) 293 private:
294 { 294 ServerIndex& index_;
295 if (query.HasTag(tag)) 295 ModalityManufacturer manufacturer_;
296 { 296 ResourceType level_;
297 const DicomValue& value = query.GetValue(tag); 297 bool isFilterApplied_;
298 if (!value.IsNull()) 298 std::set<std::string> filtered_;
299 { 299
300 std::string str = query.GetValue(tag).AsString(); 300 static void ListToSet(std::set<std::string>& target,
301 if (!IsWildcard(str)) 301 const std::list<std::string>& source)
302 { 302 {
303 index.LookupTagValue(resources, tag, str/*, level*/); 303 for (std::list<std::string>::const_iterator
304 return true; 304 it = source.begin(); it != source.end(); it++)
305 } 305 {
306 } 306 target.insert(*it);
307 } 307 }
308 308 }
309 return false; 309
310 } 310 void ApplyExactFilter(const DicomTag& tag, const std::string& value)
311 311 {
312 312 LOG(INFO) << "Applying exact filter on tag "
313 static void LookupCandidateResources(/* out */ std::list<std::string>& resources, 313 << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")";
314 /* in */ ServerIndex& index, 314
315 /* in */ ResourceType level, 315 std::list<std::string> resources;
316 /* in */ const DicomMap& query, 316 index_.LookupTagValue(resources, tag, value, level_);
317 /* in */ ModalityManufacturer manufacturer) 317
318 { 318 if (isFilterApplied_)
319 // TODO : Speed up using full querying against the MainDicomTags. 319 {
320 320 std::set<std::string> s;
321 resources.clear(); 321 ListToSet(s, resources);
322 322
323 bool done = false; 323 std::set<std::string> tmp = filtered_;
324 324 filtered_.clear();
325 switch (level) 325
326 { 326 for (std::set<std::string>::const_iterator
327 case ResourceType_Patient: 327 it = tmp.begin(); it != tmp.end(); it++)
328 done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_PATIENT_ID); 328 {
329 break; 329 if (s.find(*it) != s.end())
330 330 {
331 case ResourceType_Study: 331 filtered_.insert(*it);
332 done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID); 332 }
333 break; 333 }
334
335 case ResourceType_Series:
336 done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID);
337 break;
338
339 case ResourceType_Instance:
340 if (manufacturer == ModalityManufacturer_MedInria)
341 {
342 std::list<std::string> series;
343
344 if (LookupCandidateResourcesInternal(series, index, ResourceType_Series, query, DICOM_TAG_SERIES_INSTANCE_UID) &&
345 series.size() == 1)
346 {
347 index.GetChildInstances(resources, series.front());
348 done = true;
349 }
350 } 334 }
351 else 335 else
352 { 336 {
353 done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SOP_INSTANCE_UID); 337 assert(filtered_.empty());
354 } 338 isFilterApplied_ = true;
355 339 ListToSet(filtered_, resources);
356 break; 340 }
357 341 }
358 default: 342
359 break; 343 public:
360 } 344 CandidateResources(ServerIndex& index,
361 345 ModalityManufacturer manufacturer) :
362 if (!done) 346 index_(index),
363 { 347 manufacturer_(manufacturer),
364 Json::Value allResources; 348 level_(ResourceType_Patient),
365 index.GetAllUuids(allResources, level); 349 isFilterApplied_(false)
366 assert(allResources.type() == Json::arrayValue); 350 {
367 351 }
368 for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++) 352
369 { 353 ResourceType GetLevel() const
370 resources.push_back(allResources[i].asString()); 354 {
371 } 355 return level_;
372 } 356 }
357
358 void GoDown()
359 {
360 assert(level_ != ResourceType_Instance);
361
362 if (isFilterApplied_)
363 {
364 std::set<std::string> tmp = filtered_;
365
366 filtered_.clear();
367
368 for (std::set<std::string>::const_iterator
369 it = tmp.begin(); it != tmp.end(); it++)
370 {
371 std::list<std::string> children;
372 index_.GetChildren(children, *it);
373 ListToSet(filtered_, children);
374 }
375 }
376
377 switch (level_)
378 {
379 case ResourceType_Patient:
380 level_ = ResourceType_Study;
381 break;
382
383 case ResourceType_Study:
384 level_ = ResourceType_Series;
385 break;
386
387 case ResourceType_Series:
388 level_ = ResourceType_Instance;
389 break;
390
391 default:
392 throw OrthancException(ErrorCode_InternalError);
393 }
394 }
395
396 void Flatten(std::list<std::string>& resources) const
397 {
398 resources.clear();
399
400 if (isFilterApplied_)
401 {
402 for (std::set<std::string>::const_iterator
403 it = filtered_.begin(); it != filtered_.end(); it++)
404 {
405 resources.push_back(*it);
406 }
407 }
408 else
409 {
410 Json::Value tmp;
411 index_.GetAllUuids(tmp, level_);
412 for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++)
413 {
414 resources.push_back(tmp[i].asString());
415 }
416 }
417 }
418
419 void ApplyFilter(const DicomTag& tag, const DicomMap& query)
420 {
421 if (query.HasTag(tag))
422 {
423 const DicomValue& value = query.GetValue(tag);
424 if (!value.IsNull())
425 {
426 std::string value = query.GetValue(tag).AsString();
427 if (!IsWildcard(value))
428 {
429 ApplyExactFilter(tag, value);
430 }
431 }
432 }
433 }
434 };
373 } 435 }
374 436
375 437
376 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, 438 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
377 const DicomMap& input, 439 const DicomMap& input,
404 throw OrthancException(ErrorCode_BadRequest); 466 throw OrthancException(ErrorCode_BadRequest);
405 } 467 }
406 468
407 ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); 469 ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
408 470
409 switch (manufacturer) 471 if (level != ResourceType_Patient &&
410 { 472 level != ResourceType_Study &&
411 case ModalityManufacturer_MedInria: 473 level != ResourceType_Series &&
412 // MedInria makes FIND requests at the instance level before starting MOVE 474 level != ResourceType_Instance)
413 break; 475 {
414 476 throw OrthancException(ErrorCode_NotImplemented);
415 default: 477 }
416 if (level != ResourceType_Patient && 478
417 level != ResourceType_Study && 479
418 level != ResourceType_Series) 480 DicomArray query(input);
419 { 481 LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level);
420 throw OrthancException(ErrorCode_NotImplemented); 482
421 } 483 for (size_t i = 0; i < query.GetSize(); i++)
484 {
485 if (!query.GetElement(i).GetValue().IsNull())
486 {
487 LOG(INFO) << " " << query.GetElement(i).GetTag()
488 << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag())
489 << " = " << query.GetElement(i).GetValue().AsString();
490 }
422 } 491 }
423 492
424 493
425 /** 494 /**
426 * Retrieve the candidate resources for this query level. Whenever 495 * Retrieve the candidate resources for this query level. Whenever
427 * possible, we avoid returning ALL the resources for this query 496 * possible, we avoid returning ALL the resources for this query
428 * level, as it would imply reading the JSON file on the harddisk 497 * level, as it would imply reading the JSON file on the harddisk
429 * for each of them. 498 * for each of them.
430 **/ 499 **/
431 500
501 CandidateResources candidates(context_.GetIndex(), manufacturer);
502
503 for (;;)
504 {
505 switch (candidates.GetLevel())
506 {
507 case ResourceType_Patient:
508 candidates.ApplyFilter(DICOM_TAG_PATIENT_ID, input);
509 break;
510
511 case ResourceType_Study:
512 candidates.ApplyFilter(DICOM_TAG_STUDY_INSTANCE_UID, input);
513 candidates.ApplyFilter(DICOM_TAG_ACCESSION_NUMBER, input);
514 break;
515
516 case ResourceType_Series:
517 candidates.ApplyFilter(DICOM_TAG_SERIES_INSTANCE_UID, input);
518 break;
519
520 case ResourceType_Instance:
521 candidates.ApplyFilter(DICOM_TAG_SOP_INSTANCE_UID, input);
522 break;
523
524 default:
525 throw OrthancException(ErrorCode_InternalError);
526 }
527
528 if (candidates.GetLevel() == level)
529 {
530 break;
531 }
532
533 candidates.GoDown();
534 }
535
432 std::list<std::string> resources; 536 std::list<std::string> resources;
433 LookupCandidateResources(resources, context_.GetIndex(), level, input, manufacturer); 537 candidates.Flatten(resources);
434 538
539 LOG(INFO) << "Number of candidate resources after exact filtering: " << resources.size();
435 540
436 /** 541 /**
437 * Apply filtering on modalities for studies, if asked (this is an 542 * Apply filtering on modalities for studies, if asked (this is an
438 * extension to standard DICOM) 543 * extension to standard DICOM)
439 * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND 544 * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND
452 557
453 /** 558 /**
454 * Loop over all the resources for this query level. 559 * Loop over all the resources for this query level.
455 **/ 560 **/
456 561
457 DicomArray query(input);
458 for (std::list<std::string>::const_iterator 562 for (std::list<std::string>::const_iterator
459 resource = resources.begin(); resource != resources.end(); ++resource) 563 resource = resources.begin(); resource != resources.end(); ++resource)
460 { 564 {
461 try 565 try
462 { 566 {