Mercurial > hg > orthanc
comparison OrthancFramework/Sources/Images/NumpyWriter.cpp @ 4834:bec432ee1094
download of numpy arrays from the REST API
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 26 Nov 2021 19:03:32 +0100 |
parents | |
children | f3f93695d6df |
comparison
equal
deleted
inserted
replaced
4833:970092a67897 | 4834:bec432ee1094 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium | |
6 * Copyright (C) 2021-2021 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | |
7 * | |
8 * This program is free software: you can redistribute it and/or | |
9 * modify it under the terms of the GNU Lesser General Public License | |
10 * as published by the Free Software Foundation, either version 3 of | |
11 * the License, or (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, but | |
14 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 * Lesser General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU Lesser General Public | |
19 * License along with this program. If not, see | |
20 * <http://www.gnu.org/licenses/>. | |
21 **/ | |
22 | |
23 | |
24 #include "NumpyWriter.h" | |
25 | |
26 #if ORTHANC_ENABLE_ZLIB == 1 | |
27 # include "../Compression/ZipWriter.h" | |
28 #endif | |
29 | |
30 #if ORTHANC_SANDBOXED == 0 | |
31 # include "../SystemToolbox.h" | |
32 #endif | |
33 | |
34 #include "../OrthancException.h" | |
35 #include "../Toolbox.h" | |
36 | |
37 #include <boost/lexical_cast.hpp> | |
38 | |
39 namespace Orthanc | |
40 { | |
41 void NumpyWriter::WriteHeader(ChunkedBuffer& target, | |
42 unsigned int depth, | |
43 unsigned int width, | |
44 unsigned int height, | |
45 PixelFormat format) | |
46 { | |
47 // https://numpy.org/devdocs/reference/generated/numpy.lib.format.html | |
48 static const unsigned char VERSION[] = { | |
49 0x93, 'N', 'U', 'M', 'P', 'Y', | |
50 0x01 /* major version: 1 */, | |
51 0x00 /* minor version: 0 */ | |
52 }; | |
53 | |
54 std::string datatype; | |
55 | |
56 switch (Toolbox::DetectEndianness()) | |
57 { | |
58 case Endianness_Little: | |
59 datatype = "<"; | |
60 break; | |
61 | |
62 case Endianness_Big: | |
63 datatype = ">"; | |
64 break; | |
65 | |
66 default: | |
67 throw OrthancException(ErrorCode_InternalError); | |
68 } | |
69 | |
70 unsigned int channels; | |
71 | |
72 switch (format) | |
73 { | |
74 case PixelFormat_Grayscale8: | |
75 datatype += "u1"; | |
76 channels = 1; | |
77 break; | |
78 | |
79 case PixelFormat_Grayscale16: | |
80 datatype += "u2"; | |
81 channels = 1; | |
82 break; | |
83 | |
84 case PixelFormat_SignedGrayscale16: | |
85 datatype += "s2"; | |
86 channels = 1; | |
87 break; | |
88 | |
89 case PixelFormat_RGB24: | |
90 datatype += "u1"; | |
91 channels = 3; | |
92 break; | |
93 | |
94 case PixelFormat_Float32: | |
95 datatype += "f4"; | |
96 channels = 1; | |
97 break; | |
98 | |
99 default: | |
100 throw OrthancException(ErrorCode_NotImplemented); | |
101 } | |
102 | |
103 std::string depthString; | |
104 if (depth != 0) | |
105 { | |
106 depthString = boost::lexical_cast<std::string>(depth) + ", "; | |
107 } | |
108 | |
109 const std::string info = ("{'descr': '" + datatype + "', 'fortran_order': False, " + | |
110 "'shape': (" + depthString + boost::lexical_cast<std::string>(height) + | |
111 "," + boost::lexical_cast<std::string>(width) + | |
112 "," + boost::lexical_cast<std::string>(channels) + "), }"); | |
113 | |
114 const uint16_t minimumLength = sizeof(VERSION) + sizeof(uint16_t) + info.size() + 1 /* trailing '\n' */; | |
115 | |
116 // The length of the header must be evenly divisible by 64. This | |
117 // loop could be optimized by a "ceil()" operation, but we keep | |
118 // the code as simple as possible | |
119 uint16_t length = 64; | |
120 while (length < minimumLength) | |
121 { | |
122 length += 64; | |
123 } | |
124 | |
125 uint16_t countZeros = length - minimumLength; | |
126 uint16_t headerLength = info.size() + countZeros + 1 /* trailing '\n' */; | |
127 uint8_t highByte = headerLength / 256; | |
128 uint8_t lowByte = headerLength % 256; | |
129 | |
130 target.AddChunk(VERSION, sizeof(VERSION)); | |
131 target.AddChunk(&lowByte, 1); | |
132 target.AddChunk(&highByte, 1); | |
133 target.AddChunk(info); | |
134 target.AddChunk(std::string(countZeros, ' ')); | |
135 target.AddChunk("\n"); | |
136 } | |
137 | |
138 | |
139 void NumpyWriter::WritePixels(ChunkedBuffer& target, | |
140 const ImageAccessor& image) | |
141 { | |
142 size_t rowSize = image.GetBytesPerPixel() * image.GetWidth(); | |
143 | |
144 for (unsigned int y = 0; y < image.GetHeight(); y++) | |
145 { | |
146 target.AddChunk(image.GetConstRow(y), rowSize); | |
147 } | |
148 } | |
149 | |
150 | |
151 void NumpyWriter::Finalize(std::string& target, | |
152 ChunkedBuffer& source, | |
153 bool compress) | |
154 { | |
155 if (compress) | |
156 { | |
157 #if ORTHANC_ENABLE_ZLIB == 1 | |
158 // This is the default name of the first array if arrays are | |
159 // specified as positional arguments in "numpy.savez()" | |
160 // https://numpy.org/doc/stable/reference/generated/numpy.savez.html | |
161 const char* ARRAY_NAME = "arr_0"; | |
162 | |
163 std::string uncompressed; | |
164 source.Flatten(uncompressed); | |
165 | |
166 const bool isZip64 = (uncompressed.size() >= 1lu * 1024lu * 1024lu * 1024lu); | |
167 | |
168 ZipWriter writer; | |
169 writer.SetMemoryOutput(target, isZip64); | |
170 writer.Open(); | |
171 writer.OpenFile(ARRAY_NAME); | |
172 writer.Write(uncompressed); | |
173 writer.Close(); | |
174 #else | |
175 throw OrthancException(ErrorCode_InternalError, "Orthanc was compiled without support for zlib"); | |
176 #endif | |
177 } | |
178 else | |
179 { | |
180 source.Flatten(target); | |
181 } | |
182 } | |
183 | |
184 | |
185 #if ORTHANC_SANDBOXED == 0 | |
186 void NumpyWriter::WriteToFileInternal(const std::string& filename, | |
187 unsigned int width, | |
188 unsigned int height, | |
189 unsigned int pitch, | |
190 PixelFormat format, | |
191 const void* buffer) | |
192 { | |
193 std::string content; | |
194 WriteToMemoryInternal(content, width, height, pitch, format, buffer); | |
195 | |
196 SystemToolbox::WriteFile(content, filename); | |
197 } | |
198 #endif | |
199 | |
200 | |
201 void NumpyWriter::WriteToMemoryInternal(std::string& content, | |
202 unsigned int width, | |
203 unsigned int height, | |
204 unsigned int pitch, | |
205 PixelFormat format, | |
206 const void* buffer) | |
207 { | |
208 ChunkedBuffer chunks; | |
209 WriteHeader(chunks, 0 /* no depth */, width, height, format); | |
210 | |
211 ImageAccessor image; | |
212 image.AssignReadOnly(format, width, height, pitch, buffer); | |
213 WritePixels(chunks, image); | |
214 | |
215 Finalize(content, chunks, compressed_); | |
216 } | |
217 | |
218 | |
219 NumpyWriter::NumpyWriter() | |
220 { | |
221 compressed_ = false; | |
222 } | |
223 | |
224 | |
225 void NumpyWriter::SetCompressed(bool compressed) | |
226 { | |
227 #if ORTHANC_ENABLE_ZLIB == 1 | |
228 compressed_ = compressed; | |
229 #else | |
230 if (compressed) | |
231 { | |
232 throw OrthancException(ErrorCode_InternalError, "Orthanc was compiled without support for zlib"); | |
233 } | |
234 #endif | |
235 } | |
236 } |