From 5090003bda4146a4ffe83827c1bbc48b7447e777 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Mon, 9 Mar 2026 23:06:16 -0600 Subject: [PATCH] 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) --- tests/decode_images.rs | 149 +++++++ tests/gen_seq_images.py | 540 ++++++++++++++++++++++++ tests/images/seq-1c-10b-hpredict.tiff | Bin 0 -> 494 bytes tests/images/seq-1c-10b-miniswhite.tiff | Bin 0 -> 482 bytes tests/images/seq-1c-10b.tiff | Bin 0 -> 482 bytes tests/images/seq-1c-12b-hpredict.tiff | Bin 0 -> 558 bytes tests/images/seq-1c-12b-miniswhite.tiff | Bin 0 -> 546 bytes tests/images/seq-1c-12b.tiff | Bin 0 -> 546 bytes tests/images/seq-1c-14b-hpredict.tiff | Bin 0 -> 622 bytes tests/images/seq-1c-14b-miniswhite.tiff | Bin 0 -> 610 bytes tests/images/seq-1c-14b.tiff | Bin 0 -> 610 bytes tests/images/seq-1c-24b-hpredict.tiff | Bin 0 -> 942 bytes tests/images/seq-1c-24b-miniswhite.tiff | Bin 0 -> 930 bytes tests/images/seq-1c-24b.tiff | Bin 0 -> 930 bytes tests/images/seq-1c-3b-miniswhite.tiff | Bin 0 -> 258 bytes tests/images/seq-1c-3b.tiff | Bin 0 -> 258 bytes tests/images/seq-1c-5b-miniswhite.tiff | Bin 0 -> 322 bytes tests/images/seq-1c-5b.tiff | Bin 0 -> 322 bytes tests/images/seq-1c-6b-miniswhite.tiff | Bin 0 -> 354 bytes tests/images/seq-1c-7b-miniswhite.tiff | Bin 0 -> 386 bytes tests/images/seq-1c-7b.tiff | Bin 0 -> 386 bytes tests/images/seq-3c-10b-contig.tiff | Bin 0 -> 1152 bytes tests/images/seq-3c-10b-planar.tiff | Bin 0 -> 1176 bytes tests/images/seq-3c-12b-contig.tiff | Bin 0 -> 1344 bytes tests/images/seq-3c-12b-planar.tiff | Bin 0 -> 1368 bytes tests/images/seq-3c-14b-contig.tiff | Bin 0 -> 1536 bytes tests/images/seq-3c-14b-planar.tiff | Bin 0 -> 1560 bytes tests/images/seq-3c-24b-contig.tiff | Bin 0 -> 2496 bytes tests/images/seq-3c-24b-planar.tiff | Bin 0 -> 2520 bytes 29 files changed, 689 insertions(+) create mode 100644 tests/gen_seq_images.py create mode 100644 tests/images/seq-1c-10b-hpredict.tiff create mode 100644 tests/images/seq-1c-10b-miniswhite.tiff create mode 100644 tests/images/seq-1c-10b.tiff create mode 100644 tests/images/seq-1c-12b-hpredict.tiff create mode 100644 tests/images/seq-1c-12b-miniswhite.tiff create mode 100644 tests/images/seq-1c-12b.tiff create mode 100644 tests/images/seq-1c-14b-hpredict.tiff create mode 100644 tests/images/seq-1c-14b-miniswhite.tiff create mode 100644 tests/images/seq-1c-14b.tiff create mode 100644 tests/images/seq-1c-24b-hpredict.tiff create mode 100644 tests/images/seq-1c-24b-miniswhite.tiff create mode 100644 tests/images/seq-1c-24b.tiff create mode 100644 tests/images/seq-1c-3b-miniswhite.tiff create mode 100644 tests/images/seq-1c-3b.tiff create mode 100644 tests/images/seq-1c-5b-miniswhite.tiff create mode 100644 tests/images/seq-1c-5b.tiff create mode 100644 tests/images/seq-1c-6b-miniswhite.tiff create mode 100644 tests/images/seq-1c-7b-miniswhite.tiff create mode 100644 tests/images/seq-1c-7b.tiff create mode 100644 tests/images/seq-3c-10b-contig.tiff create mode 100644 tests/images/seq-3c-10b-planar.tiff create mode 100644 tests/images/seq-3c-12b-contig.tiff create mode 100644 tests/images/seq-3c-12b-planar.tiff create mode 100644 tests/images/seq-3c-14b-contig.tiff create mode 100644 tests/images/seq-3c-14b-planar.tiff create mode 100644 tests/images/seq-3c-24b-contig.tiff create mode 100644 tests/images/seq-3c-24b-planar.tiff diff --git a/tests/decode_images.rs b/tests/decode_images.rs index c2ff3dd..8b0b067 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -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); +} diff --git a/tests/gen_seq_images.py b/tests/gen_seq_images.py new file mode 100644 index 0000000..6a44da2 --- /dev/null +++ b/tests/gen_seq_images.py @@ -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('4L znTW=sNI6P_99h1?W6Qqp668YNF}uTW!R+09!9b-q00mYf2Y>+xOC>zLR5M*Ql>s zf7Yv?`1mJZ{K$tt`s@eZ|9ZRPe9>ihIRE+0cbjvc*F3j4`#I;j$(heO z#|=(@db3^U)TcGeHBNrYnXYo;lTN$B@sDrHWsZGZlP+=eV@|lpk&im=0*61mG3Pn- TVU0S+!4Em&EC)X5urmMv@O<3y literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-10b.tiff b/tests/images/seq-1c-10b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..03089ef0d34262ce4395bd9287349a85c6fb3a1f GIT binary patch literal 482 zcmebD)MDUZU|`^8U|?isU<9%RfS3`9%>-q00mYf2Y>+xOB(@+U3s~!=t$}0?kjvl!#31uwG|)vH%!~{H66{P26EwJ)87^4xvoH#H z2(vN>btZ~|y=d#6l6JBAD%ci`+5!cOlCuiI? z=e=BU--1uL^8mYsF(_7yX) l5)Z`frnxzGi=5k^=e#0*C0-N15x)~}h_}Q$;t%3a;yYmp4K)A& literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-12b-miniswhite.tiff b/tests/images/seq-1c-12b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..4684ffe96bd4c955536268811862ace87bedbae5 GIT binary patch literal 546 zcmYk!e`J?I90%}wwl!0em8`6ENivfpGfBE6*^(qlk}gRm$xJ4hZjvNPCds7BBuSDa zNxCFSk|aseB}tMzKkj*+d+xdCKHcVt?;jucx{vq!GceGD+7Lps5DJ=6;ML(LXmC^Q zcE4|~J(X*%?TO|DEz{je@6Lwq{CYp;3thpC>CCv#EUZ1@-Q950e^I}msr&z500+{7 zFu*mi45$JU0}dVhb@GoBza9VO*iT1)IP%@$Zxwv48oijIa<0g~jSCS-2GD*Bik|arYk|arzB*`R6l8}UCNRlK; zl4O!3Ns=VVBuSDaNfLW+uX_J@`hK3L=lQl;6MzGN1V98(ra@V#i*bil&Pj*cy|RmY zP%*GQm-C@r+uly@^I$ll1Dzwe)7RmXna6eg$rcu5wg3OWZD$Br3XXvn5G2GJl19*j z%wS}PAlHlhFckVw9D&kE45P3ajqMoh#$rDnhlw~&!f7&rDa1@Ab{cWhiJw8jOcH02 zG`oN~g_&E}d4-!__*M}X7I9IL7E@S4%~EQYQMa7>6*R1*aTQJdC9E#Zn$oT<-MZ4R zFT;j1ZY(btk?xys&WVkKk9hvSb zxTnm0WgjT_Q29qHJXZ0EN>6KeR-5OweNnrYwSQHI*L8eTr?(BfYs~w`erVjs#(!$U d=O%t>(pL@NwE3>>5AA+x|4WD8I{wk=?>~StfRq3L literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-14b-hpredict.tiff b/tests/images/seq-1c-14b-hpredict.tiff new file mode 100644 index 0000000000000000000000000000000000000000..201401046ee5d657041c7afc0c626996912edaf8 GIT binary patch literal 622 zcmaKnK?=e^3`O&kDlUrDje=f4yhEW&dn-@k(xqqU(fm^)%%bxL!aRPGdCWXN>|kcS z8NxssSg6>OkIW;v%2u+8;d9{&;S1pl;S1sW0k@tYmjD0& literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-14b-miniswhite.tiff b/tests/images/seq-1c-14b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..c053514aed50785010e26432fb0468ae061c1693 GIT binary patch literal 610 zcmYk2F^o(>7{}jpM=l}uB!VDWh2kQTQ79CWRVeNf$tn~I$tn~I$tV;G$zP#RNLEB4 z7s-k!6p~RW6p~#W4#ztj4#ztj4#&LReY^X1cV<4{U7lo%nfc8(|L;HF_l=Bf^9DT6 zTj_azpXW0T^74ClS<0(9-k0*CZbizB-WtDuDfcS#x}MziX}rP-8~x==nhUJil=3L^ zP>&~HyZ<7I4#* zJNh?n@NeoDuDeo4{=_v`?1*_>acJuwxXdM}@3`a&ZTSrsUA`^maNgw-{T1i9F7*Xx zT{e-QamHm5F^kidS~|ojE>?ZQNgKB0N1U)hOU&T7EjRTC9ODkudmOc;rhJDZw%8QY zIBe0-Z*hp*Qg3k378>$34%mD{Okuyx#rhTYaj)tnhHW;MFR<5UVljz5hUz-NZXSSo zj$J0K%V*eWg1VT%4pXk_r`XO@QBN>rN;UZyTTQVh#<9hqsvlu9k4inn22-fY2Uu(J dRWXJ^lZ*6ytm65pdl)d;NZv)i$wZ<8?;lsFKLh{( literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-14b.tiff b/tests/images/seq-1c-14b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..f1b82978bd3bd7b92b31ab9df56a63ad06eba16b GIT binary patch literal 610 zcmYk2Ka9*l6vyA2+25bt-|p`>=eXq-_dAC>cev$nINT-*E?1aDp-|XFp-|XFp-}iH z3WdUiLPB9fp-|XFp-|XFp-`Aap-|XFp-|XFp-}MdvXU)k<}+`;@6CJPm6chFi6|jr zO)O+&@er#4rxMo#Uekp`Ji>+b+@}&B8_KN?;$2A?=U`oPUo^D=KN6l9sv}oAD{HO( z|5MNqv}~3UQBF=Fp*neuj2dhT7?l?rph``4A!^iOA7O%^OpGa!+aji^ty~Fn)KNac zJax4xV?k=z3KprSU5X{@YoB46sbCCFOJO^LGn^OP2CQ|??IiA~Ja#*9SJh)bg?p+|G&}J?Ca}BkP&JEgHy*22(NE)vLdneFnT%!k;JIp- z+$>(GPRY;VrRpYTFJ8&4b{}4=UgGxSjp`?U9&a_3O&#yV0DA!MbzXJ{@j=(iegPkK zqhb!>lel6JaItPTg_*(4EvT;iv9q<^+C;fA%DP Q>t5zg;g9ZTei?uN0nayyb^rhX literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-24b-hpredict.tiff b/tests/images/seq-1c-24b-hpredict.tiff new file mode 100644 index 0000000000000000000000000000000000000000..d432951f583347434ff314e466d56d2a13770aed GIT binary patch literal 942 zcmebD)MDUZU|`^3U|?isU<9%RfS3`9%>-pj0L7W1Y>+xOB(@+U3s~@2C7sbQN00) v>K#Z_A3&n|1QOL3kf^?aMD-I$RKI{k^&3c3e}F{w7f4k9fJF5lNKy{~Kz$86 literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-24b-miniswhite.tiff b/tests/images/seq-1c-24b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..f4c6fddca5c3d71365270bc611ce20d18ed1eff2 GIT binary patch literal 930 zcmYk!2ei*+7zgm*-$h2r-l42(MMxn#2uJd=!eb0H$`+1)8eV(|u!a<542vP?@BqoT2N*h)rk&{H7HJryposdf&b;uxH zBr0 zANZc{*w43o!#?)%HD9rZ-Rxo~JJ`;be8D!hvW3lTVk4ikf%U9oEo)fKDn8>=Rt}r!%k7iH^L=E4<80 zbf7)$XiFPjq%|+lik7sXIn8KF6P~9rjcCYoG@w59s7oDc6Gts-QiJL|OEs!eh00W- zA{BUs@|2@2PxBOIc#_hTq9jjHg5o?*F^W=z!W1Hwf)pS>`N+#-JW3vNlZ!{lNe;4; njjUuLGnvRp2GWy`v^-229^ye#lZuq2-~p18jHJYngb2Z1tKWhD literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-24b.tiff b/tests/images/seq-1c-24b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..02d0760d46908db5125b749d4a282b5933f114f8 GIT binary patch literal 930 zcmYk%ceu`V7{~F~;~abB*it+Hl9_`tGAg4&Mn*=+%2tHx*rSY$Y?4`s$_|B$ ztb}aJ&dBe1uE%wqKkn=M{(i3ezCWKkAt7EWq$Lz16i~*XLP};OvM2be8dP}y{Wq+Ig=B#}u_OFXOJVX=OG>@H;?crkCBJGt{_ z6BMB+Pg0EHl%OP~c#6_IO&OlyS)Sv0%JKr`c#-l{pdyv1Och>=T$n&Ls#AlPd4-zP zqBeDSmAcfUJ`H${*Lj16G@>z0c$2qyo2E3QIq%Sdmb9WZ@6v|0w4*)m(SeS1qBHN) zg|2kt1G>|Lp7f$OAJT`u^rJrm7|0+7^AR62grR)GForXNPx*|`Nn|9W7|j^QGLG>~ zU?P+Ff-jlO6s9tbub9pZW-^Pfnav#LGLQKzU?GcG%r`9ITb8nn<*Z;OtN4!ZSO z@&oHw&yW1X&um~Lo7l`2wz7?1*v<}i@+-fwi{0$uclNT6{T$#A4swXY9N{Q`a*V$? r&IwNPH>Wtw8P0N!^IYH}m-vT&xy%)=a*gZU;3ofZi`(4cF8|*HeNKbo literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-3b-miniswhite.tiff b/tests/images/seq-1c-3b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..33756beca5b4782cfaf2f42b3e4c098e1228cd41 GIT binary patch literal 258 zcmebD)MDUZU|`^8U|?isU<9%RfS3`9%>-pL1I3x4Y>+xOC>zLR5M*Ql>s@~ literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-3b.tiff b/tests/images/seq-1c-3b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..40d959c9d37d1c6618b92d3625481b5a6620fb6b GIT binary patch literal 258 zcmebD)MDUZU|`^8U|?isU<9%RfS3`9%>-pL1I3x4Y>+xOB(@+U3s~-q$0>zo3Y>+xOC>zLR5M*Ql>s-q$0>zo3Y>+xOB(@+U3s~;M1& literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-6b-miniswhite.tiff b/tests/images/seq-1c-6b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..80401c02a1a8c34a9618df763f37d9b107b0d4d6 GIT binary patch literal 354 zcmebD)MDUZU|`^8U|?isU<9%RfS3`9%>-q$0mYf2Y>+xOC>zLR5M*Ql>sC?fhry;E01F{s%m4rY literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-7b-miniswhite.tiff b/tests/images/seq-1c-7b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..110b54231e3444f9e192a5e19d7d19d1a57c0d18 GIT binary patch literal 386 zcmebD)MDUZU|`^8U|?isU<9%RfS3`9%>-q$1I3x4Y>+xOC>zLR5M*Ql>s-kh{c(A|f?dh^LH`Yh3^(t9$VZK$)$)Y7E`lWO{3Kkrw`}z8K-kcqIH&?sm z%-9gOGwW#9loft6wOleLEU>G1c{r_ShF-)-q$1I3x4Y>+xOB(@+U3s~6{+5sQh*?i1BQc1}+BU+6Z~ wYHD`)#*;MD0KENX&Hw-a literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-10b-contig.tiff b/tests/images/seq-3c-10b-contig.tiff new file mode 100644 index 0000000000000000000000000000000000000000..e339b64127fab207a3dc955cde1c0ac51d05da4b GIT binary patch literal 1152 zcmYk0M^I}86ozkd(?dwPNp1@S2#^LuMWv~zs3f3L0;H*^s5BK7m8PPi(o{--G!+#U z2r5re383N(3)+GbAZ@{d1#Q8C1#N*BhBx!(%$f7efBsW+Iu*bG0DuDkBoH7G#3Pb) z2a^c~3C&F;c%xtWXg5qIm`|cy&;CcS=tjSq5#R9DPWZNmXkaAjwf`2O<2URfSaQdH z#T`tv0M>PA3A(y|yAuC0KoXFT(8)lzf(-(zsay()r-i7bd?QRFbywhY(rUewLFTo| znB;uBoJH;)QM1Xb9!(g?TS(@B`Tmq}uzN3q3$C7K@+f>p7N1gpWD6)gid=}YmYOG| z^0j&qwZLc)Q+p~bFm$}qz-WnP6e6uLj9x{jjIo~D70VQ8t#Qmkqb;7yU65vPcI+l z37Oy{ydsqTD6db+&f;yPaUbIgbBuo%rQ6!t4~^}@}xydsfEr!N*2n+zqQ z{z{8MwAoN<6pPx+OyXikxmnylT5S<;PSrdEixz92g^L4q&%yosO{MVWS@ZLVnA!3I zQi8U=hzuw@%aE1hO4lF(Xnc5Yij&e zT)a5(8eS5Zd>tRypQ*vO&Su|`z|6TfB?ff%8wo7)-u*BW#tS zx(Hvxc6}t=zS9tCaO^fl+C~qXB7IXw?@8gs5#R@7@hKt7`t2D8RMVUd@e(l zlE07{gDGFi><1ZLGXHsIcPz@v>WMXB*}bt2RjxHQke=5Uhvw+}<4k75K%Aq>Vv7qj zmfGXdjxtBQ$yx4-mK1I1PWF*8&gq>c^BW&*MW8Ck$;HUQb8 zP;rnRF(?~krZ`m19w1wakriy$2_Rb<%Dw<(%OIJl0cC^i0s$^Cya8lM0IiGw8t(v< zf?*G!I7lUo2Ks}8nUNttf}M$Bf(AD;!vza|7DfRNVOGX~2yr&X2^rGtj29~8IhX`G zlsTCKW~g&9O<1AL&2(XhJ`c0N31eR7fE(s~%oAQ%^D|%gVK2ZU$l)x=5-8y=#4=IC zTbSjdg}(@^phvJMYhXmU80*B0XmQqy74Z^mf*r|{Y=JYzNg1=% zIWJYr*WeQBSggqvG-J6I*Q6DzwYe_sSg*q^bYinEchHUPdfbyD_HBKAzT(&rG!Yk}?*_1ap;<_2{KlsLXJO0Tpe%te3{_%epc)>wHgp=7(AViYgNnna5x3j<%OMVwY z5l>-P!H`ICH^C{H((Zy+D&;+dL^_o{g+gYkdkIZhsqHOvWv9N6u*gYcU*V9O=6=Fc zURwJLU-@YtAR@}?94Hbh=^i99Rnt3IIXz8sTITF@$!nGKGo-{i7iUU^&0L-(HEreUY^iHI l*XKx!o!p!&9d>hjp7gYryYr>5{oG$5BhGoaP$pdRH~^$_RI~s9 literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-12b-contig.tiff b/tests/images/seq-3c-12b-contig.tiff new file mode 100644 index 0000000000000000000000000000000000000000..bb94c187925f996d68cda5d71781954f3ef6f364 GIT binary patch literal 1344 zcmYk03#blT7>3tc^WX3H`(@Vr_j{5g$;?R3ZYN2SBr_vPk|arznUN$(k|fE@NOB}c zk|Sx)j3h~tBuQpwx057Ek|gKf+NZSoTF>{c_x-*#G0_D+06-W3NB}sd*tp;yjK)li zwZF$j%;~c}&m5yM$D!+YRv&YEPM^(W=6H5ycFrTZ04PBBZ~s4Iy*S7Jj=A*5{PrI_ zegdK2=dUr%ZvX$D?MMR9Zoqkg=m$y==wV<*;rLHv64GhNWucgdN)hU1XjKuwh^!&H zfw&eDJ4opveSoYH0p=0pe1cv;a0>}>5uq$5^d*F~)CCw9xy(f`cX2CR;)F|?bm=Qy z)+#qx?Izc_>9uZdom*V*RyMfxjc#j`2k;(pvxnZ|;kJ6jZ60O2N8jPGc6z}sFS*-G z@9}bbz2ZKvvfry8@LC6bK=P4?eDq--cf=H@> zA@Y8Reh}gwhQvo9<#9-V60)9#!Lu;=JWRg`b1%coHhlu_$VttB&&r$MAl>Qp!zD33FQRPQe{~5J@#UPAfBF4C5yf-HKV`?yFgkyFz z4&!m0j5FytpN&iTxLS-G<+xodbMC& zE7;eI@J12eEHbx>{OzK2r>Nd78uyC!R0$d-e80pzDDe+V(xa04xMVyj*-y*xSs6bs zGcU^g%d+&UtiCQAZ_4)D3bZSDy28v<_;(fQeMS9HF+Nu8PgVH2ioaBuuT}nARr+34 ze^ia1Rr?o-AW0D<>m~&+Df>w+NSa~NiDDGTR1&jkEM&2q$667aW$aWbgej^Ntj;d43oGjKL|vP#n=9+i zss>u!pw=|lwGCliLtfv|HZ;tQ4QEpm@l9%Tliku3wl?K$O>KMA+|hJ)w$QE?wY$ac zX$gB<^1ha~zhxe1IS1QFYEy^W?BTX>q%9w9YscE=@wRiKgHCp+QyunnM>x}w&vvwP z9rJw0xzI&wm%7+xFLi~>UHM8^yV^Ccb)D-ybfZVz?6J3c!tI`Xr>EWRnfH3mR390A z>VBVn&=(%|E>cb*Q=vjO#dz`hs=F9-6gf%bY}z8N@ghsYjM(?fPd>Wz8BkIeD{W=o9jpXkm?Z?RcIdXpe2kS$V1^@s6 literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-12b-planar.tiff b/tests/images/seq-3c-12b-planar.tiff new file mode 100644 index 0000000000000000000000000000000000000000..a9650c0355b2a6a1a7e07e700a77e1da2a335d52 GIT binary patch literal 1368 zcmd^+kB1gP7{_P!_rAOP?(Vzq?!LSGrd=o34@r`9&S_UtC&@XfYJ6R3-bHWJAuBHK)YZqn`{-7TcQl|St>;LIgf>3gB$s?tJ zmO@sFIVlyST$G_KJBsY7a-hqhA;*@SE|Bwu73fr)mWsEi60}yrC6#ztC0$<0S153$ z;;d4<)k@H&gzZY)p`@KkzNQMjst6^6)-dIgHRrAd%>{gu~)!U*5Th(y8 z8t+ikUN!I2V4yiW|HhyGo1JQ2zZUG$!rfZDM@#o=`92-)*PR2pH=qZDdN`!V2le!j zo*%Bku;v`8c}Hu(v08Y%77y3b6Se%L0Y?mH)bLIj!D%BLGvYHwI&S0>CX7vI()6ax w;H(**Gvfk7E1kCTnL14C&h5H)rykt>4dRK1X#fBK literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-14b-contig.tiff b/tests/images/seq-3c-14b-contig.tiff new file mode 100644 index 0000000000000000000000000000000000000000..16c6a54f1adb07dc969ea3eca496bb2c14351d1a GIT binary patch literal 1536 zcmYjQU2vRJ7(Un zk);SNvIIeqo#{m{deJk}i(d4iJJX9^^rAb{i(d4iPC7Bu`R07*nfJWsJ?A{{^K9JM z#(c(DA!CNc42`%R#(CJ(*wV4xdRQy*0LI ztjciCMt>GHrQik!m1tS z&1}He!GeCP=zv8t7s&On*USg16P7Giz~it_6Cf^t{Z_0%UI+)Qc!BDIgI1!DUj&CV zE8=1}Y$XfjC2+(_6{>DHYC#dd6pm@C#AR^YN*Bq?;e?ebQay0e$`}iFnN?~4JMC;KzY)81PsB~wZRbkm&Ddk-OVuXqb=Yby zuunHi+=Bg1Y_+@<2b}n7wHY@#i86j0Zr0rsx8oKkStjqmK_^wFw%}F=%K4o*q+2TP z!fj5vT;7e_olLnJ#9=2J;rHN(?z6ZTN1a?m-iKpOKBBhbxXUVd5+`)?#r-(x#wz3k zIOWDG)DZ4)6P5fyoYsmE58;fPtdtMqPB&Glw&AP`Rs0d$rDY->#W^=!B_G2*Zl+3Y z$9Xp!<&Wcn){b}r7u{S`K8btXd{hnNlEg3&S-eRte!5=1MeTm3UQJP_pKai8Q?T|Eod)_A89e1YnMOK-f+HM&C^oA*6|GO(*_kk)BYf~PX0m%g7`YM zKnH_F2mh50Y1fM1=x~tikiXNBAl0E3>1Y7!`5$ylTUq=`$Ak2G`4^oCGV9e|IvHd; a`QLO(`&;}&r-NLl{FlxI`A)S&Xa55G2#^Z_QV0-3fB*qv)S*L% zen%ZTbm)~jv{Hv!>QIRewbY?QE7ekmR$|~v3?OyD5(5JRmKYcq;5c%nik$(8_jKR; z>D=?qIrr#$XK`_#hzlW-LP#wnli?-rV8yK2_6;xHvv>WX9xLXI40e2)%(JiD2SSL5lW&ieGFp)%EU3w?zRsTqM=+rCPh>E1g1nw zn*mI744cG^Xls|ktmtT;#+(#^F_`DTHiHFO4%{G?WF_!fEX!(WhOolfY!0ik7P>sv zWIgl+tV={@7#m!I9l@q-M6QS}*^GP%+p-liqd3TQ*}HH^wqx#Y9G0D!AHxwP;--wF zT&&%NW2zi?yK!7q;(i<_RJG6S!AU-Wy$7dMt1+6ng{uBcAJPvM#t1EzxO+yMI!Zs_uWdl=v9%7CB7 zceSoH%;y2w&o2T$Q z_s>3!KXg0op245GllF7?>;H!QqalGXrb?PsW}l^4P&V#4N(2?-=P4ajGv;~9vh?f= zR0wJr_ac>odd4r%Sb#yZpT=2_c9AB6#-MwNCWGdnU!tj?l{GKZG>g=}LNh@->t3bV zpp*5>G#82?V`-k1YhR;Z_;X5%el8`Ev)DKDy@ge zn*+4*?LY%W&PFJjPw5;F z68jmQj~YeyIbDdFMZZNCqgKg$L6>;C*e~gF)GoQN=t|Tn`E9xu6Qib1*LmdFuj$6$ DmZPx2 literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-24b-contig.tiff b/tests/images/seq-3c-24b-contig.tiff new file mode 100644 index 0000000000000000000000000000000000000000..1d26538061e151b66530b69a3c6ce3857bffa104 GIT binary patch literal 2496 zcmYk+1 zL?3i!>>)fT=Qu-R4A1x(W^nvv1NsC*___g}@HhFV`3(T#L`E9i;O`k_aDSsQI%6>AfZYL?1Lg+hH{O7=0Yd{mn23p) zgg-DTlQB6{FeOtlHPbLH(=k0WFe5WDGqW%&voSk!Feh^{H}fzr^D#dQupkSuFpID# zi}6PmX9@nql7l;3nm@A)%d#BHvjQu!5-al;R$*0EV|CVGP1a&<)?r=NV|_MYLpEY# zHepjXV{^7(OSWQbwqaYgV|#XBM|NUoc41d`V|VsoPxfMO_F-T4V}B0dK>o@>9Lymc z%HQ}qhjBPZa3n|Z502&-j^#Lx=b!wGe{%vSauO%=AO6cJoXTmO&KaD^S)9!|oXdIq zkMjrL$_u%Oi@AhLxs1!Xf-AX-tGR}2xsL0(fg8Dro4JKsxsBVogFCs4ySayZxsUsK zfCqVqhk1lYd5p(-f+u;3r+J2Fd5-6Kffsp+mwAO(d5zb3gEx7Lw|R$md5`z`fDieI zkNJd8`Hau`f-m`sula^=`Ht`TfgkyapZSGf2i^TdI(U2srf^`eeka4qa5B7%AS231 zGP3+$Mv+lvG#OpSkTGQ}8C%AYab-LiUnYp;qGL=j%)5x?k zolGw?$c!?R%q+9WtTLO-E_2A7GMCIP^T@n1pUf`{$bzzvEG&!2qOzF$Q5Kga?k|Q&a#W_D!a+@?SYcPL*(aAy3Lv^0Yi7&&qT1yu2VU z%1iRHydtm4Yx26hA#ciC^0vGq@5+1fzI-4b%183Cd?KI9XY#pxAz#W@^0j;;-^zFL zz5F0Q%1`pM{35>&diWcy4y8luF#0s-x-XI);v^W9isB zj*hG2>G(Q)#-G4ok3^RnRI5IMQ7F7batIX z=hV4$Zk+$EL>JY?^pCo@E}?(YC3PuXTK}xe=(4(;F0U)-in@}n ztbfr}bX8qVSJyRkOmhom{!Ra`hw0&ZgdV9! z=|A*nJw}h!3|Iz>IDSE1&rl;!}dZwPGXX`n7uAZm=)ARKL zy-+XGi}ez{R4>!Z^$NXGuhOga8ogGp)9dvHy-{z{oAnmGRd3VV^$xvL@6x;V9=%uZ z)BE)SeNZ3LhxHMCR3FpF^$C4apVFuG8GTlt)93XCeNkW1m-Q8WRbSKB^$mSf-_p1B y9er2d)A#iQ{ZK#BkM$G%R6o{M{ZW6?pY<30_5T2Nh=*?g literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-24b-planar.tiff b/tests/images/seq-3c-24b-planar.tiff new file mode 100644 index 0000000000000000000000000000000000000000..618ba35bef1508b056256cb6689088a543c259a2 GIT binary patch literal 2520 zcmeIycl6hD6vy$$t3^gc(UMIXB7}w&B`cAnA*+lCQ8et(lFTwIqGXgp(U!9J%HDhL zJwD&>`8mg*{^j_5&OM*!x#zy`ecf~4pWCrxV=H5IQdtUGv#|V)D-~S1@J%nQa)rMX zSFM686iu~(aVA)ZqDSvi3(=*z>K2L%p@lxyqao|F0UNRr8?yBXV+<}eQD2#(|^`p}o7Ifi37j(!}^3H0YgPU2(+a0;g~kU^Zr=?rEFLm9>y zoXJ_7%{iRQc?{=#F5p5&Fp^P>W(*fGmWvt3C0xp7T+S6-$#^Dk6<2c&*K!@#Gm%M5 z<_2zL3O8{xQ<=ss<=c8Iw{bgnFoT)g$t-4b7k6_HbC}D$+{ZlTvw(#xVlhj&pQSv& zGM4io5AiUM@FG;llsy?){};^zHA^H%0{xW zY$BV=X0o|7k}agMG?Av#OtzHfvXyKtEo2+nR<@Itvc2peJ4!2QEp4Q&>?AwOF0!la zChcT**+be(2ia3P%3iX!>?8Zie$q+ym(Fs494KAnAn7UxOE)=0x=Rn~DZS)S=`DxJ z;c|o=`LFp;|DWkUpJ|SgKGIi?mSf~tIZpb?@p6LnmlNeAIavnCDRQa|ltFTuoGyc9 zhzylsa)z8KXUW-ej+`s!$#6MeE|3dlgp8C?GFrySMKV?{mT_{4Tq>8z<#L5wDdS~= zTqRe_HFB+7C)dkFnIx0t2DwqD$W3yyOqFSJi%geWmGF$GFyX794 gBXi|mxliWFd|4n1WsxkFC33$kl?P;*ESCrW1c+^k4FCWD literal 0 HcmV?d00001