Mercurial > hg > orthanc
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 { |