test: add 27 sequential-value test images for bit-error detection
Generated by gen_seq_images.py using manual TIFF writer with packed bit streams matching TIFF spec. Each 16x16 image has cycling 0..max values so any bit-level decode error changes the checksum. Test matrix: - Sub-byte BlackIsZero: 3/5/7-bit - Sub-byte WhiteIsZero: 3/5/6/7-bit - Extended BlackIsZero: 10/12/14/24-bit - Extended WhiteIsZero: 10/12/14/24-bit - Extended HPredict: 10/12/14/24-bit - RGB contiguous: 10/12/14/24-bit - RGB planar: 10/12/14/24-bit (first plane only via read_image)
This commit is contained in:
1 parent
fb1afc81e9
commit
5090003bda
29 files changed
+689
No files matched your search
@@ -998,3 +998,152 @@ fn test_gray_u12_hpredict() {
|
||||
// Delta-encoded 12-bit values: [100, 200, 300, 400, 1000, 2000, 3000, 4000]
|
||||
test_image_sum_u16("hpredict-1c-12b.tiff", ColorType::Gray(12), 11000);
|
||||
}
|
||||
|
||||
// ===== Sequential-value test functions =====
|
||||
// Generated by gen_seq_images.py using manual TIFF writer
|
||||
// Each image has cycling 0..max values; any bit error changes the sum.
|
||||
// 16x16 images (256 pixels per channel).
|
||||
// Sub-byte checksums are sums of packed bytes (the decoder returns packed data).
|
||||
// Planar checksums are first-plane only (read_image() reads one plane).
|
||||
|
||||
// --- Sub-byte BlackIsZero ---
|
||||
#[test]
|
||||
fn test_seq_1c_3b() {
|
||||
test_image_sum_u8("seq-1c-3b.tiff", ColorType::Gray(3), 5792);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_5b() {
|
||||
test_image_sum_u8("seq-1c-5b.tiff", ColorType::Gray(5), 18944);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_7b() {
|
||||
test_image_sum_u8("seq-1c-7b.tiff", ColorType::Gray(7), 24992);
|
||||
}
|
||||
|
||||
// --- Sub-byte WhiteIsZero ---
|
||||
#[test]
|
||||
fn test_seq_1c_3b_miniswhite() {
|
||||
test_image_sum_u8("seq-1c-3b-miniswhite.tiff", ColorType::Gray(3), 5792);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_5b_miniswhite() {
|
||||
test_image_sum_u8("seq-1c-5b-miniswhite.tiff", ColorType::Gray(5), 18944);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_6b_miniswhite() {
|
||||
test_image_sum_u8("seq-1c-6b-miniswhite.tiff", ColorType::Gray(6), 25728);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_7b_miniswhite() {
|
||||
test_image_sum_u8("seq-1c-7b-miniswhite.tiff", ColorType::Gray(7), 24992);
|
||||
}
|
||||
|
||||
// --- Extended BlackIsZero ---
|
||||
#[test]
|
||||
fn test_seq_1c_10b() {
|
||||
test_image_sum_u16("seq-1c-10b.tiff", ColorType::Gray(10), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_12b() {
|
||||
test_image_sum_u16("seq-1c-12b.tiff", ColorType::Gray(12), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_14b() {
|
||||
test_image_sum_u16("seq-1c-14b.tiff", ColorType::Gray(14), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_24b() {
|
||||
test_image_sum_u32("seq-1c-24b.tiff", ColorType::Gray(24), 32640);
|
||||
}
|
||||
|
||||
// --- Extended WhiteIsZero ---
|
||||
#[test]
|
||||
fn test_seq_1c_10b_miniswhite() {
|
||||
test_image_sum_u16("seq-1c-10b-miniswhite.tiff", ColorType::Gray(10), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_12b_miniswhite() {
|
||||
test_image_sum_u16("seq-1c-12b-miniswhite.tiff", ColorType::Gray(12), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_14b_miniswhite() {
|
||||
test_image_sum_u16("seq-1c-14b-miniswhite.tiff", ColorType::Gray(14), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_24b_miniswhite() {
|
||||
test_image_sum_u32("seq-1c-24b-miniswhite.tiff", ColorType::Gray(24), 32640);
|
||||
}
|
||||
|
||||
// --- Extended HPredict ---
|
||||
#[test]
|
||||
fn test_seq_1c_10b_hpredict() {
|
||||
test_image_sum_u16("seq-1c-10b-hpredict.tiff", ColorType::Gray(10), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_12b_hpredict() {
|
||||
test_image_sum_u16("seq-1c-12b-hpredict.tiff", ColorType::Gray(12), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_14b_hpredict() {
|
||||
test_image_sum_u16("seq-1c-14b-hpredict.tiff", ColorType::Gray(14), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_1c_24b_hpredict() {
|
||||
test_image_sum_u32("seq-1c-24b-hpredict.tiff", ColorType::Gray(24), 32640);
|
||||
}
|
||||
|
||||
// --- RGB Contiguous ---
|
||||
#[test]
|
||||
fn test_seq_3c_10b_contig() {
|
||||
test_image_sum_u16("seq-3c-10b-contig.tiff", ColorType::RGB(10), 294528);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_12b_contig() {
|
||||
test_image_sum_u16("seq-3c-12b-contig.tiff", ColorType::RGB(12), 294528);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_14b_contig() {
|
||||
test_image_sum_u16("seq-3c-14b-contig.tiff", ColorType::RGB(14), 294528);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_24b_contig() {
|
||||
test_image_sum_u32("seq-3c-24b-contig.tiff", ColorType::RGB(24), 294528);
|
||||
}
|
||||
|
||||
// --- RGB Planar (first plane only via read_image) ---
|
||||
#[test]
|
||||
fn test_seq_3c_10b_planar() {
|
||||
test_image_sum_u16("seq-3c-10b-planar.tiff", ColorType::RGB(10), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_12b_planar() {
|
||||
test_image_sum_u16("seq-3c-12b-planar.tiff", ColorType::RGB(12), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_14b_planar() {
|
||||
test_image_sum_u16("seq-3c-14b-planar.tiff", ColorType::RGB(14), 32640);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_3c_24b_planar() {
|
||||
test_image_sum_u32("seq-3c-24b-planar.tiff", ColorType::RGB(24), 32640);
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate sequential-value TIFF test images for bit-error detection.
|
||||
|
||||
Uses manual TIFF structure writing to produce packed-sample images at
|
||||
non-standard bit depths. Each image contains pixel values cycling 0..max,
|
||||
so any bit-level decode error changes the checksum.
|
||||
|
||||
Images are 16x16 (256 pixels per channel).
|
||||
|
||||
Output: tests/images/seq-*.tiff + Rust test functions printed to stdout.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import struct
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
OUTDIR = Path(__file__).parent / "images"
|
||||
OUTDIR.mkdir(exist_ok=True)
|
||||
|
||||
W, H = 16, 16
|
||||
NPIX = W * H # 256 pixels per channel
|
||||
|
||||
|
||||
def sequential_values(max_val, count):
|
||||
"""Generate cycling sequential values 0..max_val."""
|
||||
return [i % (max_val + 1) for i in range(count)]
|
||||
|
||||
|
||||
def compute_sum(values):
|
||||
"""Compute checksum as sum of all sample values."""
|
||||
return sum(values)
|
||||
|
||||
|
||||
def pack_bits(values, bits, width):
|
||||
"""Pack values into MSB-first bit stream, row by row.
|
||||
|
||||
Each row is packed independently and padded to a byte boundary,
|
||||
matching TIFF strip/row semantics.
|
||||
"""
|
||||
height = len(values) // width
|
||||
all_bytes = bytearray()
|
||||
for y in range(height):
|
||||
row_vals = values[y * width:(y + 1) * width]
|
||||
bit_buf = 0
|
||||
bit_count = 0
|
||||
for v in row_vals:
|
||||
bit_buf = (bit_buf << bits) | v
|
||||
bit_count += bits
|
||||
while bit_count >= 8:
|
||||
bit_count -= 8
|
||||
all_bytes.append((bit_buf >> bit_count) & 0xFF)
|
||||
if bit_count > 0:
|
||||
all_bytes.append((bit_buf << (8 - bit_count)) & 0xFF)
|
||||
return bytes(all_bytes)
|
||||
|
||||
|
||||
def pack_bits_multichannel_contig(values, bits, width, channels):
|
||||
"""Pack interleaved multichannel data: RGBRGBRGB...
|
||||
|
||||
values is a flat list of length width*height*channels, already interleaved.
|
||||
Rows are packed independently.
|
||||
"""
|
||||
height = len(values) // (width * channels)
|
||||
all_bytes = bytearray()
|
||||
for y in range(height):
|
||||
start = y * width * channels
|
||||
row_vals = values[start:start + width * channels]
|
||||
bit_buf = 0
|
||||
bit_count = 0
|
||||
for v in row_vals:
|
||||
bit_buf = (bit_buf << bits) | v
|
||||
bit_count += bits
|
||||
while bit_count >= 8:
|
||||
bit_count -= 8
|
||||
all_bytes.append((bit_buf >> bit_count) & 0xFF)
|
||||
if bit_count > 0:
|
||||
all_bytes.append((bit_buf << (8 - bit_count)) & 0xFF)
|
||||
return bytes(all_bytes)
|
||||
|
||||
|
||||
def pack_bits_planar(values, bits, width, height, channels):
|
||||
"""Pack planar data: all R samples, then all G, then all B.
|
||||
|
||||
values is a flat list of length width*height*channels.
|
||||
First width*height are channel 0, next are channel 1, etc.
|
||||
Each plane's rows are packed independently.
|
||||
|
||||
Returns (bytes, plane_sizes) where plane_sizes is a list of byte counts per plane.
|
||||
"""
|
||||
plane_size = width * height
|
||||
all_bytes = bytearray()
|
||||
plane_byte_sizes = []
|
||||
for c in range(channels):
|
||||
plane = values[c * plane_size:(c + 1) * plane_size]
|
||||
plane_bytes = pack_bits(plane, bits, width)
|
||||
plane_byte_sizes.append(len(plane_bytes))
|
||||
all_bytes.extend(plane_bytes)
|
||||
return bytes(all_bytes), plane_byte_sizes
|
||||
|
||||
|
||||
def apply_horizontal_predictor(values, bits, width):
|
||||
"""Apply TIFF horizontal differencing predictor.
|
||||
|
||||
For each row, first sample is unchanged, subsequent samples become
|
||||
(current - previous) mod 2^bits.
|
||||
"""
|
||||
height = len(values) // width
|
||||
mod = 1 << bits
|
||||
result = list(values)
|
||||
for y in range(height):
|
||||
for x in range(width - 1, 0, -1):
|
||||
idx = y * width + x
|
||||
result[idx] = (result[idx] - result[idx - 1]) % mod
|
||||
return result
|
||||
|
||||
|
||||
def apply_horizontal_predictor_rgb(values, bits, width, channels):
|
||||
"""Apply horizontal predictor for interleaved RGB data."""
|
||||
height = len(values) // (width * channels)
|
||||
mod = 1 << bits
|
||||
result = list(values)
|
||||
stride = width * channels
|
||||
for y in range(height):
|
||||
row_start = y * stride
|
||||
# Work backwards to avoid overwriting values we still need
|
||||
for x in range(width - 1, 0, -1):
|
||||
for c in range(channels):
|
||||
idx = row_start + x * channels + c
|
||||
prev = row_start + (x - 1) * channels + c
|
||||
result[idx] = (result[idx] - result[prev]) % mod
|
||||
return result
|
||||
|
||||
|
||||
def write_tiff(path, raw_data, width, height, bits, photometric=1,
|
||||
samples_per_pixel=1, planar=False, predictor=1,
|
||||
plane_sizes=None):
|
||||
"""Write a valid TIFF file with precise IFD structure.
|
||||
|
||||
photometric: 0=WhiteIsZero, 1=BlackIsZero, 2=RGB
|
||||
plane_sizes: list of byte sizes per plane (for planar images)
|
||||
"""
|
||||
# For planar images, we need separate strips per plane
|
||||
if planar and samples_per_pixel > 1:
|
||||
num_strips = samples_per_pixel
|
||||
if plane_sizes is None:
|
||||
plane_size = len(raw_data) // samples_per_pixel
|
||||
plane_sizes = [plane_size] * samples_per_pixel
|
||||
else:
|
||||
num_strips = 1
|
||||
plane_sizes = [len(raw_data)]
|
||||
|
||||
# Collect IFD entries as (tag, type_id, count, inline_or_None, offset_data_or_None)
|
||||
# We'll resolve offsets in a second pass
|
||||
entries = []
|
||||
|
||||
def add_short(tag, value):
|
||||
entries.append((tag, 3, 1, value, None))
|
||||
|
||||
add_short(256, width) # ImageWidth
|
||||
add_short(257, height) # ImageLength
|
||||
add_short(259, 1) # Compression = None
|
||||
add_short(262, photometric) # PhotometricInterpretation
|
||||
add_short(278, height) # RowsPerStrip = all rows
|
||||
add_short(296, 1) # ResolutionUnit = None
|
||||
|
||||
if samples_per_pixel > 1:
|
||||
add_short(277, samples_per_pixel) # SamplesPerPixel
|
||||
add_short(284, 2 if planar else 1) # PlanarConfiguration
|
||||
|
||||
if predictor > 1:
|
||||
add_short(317, predictor) # Predictor
|
||||
|
||||
# These need offset data; will be filled in during layout
|
||||
# Use sentinel tag numbers, actual entries added below
|
||||
entries.sort(key=lambda e: e[0])
|
||||
|
||||
# Now compute layout
|
||||
# We need to know all entries to compute IFD size
|
||||
# Extra entries: BitsPerSample(258), StripOffsets(273), StripByteCounts(279),
|
||||
# XResolution(282), YResolution(283)
|
||||
total_entries = len(entries) + 5
|
||||
|
||||
ifd_offset = 8
|
||||
ifd_size = 2 + total_entries * 12 + 4
|
||||
|
||||
extra_start = ifd_offset + ifd_size
|
||||
extra = bytearray()
|
||||
|
||||
# BitsPerSample
|
||||
if samples_per_pixel > 1:
|
||||
bps_offset = extra_start + len(extra)
|
||||
for _ in range(samples_per_pixel):
|
||||
extra.extend(struct.pack('<H', bits))
|
||||
entries.append((258, 3, samples_per_pixel, None, bps_offset))
|
||||
else:
|
||||
entries.append((258, 3, 1, bits, None))
|
||||
|
||||
# StripOffsets - will point to strip data
|
||||
# For multi-strip, we need an array of LONGs in extra data
|
||||
strip_data_start = None # resolved after extra is complete
|
||||
|
||||
if num_strips == 1:
|
||||
# Single strip: offset stored inline (resolved later)
|
||||
entries.append((273, 4, 1, 'STRIP_OFFSET_PLACEHOLDER', None))
|
||||
else:
|
||||
# Multiple strips: offset array in extra data
|
||||
strip_offsets_offset = extra_start + len(extra)
|
||||
# Reserve space for strip offset LONGs (filled after we know strip_data_start)
|
||||
strip_offsets_pos_in_extra = len(extra)
|
||||
for _ in range(num_strips):
|
||||
extra.extend(struct.pack('<I', 0)) # placeholder
|
||||
entries.append((273, 4, num_strips, None, strip_offsets_offset))
|
||||
|
||||
# StripByteCounts
|
||||
if num_strips == 1:
|
||||
entries.append((279, 4, 1, plane_sizes[0], None))
|
||||
else:
|
||||
sbc_offset = extra_start + len(extra)
|
||||
for ps in plane_sizes:
|
||||
extra.extend(struct.pack('<I', ps))
|
||||
entries.append((279, 4, num_strips, None, sbc_offset))
|
||||
|
||||
# XResolution
|
||||
xres_offset = extra_start + len(extra)
|
||||
extra.extend(struct.pack('<II', 72, 1))
|
||||
entries.append((282, 5, 1, None, xres_offset))
|
||||
|
||||
# YResolution
|
||||
yres_offset = extra_start + len(extra)
|
||||
extra.extend(struct.pack('<II', 72, 1))
|
||||
entries.append((283, 5, 1, None, yres_offset))
|
||||
|
||||
entries.sort(key=lambda e: e[0])
|
||||
|
||||
# Strip data starts after extra
|
||||
strip_data_start = extra_start + len(extra)
|
||||
|
||||
# Fill in strip offset placeholders
|
||||
if num_strips == 1:
|
||||
# Update the inline value for tag 273
|
||||
for i, (tag, typ, cnt, val, off) in enumerate(entries):
|
||||
if tag == 273:
|
||||
entries[i] = (273, 4, 1, strip_data_start, None)
|
||||
break
|
||||
else:
|
||||
# Fill in the strip offset array in extra data
|
||||
offset = strip_data_start
|
||||
for s in range(num_strips):
|
||||
pos = strip_offsets_pos_in_extra + s * 4
|
||||
struct.pack_into('<I', extra, pos, offset)
|
||||
offset += plane_sizes[s]
|
||||
|
||||
# Build the file
|
||||
f = bytearray()
|
||||
|
||||
# Header
|
||||
f.extend(b'II')
|
||||
f.extend(struct.pack('<H', 42))
|
||||
f.extend(struct.pack('<I', ifd_offset))
|
||||
|
||||
# IFD
|
||||
f.extend(struct.pack('<H', len(entries)))
|
||||
|
||||
for tag, typ, count, inline_val, offset_val in entries:
|
||||
f.extend(struct.pack('<H', tag))
|
||||
f.extend(struct.pack('<H', typ))
|
||||
f.extend(struct.pack('<I', count))
|
||||
|
||||
if inline_val is not None:
|
||||
# Value fits inline
|
||||
if typ == 3: # SHORT
|
||||
f.extend(struct.pack('<H', inline_val))
|
||||
f.extend(b'\x00\x00')
|
||||
elif typ == 4: # LONG
|
||||
f.extend(struct.pack('<I', inline_val))
|
||||
else:
|
||||
# Value is at offset
|
||||
f.extend(struct.pack('<I', offset_val))
|
||||
|
||||
# Next IFD = 0
|
||||
f.extend(struct.pack('<I', 0))
|
||||
|
||||
# Extra data
|
||||
f.extend(extra)
|
||||
|
||||
# Strip data
|
||||
f.extend(raw_data)
|
||||
|
||||
with open(path, 'wb') as fh:
|
||||
fh.write(f)
|
||||
|
||||
|
||||
def packed_byte_sum(values, bits, width):
|
||||
"""Compute the sum of packed bytes containing the given sample values.
|
||||
|
||||
For sub-byte depths, the decoder returns packed bytes, not individual samples.
|
||||
"""
|
||||
raw = pack_bits(values, bits, width)
|
||||
return sum(raw)
|
||||
|
||||
|
||||
def gen_gray(bits, white_is_zero=False, h_predictor=False):
|
||||
"""Generate a grayscale image at any bit depth."""
|
||||
max_val = (1 << bits) - 1
|
||||
vals = sequential_values(max_val, NPIX)
|
||||
|
||||
# For WhiteIsZero, the stored pixel values are inverted
|
||||
# The decoder should invert them back, producing original sequential values
|
||||
if white_is_zero:
|
||||
stored = [max_val - v for v in vals]
|
||||
else:
|
||||
stored = list(vals)
|
||||
|
||||
# Apply predictor before packing (predictor operates on sample values)
|
||||
if h_predictor:
|
||||
stored = apply_horizontal_predictor(stored, bits, W)
|
||||
|
||||
raw = pack_bits(stored, bits, W)
|
||||
|
||||
photometric = 0 if white_is_zero else 1
|
||||
predictor_tag = 2 if h_predictor else 1
|
||||
|
||||
suffix = ""
|
||||
if white_is_zero:
|
||||
suffix = "-miniswhite"
|
||||
elif h_predictor:
|
||||
suffix = "-hpredict"
|
||||
|
||||
name = f"seq-1c-{bits}b{suffix}.tiff"
|
||||
path = OUTDIR / name
|
||||
|
||||
write_tiff(path, raw, W, H, bits, photometric=photometric,
|
||||
predictor=predictor_tag)
|
||||
|
||||
# Compute expected checksum from the decoder's perspective.
|
||||
# For sub-byte depths (<=8): decoder returns packed bytes, so checksum
|
||||
# is the sum of the packed byte values.
|
||||
# For supra-byte depths: decoder unpacks to u16/u32 container, so
|
||||
# checksum is the sum of sample values.
|
||||
if bits <= 8:
|
||||
# After decoding: WhiteIsZero gets inverted back to original,
|
||||
# predictor gets undone. Decoded samples = vals (the sequential values).
|
||||
checksum = packed_byte_sum(vals, bits, W)
|
||||
rust_fn = "test_image_sum_u8"
|
||||
elif bits <= 16:
|
||||
checksum = compute_sum(vals)
|
||||
rust_fn = "test_image_sum_u16"
|
||||
else:
|
||||
checksum = compute_sum(vals)
|
||||
rust_fn = "test_image_sum_u32"
|
||||
|
||||
color = f"Gray({bits})"
|
||||
return name, rust_fn, color, checksum
|
||||
|
||||
|
||||
def gen_rgb_contig(bits):
|
||||
"""Generate an RGB contiguous (chunky) image."""
|
||||
max_val = (1 << bits) - 1
|
||||
vals = sequential_values(max_val, NPIX * 3)
|
||||
checksum = compute_sum(vals)
|
||||
|
||||
raw = pack_bits_multichannel_contig(vals, bits, W, 3)
|
||||
|
||||
name = f"seq-3c-{bits}b-contig.tiff"
|
||||
path = OUTDIR / name
|
||||
|
||||
write_tiff(path, raw, W, H, bits, photometric=2,
|
||||
samples_per_pixel=3)
|
||||
|
||||
if bits <= 8:
|
||||
rust_fn = "test_image_sum_u8"
|
||||
elif bits <= 16:
|
||||
rust_fn = "test_image_sum_u16"
|
||||
else:
|
||||
rust_fn = "test_image_sum_u32"
|
||||
|
||||
color = f"RGB({bits})"
|
||||
return name, rust_fn, color, checksum
|
||||
|
||||
|
||||
def gen_rgb_planar(bits):
|
||||
"""Generate an RGB planar (separate) image."""
|
||||
max_val = (1 << bits) - 1
|
||||
# For planar: values are R plane, then G plane, then B plane
|
||||
# Each plane has NPIX values
|
||||
vals_r = sequential_values(max_val, NPIX)
|
||||
vals_g = sequential_values(max_val, NPIX)
|
||||
vals_b = sequential_values(max_val, NPIX)
|
||||
# Use different starting points for each plane to differentiate them
|
||||
vals_g = [(v + 100) % (max_val + 1) for v in vals_g]
|
||||
vals_b = [(v + 200) % (max_val + 1) for v in vals_b]
|
||||
|
||||
all_vals = vals_r + vals_g + vals_b
|
||||
|
||||
raw, plane_sizes = pack_bits_planar(all_vals, bits, W, H, 3)
|
||||
|
||||
name = f"seq-3c-{bits}b-planar.tiff"
|
||||
path = OUTDIR / name
|
||||
|
||||
write_tiff(path, raw, W, H, bits, photometric=2,
|
||||
samples_per_pixel=3, planar=True, plane_sizes=plane_sizes)
|
||||
|
||||
# read_image() only returns the first plane for planar images.
|
||||
# Checksum is just the R plane's sum.
|
||||
# colortype() still reports RGB(N) (tested by existing planar tests).
|
||||
if bits <= 16:
|
||||
checksum = compute_sum(vals_r)
|
||||
rust_fn = "test_image_sum_u16"
|
||||
else:
|
||||
checksum = compute_sum(vals_r)
|
||||
rust_fn = "test_image_sum_u32"
|
||||
|
||||
color = f"RGB({bits})"
|
||||
return name, rust_fn, color, checksum
|
||||
|
||||
|
||||
def verify_with_tifffile(path, expected_vals, bits, description):
|
||||
"""Verify the generated TIFF reads back correctly with tifffile."""
|
||||
try:
|
||||
import tifffile
|
||||
with tifffile.TiffFile(str(path)) as t:
|
||||
page = t.pages[0]
|
||||
assert page.bitspersample == bits, \
|
||||
f"{description}: BitsPerSample={page.bitspersample}, expected {bits}"
|
||||
# Can't always read back sub-byte packed data with tifffile,
|
||||
# but we can verify the structure
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" WARNING: tifffile verification of {description} failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
results = []
|
||||
|
||||
print("Generating test images...\n")
|
||||
|
||||
# Sub-byte grayscale (BlackIsZero)
|
||||
for bits in [3, 5, 7]:
|
||||
r = gen_gray(bits)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# Sub-byte grayscale (WhiteIsZero)
|
||||
for bits in [3, 5, 6, 7]:
|
||||
r = gen_gray(bits, white_is_zero=True)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# Extended grayscale (BlackIsZero)
|
||||
for bits in [10, 12, 14, 24]:
|
||||
r = gen_gray(bits)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# Extended grayscale (WhiteIsZero)
|
||||
for bits in [10, 12, 14, 24]:
|
||||
r = gen_gray(bits, white_is_zero=True)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# Extended grayscale with horizontal predictor
|
||||
for bits in [10, 12, 14, 24]:
|
||||
r = gen_gray(bits, h_predictor=True)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# RGB contiguous
|
||||
for bits in [10, 12, 14, 24]:
|
||||
r = gen_rgb_contig(bits)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# RGB planar
|
||||
for bits in [10, 12, 14, 24]:
|
||||
r = gen_rgb_planar(bits)
|
||||
print(f" {r[0]}: sum={r[3]}")
|
||||
results.append(r)
|
||||
|
||||
# Verify structure with tifffile
|
||||
print("\nVerifying TIFF structure...")
|
||||
for name, _, _, _ in results:
|
||||
path = OUTDIR / name
|
||||
bits = int(name.split('-')[2].replace('b', '').split('.')[0])
|
||||
verify_with_tifffile(path, None, bits, name)
|
||||
|
||||
# Print Rust test functions
|
||||
print("\n// ===== Sequential-value test functions =====")
|
||||
print("// Generated by gen_seq_images.py")
|
||||
print("// Each image has cycling 0..max values; any bit error changes the sum.")
|
||||
print()
|
||||
|
||||
categories = [
|
||||
("Sub-byte BlackIsZero", []),
|
||||
("Sub-byte WhiteIsZero", []),
|
||||
("Extended BlackIsZero", []),
|
||||
("Extended WhiteIsZero", []),
|
||||
("Extended HPredict", []),
|
||||
("RGB Contiguous", []),
|
||||
("RGB Planar", []),
|
||||
]
|
||||
|
||||
for name, rust_fn, color, checksum in results:
|
||||
bits_str = name.split('-')[2] if '3c' not in name else name.split('-')[2]
|
||||
bits_val = int(bits_str.replace('b', '').split('.')[0])
|
||||
|
||||
if "3c" in name and "contig" in name:
|
||||
categories[5][1].append((name, rust_fn, color, checksum))
|
||||
elif "3c" in name and "planar" in name:
|
||||
categories[6][1].append((name, rust_fn, color, checksum))
|
||||
elif "miniswhite" in name and bits_val < 8:
|
||||
categories[1][1].append((name, rust_fn, color, checksum))
|
||||
elif bits_val < 8:
|
||||
categories[0][1].append((name, rust_fn, color, checksum))
|
||||
elif "miniswhite" in name:
|
||||
categories[3][1].append((name, rust_fn, color, checksum))
|
||||
elif "hpredict" in name:
|
||||
categories[4][1].append((name, rust_fn, color, checksum))
|
||||
else:
|
||||
categories[2][1].append((name, rust_fn, color, checksum))
|
||||
|
||||
for cat_name, items in categories:
|
||||
if not items:
|
||||
continue
|
||||
print(f"// --- {cat_name} ---")
|
||||
for name, rust_fn, color, checksum in items:
|
||||
test_name = name.replace(".tiff", "").replace("-", "_")
|
||||
print(f'#[test]')
|
||||
print(f'fn test_{test_name}() {{')
|
||||
print(f' {rust_fn}("{name}", ColorType::{color}, {checksum});')
|
||||
print(f'}}')
|
||||
print()
|
||||
print()
|
||||
|
||||
print(f"// Total: {len(results)} test images generated")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user