0
|
1 /**
|
|
2 * Orthanc - A Lightweight, RESTful DICOM Store
|
|
3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
|
|
4 * Department, University Hospital of Liege, Belgium
|
|
5 *
|
|
6 * This program is free software: you can redistribute it and/or
|
|
7 * modify it under the terms of the GNU Affero General Public License
|
|
8 * as published by the Free Software Foundation, either version 3 of
|
|
9 * the 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 * Affero General Public License for more details.
|
|
15 *
|
|
16 * You should have received a copy of the GNU Affero General Public License
|
|
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18 **/
|
|
19
|
|
20
|
|
21 #include <boost/thread.hpp>
|
|
22 #include <boost/lexical_cast.hpp>
|
|
23 #include <EmbeddedResources.h>
|
|
24
|
|
25 #include "../Orthanc/OrthancException.h"
|
|
26 #include "ViewerToolbox.h"
|
|
27 #include "ViewerPrefetchPolicy.h"
|
|
28 #include "DecodedImageAdapter.h"
|
|
29 #include "InstanceInformationAdapter.h"
|
|
30 #include "SeriesInformationAdapter.h"
|
|
31
|
|
32
|
|
33
|
|
34 class CacheContext
|
|
35 {
|
|
36 private:
|
|
37 std::auto_ptr<Orthanc::FilesystemStorage> storage_;
|
|
38 std::auto_ptr<Orthanc::SQLite::Connection> db_;
|
|
39 std::auto_ptr<OrthancPlugins::CacheManager> cache_;
|
|
40 std::auto_ptr<OrthancPlugins::CacheScheduler> scheduler_;
|
|
41
|
|
42 public:
|
|
43 CacheContext(const std::string& path)
|
|
44 {
|
|
45 boost::filesystem::path p(path);
|
|
46
|
|
47 storage_.reset(new Orthanc::FilesystemStorage(path));
|
|
48 db_.reset(new Orthanc::SQLite::Connection());
|
|
49 db_->Open((p / "cache.db").string());
|
|
50
|
|
51 cache_.reset(new OrthancPlugins::CacheManager(*db_, *storage_));
|
|
52 //cache_->SetSanityCheckEnabled(true); // For debug
|
|
53
|
|
54 scheduler_.reset(new OrthancPlugins::CacheScheduler(*cache_, 100));
|
|
55 }
|
|
56
|
|
57 OrthancPlugins::CacheScheduler& GetScheduler()
|
|
58 {
|
|
59 return *scheduler_;
|
|
60 }
|
|
61 };
|
|
62
|
|
63
|
|
64 static OrthancPluginContext* context_ = NULL;
|
|
65 static CacheContext* cache_ = NULL;
|
|
66
|
|
67
|
|
68
|
|
69 static int32_t OnChangeCallback(OrthancPluginChangeType changeType,
|
|
70 OrthancPluginResourceType resourceType,
|
|
71 const char* resourceId)
|
|
72 {
|
|
73 try
|
|
74 {
|
|
75 if (changeType == OrthancPluginChangeType_NewInstance &&
|
|
76 resourceType == OrthancPluginResourceType_Instance)
|
|
77 {
|
|
78 // On the reception of a new instance, precompute its spatial position
|
|
79 cache_->GetScheduler().Prefetch(OrthancPlugins::CacheBundle_InstanceInformation, resourceId);
|
|
80
|
|
81 // Indalidate the parent series of the instance
|
|
82 std::string uri = "/instances/" + std::string(resourceId);
|
|
83 Json::Value instance;
|
|
84 if (OrthancPlugins::GetJsonFromOrthanc(instance, context_, uri))
|
|
85 {
|
|
86 std::string seriesId = instance["ParentSeries"].asString();
|
|
87 cache_->GetScheduler().Invalidate(OrthancPlugins::CacheBundle_SeriesInformation, seriesId);
|
|
88 }
|
|
89 }
|
|
90
|
|
91 return 0;
|
|
92 }
|
|
93 catch (std::runtime_error& e)
|
|
94 {
|
|
95 OrthancPluginLogError(context_, e.what());
|
|
96 return 0; // Ignore error
|
|
97 }
|
|
98 }
|
|
99
|
|
100
|
|
101
|
|
102 template <enum OrthancPlugins::CacheBundle bundle>
|
|
103 int32_t ServeCache(OrthancPluginRestOutput* output,
|
|
104 const char* url,
|
|
105 const OrthancPluginHttpRequest* request)
|
|
106 {
|
|
107 try
|
|
108 {
|
|
109 if (request->method != OrthancPluginHttpMethod_Get)
|
|
110 {
|
|
111 OrthancPluginSendMethodNotAllowed(context_, output, "GET");
|
|
112 return 0;
|
|
113 }
|
|
114
|
|
115 const std::string id = request->groups[0];
|
|
116 std::string content;
|
|
117
|
|
118 if (cache_->GetScheduler().Access(content, bundle, id))
|
|
119 {
|
|
120 OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), "application/json");
|
|
121 }
|
|
122 else
|
|
123 {
|
|
124 OrthancPluginSendHttpStatusCode(context_, output, 404);
|
|
125 }
|
|
126
|
|
127 return 0;
|
|
128 }
|
|
129 catch (Orthanc::OrthancException& e)
|
|
130 {
|
|
131 OrthancPluginLogError(context_, e.What());
|
|
132 return -1;
|
|
133 }
|
|
134 catch (std::runtime_error& e)
|
|
135 {
|
|
136 OrthancPluginLogError(context_, e.what());
|
|
137 return -1;
|
|
138 }
|
|
139 catch (boost::bad_lexical_cast&)
|
|
140 {
|
|
141 OrthancPluginLogError(context_, "Bad lexical cast");
|
|
142 return -1;
|
|
143 }
|
|
144 }
|
|
145
|
|
146
|
|
147
|
|
148
|
|
149 #if ORTHANC_STANDALONE == 0
|
|
150 static int32_t ServeWebViewer(OrthancPluginRestOutput* output,
|
|
151 const char* url,
|
|
152 const OrthancPluginHttpRequest* request)
|
|
153 {
|
|
154 if (request->method != OrthancPluginHttpMethod_Get)
|
|
155 {
|
|
156 OrthancPluginSendMethodNotAllowed(context_, output, "GET");
|
|
157 return 0;
|
|
158 }
|
|
159
|
|
160 const std::string path = std::string(WEB_VIEWER_PATH) + std::string(request->groups[0]);
|
|
161 const char* mime = OrthancPlugins::GetMimeType(path);
|
|
162
|
|
163 std::string s;
|
|
164 if (OrthancPlugins::ReadFile(s, path))
|
|
165 {
|
|
166 const char* resource = s.size() ? s.c_str() : NULL;
|
|
167 OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
|
|
168 }
|
|
169 else
|
|
170 {
|
|
171 std::string s = "Inexistent file in served folder: " + path;
|
|
172 OrthancPluginLogError(context_, s.c_str());
|
|
173 OrthancPluginSendHttpStatusCode(context_, output, 404);
|
|
174 }
|
|
175
|
|
176 return 0;
|
|
177 }
|
|
178 #endif
|
|
179
|
|
180
|
|
181
|
|
182 template <enum OrthancPlugins::EmbeddedResources::DirectoryResourceId folder>
|
|
183 static int32_t ServeEmbeddedFolder(OrthancPluginRestOutput* output,
|
|
184 const char* url,
|
|
185 const OrthancPluginHttpRequest* request)
|
|
186 {
|
|
187 if (request->method != OrthancPluginHttpMethod_Get)
|
|
188 {
|
|
189 OrthancPluginSendMethodNotAllowed(context_, output, "GET");
|
|
190 return 0;
|
|
191 }
|
|
192
|
|
193 std::string path = "/" + std::string(request->groups[0]);
|
|
194 const char* mime = OrthancPlugins::GetMimeType(path);
|
|
195
|
|
196 try
|
|
197 {
|
|
198 std::string s;
|
|
199 OrthancPlugins::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str());
|
|
200
|
|
201 const char* resource = s.size() ? s.c_str() : NULL;
|
|
202 OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
|
|
203
|
|
204 return 0;
|
|
205 }
|
|
206 catch (std::runtime_error&)
|
|
207 {
|
|
208 std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
|
|
209 OrthancPluginLogError(context_, s.c_str());
|
|
210 OrthancPluginSendHttpStatusCode(context_, output, 404);
|
|
211 return 0;
|
|
212 }
|
|
213 }
|
|
214
|
|
215
|
|
216
|
|
217
|
|
218 extern "C"
|
|
219 {
|
|
220 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
|
|
221 {
|
|
222 using namespace OrthancPlugins;
|
|
223
|
|
224 context_ = context;
|
|
225 OrthancPluginLogWarning(context_, "Initializing the Web viewer");
|
|
226
|
|
227
|
|
228 /* Check the version of the Orthanc core */
|
|
229 if (OrthancPluginCheckVersion(context_) == 0)
|
|
230 {
|
|
231 char info[1024];
|
|
232 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
|
|
233 context_->orthancVersion,
|
|
234 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
|
|
235 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
|
|
236 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
|
|
237 OrthancPluginLogError(context_, info);
|
|
238 return -1;
|
|
239 }
|
|
240
|
|
241 OrthancPluginSetDescription(context_, "Provides a Web viewer of DICOM series within Orthanc.");
|
|
242
|
|
243
|
|
244 /* By default, use half of the available processing cores for the decoding of DICOM images */
|
|
245 int decodingThreads = boost::thread::hardware_concurrency() / 2;
|
|
246 if (decodingThreads == 0)
|
|
247 {
|
|
248 decodingThreads = 1;
|
|
249 }
|
|
250
|
|
251
|
|
252 try
|
|
253 {
|
|
254 /* Read the configuration of the Web viewer */
|
|
255 Json::Value configuration;
|
|
256 if (!ReadConfiguration(configuration, context))
|
|
257 {
|
|
258 OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc");
|
|
259 return -1;
|
|
260 }
|
|
261
|
|
262 std::string cachePath = "WebViewerCache";
|
|
263
|
|
264 if (configuration.isMember("WebViewer"))
|
|
265 {
|
|
266 cachePath = GetStringValue(configuration["WebViewer"], "Cache", cachePath);
|
|
267 decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads);
|
|
268 }
|
|
269
|
|
270 std::string message = ("Web viewer using " + boost::lexical_cast<std::string>(decodingThreads) +
|
|
271 " threads for the decoding of the DICOM images");
|
|
272 OrthancPluginLogWarning(context_, message.c_str());
|
|
273
|
|
274 if (decodingThreads <= 0)
|
|
275 {
|
|
276 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
277 }
|
|
278
|
|
279 message = "Storing the cache of the Web viewer in folder: " + cachePath;
|
|
280 OrthancPluginLogWarning(context_, message.c_str());
|
|
281
|
|
282
|
|
283 /* Create the cache */
|
|
284 cache_ = new CacheContext(cachePath);
|
|
285 cache_->GetScheduler().RegisterPolicy(new ViewerPrefetchPolicy(context_));
|
|
286 cache_->GetScheduler().Register(CacheBundle_SeriesInformation,
|
|
287 new SeriesInformationAdapter(context_, cache_->GetScheduler()), 1);
|
|
288 cache_->GetScheduler().Register(CacheBundle_InstanceInformation,
|
|
289 new InstanceInformationAdapter(context_), 1);
|
|
290 cache_->GetScheduler().Register(CacheBundle_DecodedImage,
|
|
291 new DecodedImageAdapter(context_), decodingThreads);
|
|
292 }
|
|
293 catch (std::runtime_error& e)
|
|
294 {
|
|
295 OrthancPluginLogError(context_, e.what());
|
|
296 return -1;
|
|
297 }
|
|
298 catch (Orthanc::OrthancException& e)
|
|
299 {
|
|
300 OrthancPluginLogError(context_, e.What());
|
|
301 return -1;
|
|
302 }
|
|
303
|
|
304
|
|
305 /* Install the callbacks */
|
|
306 OrthancPluginRegisterRestCallback(context_, "/web-viewer/series/(.*)", ServeCache<CacheBundle_SeriesInformation>);
|
|
307 OrthancPluginRegisterRestCallback(context_, "/web-viewer/instances/(.*)", ServeCache<CacheBundle_DecodedImage>);
|
|
308 OrthancPluginRegisterRestCallback(context, "/web-viewer/libs/(.*)", ServeEmbeddedFolder<EmbeddedResources::JAVASCRIPT_LIBS>);
|
|
309
|
|
310 #if ORTHANC_STANDALONE == 1
|
|
311 OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeEmbeddedFolder<EmbeddedResources::WEB_VIEWER>);
|
|
312 #else
|
|
313 OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeWebViewer);
|
|
314 #endif
|
|
315
|
|
316 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
|
|
317
|
|
318
|
|
319 /* Extend the default Orthanc Explorer with custom JavaScript */
|
|
320 std::string explorer;
|
|
321 EmbeddedResources::GetFileResource(explorer, EmbeddedResources::ORTHANC_EXPLORER);
|
|
322 OrthancPluginExtendOrthancExplorer(context_, explorer.c_str());
|
|
323
|
|
324 return 0;
|
|
325 }
|
|
326
|
|
327
|
|
328 ORTHANC_PLUGINS_API void OrthancPluginFinalize()
|
|
329 {
|
|
330 OrthancPluginLogWarning(context_, "Finalizing the Web viewer");
|
|
331
|
|
332 if (cache_ != NULL)
|
|
333 {
|
|
334 delete cache_;
|
|
335 cache_ = NULL;
|
|
336 }
|
|
337 }
|
|
338
|
|
339
|
|
340 ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
|
|
341 {
|
|
342 return "web-viewer";
|
|
343 }
|
|
344
|
|
345
|
|
346 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
|
|
347 {
|
|
348 return "1.0";
|
|
349 }
|
|
350 }
|