comparison PalantirServer/PalantirRestApi.cpp @ 0:3959d33612cc

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 19 Jul 2012 14:32:22 +0200
parents
children 3a584803783e
comparison
equal deleted inserted replaced
-1:000000000000 0:3959d33612cc
1 /**
2 * Palantir - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
4 * 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 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 **/
19
20
21 #include "PalantirRestApi.h"
22
23 #include "PalantirInitialization.h"
24 #include "FromDcmtkBridge.h"
25 #include "../Core/Uuid.h"
26
27 #include <dcmtk/dcmdata/dcistrmb.h>
28 #include <dcmtk/dcmdata/dcfilefo.h>
29 #include <boost/lexical_cast.hpp>
30
31 namespace Palantir
32 {
33 static void SendJson(HttpOutput& output,
34 const Json::Value& value)
35 {
36 Json::StyledWriter writer;
37 std::string s = writer.write(value);
38 output.AnswerBufferWithContentType(s, "application/json");
39 }
40
41 bool PalantirRestApi::Store(Json::Value& result,
42 const std::string& postData)
43 {
44 // Prepare an input stream for the memory buffer
45 DcmInputBufferStream is;
46 if (postData.size() > 0)
47 {
48 is.setBuffer(&postData[0], postData.size());
49 }
50 is.setEos();
51
52 printf("[%d]\n", postData.size());
53
54 DcmFileFormat dicomFile;
55 if (dicomFile.read(is).good())
56 {
57 DicomMap dicomSummary;
58 FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
59
60 Json::Value dicomJson;
61 FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
62
63 std::string instanceUuid;
64 StoreStatus status = StoreStatus_Failure;
65 if (postData.size() > 0)
66 {
67 status = index_.Store
68 (instanceUuid, storage_, reinterpret_cast<const char*>(&postData[0]),
69 postData.size(), dicomSummary, dicomJson, "");
70 }
71
72 switch (status)
73 {
74 case StoreStatus_Success:
75 result["ID"] = instanceUuid;
76 result["Path"] = "/instances/" + instanceUuid;
77 result["Status"] = "Success";
78 return true;
79
80 case StoreStatus_AlreadyStored:
81 result["ID"] = instanceUuid;
82 result["Path"] = "/instances/" + instanceUuid;
83 result["Status"] = "AlreadyStored";
84 return true;
85
86 default:
87 return false;
88 }
89 }
90
91 return false;
92 }
93
94 void PalantirRestApi::ConnectToModality(DicomUserConnection& c,
95 const std::string& name)
96 {
97 std::string aet, address;
98 int port;
99 GetDicomModality(name, aet, address, port);
100 c.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "PALANTIR"));
101 c.SetDistantApplicationEntityTitle(aet);
102 c.SetDistantHost(address);
103 c.SetDistantPort(port);
104 c.Open();
105 }
106
107 bool PalantirRestApi::MergeQueryAndTemplate(DicomMap& result,
108 const std::string& postData)
109 {
110 Json::Value query;
111 Json::Reader reader;
112
113 if (!reader.parse(postData, query) ||
114 !query.type() == Json::objectValue)
115 {
116 return false;
117 }
118
119 Json::Value::Members members = query.getMemberNames();
120 for (size_t i = 0; i < members.size(); i++)
121 {
122 DicomTag t = FromDcmtkBridge::FindTag(members[i]);
123 result.SetValue(t, query[members[i]].asString());
124 }
125
126 return true;
127 }
128
129 bool PalantirRestApi::DicomFindPatient(Json::Value& result,
130 DicomUserConnection& c,
131 const std::string& postData)
132 {
133 DicomMap m;
134 DicomMap::SetupFindPatientTemplate(m);
135 if (!MergeQueryAndTemplate(m, postData))
136 {
137 return false;
138 }
139
140 DicomFindAnswers answers;
141 c.FindPatient(answers, m);
142 answers.ToJson(result);
143 return true;
144 }
145
146 bool PalantirRestApi::DicomFindStudy(Json::Value& result,
147 DicomUserConnection& c,
148 const std::string& postData)
149 {
150 DicomMap m;
151 DicomMap::SetupFindStudyTemplate(m);
152 if (!MergeQueryAndTemplate(m, postData))
153 {
154 return false;
155 }
156
157 if (m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
158 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2)
159 {
160 return false;
161 }
162
163 DicomFindAnswers answers;
164 c.FindStudy(answers, m);
165 answers.ToJson(result);
166 return true;
167 }
168
169 bool PalantirRestApi::DicomFindSeries(Json::Value& result,
170 DicomUserConnection& c,
171 const std::string& postData)
172 {
173 DicomMap m;
174 DicomMap::SetupFindSeriesTemplate(m);
175 if (!MergeQueryAndTemplate(m, postData))
176 {
177 return false;
178 }
179
180 if ((m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
181 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) ||
182 m.GetValue(DicomTag::STUDY_UID).AsString().size() <= 2)
183 {
184 return false;
185 }
186
187 DicomFindAnswers answers;
188 c.FindSeries(answers, m);
189 answers.ToJson(result);
190 return true;
191 }
192
193 bool PalantirRestApi::DicomFind(Json::Value& result,
194 DicomUserConnection& c,
195 const std::string& postData)
196 {
197 DicomMap m;
198 DicomMap::SetupFindPatientTemplate(m);
199 if (!MergeQueryAndTemplate(m, postData))
200 {
201 return false;
202 }
203
204 DicomFindAnswers patients;
205 c.FindPatient(patients, m);
206
207 // Loop over the found patients
208 result = Json::arrayValue;
209 for (size_t i = 0; i < patients.GetSize(); i++)
210 {
211 Json::Value patient(Json::objectValue);
212 FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i));
213
214 DicomMap::SetupFindStudyTemplate(m);
215 if (!MergeQueryAndTemplate(m, postData))
216 {
217 return false;
218 }
219 m.CopyTagIfExists(patients.GetAnswer(i), DicomTag::PATIENT_ID);
220
221 DicomFindAnswers studies;
222 c.FindStudy(studies, m);
223
224 patient["Studies"] = Json::arrayValue;
225
226 // Loop over the found studies
227 for (size_t j = 0; j < studies.GetSize(); j++)
228 {
229 Json::Value study(Json::objectValue);
230 FromDcmtkBridge::ToJson(study, studies.GetAnswer(j));
231
232 DicomMap::SetupFindSeriesTemplate(m);
233 if (!MergeQueryAndTemplate(m, postData))
234 {
235 return false;
236 }
237 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::PATIENT_ID);
238 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::STUDY_UID);
239
240 DicomFindAnswers series;
241 c.FindSeries(series, m);
242
243 // Loop over the found series
244 study["Series"] = Json::arrayValue;
245 for (size_t k = 0; k < series.GetSize(); k++)
246 {
247 Json::Value series2(Json::objectValue);
248 FromDcmtkBridge::ToJson(series2, series.GetAnswer(k));
249 study["Series"].append(series2);
250 }
251
252 patient["Studies"].append(study);
253 }
254
255 result.append(patient);
256 }
257
258 return true;
259 }
260
261
262
263 bool PalantirRestApi::DicomStore(Json::Value& result,
264 DicomUserConnection& c,
265 const std::string& postData)
266 {
267 Json::Value found(Json::objectValue);
268
269 if (!Toolbox::IsUuid(postData))
270 {
271 // This is not a UUID, assume this is a DICOM instance
272 c.Store(postData);
273 }
274 else if (index_.GetSeries(found, postData))
275 {
276 // The UUID corresponds to a series
277 for (size_t i = 0; i < found["Instances"].size(); i++)
278 {
279 std::string uuid = found["Instances"][i].asString();
280 Json::Value instance(Json::objectValue);
281 if (index_.GetInstance(instance, uuid))
282 {
283 std::string content;
284 storage_.ReadFile(content, instance["FileUuid"].asString());
285 c.Store(content);
286 }
287 else
288 {
289 return false;
290 }
291 }
292 }
293 else if (index_.GetInstance(found, postData))
294 {
295 // The UUID corresponds to an instance
296 std::string content;
297 storage_.ReadFile(content, found["FileUuid"].asString());
298 c.Store(content);
299 }
300 else
301 {
302 return false;
303 }
304
305 return true;
306 }
307
308
309 PalantirRestApi::PalantirRestApi(ServerIndex& index,
310 const std::string& path) :
311 index_(index),
312 storage_(path)
313 {
314 GetListOfDicomModalities(modalities_);
315 }
316
317
318 void PalantirRestApi::Handle(
319 HttpOutput& output,
320 const std::string& method,
321 const UriComponents& uri,
322 const Arguments& headers,
323 const Arguments& arguments,
324 const std::string& postData)
325 {
326 if (uri.size() == 0)
327 {
328 if (method == "GET")
329 {
330 output.Redirect("/app/explorer.html");
331 }
332 else
333 {
334 output.SendMethodNotAllowedError("GET");
335 }
336
337 return;
338 }
339
340 bool existingResource = false;
341 Json::Value result(Json::objectValue);
342
343
344 // List all the instances ---------------------------------------------------
345
346 if (uri.size() == 1 && uri[0] == "instances")
347 {
348 if (method == "GET")
349 {
350 result = Json::Value(Json::arrayValue);
351 index_.GetAllUuids(result, "Instances");
352 existingResource = true;
353 }
354 else if (method == "POST")
355 {
356 // Add a new instance to the storage
357 if (Store(result, postData))
358 {
359 SendJson(output, result);
360 return;
361 }
362 else
363 {
364 output.SendHeader(HttpStatus_415_UnsupportedMediaType);
365 return;
366 }
367 }
368 else
369 {
370 output.SendMethodNotAllowedError("GET,POST");
371 return;
372 }
373 }
374
375
376 // List all the patients, studies or series ---------------------------------
377
378 if (uri.size() == 1 &&
379 (uri[0] == "series" ||
380 uri[0] == "studies" ||
381 uri[0] == "patients"))
382 {
383 if (method == "GET")
384 {
385 result = Json::Value(Json::arrayValue);
386
387 if (uri[0] == "instances")
388 index_.GetAllUuids(result, "Instances");
389 else if (uri[0] == "series")
390 index_.GetAllUuids(result, "Series");
391 else if (uri[0] == "studies")
392 index_.GetAllUuids(result, "Studies");
393 else if (uri[0] == "patients")
394 index_.GetAllUuids(result, "Patients");
395
396 existingResource = true;
397 }
398 else
399 {
400 output.SendMethodNotAllowedError("GET");
401 return;
402 }
403 }
404
405
406 // Information about a single object ----------------------------------------
407
408 else if (uri.size() == 2 &&
409 (uri[0] == "instances" ||
410 uri[0] == "series" ||
411 uri[0] == "studies" ||
412 uri[0] == "patients"))
413 {
414 if (method == "GET")
415 {
416 if (uri[0] == "patients")
417 {
418 existingResource = index_.GetPatient(result, uri[1]);
419 }
420 else if (uri[0] == "studies")
421 {
422 existingResource = index_.GetStudy(result, uri[1]);
423 }
424 else if (uri[0] == "series")
425 {
426 existingResource = index_.GetSeries(result, uri[1]);
427 }
428 else if (uri[0] == "instances")
429 {
430 existingResource = index_.GetInstance(result, uri[1]);
431 }
432 }
433 else if (method == "DELETE")
434 {
435 if (uri[0] == "patients")
436 {
437 existingResource = index_.DeletePatient(result, uri[1]);
438 }
439 else if (uri[0] == "studies")
440 {
441 existingResource = index_.DeleteStudy(result, uri[1]);
442 }
443 else if (uri[0] == "series")
444 {
445 existingResource = index_.DeleteSeries(result, uri[1]);
446 }
447 else if (uri[0] == "instances")
448 {
449 existingResource = index_.DeleteInstance(result, uri[1]);
450 }
451
452 if (existingResource)
453 {
454 result["Status"] = "Success";
455 }
456 }
457 else
458 {
459 output.SendMethodNotAllowedError("GET,DELETE");
460 return;
461 }
462 }
463
464
465 // Get the DICOM or the JSON file of one instance ---------------------------
466
467 else if (uri.size() == 3 &&
468 uri[0] == "instances" &&
469 (uri[2] == "file" ||
470 uri[2] == "all-tags"))
471 {
472 std::string fileUuid, contentType;
473 if (uri[2] == "file")
474 {
475 existingResource = index_.GetDicomFile(fileUuid, uri[1]);
476 contentType = "application/dicom";
477 }
478 else
479 {
480 existingResource = index_.GetJsonFile(fileUuid, uri[1]);
481 contentType = "application/json";
482 }
483
484 if (existingResource)
485 {
486 output.AnswerFile(storage_, fileUuid, contentType);
487 return;
488 }
489 }
490
491
492 else if (uri.size() == 3 &&
493 uri[0] == "instances" &&
494 uri[2] == "normalized-image")
495 {
496 std::string uuid;
497 existingResource = index_.GetDicomFile(uuid, uri[1]);
498
499 if (existingResource)
500 {
501 std::string dicomContent, png;
502 storage_.ReadFile(dicomContent, uuid);
503 try
504 {
505 FromDcmtkBridge::ExtractNormalizedImage(png, dicomContent);
506 output.AnswerBufferWithContentType(png, "image/png");
507 return;
508 }
509 catch (PalantirException&)
510 {
511 output.Redirect("/app/images/Unsupported.png");
512 return;
513 }
514 }
515 }
516
517
518
519 // Changes API --------------------------------------------------------------
520
521 if (uri.size() == 1 && uri[0] == "changes")
522 {
523 if (method == "GET")
524 {
525 const static unsigned int MAX_RESULTS = 100;
526
527 std::string filter = GetArgument(arguments, "filter", "");
528 int64_t since;
529 unsigned int limit;
530 try
531 {
532 since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0"));
533 limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0"));
534 }
535 catch (boost::bad_lexical_cast)
536 {
537 output.SendHeader(HttpStatus_400_BadRequest);
538 return;
539 }
540
541 if (limit == 0 || limit > MAX_RESULTS)
542 {
543 limit = MAX_RESULTS;
544 }
545
546 if (!index_.GetChanges(result, since, filter, limit))
547 {
548 output.SendHeader(HttpStatus_400_BadRequest);
549 return;
550 }
551
552 existingResource = true;
553 }
554 else
555 {
556 output.SendMethodNotAllowedError("GET");
557 return;
558 }
559 }
560
561
562 // DICOM bridge -------------------------------------------------------------
563
564 if (uri.size() == 1 &&
565 uri[0] == "modalities")
566 {
567 if (method == "GET")
568 {
569 result = Json::Value(Json::arrayValue);
570 existingResource = true;
571
572 for (Modalities::const_iterator it = modalities_.begin();
573 it != modalities_.end(); it++)
574 {
575 result.append(*it);
576 }
577 }
578 else
579 {
580 output.SendMethodNotAllowedError("GET");
581 return;
582 }
583 }
584
585 if ((uri.size() == 2 ||
586 uri.size() == 3) &&
587 uri[0] == "modalities")
588 {
589 if (modalities_.find(uri[1]) == modalities_.end())
590 {
591 // Unknown modality
592 }
593 else if (uri.size() == 2)
594 {
595 if (method != "GET")
596 {
597 output.SendMethodNotAllowedError("POST");
598 return;
599 }
600 else
601 {
602 existingResource = true;
603 result = Json::arrayValue;
604 result.append("find-patient");
605 result.append("find-study");
606 result.append("find-series");
607 result.append("find");
608 result.append("store");
609 }
610 }
611 else if (uri.size() == 3)
612 {
613 if (uri[2] != "find-patient" &&
614 uri[2] != "find-study" &&
615 uri[2] != "find-series" &&
616 uri[2] != "find" &&
617 uri[2] != "store")
618 {
619 // Unknown request
620 }
621 else if (method != "POST")
622 {
623 output.SendMethodNotAllowedError("POST");
624 return;
625 }
626 else
627 {
628 DicomUserConnection connection;
629 ConnectToModality(connection, uri[1]);
630 existingResource = true;
631
632 if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) ||
633 (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) ||
634 (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) ||
635 (uri[2] == "find" && !DicomFind(result, connection, postData)) ||
636 (uri[2] == "store" && !DicomStore(result, connection, postData)))
637 {
638 output.SendHeader(HttpStatus_400_BadRequest);
639 return;
640 }
641 }
642 }
643 }
644
645
646 if (existingResource)
647 {
648 SendJson(output, result);
649 }
650 else
651 {
652 output.SendHeader(HttpStatus_404_NotFound);
653 }
654 }
655 }