OpenJPH
Open-source implementation of JPEG2000 Part-15
Loading...
Searching...
No Matches
test_nlt.cpp
Go to the documentation of this file.
1//***************************************************************************/
2// This software is released under the 2-Clause BSD license, included
3// below.
4//
5// Copyright (c) 2019, Aous Naman
6// Copyright (c) 2019, Kakadu Software Pty Ltd, Australia
7// Copyright (c) 2019, The University of New South Wales, Australia
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions are
11// met:
12//
13// 1. Redistributions of source code must retain the above copyright
14// notice, this list of conditions and the following disclaimer.
15//
16// 2. Redistributions in binary form must reproduce the above copyright
17// notice, this list of conditions and the following disclaimer in the
18// documentation and/or other materials provided with the distribution.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31//***************************************************************************/
32// This file is part of the OpenJPH software implementation.
33// File: test_nlt.cpp
34//***************************************************************************/
35// Tests of the non-linearity point transformation (NLT) marker segment, for
36// the three nonlinearities this library supports, namely
37//
38// OJPH_NLT_LUT_STYLE_NLT = 2 -- LUT style
39// OJPH_NLT_BINARY_COMPLEMENT_NLT = 3 -- binary complement (SMAG)
40// OJPH_NLT_BINARY_COMPLEMENT_PLUS_LUT = 4 -- binary complement plus a LUT
41//
42// The tests drive the library directly rather than the ojph_compress and
43// ojph_expand executables, because only the library lets a caller choose the
44// nonlinearity and describe the look-up table it uses. Encoding and
45// decoding are both exercised: each test encodes an image with a
46// nonlinearity in effect and then decodes the codestream it produced,
47// comparing the decoded samples with the samples that were fed in.
48//
49// The nonlinearity is a point transformation applied before the wavelet
50// transform when encoding, and undone after the inverse wavelet transform
51// when decoding. Type 3 is exactly reversible, so a round trip through a
52// reversible (5/3) codestream returns the samples bit for bit. Types 2 and 4
53// describe the transformation with a look-up table, which is an
54// approximation, so those round trips are only approximately lossless.
55//***************************************************************************/
56
57#include <algorithm>
58#include <cmath>
59#include <cstdio>
60#include <cstring>
61#include <iostream>
62#include <string>
63#include <vector>
64
65#include "ojph_arch.h"
66#include "ojph_mem.h"
67#include "ojph_message.h"
68#include "ojph_file.h"
69#include "ojph_codestream.h"
70#include "ojph_params.h"
71#include "gtest/gtest.h"
72
73using namespace ojph;
74
75#ifdef OJPH_OS_WINDOWS
76#define REF_FILE_DIR ".\\jp2k_test_codestreams\\openjph\\references\\"
77#define OUT_FILE_DIR ".\\"
78#else
79#define REF_FILE_DIR "./jp2k_test_codestreams/openjph/references/"
80#define OUT_FILE_DIR "./"
81#endif
82
83// the JPEG2000 Part 2 marker segment that carries the nonlinearity; see
84// JP2K_MARKER::NLT
85#define NLT_MARKER_HIGH 0xFF
86#define NLT_MARKER_LOW 0x76
87
88namespace {
89
91 // The look-up table that ojph_compress installs for floating point (.pfm)
92 // images. It is repeated here so that this test does not depend on the
93 // application. Dmin and Dmax are the smallest and the largest 32 bit
94 // pattern the samples of an image can take, the points are the look-up
95 // table itself, and the precision of a point (pt_val) is 32 bits.
97 const ui32 pfmLutNumPoints = 152;
98 const ui32 pfmLutDmin = 67109888;
99 const ui32 pfmLutDmax = 4227857408;
100 const ui32 pfmLutPoints[pfmLutNumPoints] = {
101 67109888, 88737098, 106890847, 122160968, 135661590, 157092189,
102 175049327, 190122837, 204213292, 225447280, 243207807, 258084706,
103 272764994, 293736834, 311300750, 326046575, 341316696, 362026388,
104 379393693, 394008444, 409802861, 430315942, 447486636, 461904776,
105 478289026, 498605496, 515579579, 529866645, 546775191, 566829513,
106 583672522, 597762977, 615261356, 635053530, 651699928, 665659309,
107 683681984, 703343084, 719792871, 733555641, 752102612, 771501564,
108 787820277, 801451973, 820523240, 839725581, 855847683, 869348305,
109 888943868, 907949598, 923875089, 937179100, 957364496, 976108078,
110 991836958, 1005075432, 1025719587, 1044266558, 1059864364, 1072906227,
111 1094074678, 1112425038, 1127891770, 1140802559, 1163281750, 1183532683,
112 1201620895, 1227180325, 1254836939, 1281838183, 1321029309, 1362907452,
113 1412912183, 1485985938, 1596677931, 1830579484, 2464387812, 2698289365,
114 2808981358, 2882055113, 2932059844, 2973937987, 3013129113, 3040130357,
115 3067786971, 3093346401, 3111434613, 3131685546, 3154164737, 3167075526,
116 3182542258, 3200892618, 3222061069, 3235102932, 3250700738, 3269247709,
117 3289891864, 3303130338, 3318859218, 3337602800, 3357788196, 3371092207,
118 3387017698, 3406023428, 3425618991, 3439119613, 3455241715, 3474444056,
119 3493515323, 3507147019, 3523465732, 3542864684, 3561411655, 3575174425,
120 3591624212, 3611285312, 3629307987, 3643267368, 3659913766, 3679705940,
121 3697204319, 3711294774, 3728137783, 3748192105, 3765100651, 3779387717,
122 3796361800, 3816678270, 3833062520, 3847480660, 3864651354, 3885164435,
123 3900958852, 3915573603, 3932940908, 3953650600, 3968920721, 3983666546,
124 4001230462, 4022202302, 4036882590, 4051759489, 4069520016, 4090754004,
125 4104844459, 4119917969, 4137875107, 4159305706, 4172806328, 4188076449,
126 4206230198, 4227857408};
127
129 // A look-up table that runs over the whole range of 32 bit patterns. The
130 // curve is a straight line, hence this table describes an identity
131 // transformation and a round trip through it has to be as accurate as a
132 // round trip without a nonlinearity.
134 const ui32 identityLutNumPoints = 129;
135 ui32 identityLutPoints[identityLutNumPoints];
136 bool identityLutReady = false;
137
138 void prepare_identity_lut()
139 {
140 if (identityLutReady)
141 return;
142 for (ui32 i = 0; i < identityLutNumPoints; ++i)
143 identityLutPoints[i] =
144 (ui32)(((double)i / (identityLutNumPoints - 1)) * 4294967295.0);
145 identityLutReady = true;
146 }
147
149 // An image held in memory. A sample is the 32 bit pattern of an image
150 // sample, which is what a .pfm file holds; for a floating point image these
151 // are the IEEE-754 single precision bit patterns of the samples.
153 struct test_image
154 {
155 ui32 width = 0, height = 0, num_comps = 0;
156 std::vector<std::vector<si32> > samples; // one entry per component
157
158 size_t num_samples() const
159 { return (size_t)width * height * num_comps; }
160 };
161
163 // How well a decoded image agrees with the image it was decoded from
165 struct comparison
166 {
167 long long max_abs_error = 0; // largest error in 32 bit pattern units
168 double mean_abs_error = 0.0;
169 double max_rel_error = 0.0; // largest error relative to the sample
170 double mean_rel_error = 0.0;
171 size_t num_bit_exact = 0; // samples that came back unchanged
172 size_t num_samples = 0;
173
174 bool is_bit_exact() const { return num_bit_exact == num_samples; }
175 };
176
178 // Reads a pfm file. The samples of a pfm file are 32 bit floating point
179 // numbers, and they are handed to the library as the 32 bit patterns they
180 // are stored as; the library works on those patterns.
182 bool load_pfm(const std::string& filename, test_image& img)
183 {
184 FILE* f = fopen(filename.c_str(), "rb");
185 if (f == NULL)
186 return false;
187
188 char magic[3] = { 0, 0, 0 };
189 if (fscanf(f, "%2s", magic) != 1 ||
190 (magic[1] != 'f' && magic[1] != 'F'))
191 {
192 fclose(f);
193 return false;
194 }
195
196 int w = 0, h = 0;
197 float scale = 0.0f;
198 if (fscanf(f, "%d %d %f", &w, &h, &scale) != 3 || w <= 0 || h <= 0)
199 {
200 fclose(f);
201 return false;
202 }
203 fgetc(f); // the single white space that follows the header
204
205 // a positive scale means the file is big endian, a negative one means it
206 // is little endian. The samples are read as 32 bit words, whose byte
207 // order is the machine's, so they have to be swapped whenever the file's
208 // byte order is not the machine's -- the test has to give the same samples
209 // to the library on a little endian and on a big endian machine.
210 const bool file_little_endian = scale < 0.0f;
211 const bool swap = file_little_endian != is_machine_little_endian;
212
213 img.width = (ui32)w;
214 img.height = (ui32)h;
215 img.num_comps = magic[1] == 'f' ? 1 : 3;
216 img.samples.assign(img.num_comps,
217 std::vector<si32>((size_t)w * (size_t)h, 0));
218
219 std::vector<ui32> line((size_t)w * img.num_comps, 0);
220 for (int y = h - 1; y >= 0; --y) // pfm stores the last line first
221 {
222 if (fread(&line[0], sizeof(ui32), (size_t)w * img.num_comps, f)
223 != (size_t)w * img.num_comps)
224 {
225 fclose(f);
226 return false;
227 }
228 for (int x = 0; x < w; ++x)
229 for (ui32 c = 0; c < img.num_comps; ++c)
230 {
231 ui32 u = line[(size_t)x * img.num_comps + c];
232 if (swap)
233 u = (u >> 24) | ((u >> 8) & 0x0000FF00u) |
234 ((u << 8) & 0x00FF0000u) | (u << 24);
235 img.samples[c][(size_t)y * (size_t)w + (size_t)x] = (si32)u;
236 }
237 }
238
239 fclose(f);
240 return true;
241 }
242
244 // Locates one of the images the tests need. The images belong to the
245 // repository that stores the test images, jp2k_test_codestreams. The other
246 // directories looked in are a convenience, so that an image can be tested
247 // before it has been added to that repository. An empty string is returned
248 // when the image cannot be found.
250 std::string find_test_image(const std::string& name)
251 {
252 const char* dirs[] = { REF_FILE_DIR };
253 for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i)
254 {
255 std::string path = std::string(dirs[i]) + name;
256 FILE* f = fopen(path.c_str(), "rb");
257 if (f != NULL)
258 {
259 fclose(f);
260 return path;
261 }
262 }
263 return std::string();
264 }
265
267 // The two images the tests use: random.pfm is a single component image of
268 // floating point samples that are large and of both signs, and rgb.pfm has
269 // three components.
271 const char* const image_names[2] = { "random.pfm", "rgb.pfm" };
272 const size_t num_images = sizeof(image_names) / sizeof(image_names[0]);
273
275 // Loads both images; returns the names of those that could not be loaded.
277 std::string load_test_images(test_image images[num_images])
278 {
279 std::string missing;
280 for (size_t i = 0; i < num_images; ++i)
281 {
282 std::string path = find_test_image(image_names[i]);
283 if (path.empty() || !load_pfm(path, images[i]))
284 {
285 if (!missing.empty())
286 missing += " and ";
287 missing += image_names[i];
288 }
289 }
290 return missing;
291 }
292
294 // The message used when the test images are not available
296 // std::string missing_images_message(const std::string& missing)
297 // {
298 // return std::string("the test image(s) ") + missing + " could not be "
299 // "found; they belong to the repository that stores the test images, "
300 // "jp2k_test_codestreams, in the folder openjph/references, and the tests "
301 // "that use them are skipped until they are added there";
302 // }
303
305 // Fills an image with a ramp that covers the range of 32 bit patterns.
306 // Samples whose magnitude is very small are left out, because the parts of
307 // the transformation that work with floating point numbers cannot resolve
308 // the samples close to the middle of the range.
310 void make_synthetic_image(test_image& img, ui32 width, ui32 height)
311 {
312 img.width = width;
313 img.height = height;
314 img.num_comps = 1;
315 img.samples.assign(1, std::vector<si32>((size_t)width * height, 0));
316
317 // the limits below sit inside the range the look-up table for .pfm
318 // images covers, so that no sample is clamped by it
319 const si64 lowest = -2070000000LL;
320 const si64 highest = 2070000000LL;
321 const si64 gap = 1LL << 20; // the samples around zero are skipped
322 const si64 half = (si64)((size_t)width * height / 2);
323 std::vector<si32>& s = img.samples[0];
324 for (size_t i = 0; i < s.size(); ++i)
325 {
326 if (i < (size_t)half)
327 s[i] = (si32)(gap + (highest - gap) * (si64)i / (half - 1));
328 else
329 s[i] = (si32)(-gap +
330 (lowest + gap) * (si64)(i - (size_t)half) / (half - 1));
331 }
332 }
333
335 // The nonlinearity a test asks for, together with the coding settings used
336 // with it
338 struct nlt_setting
339 {
340 ui8 type; // 0, 2, 3 or 4
341 bool use_pfm_lut; // true: the look-up table used for .pfm images
342 bool reversible; // false: the 9/7 wavelet, true: the 5/3 wavelet
343 float qstep; // the quantization step used with the 9/7 wavelet
344 };
345
347 // The most common setting used below, the 9/7 wavelet with a quantization
348 // step of 1/16384, which is the smallest step ojph_compress uses for .pfm
349 // images.
351 float default_qstep() { return 1.0f / 16384.0f; }
352
354 // Installs the nonlinearity on a codestream that is about to be written
356 void apply_nlt(codestream& cs, const nlt_setting& setting)
357 {
358 param_nlt nlt = cs.access_nlt();
360 {
363 }
364 else if (setting.type == param_nlt::OJPH_NLT_LUT_STYLE_NLT ||
366 {
367 ui32 d_min, d_max, num_points;
368 void* points;
369 if (setting.use_pfm_lut)
370 {
371 d_min = pfmLutDmin;
372 d_max = pfmLutDmax;
373 num_points = pfmLutNumPoints;
374 points = (void*)pfmLutPoints;
375 }
376 else
377 { // the identity table runs over the whole range of 32 bit patterns
378 prepare_identity_lut();
379 d_min = 0;
380 d_max = 0xFFFFFFFFu;
381 num_points = identityLutNumPoints;
382 points = (void*)identityLutPoints;
383 }
385 d_min, d_max, 32, (ui16)num_points, points, setting.type);
386 }
387 }
388
390 // Encodes an image with the requested nonlinearity in effect
392 void encode_image(const std::string& filename, const test_image& img,
393 const nlt_setting& setting)
394 {
395 codestream cs;
396
397 param_siz siz = cs.access_siz();
398 siz.set_image_extent(point(img.width, img.height));
399 siz.set_num_components(img.num_comps);
400 for (ui32 c = 0; c < img.num_comps; ++c)
401 siz.set_component(c, point(1, 1), 32, true);
402 siz.set_image_offset(point(0, 0));
403 siz.set_tile_size(size(img.width, img.height));
404 siz.set_tile_offset(point(0, 0));
405
406 param_cod cod = cs.access_cod();
408 cod.set_block_dims(64, 64);
409 cod.set_color_transform(false);
410 cod.set_reversible(setting.reversible);
411 if (!setting.reversible)
412 cs.access_qcd().set_irrev_quant(setting.qstep);
413
414 apply_nlt(cs, setting);
415
416 cs.set_planar(true);
417
418 j2c_outfile file;
419 file.open(filename.c_str());
420 cs.write_headers(&file);
421
422 ui32 next_comp;
423 line_buf* cur_line = cs.exchange(NULL, next_comp);
424 ASSERT_TRUE(cur_line != NULL) << filename;
425 for (ui32 c = 0; c < img.num_comps; ++c)
426 for (ui32 y = 0; y < img.height; ++y)
427 {
428 ASSERT_EQ(next_comp, c) << filename;
429 for (ui32 x = 0; x < img.width; ++x)
430 cur_line->i32[x] = img.samples[c][(size_t)y * img.width + x];
431 cur_line = cs.exchange(cur_line, next_comp);
432 }
433
434 cs.flush();
435 cs.close();
436 }
437
439 // Decodes an image; also reports the nonlinearity found in the codestream
441 void decode_image(const std::string& filename, test_image& img,
442 bool* has_nlt, ui8* nlt_type, ui8* nlt_bit_depth,
443 bool* nlt_is_signed)
444 {
445 codestream cs;
446 j2c_infile file;
447 file.open(filename.c_str());
448 cs.read_headers(&file);
449
450 if (has_nlt != NULL || nlt_type != NULL || nlt_bit_depth != NULL ||
451 nlt_is_signed != NULL)
452 {
453 ui8 bit_depth = 0, type = 0;
454 bool is_signed = false;
455 bool found = cs.access_nlt().get_nonlinear_transform(
456 param_nlt::ALL_COMPS, bit_depth, is_signed, type);
457 if (has_nlt != NULL) *has_nlt = found;
458 if (nlt_type != NULL) *nlt_type = type;
459 if (nlt_bit_depth != NULL) *nlt_bit_depth = bit_depth;
460 if (nlt_is_signed != NULL) *nlt_is_signed = is_signed;
461 }
462
463 param_siz siz = cs.access_siz();
464 img.num_comps = siz.get_num_components();
465 img.width = siz.get_recon_width(0);
466 img.height = siz.get_recon_height(0);
467 img.samples.assign(img.num_comps,
468 std::vector<si32>((size_t)img.width * img.height, 0));
469
471 cs.set_planar(true);
472 cs.create();
473
474 for (ui32 c = 0; c < img.num_comps; ++c)
475 for (ui32 y = 0; y < img.height; ++y)
476 {
477 ui32 comp_num = 0;
478 line_buf* line = cs.pull(comp_num);
479 ASSERT_TRUE(line != NULL) << filename;
480 ASSERT_EQ(comp_num, c) << filename;
481 for (ui32 x = 0; x < img.width; ++x)
482 img.samples[c][(size_t)y * img.width + x] = line->i32[x];
483 }
484
485 cs.close();
486 }
487
489 // Compares the image that was encoded with the image that was decoded. The
490 // samples are compared both as the 32 bit patterns they are and as the
491 // floating point numbers those patterns stand for.
493 comparison compare_images(const test_image& before, const test_image& after)
494 {
495 comparison result;
496
497 double peak = 0.0;
498 for (ui32 c = 0; c < before.num_comps; ++c)
499 for (size_t i = 0; i < before.samples[c].size(); ++i)
500 {
501 float v;
502 memcpy(&v, &before.samples[c][i], sizeof(float));
503 peak = std::max(peak, (double)fabs(v));
504 }
505 // a relative error is not meaningful for a sample that is essentially
506 // zero; the floor below keeps such samples from dominating the result
507 const double floor = std::max(peak * 1e-5, 1e-30);
508
509 double sum_abs = 0.0, sum_rel = 0.0;
510 for (ui32 c = 0; c < before.num_comps; ++c)
511 for (size_t i = 0; i < before.samples[c].size(); ++i)
512 {
513 si32 a = before.samples[c][i], b = after.samples[c][i];
514 si64 err = (si64)a - (si64)b;
515 if (err < 0)
516 err = -err;
517 result.max_abs_error = std::max(result.max_abs_error, (long long)err);
518 sum_abs += (double)err;
519
520 float av, bv;
521 memcpy(&av, &a, sizeof(float));
522 memcpy(&bv, &b, sizeof(float));
523 double rel = fabs((double)av - (double)bv) /
524 std::max((double)fabs(av), floor);
525 result.max_rel_error = std::max(result.max_rel_error, rel);
526 sum_rel += rel;
527
528 if (a == b)
529 ++result.num_bit_exact;
530 ++result.num_samples;
531 }
532
533 result.mean_abs_error = sum_abs / (double)result.num_samples;
534 result.mean_rel_error = sum_rel / (double)result.num_samples;
535 return result;
536 }
537
539 // Encodes an image and decodes it again, and reports how well the decoded
540 // image agrees with the image that was encoded
542 comparison round_trip(const std::string& tag, const test_image& img,
543 const nlt_setting& setting)
544 {
545 const std::string filename = std::string(OUT_FILE_DIR) + tag + ".j2c";
546 encode_image(filename, img, setting);
547
548 test_image decoded;
549 decode_image(filename, decoded, NULL, NULL, NULL, NULL);
550
551 EXPECT_EQ(decoded.width, img.width) << tag;
552 EXPECT_EQ(decoded.height, img.height) << tag;
553 EXPECT_EQ(decoded.num_comps, img.num_comps) << tag;
554
555 return compare_images(img, decoded);
556 }
557
559 // Tells whether a codestream holds an NLT marker segment
561 bool has_nlt_marker(const std::string& filename)
562 {
563 FILE* f = fopen(filename.c_str(), "rb");
564 if (f == NULL)
565 return false;
566
567 bool found = false;
568 int prev = EOF, cur;
569 while ((cur = fgetc(f)) != EOF)
570 {
571 if (prev == NLT_MARKER_HIGH && cur == NLT_MARKER_LOW)
572 {
573 found = true;
574 break;
575 }
576 prev = cur;
577 }
578 fclose(f);
579 return found;
580 }
581
583 // Collects the messages the library issues, so that a test can tell the
584 // message it expects apart from an unrelated failure.
586 struct warning_collector : public message_warning
587 {
588 int num_messages = 0;
589 int last_code = 0;
590
591 virtual void operator() (int warn_code, const char* file_name,
592 int line_num, const char* fmt, ...)
593 {
594 (void)file_name;
595 (void)line_num;
596 (void)fmt;
597 ++num_messages;
598 last_code = warn_code;
599 }
600 };
601
602 struct error_collector : public message_error
603 {
604 int num_messages = 0;
605 int last_code = 0;
606
607 virtual void operator() (int error_code, const char* file_name,
608 int line_num, const char* fmt, ...)
609 {
610 (void)file_name;
611 (void)line_num;
612 (void)fmt;
613 ++num_messages;
614 last_code = error_code;
615 throw std::runtime_error("ojph error"); // as the library does
616 }
617 };
618
619 struct message_capture
620 {
621 warning_collector warnings;
622 error_collector errors;
623 message_warning* previous_warning = NULL;
624 message_error* previous_error = NULL;
625
626 message_capture()
627 {
628 previous_warning = get_warning();
629 previous_error = get_error();
630 configure_warning(&warnings);
631 configure_error(&errors);
632 }
633
634 ~message_capture()
635 {
636 configure_warning(previous_warning);
637 configure_error(previous_error);
638 }
639 };
640
642 // Inserts an NLT marker segment for a LUT style nonlinearity into a
643 // codestream, right before its first tile part. The library refuses to
644 // write that combination, so a codestream that carries it has to be put
645 // together here, the way another encoder may have produced it.
647 bool insert_lut_nlt_marker(const std::string& filename, ui8 nlt_type)
648 {
649 FILE* f = fopen(filename.c_str(), "rb");
650 if (f == NULL)
651 return false;
652
653 std::vector<ui8> data;
654 ui8 buffer[4096];
655 size_t num_read;
656 while ((num_read = fread(buffer, 1, sizeof(buffer), f)) > 0)
657 data.insert(data.end(), buffer, buffer + num_read);
658 fclose(f);
659
660 // the marker goes just before the first tile part
661 size_t sot = data.size();
662 for (size_t i = 0; i + 1 < data.size(); ++i)
663 if (data[i] == 0xFF && data[i + 1] == 0x90)
664 {
665 sot = i;
666 break;
667 }
668 if (sot == data.size())
669 return false;
670
671 const ui16 num_points = 17;
672 const ui32 d_min = 0, d_max = 0xFFFFFFFFu;
673 const ui32 length = 17u + 4u * (ui32)num_points; // as the library does
674
675 std::vector<ui8> marker;
676 marker.push_back(0xFF);
677 marker.push_back(0x76); // NLT
678 marker.push_back((ui8)(length >> 8));
679 marker.push_back((ui8)(length & 0xFF));
680 marker.push_back(0xFF);
681 marker.push_back(0xFF); // Cnlt = ALL_COMPS
682 marker.push_back(0x9F); // BDnlt: 32 bits, signed
683 marker.push_back(nlt_type);
684 marker.push_back((ui8)((num_points - 1) >> 8)); // the field carries
685 marker.push_back((ui8)((num_points - 1) & 0xFF)); // one point less
686 for (int i = 3; i >= 0; --i)
687 marker.push_back((ui8)(d_min >> (8 * i)));
688 for (int i = 3; i >= 0; --i)
689 marker.push_back((ui8)(d_max >> (8 * i)));
690 marker.push_back(32); // pt_val
691 for (ui32 i = 0; i < (ui32)num_points; ++i)
692 {
693 ui32 point =
694 (ui32)(((double)i / (num_points - 1)) * 4294967295.0);
695 for (int b = 3; b >= 0; --b)
696 marker.push_back((ui8)(point >> (8 * b)));
697 }
698
699 data.insert(data.begin() + (long)sot, marker.begin(), marker.end());
700
701 f = fopen(filename.c_str(), "wb");
702 if (f == NULL)
703 return false;
704 bool ok = fwrite(&data[0], 1, data.size(), f) == data.size();
705 fclose(f);
706 return ok;
707 }
708
710 // Encodes a two component image in which component 1 is coded with the
711 // irreversible wavelet while component 0 keeps the reversible one; a COC
712 // marker segment is what lets the two differ. The LUT style nonlinearity
713 // is attached to the component the caller names.
715 void encode_two_component_image(const std::string& filename,
716 const test_image& img, ui32 nlt_comp)
717 {
718 codestream cs;
719
720 param_siz siz = cs.access_siz();
721 siz.set_image_extent(point(img.width, img.height));
722 siz.set_num_components(2);
723 for (ui32 c = 0; c < 2; ++c)
724 siz.set_component(c, point(1, 1), 32, true);
725 siz.set_image_offset(point(0, 0));
726 siz.set_tile_size(size(img.width, img.height));
727 siz.set_tile_offset(point(0, 0));
728
729 param_cod cod = cs.access_cod();
731 cod.set_block_dims(64, 64);
732 cod.set_color_transform(false);
733 cod.set_reversible(true); // component 0 stays reversible
734 cod.set_reversible(1, false); // component 1 is irreversible
735 cs.access_qcd().set_irrev_quant(default_qstep());
736
737 cs.access_nlt().set_nonlinear_transform(nlt_comp, 32, true,
738 pfmLutDmin, pfmLutDmax, 32, (ui16)pfmLutNumPoints, (void*)pfmLutPoints,
740
741 cs.set_planar(true);
742
743 j2c_outfile file;
744 file.open(filename.c_str());
745 cs.write_headers(&file);
746
747 ui32 next_comp;
748 line_buf* cur_line = cs.exchange(NULL, next_comp);
749 ASSERT_TRUE(cur_line != NULL) << filename;
750 for (ui32 c = 0; c < 2; ++c)
751 for (ui32 y = 0; y < img.height; ++y)
752 {
753 ASSERT_EQ(next_comp, c) << filename;
754 for (ui32 x = 0; x < img.width; ++x)
755 cur_line->i32[x] = img.samples[0][(size_t)y * img.width + x];
756 cur_line = cs.exchange(cur_line, next_comp);
757 }
758
759 cs.flush();
760 cs.close();
761 }
762
763} // anonymous namespace
764
766// The NLT marker segment has to survive a write and a read of the codestream
767// headers, for each of the three nonlinearities, and the nonlinearity and the
768// format of the samples the nonlinearity works on have to come back unchanged.
770TEST(NltTest, MarkerSegmentRoundTrip)
771{
772 test_image img;
773 make_synthetic_image(img, 64, 64);
774
775 const ui8 types[3] = { param_nlt::OJPH_NLT_LUT_STYLE_NLT,
778 for (size_t i = 0; i < 3; ++i)
779 {
780 nlt_setting setting;
781 setting.type = types[i];
782 setting.use_pfm_lut = true;
783 setting.reversible = false;
784 setting.qstep = default_qstep();
785
786 const std::string filename = OUT_FILE_DIR "nlt_marker.j2c";
787 encode_image(filename, img, setting);
788
789 bool has_nlt = false, is_signed = false;
790 ui8 type = 0, bit_depth = 0;
791 test_image decoded;
792 decode_image(filename, decoded, &has_nlt, &type, &bit_depth, &is_signed);
793
794 EXPECT_TRUE(has_nlt) << "nonlinearity " << (int)types[i];
795 EXPECT_EQ((int)type, (int)types[i]) << "nonlinearity " << (int)types[i];
796 EXPECT_EQ((int)bit_depth, 32) << "nonlinearity " << (int)types[i];
797 EXPECT_TRUE(is_signed) << "nonlinearity " << (int)types[i];
798 EXPECT_TRUE(has_nlt_marker(filename)) << "nonlinearity " << (int)types[i];
799 }
800}
801
803// A codestream that does not ask for a nonlinearity must not hold an NLT
804// marker segment, and must not report a nonlinearity when its headers are
805// read back.
807TEST(NltTest, NoMarkerSegmentWithoutNlt)
808{
809 test_image img;
810 make_synthetic_image(img, 64, 64);
811
812 nlt_setting setting;
813 setting.type = param_nlt::OJPH_NLT_NO_NLT;
814 setting.use_pfm_lut = true;
815 setting.reversible = false;
816 setting.qstep = default_qstep();
817
818 const std::string filename = OUT_FILE_DIR "nlt_none.j2c";
819 encode_image(filename, img, setting);
820
821 bool has_nlt = true;
822 test_image decoded;
823 decode_image(filename, decoded, &has_nlt, NULL, NULL, NULL);
824
825 EXPECT_FALSE(has_nlt);
826 EXPECT_FALSE(has_nlt_marker(filename));
827}
828
830// With an identity look-up table the LUT style nonlinearities, 2 and 4, do
831// not change the samples, so a round trip through either of them has to be as
832// accurate as a round trip that does not use a nonlinearity. This isolates
833// the nonlinearity from the accuracy of the table itself: it fails if the
834// table is not inverted correctly, or if the samples are not exchanged in the
835// format the nonlinearity expects.
837TEST(NltTest, LutStyleWithIdentityLutKeepsSamples)
838{
839 test_image img;
840 make_synthetic_image(img, 128, 128);
841
842 nlt_setting baseline;
843 baseline.type = param_nlt::OJPH_NLT_NO_NLT;
844 baseline.use_pfm_lut = false;
845 baseline.reversible = false;
846 baseline.qstep = 1e-5f;
847 comparison plain = round_trip("nlt_identity_plain", img, baseline);
848
849 const ui8 types[2] = { param_nlt::OJPH_NLT_LUT_STYLE_NLT,
851 for (size_t i = 0; i < 2; ++i)
852 {
853 nlt_setting setting = baseline;
854 setting.type = types[i];
855 const std::string tag = "nlt_identity_" + std::to_string((int)types[i]);
856 comparison nlt = round_trip(tag, img, setting);
857
858 // the identity table only adds the rounding of a transformation that does
859 // nothing, so the average error has to stay in the neighbourhood of the
860 // error of the round trip that does not use a nonlinearity
861 EXPECT_LE(nlt.mean_abs_error, 3.0 * plain.mean_abs_error + 16.0)
862 << "nonlinearity " << (int)types[i] << ": mean absolute error "
863 << nlt.mean_abs_error << " against " << plain.mean_abs_error
864 << " for a round trip without a nonlinearity";
865 EXPECT_LT(nlt.max_rel_error, 0.01)
866 << "nonlinearity " << (int)types[i] << " with an identity table";
867 }
868}
869
871// Type 3 is a reversible point transformation, so with the reversible (5/3)
872// wavelet the samples of both test images come back bit for bit.
874TEST(NltTest, BinaryComplementIsLossless)
875{
876 test_image images[num_images];
877 std::string missing = load_test_images(images);
878 // if (!missing.empty())
879 // {
880 // GTEST_SKIP() << missing_images_message(missing);
881 // return;
882 // }
883
884 for (size_t i = 0; i < num_images; ++i)
885 {
886 nlt_setting setting;
888 setting.use_pfm_lut = false;
889 setting.reversible = true;
890 setting.qstep = default_qstep();
891
892 const std::string tag = std::string("nlt_lossless_") + image_names[i];
893 comparison result = round_trip(tag, images[i], setting);
894
895 EXPECT_TRUE(result.is_bit_exact()) << image_names[i] << ": "
896 << (result.num_samples - result.num_bit_exact) << " of "
897 << result.num_samples << " samples changed; the largest change is "
898 << result.max_abs_error << " in a 32 bit pattern";
899 }
900}
901
903// Round trips of the test images with each nonlinearity, using the look-up
904// table that ojph_compress installs for .pfm images. The 9/7 wavelet is
905// lossy, so the decoded image is compared with a round trip of the same image
906// that does not use a nonlinearity: a nonlinearity must not add much to the
907// error of that round trip, and the samples have to come back to within a
908// fraction of a percent of their value.
910TEST(NltTest, RoundTripOfTestImages)
911{
912 test_image images[num_images];
913 std::string missing = load_test_images(images);
914 // if (!missing.empty())
915 // {
916 // GTEST_SKIP() << missing_images_message(missing);
917 // return;
918 // }
919
920 const ui8 types[3] = { param_nlt::OJPH_NLT_LUT_STYLE_NLT,
923
924 for (size_t n = 0; n < num_images; ++n)
925 {
926 nlt_setting baseline;
927 baseline.type = param_nlt::OJPH_NLT_NO_NLT;
928 baseline.use_pfm_lut = true;
929 baseline.reversible = false;
930 baseline.qstep = default_qstep();
931 comparison plain = round_trip(std::string("nlt_plain_") + image_names[n],
932 images[n], baseline);
933
934 for (size_t t = 0; t < 3; ++t)
935 {
936 nlt_setting setting = baseline;
937 setting.type = types[t];
938 const std::string tag = std::string("nlt_") +
939 std::to_string((int)types[t]) + "_" + image_names[n];
940 comparison nlt = round_trip(tag, images[n], setting);
941
942 std::cout << image_names[n] << " with nonlinearity " << (int)types[t]
943 << ": mean absolute error " << nlt.mean_abs_error
944 << ", mean relative error " << nlt.mean_rel_error
945 << ", largest relative error " << nlt.max_rel_error
946 << " (without a nonlinearity the mean absolute error is "
947 << plain.mean_abs_error << ")" << std::endl;
948
949 // type 3 is a reversible transformation of the samples, so it has to
950 // behave much like no nonlinearity at all
951 const double factor =
952 types[t] == param_nlt::OJPH_NLT_BINARY_COMPLEMENT_NLT ? 2.0 : 5.0;
953
954 EXPECT_LE(nlt.mean_abs_error, factor * plain.mean_abs_error + 1024.0)
955 << image_names[n] << " with nonlinearity " << (int)types[t]
956 << ": mean absolute error " << nlt.mean_abs_error << " against "
957 << plain.mean_abs_error << " for a round trip without a nonlinearity";
958 EXPECT_LT(nlt.mean_rel_error, 0.05)
959 << image_names[n] << " with nonlinearity " << (int)types[t];
960 EXPECT_LT(nlt.max_rel_error, 0.30)
961 << image_names[n] << " with nonlinearity " << (int)types[t];
962 }
963 }
964}
965
967// Writing a codestream that combines a LUT style nonlinearity with the
968// reversible wavelet has to fail: the nonlinearity cannot be applied with that
969// wavelet, so the codestream would advertise a points transformation that its
970// samples do not follow.
972TEST(NltTest, LutStyleIsRejectedWithReversibleWavelet)
973{
974 test_image img;
975 make_synthetic_image(img, 64, 64);
976
977 const ui8 types[2] = { param_nlt::OJPH_NLT_LUT_STYLE_NLT,
979 for (size_t i = 0; i < 2; ++i)
980 {
981 nlt_setting setting;
982 setting.type = types[i];
983 setting.use_pfm_lut = true;
984 setting.reversible = true; // the combination that is refused
985 setting.qstep = default_qstep();
986
987 const std::string filename = OUT_FILE_DIR "nlt_rejected.j2c";
988 message_capture capture;
989 EXPECT_THROW(encode_image(filename, img, setting), std::runtime_error)
990 << "nonlinearity " << (int)types[i] << " with the reversible wavelet";
991 EXPECT_EQ(capture.errors.last_code, 0x000501B1)
992 << "nonlinearity " << (int)types[i];
993 }
994
995 // the same nonlinearity is accepted with the irreversible wavelet, and the
996 // binary complement nonlinearity is accepted with either wavelet; those
997 // combinations are covered by RoundTripOfTestImages and
998 // BinaryComplementIsLossless
999 nlt_setting accepted;
1000 accepted.type = param_nlt::OJPH_NLT_LUT_STYLE_NLT;
1001 accepted.use_pfm_lut = true;
1002 accepted.reversible = false;
1003 accepted.qstep = default_qstep();
1004 EXPECT_NO_THROW(encode_image(OUT_FILE_DIR "nlt_accepted.j2c", img, accepted));
1005}
1006
1008// The wavelet can be chosen per component, with a COC marker segment, so the
1009// refusal has to follow the component the nonlinearity is attached to.
1011TEST(NltTest, LutStyleIsRejectedPerComponent)
1012{
1013 test_image img;
1014 make_synthetic_image(img, 64, 64);
1015
1016 // component 1 is coded with the irreversible wavelet, so the nonlinearity is
1017 // accepted there
1018 {
1019 const std::string filename = OUT_FILE_DIR "nlt_component_accepted.j2c";
1020 EXPECT_NO_THROW(encode_two_component_image(filename, img, 1));
1021 test_image decoded;
1022 decode_image(filename, decoded, NULL, NULL, NULL, NULL);
1023 EXPECT_EQ(decoded.num_comps, 2u);
1024 }
1025
1026 // component 0 keeps the reversible wavelet, so the nonlinearity is refused
1027 // there
1028 {
1029 const std::string filename = OUT_FILE_DIR "nlt_component_rejected.j2c";
1030 message_capture capture;
1031 EXPECT_THROW(encode_two_component_image(filename, img, 0),
1032 std::runtime_error);
1033 EXPECT_EQ(capture.errors.last_code, 0x000501B1);
1034 }
1035}
1036
1038// A codestream that carries the combination, which another encoder may have
1039// produced, has to be reported when its headers are read: this library cannot
1040// invert such a nonlinearity, so it says so rather than return samples that
1041// look like image samples.
1043TEST(NltTest, LutStyleWithReversibleWaveletIsReportedWhenRead)
1044{
1045 test_image img;
1046 make_synthetic_image(img, 64, 64);
1047
1048 // a reversible codestream without a nonlinearity, into which an NLT marker
1049 // segment for a LUT style nonlinearity is inserted
1050 nlt_setting setting;
1051 setting.type = param_nlt::OJPH_NLT_NO_NLT;
1052 setting.use_pfm_lut = true;
1053 setting.reversible = true;
1054 setting.qstep = default_qstep();
1055
1056 const std::string filename = OUT_FILE_DIR "nlt_foreign.j2c";
1057 encode_image(filename, img, setting);
1058 ASSERT_TRUE(insert_lut_nlt_marker(filename,
1060
1061 message_capture capture;
1062 codestream cs;
1063 j2c_infile file;
1064 file.open(filename.c_str());
1065 EXPECT_THROW(cs.read_headers(&file), std::runtime_error);
1066 EXPECT_GE(capture.errors.num_messages, 1);
1067 EXPECT_EQ(capture.errors.last_code, 0x00030059);
1068
1069 ui8 bit_depth = 0, type = 0;
1070 bool is_signed = false;
1072 bit_depth, is_signed, type));
1073 EXPECT_EQ((int)type, (int)param_nlt::OJPH_NLT_LUT_STYLE_NLT);
1074 cs.close();
1075}
The object represent a codestream.
param_siz access_siz()
Returns the underlying SIZ marker segment object.
param_cod access_cod()
Returns the underlying COD marker segment object.
void restrict_input_resolution(ui32 skipped_res_for_data, ui32 skipped_res_for_recon)
This function restricts resolution decoding for a codestream. It is for a reading (decoding) codestre...
void close()
Call this function to close the underlying file; works for both encoding and decoding codestreams.
void set_planar(bool planar)
Sets the sequence of pushing or pull rows from the machinery.
line_buf * exchange(line_buf *line, ui32 &next_component)
This call is used to send image data rows to the library. We expect to send one row from a single com...
param_qcd access_qcd()
Returns the underlying QCD marker segment object.
void read_headers(infile_base *file)
This call reads the headers of a codestream. It is for a reading (or decoding) codestream,...
void write_headers(outfile_base *file, const comment_exchange *comments=NULL, ui32 num_comments=0)
Writes codestream headers when the codestream is used for writing. This function should be called aft...
void create()
This call is for a decoding (or reading) codestream. Call this function after calling restrict_input_...
param_nlt access_nlt()
Returns the underlying NLT marker segment object.
void flush()
This is the last call to a writing (encoding) codestream. This will write encoded bitstream data to t...
line_buf * pull(ui32 &comp_num)
This call is to pull one row from the codestream, being decoded. The returned line_buf object holds o...
void open(const char *filename)
void open(const char *filename)
Definition ojph_file.cpp:62
Derived from message_base to handle error messages.
Derived from message_base to handle warning messages.
void set_num_decomposition(ui32 num_decompositions)
void set_block_dims(ui32 width, ui32 height)
void set_color_transform(bool color_transform)
void set_reversible(bool reversible)
non-linearity point transformation object (implements NLT marker segment)
@ OJPH_NLT_BINARY_COMPLEMENT_NLT
@ OJPH_NLT_BINARY_COMPLEMENT_PLUS_LUT
bool get_nonlinear_transform(ui32 comp_num, ui8 &decoded_bit_depth, bool &decoded_signedness, ui8 &nl_type) const
void set_nonlinear_transform(ui32 comp_num, ui8 nl_type)
void set_irrev_quant(float delta)
Set the irreversible quantization base delta.
void set_tile_size(size s)
void set_component(ui32 comp_num, const point &downsampling, ui32 bit_depth, bool is_signed)
void set_num_components(ui32 num_comps)
void set_tile_offset(point offset)
void set_image_offset(point offset)
ui32 get_recon_height(ui32 comp_num) const
void set_image_extent(point extent)
ui32 get_recon_width(ui32 comp_num) const
ui32 get_num_components() const
OJPH_EXPORT message_warning * get_warning()
Get the warning message object, whose operator() member class is called for warning messages – See th...
OJPH_EXPORT void configure_error(message_error *error)
This overrides the default behaviour of handling error messages.
int64_t si64
Definition ojph_defs.h:57
const bool is_machine_little_endian
Definition ojph_arch.h:401
OJPH_EXPORT message_error * get_error()
Get the error message object, whose operator() member class is called for error messages – See the ma...
uint16_t ui16
Definition ojph_defs.h:52
OJPH_EXPORT void configure_warning(message_warning *warn)
This overrides the default behaviour of handling warning messages.
int32_t si32
Definition ojph_defs.h:55
uint32_t ui32
Definition ojph_defs.h:54
uint8_t ui8
Definition ojph_defs.h:50
point(ui32 x=0, ui32 y=0)
Definition ojph_base.h:59
size(ui32 w=0, ui32 h=0)
Definition ojph_base.h:49
#define REF_FILE_DIR
#define OUT_FILE_DIR
TEST(TestExecutables, OpenJPHCompressNoArguments)
#define NLT_MARKER_HIGH
Definition test_nlt.cpp:85
#define NLT_MARKER_LOW
Definition test_nlt.cpp:86