npy.cc (9641B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "lib/extras/enc/npy.h" 7 8 #include <jxl/types.h> 9 10 #include <memory> 11 #include <sstream> 12 #include <string> 13 #include <vector> 14 15 #include "lib/extras/packed_image.h" 16 #include "lib/jxl/base/common.h" 17 18 namespace jxl { 19 namespace extras { 20 namespace { 21 22 // JSON value writing 23 24 class JSONField { 25 public: 26 virtual ~JSONField() = default; 27 virtual void Write(std::ostream& o, uint32_t indent) const = 0; 28 29 protected: 30 JSONField() = default; 31 }; 32 33 class JSONValue : public JSONField { 34 public: 35 template <typename T> 36 explicit JSONValue(const T& value) : value_(std::to_string(value)) {} 37 38 explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {} 39 40 explicit JSONValue(bool value) : value_(value ? "true" : "false") {} 41 42 void Write(std::ostream& o, uint32_t indent) const override { o << value_; } 43 44 private: 45 std::string value_; 46 }; 47 48 class JSONDict : public JSONField { 49 public: 50 JSONDict() = default; 51 52 template <typename T> 53 T* AddEmpty(const std::string& key) { 54 static_assert(std::is_convertible<T*, JSONField*>::value, 55 "T must be a JSONField"); 56 T* ret = new T(); 57 JSONField* field = static_cast<JSONField*>(ret); 58 auto handle = std::unique_ptr<JSONField>(field); 59 values_.emplace_back(key, std::move(handle)); 60 return ret; 61 } 62 63 template <typename T> 64 void Add(const std::string& key, const T& value) { 65 JSONField* field = static_cast<JSONField*>(new JSONValue(value)); 66 auto handle = std::unique_ptr<JSONField>(field); 67 values_.emplace_back(key, std::move(handle)); 68 } 69 70 void Write(std::ostream& o, uint32_t indent) const override { 71 std::string indent_str(indent, ' '); 72 o << "{"; 73 bool is_first = true; 74 for (const auto& key_value : values_) { 75 if (!is_first) { 76 o << ","; 77 } 78 is_first = false; 79 o << "\n" << indent_str << " \"" << key_value.first << "\": "; 80 key_value.second->Write(o, indent + 2); 81 } 82 if (!values_.empty()) { 83 o << "\n" << indent_str; 84 } 85 o << "}"; 86 } 87 88 private: 89 // Dictionary with order. 90 std::vector<std::pair<std::string, std::unique_ptr<JSONField>>> values_; 91 }; 92 93 class JSONArray : public JSONField { 94 public: 95 JSONArray() = default; 96 97 template <typename T> 98 T* AddEmpty() { 99 static_assert(std::is_convertible<T*, JSONField*>::value, 100 "T must be a JSONField"); 101 T* ret = new T(); 102 values_.emplace_back(ret); 103 return ret; 104 } 105 106 template <typename T> 107 void Add(const T& value) { 108 values_.emplace_back(new JSONValue(value)); 109 } 110 111 void Write(std::ostream& o, uint32_t indent) const override { 112 std::string indent_str(indent, ' '); 113 o << "["; 114 bool is_first = true; 115 for (const auto& value : values_) { 116 if (!is_first) { 117 o << ","; 118 } 119 is_first = false; 120 o << "\n" << indent_str << " "; 121 value->Write(o, indent + 2); 122 } 123 if (!values_.empty()) { 124 o << "\n" << indent_str; 125 } 126 o << "]"; 127 } 128 129 private: 130 std::vector<std::unique_ptr<JSONField>> values_; 131 }; 132 133 void GenerateMetadata(const PackedPixelFile& ppf, std::vector<uint8_t>* out) { 134 JSONDict meta; 135 // Same order as in 18181-3 CD. 136 137 // Frames. 138 auto* meta_frames = meta.AddEmpty<JSONArray>("frames"); 139 for (size_t i = 0; i < ppf.frames.size(); i++) { 140 auto* frame_i = meta_frames->AddEmpty<JSONDict>(); 141 if (ppf.info.have_animation) { 142 frame_i->Add("duration", 143 JSONValue(ppf.frames[i].frame_info.duration * 1.0f * 144 ppf.info.animation.tps_denominator / 145 ppf.info.animation.tps_numerator)); 146 } 147 148 frame_i->Add("name", JSONValue(ppf.frames[i].name)); 149 150 if (ppf.info.animation.have_timecodes) { 151 frame_i->Add("timecode", JSONValue(ppf.frames[i].frame_info.timecode)); 152 } 153 } 154 155 #define METADATA(FIELD) meta.Add(#FIELD, ppf.info.FIELD) 156 157 METADATA(intensity_target); 158 METADATA(min_nits); 159 METADATA(relative_to_max_display); 160 METADATA(linear_below); 161 162 if (ppf.info.have_preview) { 163 meta.AddEmpty<JSONDict>("preview"); 164 // TODO(veluca): can we have duration/name/timecode here? 165 } 166 167 { 168 auto* ectype = meta.AddEmpty<JSONArray>("extra_channel_type"); 169 auto* bps = meta.AddEmpty<JSONArray>("bits_per_sample"); 170 auto* ebps = meta.AddEmpty<JSONArray>("exp_bits_per_sample"); 171 bps->Add(ppf.info.bits_per_sample); 172 ebps->Add(ppf.info.exponent_bits_per_sample); 173 for (const auto& eci : ppf.extra_channels_info) { 174 switch (eci.ec_info.type) { 175 case JXL_CHANNEL_ALPHA: { 176 ectype->Add(std::string("Alpha")); 177 break; 178 } 179 case JXL_CHANNEL_DEPTH: { 180 ectype->Add(std::string("Depth")); 181 break; 182 } 183 case JXL_CHANNEL_SPOT_COLOR: { 184 ectype->Add(std::string("SpotColor")); 185 break; 186 } 187 case JXL_CHANNEL_SELECTION_MASK: { 188 ectype->Add(std::string("SelectionMask")); 189 break; 190 } 191 case JXL_CHANNEL_BLACK: { 192 ectype->Add(std::string("Black")); 193 break; 194 } 195 case JXL_CHANNEL_CFA: { 196 ectype->Add(std::string("CFA")); 197 break; 198 } 199 case JXL_CHANNEL_THERMAL: { 200 ectype->Add(std::string("Thermal")); 201 break; 202 } 203 default: { 204 ectype->Add(std::string("UNKNOWN")); 205 break; 206 } 207 } 208 bps->Add(eci.ec_info.bits_per_sample); 209 ebps->Add(eci.ec_info.exponent_bits_per_sample); 210 } 211 } 212 213 std::ostringstream os; 214 meta.Write(os, 0); 215 out->resize(os.str().size()); 216 memcpy(out->data(), os.str().data(), os.str().size()); 217 } 218 219 void Append(std::vector<uint8_t>* out, const void* data, size_t size) { 220 size_t pos = out->size(); 221 out->resize(pos + size); 222 memcpy(out->data() + pos, data, size); 223 } 224 225 void WriteNPYHeader(size_t xsize, size_t ysize, uint32_t num_channels, 226 size_t num_frames, std::vector<uint8_t>* out) { 227 const uint8_t header[] = "\x93NUMPY\x01\x00"; 228 Append(out, header, 8); 229 std::stringstream ss; 230 ss << "{'descr': '<f4', 'fortran_order': False, 'shape': (" << num_frames 231 << ", " << ysize << ", " << xsize << ", " << num_channels << "), }\n"; 232 // 16-bit little endian header length. 233 uint8_t header_len[2] = {static_cast<uint8_t>(ss.str().size() % 256), 234 static_cast<uint8_t>(ss.str().size() / 256)}; 235 Append(out, header_len, 2); 236 Append(out, ss.str().data(), ss.str().size()); 237 } 238 239 bool WriteFrameToNPYArray(size_t xsize, size_t ysize, const PackedFrame& frame, 240 std::vector<uint8_t>* out) { 241 const auto& color = frame.color; 242 if (color.xsize != xsize || color.ysize != ysize) { 243 return false; 244 } 245 for (const auto& ec : frame.extra_channels) { 246 if (ec.xsize != xsize || ec.ysize != ysize) { 247 return false; 248 } 249 } 250 // interleave the samples from color and extra channels 251 for (size_t y = 0; y < ysize; ++y) { 252 for (size_t x = 0; x < xsize; ++x) { 253 { 254 size_t sample_size = color.pixel_stride(); 255 size_t offset = y * color.stride + x * sample_size; 256 uint8_t* pixels = reinterpret_cast<uint8_t*>(color.pixels()); 257 JXL_ASSERT(offset + sample_size <= color.pixels_size); 258 Append(out, pixels + offset, sample_size); 259 } 260 for (const auto& ec : frame.extra_channels) { 261 size_t sample_size = ec.pixel_stride(); 262 size_t offset = y * ec.stride + x * sample_size; 263 uint8_t* pixels = reinterpret_cast<uint8_t*>(ec.pixels()); 264 JXL_ASSERT(offset + sample_size <= ec.pixels_size); 265 Append(out, pixels + offset, sample_size); 266 } 267 } 268 } 269 return true; 270 } 271 272 // Writes a PackedPixelFile as a numpy 4D ndarray in binary format. 273 bool WriteNPYArray(const PackedPixelFile& ppf, std::vector<uint8_t>* out) { 274 size_t xsize = ppf.info.xsize; 275 size_t ysize = ppf.info.ysize; 276 WriteNPYHeader(xsize, ysize, 277 ppf.info.num_color_channels + ppf.extra_channels_info.size(), 278 ppf.frames.size(), out); 279 for (const auto& frame : ppf.frames) { 280 if (!WriteFrameToNPYArray(xsize, ysize, frame, out)) { 281 return false; 282 } 283 } 284 return true; 285 } 286 287 class NumPyEncoder : public Encoder { 288 public: 289 Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, 290 ThreadPool* pool) const override { 291 JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); 292 GenerateMetadata(ppf, &encoded_image->metadata); 293 encoded_image->bitstreams.emplace_back(); 294 if (!WriteNPYArray(ppf, &encoded_image->bitstreams.back())) { 295 return false; 296 } 297 if (ppf.preview_frame) { 298 size_t xsize = ppf.info.preview.xsize; 299 size_t ysize = ppf.info.preview.ysize; 300 WriteNPYHeader(xsize, ysize, ppf.info.num_color_channels, 1, 301 &encoded_image->preview_bitstream); 302 if (!WriteFrameToNPYArray(xsize, ysize, *ppf.preview_frame, 303 &encoded_image->preview_bitstream)) { 304 return false; 305 } 306 } 307 return true; 308 } 309 std::vector<JxlPixelFormat> AcceptedFormats() const override { 310 std::vector<JxlPixelFormat> formats; 311 for (const uint32_t num_channels : {1, 3}) { 312 formats.push_back(JxlPixelFormat{num_channels, JXL_TYPE_FLOAT, 313 JXL_LITTLE_ENDIAN, /*align=*/0}); 314 } 315 return formats; 316 } 317 }; 318 319 } // namespace 320 321 std::unique_ptr<Encoder> GetNumPyEncoder() { 322 return jxl::make_unique<NumPyEncoder>(); 323 } 324 325 } // namespace extras 326 } // namespace jxl