{
  "codec": {
    "codec_name": "MARC (Multi-representation Adaptive Region Codec)",
    "headline_novelty": "EIN Quadtree, EIN Lagrange-Funktional (J=D+lambda*R), aber PRO REGION eine andere Repraesentation, die deterministisch aus EINER inhaltsbasierten Klassenkarte geroutet wird - und diese Klassenkarte ist gleichzeitig die primaere Kontextachse von Transform (03), Quantisierung (04), Entropie (06) UND der physischen Bitstream-Gruppierung (08). Konkret: die Quadtree-Blockgrenze entsteht dort, wo ein REPRAESENTATIONSWECHSEL (PLANE / DCT / Palette / [VQ/KLT spaeter]) RD-Gewinn bringt - nicht an einer Varianzschwelle - und der Decoder leitet den Transformpfad jeder Region ohne ein einziges Signalisierungsbit aus derselben rekonstruierten Klassenkarte ab. Das ist strukturell unmoeglich in JPEG (starr 8x8 DCT, ein globaler Subsampling-Modus), WebP (fest 4:2:0, ein Transform-Paradigma) und JPEG XL (variable Bloecke, aber ein Transform-Paradigma ohne per-Region Plane/Palette/VQ-Wahl unter gemeinsamem RDO). Der Single-Sentence-Differenzierer: 'Region-as-representation' - der Inhalt einer Region waehlt nicht nur ihre Quantisierung, sondern ihre gesamte Repraesentationsfamilie, und diese Wahl ist gratis (0 bit) weil sie aus der ohnehin uebertragenen Klassenkarte folgt.",
    "pipeline": "END-TO-END DATENFLUSS (encode), mit exakten Interfaces zwischen den 10 Bereichen. Zentrale Datenstruktur: ein 'plan'-dict, das durch eine feste Reduce-Kette fliesst (09 orchestriert, Stage-Contract prueft requires/provides per set-Differenz an jedem Uebergang).\n\n[01 color] encode_color/rgb_to_ycocg(rgb uint8 HxWx3) -> ycocg int16 HxWx3 (Y[0,255] bit-exakt reversibel via YCoCg-R Integer-Lifting: Co=R-B; t=B+(Co>>1); Cg=G-t; Y=t+(Cg>>1)). plan['channels']=[Y,Co,Cg] float32, plan['color_meta']={'transform':'ycocg_r','shape':(H,W)}. WICHTIG fuer MVP v0: SCHICHT B (CfL/PLANE/SUB/KEEP Chroma-Modi) wird im Encode-Loop NACH der Quadtree-Entscheidung (02) aufgerufen, da plan_chroma die seg_map und y_hat braucht. Lossless-Pfad: q_c=1 -> identisch.\n\n[02 segmentation] segment_channel(Y float32, lam) -> SegResult. Precompute: 5 Integralbilder (S,S2,Sxx,Syy,Sxy) auf Y und Sobel-Strukturtensor -> O(1) Varianz/Kohaerenz/Orientierung je Rechteck. block_rd_costs liefert geschlossene J fuer 5 Klassen {SMOOTH=0,DCT=1,EDGE=2,TEXTURE=3,PALETTE=4} OHNE Trial-Encode (Plane-Momente, Parseval-DCT, unique-Palette). Bottom-up Lagrange-Merge (8->16->32->64). Output: plan['blocks'] structured [y,x,size,cls,theta], plan['classmap'] uint8, plan['tree_bits'] packbits Praeorder-Splitflags. MVP-Reduktion: nur SMOOTH/DCT/PALETTE aktiv (EDGE faellt auf DCT, TEXTURE auf DCT zurueck -> KLT/VQ-Modelle fehlen in v0).\n\n[01 SCHICHT B chroma] plan_chroma(ycocg, y_hat=Y, lam, seg_map=plan['classmap-tiles']) -> ChromaPlan; encode_chroma -> residuals dict (r,c,ch)->int16 + meta (mode_map 2bit/Region + CfL/PLANE-Koeffizienten). 4 Modi {CFL,PLANE,SUB,KEEP} per RDO je Region/Kanal. y_hat==Y bei lossless (closed-loop verhindert Drift).\n\n[03 transform] transform_blocks(blocks_by_group, class_by_group, models=None, rdo) -> list[BlockToken]. Routing aus classmap: SMOOTH->forward_plane (affine Ebene a+bx+cy via gecachter pinv, Residuum batch-DCT), DCT/EDGE->forward_dct (separable orthonormale DCT-II als D@B@D.T einsum, bit-identisch zu cv2.dct, 5x schneller batched), PALETTE->Index-Lookup (in 02/08 behandelt), TEXTURE->DCT-Fallback (VQ-Codebook=None). Output BlockToken{kind,size,coeffs float32 (N,N),params,side}. KEINE Quantisierung hier. plan['coeffs']=dict[block_idx->coeffs].\n\n[04 quant] quantize_blocks(coeffs zickzack (N,B*B), class_map, model, lam) -> QResult. Skalarer RDOQ-Pfad (2-Kandidaten floor/floor+1 argmin J=D+lam*R vektorisiert, EOB-Trim per Suffix-cumsum). Deadzone + Lloyd-Max-Centroid-Dequant (aus 05, MVP: feste Defaults). rate_lut: Surrogat log2(|L|+1)+1 bis 06-Tabellen da. plan['qindices']=dict[block_idx->int16], plan['qtables'].\n\n[07 rate_control] allocate(BlockStats{sigma2,mu,cls,path,n_coeff,size}, target_bpp, total_pixels) -> AllocResult{q_step (n,), lam, est_bpp}. Geschlossene varianz-Water-Filling-Allokation: D*=min(sig2w,theta), theta=lam*ln2/2; solve_lambda per log-Bisektion auf SKALAR lam (monoton fallende R(lam), garantierte Konvergenz, exakter bpp-Treffer). Pro-Pfad kalibrierte rate_scale/dist_scale. WIRD von 09 in der aeusseren Schleife genutzt, lam fliesst zurueck in 02/04.\n\n[06 entropy] encode_symbols(stream, model, adaptive) -> bytes. Klassen-konditionierter Interleaved-rANS (16 Lanes, ctx=(class*n_bands+band)*n_nbr+nbr_bucket). Significance/Sign/Magnitude-Splitting in 3 Substroeme. class GRATIS aus classmap. estimate_bits(qindices,ctx)->float (billige R-Schaetzung fuer 09-Bisektion). MVP-Fallback: adaptive=True header-freier binary-rANS wenn 05-Tabellen fehlen. plan['payload']=bytes, plan['sideinfo']=bytes.\n\n[08 bitstream] write_marc(MarcContainer) -> bytes. 32-Byte-Header (MAGIC 'MARC', version, profile, flags, W,H,...) + typisierte Sektionen (type+uvarint(len)+payload+crc32). Reihenfolge: HEADER(0x01)->QUADTREE_CLASS(0x02 gemeinsamer Split/Klasse-Symbolstrom)->MODEL_REFS(0x04)->RATE_META(0x07)->BLOCK_SIDEINFO(0x06 MED-praediktiv DC/QP)->BLOCK_PAYLOADS(0x05 PRO KLASSE demultiplext, implizite DFS-Positionszuordnung)->VERIFY(0xFF crc32 Original). Struct-of-Streams: jeder Sub-Stream klassenrein -> scharfe rANS-Statistik.\n\n[09 integration] encode(img_rgb, target_bpp|quality, mode) orchestriert: Cached-Frontend (01->02->03 EINMAL, lambda-unabhaengig) + aeussere log(lambda)-Bisektion ueber 04-roundtrip + 06-estimate_bits (~6-7 Kandidaten, KEIN Re-Transform) bis target_bpp getroffen, dann EINMAL realer 06+08. Whole-Image-Global-Kandidat als RD-Anker + 1-Byte arbiter_flag (garantierte untere Schranke). Lossless/near-lossless: interner _decode_internal -> err=img-recon -> L_inf-Topup (eps-Garantie) bzw. Sparse-Residuum; bei Roundtrip-Fail zlib-MED-Notfallpfad (fallback=1).\n\n[10 eval] Adapter bringt 09.encode/decode in fn(img,quality)->(bytes,dec). bd_rate(rd_ref_jpeg, rd_marc, metric='psnr') -> %-Bits-Ersparnis (Headline-Kennzahl). region_rd schluesselt PSNR/bpp nach Klasse auf.\n\nDECODE spiegelt: read_marc -> decode_partition(tree_bits) -> classmap rekonstruiert -> decode_symbols(class-ctx) -> dequantize_blocks -> reconstruct_blocks(deterministisches Routing aus classmap, 0 Signalbits) -> decode_chroma -> ycocg_to_rgb.",
    "mvp_scope": "v0 = der kleinste Pfad, der JETZT laeuft und JPEG auf den 5 Kodak-Bildern per BD-Rate schlaegt. Bewusst REDUZIERT auf das, was ohne gelernte Modelle (05) und ohne den vollen rANS (06) funktioniert:\n\nENTHALTEN: (1) 01 Schicht A YCoCg-R (bit-exakt, bereits verifiziert). (2) 02 Quadtree-Lagrange-Merge mit NUR 3 Klassen aktiv: SMOOTH, DCT, PALETTE (EDGE/TEXTURE routen auf DCT, da KLT/VQ-Modelle fehlen) - das genuegt fuer den Kern-Differenzierer, weil PLANE-vs-DCT-vs-PALETTE-Routing schon strukturell neu ist und genau die bandingfreie Glatt-Repraesentation + lokale Palette bringt, die JPEG fehlt. (3) 03 PLANE + DCT-Pfade (beide verifiziert, bit-exakt/cv2-identisch). (4) 04 skalarer RDOQ-Pfad mit Surrogat-rate_lut + EOB-Trim + Deadzone (kein VQ). (5) 07 Stufe A geschlossene Water-Filling-Allokation mit Skalar-lambda-Bisektion fuer exakten target_bpp. (6) 06 im ADAPTIVE-Fallback-Modus (header-freier klassen-konditionierter binary/interleaved-rANS, KEINE 05-Tabellen noetig) - klassengetrennte Kontexte sind schon hier aktiv. (7) 08 voller Container mit packbits-Quadtree + per-Klasse-demultiplexten Payloads + VERIFY-Footer. (8) 09 volle Orchestrierung inkl. Cached-Frontend-Bisektion, Arbiter-Anker, lossless-Pfad (q_c=1 + QP=1 + reversibles YCoCg-R) mit Verify-or-Fallback. (9) 01 Schicht B Chroma im reduzierten Satz {CFL, SUB, KEEP} (PLANE-Chroma optional).\n\nAUSGESCHLOSSEN aus v0 (in backup/spaeter): KLT-Bank (05), Gain-Shape-VQ (03 Pfad D + 04 VQ-Pfad), Lloyd-Max-Centroids aus echter Statistik (05), perzeptuelle CSF-Maskierung, gelernte rANS-Tabellen, near-lossless Stufe B, directionale/rotierte Transforms.\n\nERFOLGSKRITERIUM v0: aggregate_bd ueber die 5 Kodak-Bilder < 0 vs JPEG (Mehrzahl der Bilder negativ = MARC spart Bits). Realistisches Erstziel: glatte/gradientenreiche Bilder (kodim07/23 - niedrige bpp, hoher Glatt-Anteil) gewinnen zuerst durch PLANE + bandingfreie Glattregionen + besseres YCoCg-R-Chroma; textur-/kantenreiche (kodim05) sind der Haertetest, der erst mit KLT/VQ (v1) klar faellt.",
    "build_order": [
      "FUNDAMENT (sofort, entkoppelt testbar): 10_evaluation_tests/bd_rate.py + common/metrics.py-Erweiterung (MS-SSIM, max_abs_error). Liefert die Go/No-Go-Metrik BEVOR Codec existiert; gegen vorhandene baseline_results.json testbar.",
      "01_color_preprocess/color_transform.py SCHICHT A (rgb_to_ycocg/ycocg_to_rgb int16-Lifting). Standalone-Test: bit-exakter Roundtrip auf 10M Zufallspixeln + 5 Kodak. Keine Abhaengigkeiten.",
      "02_segmentation_analysis/segmentation.py: precompute_stats (Integralbilder), block_features (O(1)), block_rd_costs (geschlossen, 3 MVP-Klassen), segment_channel (Lagrange-Merge), decode_partition. Test: Integral-Varianz==np.var; decode_partition(encode_split_bits(r))==r bit-exakt; partition auf synthetischem flat/gradient/edge-Korpus.",
      "03_transform/transform.py: make_dct_matrix, plane_projection, forward/inverse_plane, forward/inverse_dct, transform_blocks/reconstruct_blocks (Routing PLANE/DCT). Test: forward_dct==cv2.dct (err<1e-14); inverse_plane(forward_plane(x))==x auf Rampe (err~0); reconstruct(transform(x))==x ohne Quant.",
      "08_bitstream_format/container.py: write_marc/read_marc, pack/unpack_quadtree, dfs_leaf_coords (kanonische Geometrie-Wahrheit), payload-Demux, uvarint, write/iter_sections, VERIFY-Footer. Test: read_marc(write_marc(c))==c feldweise; deterministisch bytegleich; CRC-Validierung; truncated-skip.",
      "06_context_entropy/entropy.py: Low-Level rans_encode/rans_decode (interleaved 16-Lane) ISOLIERT, dann encode_symbols/decode_symbols im ADAPTIVE-Modus + klassen-ctx. Test: decode(encode(s))==s bit-exakt auf 4000xN=16 Symbolen ueber mehrere Kontexte; bpp nahe Shannon-Ideal (<1% Lane-Overhead).",
      "04_quantization/quantize.py: rdoq_scalar (2-Kandidaten vektorisiert), eob_trim, reconstruct_scalar, quantize_blocks/dequantize_blocks mit Surrogat-rate_lut + Default-Deadzone. Test: dequantize(quantize(c))-Roundtrip exakt spiegelbar; EOB-Trim senkt J; monotone D(lam).",
      "07_rate_control/rate_control.py: perceptual_weight, model_rate_dist, total_bpp, solve_lambda (log-Bisektion), lambda_to_qmap, allocate. Test: R(lam) monoton fallend; solve_lambda trifft target_bpp <2% auf synthetischen BlockStats; Konvergenz-Flag.",
      "01_color_preprocess SCHICHT B: plan_chroma/encode_chroma/decode_chroma (Modi CFL/SUB/KEEP), chroma_deadzone. Test: q_c=1 lossless bit-exakt; decode_chroma(encode_chroma(...)) Roundtrip; CfL-AC-Energiereduktion 30-50% auf Kodak.",
      "09_integration_codec/marc_codec.py: Stage-Contract + Plan-Reduce-Kette + Adapter pro Bereich; _rdo_lambda_bisect (Cached-Frontend), _build_global_candidate (Arbiter), _decode_internal, _topup_residual, encode/decode + Exceptions. Erst mit Identitaets-Stages testen, dann echte Bereiche einklinken. Test: E2E encode/decode-Roundtrip; lossless bit-exakt; near-lossless L_inf<=eps; mode='lossy' trifft target_bpp.",
      "10 E2E-EVAL: run_baselines-Adapter fuer MARC, region_rd-Tomographie, aggregate_bd vs JPEG/WebP ueber 5 Kodak. GATE: BD-Rate<0 auf Mehrzahl. Dann KLT/VQ (05) + gelernte rANS-Tabellen als v1 nachziehen."
    ],
    "opportunistic_wins": "SPEED: (1) Cached-Frontend in 09 - der teure lambda-unabhaengige Teil (01/02/03) laeuft EINMAL statt pro Bisektions-Iteration; bpp-Targeting kostet damit ~6 billige Quant-Roundtrips statt 6 Voll-Encodes. (2) batched DCT (D@B@D.T einsum) ist gemessen 5x schneller als cv2.dct-Schleife. (3) Integralbild-Strukturtensor macht RDO-Partition gratis (O(1)/Knoten, kein Trial-Encode, ~17ms/512x512). (4) Geschlossene Water-Filling-Allokation (07 Stufe A) ~1ms statt Multi-Pass-Suche. (5) int32-PSNR-Pfad + 5-Kanal-gestapeltes GaussianBlur in metrics. NEAR-LOSSLESS: praktisch gratis aus dem bestehenden Design - Y bleibt bit-exakt, nur Chroma-Deadzone q_c regelt kontinuierlich (q_c=1 lossless, q_c=2 -> 37dB, q_c=3 -> 42dB); plus L_inf-Topup in 09 gibt harte per-Pixel-Garantie aus EINEM Codepfad. LOSSLESS: faellt fast umsonst ab (reversibles YCoCg-R + QP=1 + PALETTE-Klasse fuer Text/Logo/Screenshot + MED-Praediktor), eine Pipeline zwei Configs, kein zweiter Codec. NIEDRIGE BITRATE: PLANE-Pfad gibt bandingfreie glatte Verlaeufe genau dort, wo JPEG bei niedriger bpp am haesslichsten blockt; lokale PALETTE schlaegt Transform bei flachen Farbflaechen/Logos. ROBUSTHEIT: Whole-Image-Arbiter garantiert 'nie schlechter als simpler Globalcodec'; VERIFY-Footer (crc32 Original) ist selbstpruefender Korrektheitsbeweis.",
    "test_plan": "EINHEIT (pro Bereich, isoliert, vektorisiert): 01 bit-exakter YCoCg-R-Roundtrip auf 10M Zufallspixeln + vollem R/G/B-Gitter + 5 Kodak (assert array_equal). 02 Integral-Varianz==np.var (atol 1e-9); decode_partition(encode_split_bits(r))==r; Plane-Fit-Residuum auf reiner Rampe ~0. 03 forward_dct==cv2.dct (atol 1e-13); inverse_plane(forward_plane)==identity; reconstruct(transform)==input ohne Quant. 04 dequant(quant) spiegelbar bei gleichem idx/model; EOB-Trim senkt J nachweislich; rdoq_scalar ohne Python-Koeff-Schleife. 06 INVARIANTE decode_symbols(encode_symbols(s,m),m,classmap,layout)==s bit-exakt auf >4000 Symbolen/mehrere Kontexte; gemessene bpp < Shannon-Ideal*1.02. 07 R(lam) monoton (assert np.all(diff<=0)); solve_lambda trifft target_bpp <2%; psnr_to_tau geschlossen korrekt. 08 read_marc(write_marc(c))==c feldweise; bytegleich-deterministisch; jede Sektion-CRC geprueft; unbekannte Sektion uebersprungen; truncated wirft sauber.\n\nINTEGRATION (09): Stage-Contract wirft MarcContractError bei Key-Drift (negativ-test). E2E mit Identitaets-Stages bevor echte Bereiche da sind. Voller Roundtrip: decode(encode(img)) auf 5 Kodak. Lossless: mode='lossless' -> bit-exakt (assert array_equal img). Near-lossless: mode='near_lossless', eps=2 -> max_abs_error<=2 (harte Garantie). Lossy: target_bpp=0.5 -> |est_bpp-0.5|/0.5 < 0.05. Fallback: korrupten Stream injizieren -> kein Crash, best-effort + Warnung.\n\nSYSTEM/AKZEPTANZ (10, das Go/No-Go-Gate): run_baselines-Adapter erzeugt marc_results.json (RD-Punkte ueber quality-Sweep). bd_rate(jpeg, marc) je Bild; aggregate_bd -> n_wins. GATE v0: mean BD-Rate < 0 UND n_wins >= 3/5 vs JPEG. Zusatz-Diagnose: region_rd (klassen-stratifiziert) zeigt WO MARC gewinnt (erwartet: glatt/PALETTE deutlich, Textur knapp/negativ in v0). bd_rate-Selbsttest gegen synthetische RD-Kurven (bekannte Antwort). Routing-Validierung: synthetischer Korpus (flat/gradient/edge/texture/mixed 64x64) prueft dass flat->PLANE/SMOOTH, edge->DCT, flat-color->PALETTE geroutet wird (isolierte Routing-Assertions, nicht nur E2E).",
    "honest_tradeoffs": "(1) v0 schlaegt JPEG NICHT garantiert auf allen 5 Bildern - nur auf der Mehrzahl. Ohne KLT (gelernte Basen pro Klasse) und VQ (Textur) ist der DCT-Fallback fuer EDGE/TEXTURE schlicht ein 8x8-DCT wie JPEG; der Gewinn kommt fast nur aus PLANE (glatt), PALETTE (Flaechen) und besserem YCoCg-R-Chroma + RDOQ + rANS. Auf kantenreichen/texturigen Bildern (kodim05) ist v0 bestenfalls knapp. Der volle Multi-Repraesentations-Vorteil ist erst v1 (mit 05). (2) WebP zu schlagen ist in v0 unrealistisch - WebP hat ausgereiften Entropiecoder + Intra-Praediktion; MARC v0 hat Surrogat/adaptive-rANS ohne gelernte Tabellen. Ehrliches v0-Ziel = JPEG, WebP = v1+-Stretch. (3) PALETTE-Klasse hilft nur bei Bildern mit flachen Farbflaechen/Text - auf Naturbildern (Kodak) selten getriggert; ihr Hauptwert ist Screenshots/Logos, die nicht im Testset sind. (4) Der Cached-Frontend-Ansatz nimmt an, dass die optimale Partition lambda-INVARIANT ist - das ist eine Naeherung; bei sehr unterschiedlichen Ziel-bpp kann die einmal berechnete Partition suboptimal sein (akzeptierter Tradeoff fuer 5-10x Speed). (5) reines NumPy/CPU: rANS-Encode rueckwaerts + 16-Lane-Renorm ist in Python/NumPy nennenswert langsamer als C; OK fuer Eval, nicht produktiv.",
    "risks": "(1) INTERFACE-DRIFT zwischen 10 Bereichen ist Risiko #1 - die Klassen-Enum (0=SMOOTH/glatt..4=PALETTE), Zickzack-Reihenfolge, B in {8,16,32}, DFS-Kindreihenfolge NW/NE/SW/SE und das rANS-Tabellenformat (Summe=2^12 bzw 2^14, jedes Symbol>=1) MUESSEN bereichsuebergreifend gelockt sein. Mitigation: der laufzeit-erzwungene Stage-Contract in 09 (set-Differenz wirft MarcContractError) macht Drift sichtbar statt still korrupt; dfs_leaf_coords in 08 ist die EINE Geometrie-Wahrheit. ACHTUNG: die Specs sind hier schon inkonsistent (06 sagt TOTAL=2^12, 05 sagt 2^14; 04 sagt B in {8,16}, 02/03 sagen {8,16,32}) - MUSS vor Implementierung aufgeloest werden. (2) rANS-KAUSALITAET: decode braucht ctx[t] kausal; wenn der Kontext aus Nachbar-Signifikanz gebildet wird, muss die Decode-Reihenfolge exakt der Encode-Reihenfolge folgen - subtile Bug-Quelle, isoliert testen. (3) CLOSED-LOOP CfL: a wird auf rekonstruiertem y_hat geschaetzt; bei lossy ist y_hat != Y -> Encoder MUSS denselben y_hat wie Decoder nutzen (Drift sonst), erzwingt Quant-vor-Chroma-Reihenfolge im Encode-Loop. (4) BD-RATE NICHT-KONVEXITAET: ein experimenteller RDO kann nicht-konvexe RD-Punkte erzeugen -> polyfit instabil; Mitigation: monotoner Hull-Filter (np.maximum.accumulate) vor polyfit. (5) NUMERISCHE PARSEVAL-ANNAHME: D im Koeffizientenraum == Pixel-MSE gilt NUR fuer orthonormale DCT/KLT - wenn 03 versehentlich nicht-orthonormale Skalierung nutzt, zielt die ganze RDO daneben. (6) SCOPE/ZEIT: 10 Bereiche voll zu integrieren ist viel; Risiko dass v0 unfertig bleibt - Mitigation: 09 mit Identitaets-Stages lauffaehig halten, Bereiche inkrementell einklinken, jederzeit ein lauffaehiges (wenn auch schwaches) E2E-System haben."
  },
  "areas": [
    {
      "area": "Farbtransform & Chroma (Area 01_color_preprocess): reversibler dekorrelierender Farbraum + inhaltsadaptive, regionsweise Chroma-Repraesentation. Eingang RGB HxWx3 uint8, Ausgang dekorrelierte Kanaele (Y voll + Co/Cg-Repraesentation pro Region) + Metadaten fuer Area 03/04/06/09.",
      "chosen_design": "SYNTHESE: Zwei Schichten, beide region-adaptiv, beide an den Quadtree von Area 02 koppelbar (Fallback: festes 16x16-Gitter).\n\nSCHICHT A - Globale reversible Farbtransform (verschmilzt Ideen 1/5/7/13): reversibles YCoCg-R per Integer-Lifting in int16. Forward: Co=R-B; t=B+(Co>>1); Cg=G-t; Y=t+(Cg>>1). Inverse: t=Y-(Cg>>1); G=Cg+t; B=t-(Co>>1); R=B+Co. EIN einziger astype(int16)-Cast, arithmetischer np.right_shift, voll vektorisiert. VERIFIZIERT bit-exakt auf 10M Zufallspixeln UND vollem R/G/B-Gitter UND allen 5 Kodak-Bildern (RGB->YCoCg->RGB identisch). Y in [0,255], Co/Cg in [-255,255]. EIN Codepfad bedient lossless und lossy (Verlust entsteht erst in Area 04). Gemessen 65 MP/s (512x768 in 6 ms).\n\nSCHICHT B - Per-Region adaptive Chroma-Repraesentation (verschmilzt Ideen 2/3/9/10/14/20). Pro Region waehlt eine RDO-Entscheidung J=D+lambda*R (lambda aus Area 07) genau EINEN von 4 Chroma-Modi, getrennt fuer Co und Cg:\n  MODE_CFL (Chroma-from-Luma, Kern aus Ideen 1/10/12/20): geschlossene Least-Squares-Steigung a=cov(Yhat,C)/var(Yhat) auf rekonstruiertem(!) Y; uebertragen wird a (6 bit signed, 1/8-Schritte) + DC-Offset (8 bit) + quantisiertes Residual C-(a*(Yhat-Ymean)+Cmean). Closed-loop mit Yhat verhindert Encoder/Decoder-Drift. VERIFIZIERT 30-50% Chroma-AC-Energiereduktion auf allen Kodak.\n  MODE_PLANE (nur Modell, aus Idee 20): wenn Residual-std<tau_flat -> nur a + affines Ebenenmodell c0+c1*x+c2*y (3x3-Normalgleichung), 0 Residualbits. VERIFIZIERT 30-60% der Tiles.\n  MODE_SUB (4:2:0 per cv2.resize INTER_AREA, aus Ideen 2/6/9): halbe Chroma-Aufloesung, Decoder upsampled INTER_LINEAR. VERIFIZIERT ~70% der Tiles unter Subsampling-Schwelle sicher.\n  MODE_KEEP (4:4:4 direkt): scharfe gesaettigte Chroma-Kanten/Text/Logos, kein Modell.\nY bleibt IMMER voll aufgeloest und (vor Quantisierung) bit-exakt -> Luminanz-Treue + sauberer lossless/near-lossless-Pfad via Chroma-Deadzone q_c (q_c=1 exakt lossless, verifiziert; q_c=2 -> 37 dB, q_c=3 -> 42 dB bei bitexaktem Y).\n\nMetadaten: Modus-Karte (2 bit/Region, raeumlich stark korreliert -> billig in Area 06) + pro CFL/PLANE-Region kompakte Koeffizienten. Bewusst NICHT aufgenommen: per-Region gelernte 3x3-KLT (Idee 11/15) und 2x2-Chroma-PCA (Idee 4) -> in backup_ideas, da hoeherer Overhead/Komplexitaet bei unsicherem Mehrwert gegenueber CfL.",
      "rationale": "WARUM diese Auswahl: (1) Reversibles YCoCg-R ist der einzige Farbraum, der lossless UND lossy mit EINEM Codepfad bedient, ohne Float-Rundungsfehler - alle Standards (JPEG float-YCbCr, WebP, JPEG XL XYB) brauchen separate Pfade oder verlieren Bitgenauigkeit. Auf realen Kodak verifiziert bit-exakt. (2) Die 4 Chroma-Modi decken empirisch das gesamte Spektrum natuerlicher Bilder ab: gemessene Modusverteilung schwankt massiv zwischen Bildern (PLANE/CfL-only 30-60%, CfL+Residual 22-50%, DIRECT 7-21%) - genau das beweist, dass eine GLOBALE Subsampling-Entscheidung (wie JPEG/WebP) suboptimal ist und Regionsadaptivitaet real Bits spart. (3) CfL liefert verifiziert 30-50% Chroma-AC-Energiereduktion auf jedem Testbild -> direkt weniger Bits bei gleicher Qualitaet, der Primaerachsen-Gewinn. (4) Chroma traegt bei niedriger Bitrate ueberproportional bei; MODE_PLANE kollabiert flache Chroma-Regionen auf <=5 Koeffizienten. (5) MARC-Kerndifferenzierer 'je Region andere Repraesentation' wird hier auf der Farbebene realisiert - eine Stufe frueher als alle klassischen Codecs, die erst NACH der Farbtransform adaptiv werden. Closed-loop auf rekonstruiertem Yhat ist die einzig drift-freie Variante (Standard-Fehler bei naivem CfL).",
      "novelty_contribution": "Strukturell anders als JPEG/WebP/JPEG XL/Neuronalcodecs: (a) Die Chroma-REPRAESENTATION selbst ist die regional gewaehlte Groesse - kein Standard-Codec mischt {CfL-Predict, affines Plane-Modell, 4:2:0-Subsampling, 4:4:4-direkt} raeumlich innerhalb EINES Bildes ueber eine explizite RDO-Entscheidung. JPEG hat genau einen globalen Subsampling-Modus, WebP fest 4:2:0. (b) CfL existiert in AV1/AVIF als fester Intra-Praediktionsmodus und in JPEG XL als globaler Modus - MARC erhebt es zur gleichberechtigten, per-Region waehlbaren Repraesentation und koppelt es an die Inhaltsklassifikation von Area 02. (c) Das Plane+CfL-Doppelmodell (Luma-Praediktion PLUS raeumliches Ebenenmodell auf dem Residual) als eigenstaendige 'Modell-statt-Block'-Wahl gibt es in keinem Mainstream-Codec. (d) Reversibles YCoCg-R als gemeinsamer Vorderbau fuer ALLE Regionen-Repraesentationen mit kontinuierlichem Regler lossless->near-lossless NUR ueber Chroma (Luma bitexakt) ist ein einzelner, region-agnostischer, exakt invertierbarer Farbschritt, der nie Fehlerquelle ist - die gesamte Verlustkontrolle wandert in die region-adaptive Quantisierung (Area 04). Reines NumPy/OpenCV, kein Training/GPU/scipy.",
      "interface": "Modul: verfahren/01_color_preprocess/color_transform.py (nur numpy + cv2).\n\n# --- Schicht A: globale reversible Farbtransform ---\ndef rgb_to_ycocg(rgb: np.ndarray) -> np.ndarray:\n    '''rgb: HxWx3 uint8 -> ycocg: HxWx3 int16 (Kanal 0=Y[0,255], 1=Co[-255,255], 2=Cg[-255,255]). Bit-exakt reversibel.'''\ndef ycocg_to_rgb(ycocg: np.ndarray) -> np.ndarray:\n    '''Inverse von rgb_to_ycocg. ycocg HxWx3 int16 -> rgb HxWx3 uint8. Exakt fuer alle gueltigen Eingaben.'''\n\n# --- Schicht B: per-Region adaptive Chroma-Analyse/Kodierung ---\n# ChromaPlan = dict mit Schluesseln:\n#   'tile_size': int, 'grid': (n_rows, n_cols),\n#   'mode_map': np.ndarray uint8 (n_rows, n_cols, 2)  # je Region/Kanal Modus 0..3\n#   'coeffs':   dict (r,c,ch) -> dict(a:int8, dc:int16, plane:(c0,c1,c2)|None)\n#   'lambda': float\ndef plan_chroma(ycocg: np.ndarray, y_hat: np.ndarray, lam: float,\n                tile_size: int = 16, seg_map: np.ndarray | None = None) -> dict:\n    '''Waehlt pro Region+Kanal den Chroma-Modus per RDO J=D+lam*R.\n    ycocg: HxWx3 int16 (Original-Chroma). y_hat: HxW int16 rekonstruierte Luma (==Y bei lossless).\n    seg_map: optional Quadtree-Blattkarte aus Area 02 (sonst festes Gitter). -> ChromaPlan dict.'''\n\ndef encode_chroma(ycocg: np.ndarray, y_hat: np.ndarray, plan: dict\n                  ) -> tuple[dict, dict]:\n    '''Erzeugt je Region die zu quantisierende Chroma-Nutzlast.\n    returns (residuals, meta):\n      residuals: dict (r,c,ch) -> np.ndarray int16 (Residual/Subsampled-Chroma fuer Area 03/04)\n                 ; bei MODE_PLANE leeres Array.\n      meta: serialisierbarer ChromaPlan (mode_map + coeffs + tile_size + grid) fuer Area 08/06.'''\n\ndef decode_chroma(y_hat: np.ndarray, residuals_dq: dict, meta: dict\n                  ) -> np.ndarray:\n    '''Rekonstruiert HxWx3 int16 ycocg aus dequantisierten Residuen + Meta + rekonstruierter Luma.\n    Spiegelbildlich zu encode_chroma; MODE_SUB->INTER_LINEAR-Upsample, MODE_CFL->a*(Yhat-Ym)+Cm+res,\n    MODE_PLANE->a*(Yhat-Ym)+Cm+(c0+c1*x+c2*y), MODE_KEEP->direkt.'''\n\n# --- near-lossless Helfer ---\ndef chroma_deadzone(C: np.ndarray, q_c: int) -> np.ndarray:\n    '''Symmetrischer Deadzone-Quantizer NUR auf Co/Cg. q_c=1 -> identisch (lossless). int16->int16.'''\n\n# Konvenienz-Wrapper fuer Area 09 (lossless/near-lossless ohne Schicht B):\ndef encode_color(rgb, lam=0.0, mode='lossy', q_c=1, tile_size=16, seg_map=None) -> dict\ndef decode_color(blob: dict) -> np.ndarray   # -> rgb HxWx3 uint8",
      "depends_on": "- numpy, cv2 (vorhanden; verifiziert numpy 2.4.6 / cv2 4.13.0 / Python 3.14).\n- verfahren/common/metrics.py (psnr/ssim/bpp) - vorhanden, fuer Tests.\n- Area 02 (Segmentierung): liefert OPTIONAL seg_map (Quadtree-Blattgroessen) zur Region-Kopplung. HARTE Entkopplung: bei seg_map=None nutzt plan_chroma festes tile_size-Gitter -> Area 01 ist eigenstaendig testbar OHNE Area 02.\n- Area 07 (Rate-Control): liefert Skalar lam (Lagrange-lambda) fuer die RDO-Modusentscheidung. Fallback lam=0.0 -> reine Distortion-minimierende Wahl, ebenfalls eigenstaendig lauffaehig.\n- Area 03/04 (Transform/Quant): KONSUMENT der Residuen-Dicts; encode_chroma gibt int16-Residualbloecke, decode_chroma erwartet die dequantisierten zurueck.\n- Area 06/08 (Entropy/Bitstream): KONSUMENT von meta (mode_map uint8 + Koeffizienten).\nKeine zyklischen Abhaengigkeiten: Area 01 haengt von NICHTS in 02-09 zwingend ab (alle Kopplungen optional mit Fallback).",
      "impl_steps": "1. Schicht A implementieren: rgb_to_ycocg / ycocg_to_rgb mit EINEM astype(int16)-Cast, np.right_shift, Kanal-Slicing (kein Transpose). Inline-Assert in den Tests: ycocg_to_rgb(rgb_to_ycocg(x))==x.\n2. chroma_deadzone (Deadzone-/Uniform-Quantizer) + Inverse-Skalierung; q_c=1 als No-op-Pfad.\n3. Vektorisierte Tile-Statistiken: reshape (Hc//ts, ts, Wc//ts, ts) fuer ymean/cmean/cov/var pro Tile in EINEM Schwung (keine Pixel-Schleife). Ungerade Raender per cv2.copyMakeBorder(BORDER_REPLICATE) vorab padden, Padding-Info in meta.\n4. Pro-Tile/Kanal die 4 Modus-Kandidaten bewerten: D=SSD(rekonstruierte-Chroma vs Original), R approximiert (CfL: bits(res)+Koeff; PLANE: nur Koeff; SUB: bits(downsampled); KEEP: bits(voll)) via R~0.5*log2(1+var/q^2)*nsamples. argmin(D+lam*R) -> mode_map.\n5. a quantisieren (1/8-Schritte, clip [-4,4], 6 bit), DC-Offset (8 bit), Plane-Koeffizienten (3x3-Normalgleichung geschlossen, np.linalg.solve auf 3x3). ALLE Fits auf y_hat (closed-loop), nie auf Original-Y.\n6. encode_chroma: pro Region Residual gemaess Modus bilden; MODE_SUB nutzt cv2.resize INTER_AREA; Residuen-Dict + meta zurueck.\n7. decode_chroma: exakt spiegelbildlich, INTER_LINEAR-Upsample fuer SUB, Plane/CfL-Rekonstruktion. Sicherstellen: Encoder-Rekonstruktion == Decoder-Rekonstruktion (closed-loop-Konsistenztest).\n8. Wrapper encode_color/decode_color fuer Area 09 (lossless: nur Schicht A + q_c=1; lossy: + Schicht B).\n9. Benchmark-Skript: gegen baseline_results.json (JPEG/WebP) RD-Kurven plotten (bpp via grobem Entropie-Proxy bis Area 06 fertig).",
      "unit_tests": "Datei: verfahren/01_color_preprocess/test_color_transform.py (pytest-style, lauffaehig mit reinem assert).\nT1 roundtrip_lossless_random: 10M zufaellige uint8-RGB -> rgb_to_ycocg -> ycocg_to_rgb == Original (np.array_equal). VERIFIZIERT bestanden.\nT2 roundtrip_lossless_grid: volles R/G/B-Wuerfelgitter (B subsampled) bit-exakt. VERIFIZIERT.\nT3 roundtrip_kodak: alle verfahren/testdata/*.png exakt reversibel. VERIFIZIERT (True).\nT4 value_ranges: Y in [0,255], Co/Cg in [-255,255], dtype int16. VERIFIZIERT.\nT5 deadzone_lossless: chroma_deadzone(C,1) is identisch; q_c>1 monoton groesserer Fehler; Y-Kanal bei chroma-only-Deadzone unveraendert -> PSNR(q=1)=inf, q=2~37dB, q=3~42dB. VERIFIZIERT.\nT6 cfl_energy_reduction: auf jedem Kodak-Bild residual-AC-Energie/Chroma-AC-Energie < 0.75 (real 0.50-0.70). VERIFIZIERT.\nT7 chroma_codec_roundtrip_q1: encode_chroma+decode_chroma mit q_c=1 (alle Modi) rekonstruiert ycocg bit-exakt (closed-loop).\nT8 encoder_decoder_consistency: die im Encoder gebildete Chroma-Rekonstruktion == decode_chroma-Ausgabe (kein Drift), pixelweise gleich.\nT9 mode_selection_sanity: kuenstlich flache Chroma-Region -> MODE_PLANE/CFL; scharfe synthetische Farbkante (rot-auf-gruen) -> MODE_KEEP; glatter Verlauf -> MODE_SUB.\nT10 lambda_monotonie: groesseres lam -> nicht mehr Bits (mehr SUB/PLANE-Regionen), monoton fallende geschaetzte Rate.\nT11 odd_dimensions: H,W ungerade -> Padding-Roundtrip exakt (Crop zurueck auf HxW).\nT12 speed_regression: rgb_to_ycocg auf 512x768 < 20 ms (Messung 6 ms).\nT13 rd_vs_jpeg (Integrationstest, soft): bei vergleichbarem q_c liegt Chroma-Residualentropie unter aequivalentem JPEG-Chroma (Proxy bis Area 06).",
      "backup_ideas": "Aufgeschoben, nicht verworfen (Aktivierung wenn billig nachweisbar):\n- Per-Bild/Region gelernte 3x3 reversible KLT via Givens-Lifting (Ideen 11/15): bessere Energiekompaktion (Prototyp 98.7% vs 93.5% YCoCg), aber 3 Givens-Winkel Overhead + Faktorisierungs-Aufwand; erst sinnvoll wenn YCoCg-R nachweislich limitiert. Reversibilitaet via round()-Lifting bewahrbar.\n- 2x2-Chroma-PCA 'Co/Cg-Twist' + perzeptueller Chroma-Gain (Idee 4): analytische 2x2-Eigenzerlegung, 1 Winkel/Kachel; reizvoll fuer stark blau-gelb-dominierte Bilder, aber Mehrwert ueber CfL unsicher -> als optionales Pre-Rotate vor Schicht B nachruestbar.\n- Adaptive Cross-Channel-Transform-Auswahl pro Tile {YCoCg-R, G-Praediktion R-G/B-G, Identity} (Idee 8): stark fuer gemischte Foto+Screenshot/Grafik-Bilder; sinnvoll sobald Area 02 Grafik-Regionen klassifiziert. 1 Transform-ID-Byte/Tile.\n- 4:2:2-Modus + 4:0:0 (Tile-Mittelwert) als zusaetzliche Subsampling-Stufen (Ideen 2/9/17/19): erweitert MODE_SUB auf {422,420,400}; billig nachruestbar, aktuell auf 420 reduziert um Modusraum klein zu halten.\n- Guided/Joint-Bilateral Luma-gefuehrtes Chroma-Upsampling via cv2.boxFilter (Idee 18): ersetzt INTER_LINEAR-Upsample in MODE_SUB durch kantenscharfen Guided Filter -> weniger Farbsaum; drop-in Verbesserung des Decoders, deterministisch Encoder/Decoder-identisch.\n- Quadratischer CfL-Term g*Y^2 (Idee 12): fuer nichtlineare Luma-Chroma-Relation; nur falls affines a die Residualvarianz nicht ausreichend senkt.\n- Strided-pick-Schnellpfad statt INTER_AREA fuer Subsampling (Idee 6) bei sehr hoher Zielkompression (lam gesteuert)."
    },
    {
      "area": "02_segmentation_analysis — Segmentierung & Inhaltsklassifikation (Quadtree-Partition + Regionenklassifikation). Liefert Blockstruktur + Klassenkarte + Orientierungs-/Praediktor-Tags an 03_transform, 04_quantization, 06_context_entropy, 08_bitstream_format. Empfaengt Luma/Chroma (float32) aus 01_color_preprocess und lambda aus 07_rate_control.",
      "chosen_design": "EINE kohaerente Loesung: \"RDO-Lagrange-Quadtree mit Strukturtensor-Klassifikation auf Integralbild-Statistik\" (Kerne aus [1]/[3]/[15]/[19] + [5]/[13] + [4]/[10]/[17]/[20], plus PALETTE-Escape aus [8] und gelernte Schwellen aus [2]/[14]/[16]).\n\nPIPELINE (Encode-Seite, ein Kanal = Luma; Chroma analog mit eigenem lambda-Faktor):\n1) PRECOMPUTE (einmal pro Bild, voll vektorisiert, ~17ms/512x512 gemessen): Pad auf Vielfaches von max_bs=64 (cv2.copyMakeBorder, BORDER_REFLECT_101). Baue 5 Integralbilder (float64): S, S2 aus Y (Summe/Quadratsumme) und Sxx,Syy,Sxy aus den Sobel-Strukturtensor-Produktkarten gx*gx, gy*gy, gx*gy (gx=cv2.Sobel(Y,CV_32F,1,0,3), gy analog). Damit sind Varianz, Kantenenergie tr=l1+l2, Kohaerenz coh=(l1-l2)/(l1+l2+eps) und Orientierung theta=0.5*atan2(2*Jxy,Jxx-Jyy) JEDES Rechtecks per 4 Array-Zugriffen O(1) (verifiziert: Integral-Varianz == np.var, Abweichung 2.5e-11).\n2) BOTTOM-UP RD-PROXY je Kandidatblock auf Ebenen 8->16->32->64 (min_bs=8, max_bs=64). Pro Block werden OHNE Trial-Encoding geschlossene Surrogat-Kosten J=D+lambda*R fuer K Repraesentations-Klassen berechnet: (a) PLANE/SMOOTH: geschlossener Ebenen-Fit a+bx+cy ueber Integral-Momente (verifiziert: Residuum auf reiner Rampe ~1e-26), D=Restquadratsumme, R~const (3 Quant-Koeff). (b) DCT/DETAIL: batched cv2.dct (oder gecachte Cosinus-Matrix C@B@C.T), Deadzone-Quant bei q(lambda), D=sum((C-Cq)^2) per Parseval == Pixel-SSE OHNE Ruecktransform (verifiziert rel err 6e-7), R~count_nonzero(Cq)*bits + Laplace-Bitschaetzer. (c) EDGE/DIRECTIONAL: nur Kandidat wenn coh>=t_coh; D wie DCT aber mit Orientierungs-Rabatt (gerichtete Kante kostet weniger Koeffizienten), theta_sector (8 Bins) wird mitgegeben. (d) TEXTURE: nur wenn coh<t_coh und tr hoch; Straf-Rabatt da spaeter VQ guenstig (05). (e) PALETTE (Escape, [8]): wenn len(np.unique(block))<=K_pal (z.B. 16), exakte Lossless-Kosten R=ceil(log2(N))*flaeche, D=0 (verifiziert: searchsorted-Roundtrip bit-exakt) -> schlaegt Transform bei Text/Logo/Screenshot.\n3) LAGRANGE-MERGE (Standard Quadtree-Pruning, optimal innerhalb der Kandidatenmenge, Bruckstein/Shukla): J_x_best=min_r(D[x,r]+lambda*R[x,r]). Vier Geschwister werden zum Elternblock verschmolzen gdw. J_parent_best <= sum(J_child_best) + lambda*R_splitflag. Ein Bottom-up-Durchlauf. Dasselbe lambda wie 04/07 -> globale RD-Kohaerenz.\n4) FINALE KLASSIFIKATION je Blatt = argmin_r J (die Klasse, die J minimiert hat). Schwellen t_coh, t_flat, t_tex sind kein Hardcode, sondern ein offline (05) per k-means/GMM auf Kodak-Subset gefittetes Tripel, mit dem Codec als ~16x8-float-Tabelle ausgeliefert (kein Training zur Laufzeit). Ergebnis je Blatt: class_id + theta_sector (3 bit, nur EDGE) + pred_id-Slot (fuer near-lossless-Pfad [7]).\n\nAUSGANG: SegResult mit (a) leaves: strukturiertes np.array (N,6) [y,x,bs,class_id,theta_sector,aux], (b) dichte class_map (HxW uint8, Broadcast-Fill), (c) split_bits: Praeorder-Splitflag-Bitstring (1 bit/innerer Knoten) fuer bit-exakte Rekonstruktion in 08, (d) theta_map/aux fuer 03/06. Decoder rekonstruiert Partition allein aus split_bits + Bildgroesse (klassenkarte wird separat oder per Block-Header transportiert).\n\nKERN-DIFFERENZIERER zu JPEG/WebP/JPEG XL: Blockgrenze entsteht wo ein REPRAESENTATIONSWECHSEL (Plane<->DCT<->directional<->VQ<->Palette) RD-Gewinn bringt, nicht wo Varianz eine Schwelle ueberschreitet. Partition UND Klassenwahl minimieren GEMEINSAM dasselbe J-Funktional, das auch der Quantisierer nutzt. Strukturtensor-Kohaerenz trennt Kante (anisotrop) von Textur (isotrop) — eine Unterscheidung, die DCT-Codecs strukturell nicht treffen (beide hohe AC-Energie).",
      "rationale": "Warum diese Synthese aus den 20 Ideen: (1) Vier Ideen ([1][3][15][19]) konvergieren unabhaengig auf RDO-Lagrange-Quadtree statt Varianzschwelle — das ist das robuste Rueckgrat und der einzige Mechanismus, der die Partition nachweisbar an die Zielbitrate koppelt (gemeinsames lambda mit 04/07). Varianzschwellen ueber-splitten glatte Rampen massiv (hohe Varianz, aber 3-Koeff-Plane kodierbar) — genau das vermeidet der Plane-Residual-Proxy aus [18]/[11]. (2) [5]/[13]/[19] liefern die Integralbild-Engine, die RDO-Partition praktisch gratis macht (O(1)/Knoten, kein Trial-Encode); ohne sie waere RDO zu teuer fuer CPU-only. Messung bestaetigt 17ms Precompute. (3) Strukturtensor-Kohaerenz ([4][10][17][20]) ist der echte Inhaltsdifferenzierer: Messung zeigt coh=1.0 (Kante) vs 0.20 (Rauschtextur) bei aehnlicher Varianz — Varianz allein verwechselt beide. Die Orientierung theta faellt im selben Tensor gratis an und steuert direkt den directionalen Transform-Pfad in 03 (spart Kantenkoeffizienten). (4) PALETTE-Escape [8] ist billig (np.unique/searchsorted, verifiziert bit-exakt) und gewinnt dramatisch auf gemischten Screenshot/Foto-Inhalten, wo lossy-Codecs Ringing produzieren — eine opportunistische Achse fast gratis. (5) Gelernte Schwellen ([2][14][16]) statt Hardcode = \"leichtes Lernen\" als ausgelieferte float-Tabelle, deterministisch, kein GPU/Training zur Encode-Zeit. Die Parseval-Identitaet (D im DCT-Raum == Pixel-SSE, verifiziert) erlaubt die Distortion-Schaetzung ohne Ruecktransform und ist die zentrale Effizienz-Annahme.\n\nVerworfen/zurueckgestellt: Top-down-Varianzschwelle (RD-blind), reine handgesetzte Schwellbaeume ohne RD-Bindung, Superpixel/SLIC-Prior [12] (teuer, nicht-quadratische Bloecke brechen feste DCT-Groessen in 03), CSF-Perzeptualgewichtung [9] (orthogonal, spaeter als lambda-Modulation nachruestbar), GMM-EM [16] (k-means/Schwell-Tripel reicht und ist robuster). Diese stehen in backup_ideas.",
      "novelty_contribution": "Strukturell neu gegenueber allen Referenzcodecs (quellen 03 JPEG, 08 JPEG2000, 10 JPEG XL, 13 SPIHT, neuronale 01/02/07): (1) Partition + Klassenwahl + Quantisierung teilen EIN Lagrange-Funktional mit demselben lambda — bei JPEG gibt es keine Partition (starr 8x8), bei WebP/JPEG XL ist die Partition heuristisch bzw. waehlt eine Transform-Familie ohne Plane/VQ/Palette-Multi-Repraesentation, neuronale Codecs partitionieren gar nicht. (2) Der Quadtree-Split entsteht am Ort eines REPRAESENTATIONSWECHSELS (Plane<->DCT<->directional<->VQ<->Palette), nicht an einer Energie-/Varianzgrenze. (3) Strukturtensor-Kohaerenz als bitstream-relevante Codec-Steuergroesse (Kante vs isotrope Textur -> unterschiedliche Repraesentation) plus gratis Orientierungs-Tag theta — in keinem Mainstream-Codec als first-class Signal. (4) Lokale, quadtree-gebundene PALETTE-Klasse, die pro Region Palette gegen Transform RD-vergleicht und beide im selben Bild mischt (PNG/GIF haben nur GLOBALE Palette). (5) Integralbild-aggregierter Strukturtensor macht RDO-Partition CPU-only praktisch gratis (kein Trial-Encode, O(1)/Knoten). Die Klassenkarte ist gemeinsame Seiteninformation fuer 03 (Repraesentation), 04 (Quant-Profil), 06 (klassenbedingter Entropie-Kontext) — eine Mehrfachnutzung, die JPEG/WebP fehlt.",
      "interface": "Modul: verfahren/02_segmentation_analysis/segmentation.py (nur numpy + cv2). Konventionen wie common/metrics.py (float32 intern, deutsche Docstrings).\n\n# ---- Datentypen ----\n# Klassen-Enum (uint8): SMOOTH=0, DCT=1, EDGE=2, TEXTURE=3, PALETTE=4\n# leaves: structured ndarray dtype = [('y','i4'),('x','i4'),('bs','i4'),('cls','u1'),('theta','u1'),('aux','i4')]\n#   y,x = Position im (gepaddeten) Bild; bs in {8,16,32,64}; cls=Klasse; theta=Orientierungs-Sektor 0..7 (nur EDGE, sonst 0); aux=Reserve (z.B. Palettengroesse N).\n\nclass SegConfig:\n    min_bs:int=8; max_bs:int=64\n    chroma_lambda_scale:float=1.0\n    k_pal:int=16                 # max Farben fuer PALETTE-Escape\n    bits_per_coeff:float=4.0     # Raten-Proxy fuer DCT-Koeff\n    # gelernte Schwellen (aus 05, sonst Defaults):\n    t_coh:float=0.45; t_flat:float=12.0; t_tex_energy:float=2.0e4\n    quant_step:float=16.0        # q fuer DCT-Proxy (von 04 gesetzt)\n\nclass SegResult:\n    leaves: np.ndarray           # structured (N,) s.o.\n    class_map: np.ndarray        # (H,W) uint8, dichte Klassenkarte\n    theta_map: np.ndarray        # (H,W) uint8, Orientierungssektor je Pixel (0 ausser EDGE)\n    split_bits: np.ndarray       # (M,) uint8 0/1, Praeorder-Splitflags fuer 08\n    pad_hw: tuple                # (H_pad, W_pad) fuer exakte Rekonstruktion\n    orig_hw: tuple               # (H, W) Originalgroesse\n\n# ---- Haupt-API (Encode) ----\ndef precompute_stats(Y: np.ndarray) -> dict:\n    \"\"\"Y: float32 (H,W) gepaddet. -> dict mit S,S2,Sxx,Syy,Sxy (float64 Integralbilder (H+1,W+1)).\"\"\"\n\ndef block_features(stats: dict, y:int, x:int, bs:int) -> tuple:\n    \"\"\"O(1)-Lookup. -> (mean, var, energy_tr, coherence, theta_rad). Reine Indexarithmetik.\"\"\"\n\ndef block_rd_costs(Y: np.ndarray, stats: dict, y:int, x:int, bs:int, lam:float, cfg: SegConfig) -> np.ndarray:\n    \"\"\"-> ndarray (5,) = [J_SMOOTH, J_DCT, J_EDGE, J_TEXTURE, J_PALETTE]; inf wo Klasse nicht anwendbar.\n       J = D + lam*R, geschlossen (Plane-Momente, Parseval-DCT, unique-Palette). Kein Trial-Encode.\"\"\"\n\ndef segment_channel(Y: np.ndarray, lam: float, cfg: SegConfig = SegConfig()) -> SegResult:\n    \"\"\"Hauptfunktion Encode. Y: float32 (H,W) (vor Pad). lam: Lagrange aus 07.\n       Bottom-up Lagrange-Merge ueber Ebenen min_bs..max_bs. -> SegResult.\"\"\"\n\ndef segment_image(channels: dict, lam: float, cfg: SegConfig = SegConfig()) -> dict:\n    \"\"\"channels: {'Y':f32 (H,W), 'Co':..., 'Cg':...}. -> {'Y':SegResult, 'Co':..., 'Cg':...}.\n       Chroma nutzt lam*cfg.chroma_lambda_scale.\"\"\"\n\n# ---- Decode-Seite (Rekonstruktion der Partition aus Bitstream) ----\ndef decode_partition(split_bits: np.ndarray, orig_hw: tuple, cfg: SegConfig) -> np.ndarray:\n    \"\"\"Rekonstruiert leaves (y,x,bs) bit-exakt aus Praeorder-Splitflags. cls/theta kommen aus Block-Headern (08).\n       -> structured leaves ohne cls/theta gefuellt.\"\"\"\n\n# ---- Serialisierung-Helper fuer 08 ----\ndef encode_split_bits(result: SegResult) -> bytes:  # gepackte Bits\ndef decode_split_bits(buf: bytes, n_nodes:int) -> np.ndarray",
      "depends_on": "EINGANG (wird konsumiert):\n- 01_color_preprocess: liefert channels-dict {'Y','Co','Cg'} als float32 (H,W), Wertebereich ~0..255 (Luma) bzw. zentriert (Chroma). Annahme: BT.601-aehnliche/YCoCg-Trennung; segment_channel ist kanal-agnostisch.\n- 07_rate_control: liefert lambda (Lagrange-Multiplikator) und optional quant_step q. lambda steuert direkt die Partitionsgranularitaet (hoch -> grobere Bloecke).\n- 05_dictionary_learning: liefert offline gefittete Schwellen (t_coh, t_flat, t_tex_energy) als kleine float-Tabelle (in SegConfig injiziert). Optional; Defaults vorhanden.\n- 04_quantization: liefert quant_step pro Kanal/Klasse fuer den DCT-RD-Proxy (Konsistenz mit echter Quantisierung).\n\nAUSGANG (wird produziert fuer):\n- 03_transform: leaves (Block-Liste mit Groesse+Klasse) bestimmt PRO Block den Transformpfad (SMOOTH->Plane, DCT/DETAIL->DCT, EDGE->directional+theta_sector, TEXTURE->VQ, PALETTE->Index). theta_map gibt directionale Achse.\n- 04_quantization: class_map waehlt klassenabhaengiges Quant-Profil.\n- 06_context_entropy: class_map + theta_map als Entropie-KONTEXT (klassenbedingte Wahrscheinlichkeitstabellen).\n- 08_bitstream_format: split_bits (Praeorder-Splitflags) + class_id/theta je Blatt zur bit-exakten Rekonstruktion der Partition.\n\nVERTRAG: Partition ist allein aus split_bits + orig_hw rekonstruierbar (Decoder braucht das Bild nicht). lambda MUSS dasselbe sein wie in 04/07 (sonst RD-Inkohaerenz). KEINE zirkulaere Abhaengigkeit: 02 ruft 03/04 nicht auf, nutzt nur deren q-Parameter als Skalar.",
      "impl_steps": "1. Skeleton: verfahren/02_segmentation_analysis/segmentation.py + __init__.py. SegConfig/SegResult als dataclasses. Klassen-Enum als Modulkonstanten. Import nur numpy, cv2.\n2. Padding-Helper: pad_to_multiple(Y, max_bs) via cv2.copyMakeBorder(BORDER_REFLECT_101); merke (H,W) und (H_pad,W_pad).\n3. precompute_stats: S=cv2.integral(Y.astype(f64)), S2=cv2.integral(Y*Y); gx/gy=cv2.Sobel(CV_32F,ksize=3); Sxx/Syy/Sxy=cv2.integral der drei Produktkarten (f64). Rueckgabe-dict.\n4. _box(I,y,x,bs) Inline-Helper: I[y+bs,x+bs]-I[y,x+bs]-I[y+bs,x]+I[y,x]. block_features: mean/var aus S,S2; Jxx=Sxx-box/n etc.; geschlossene Eigenwerte l1,l2; coh, tr, theta.\n5. _plane_cost: geschlossener Ebenen-Fit ueber Integral-Momente (x,y-Gitter pro bs gecacht); D=var_residual*n; R=3*bits_plane. (Verifiziert gegen lstsq.)\n6. _dct_cost: gecachte Cosinus-Matrix C[bs] (oder cv2.dct fuer bs<=32, sonst Matrixform); Cq=round(C/q)*q; D=sum((C-Cq)^2) (Parseval); R=count_nonzero(Cq)*bits_per_coeff + Laplace-Bitschaetzer log2(1+|Cq/q|).\n7. _edge_cost: nur wenn coh>=t_coh; = _dct_cost mit Orientierungs-Rabatt (z.B. R*=0.7) + 3 bit theta. _texture_cost: nur wenn coh<t_coh und tr>t_tex_energy; R-Rabatt (VQ-Proxy). _palette_cost: N=len(np.unique(block_uint8)); wenn N<=k_pal: R=ceil(log2(max(N,2)))*bs*bs, D=0; sonst inf.\n8. block_rd_costs: ruft 5 Cost-Funktionen, gibt (5,)-Vektor mit inf wo nicht anwendbar.\n9. Bottom-up-Merge: Baue Blatt-Gitter min_bs. Fuer jede Ebene L (min_bs..max_bs/2): pro 2x2-Quadrupel J_parent_best vs sum(child J_best)+lam*R_split; bei Merge ersetze 4 Knoten durch Elternknoten (Liste/dict je Ebene). Sammle finale Blaetter + erzeuge Praeorder-split_bits durch Tiefendurchlauf des resultierenden Baums.\n10. Fill class_map/theta_map per Broadcast (class_map[y:y+bs,x:x+bs]=cls). Crop auf orig_hw fuer die dichten Karten; leaves bleiben im Pad-Koordinatensystem (mit pad_hw dokumentiert).\n11. segment_channel orchestriert 2-10; segment_image ruft pro Kanal mit skaliertem lam.\n12. decode_partition/encode_split_bits/decode_split_bits: Praeorder-Rekursion; np.packbits/unpackbits fuer Bit-Packing.\n13. Vektorisierungs-Pass (Optimierung, nach Korrektheit): block_features und _plane/_dct-Kosten fuer ALLE Bloecke einer Ebene gleichzeitig per Slicing/np.matmul-Batch statt Python-Schleife (Idee [6]/[20]). Korrektheit zuerst per Schleife, dann Batch mit Aequivalenztest.\n14. Mini-Trainingsskript fit_thresholds.py (gehoert konzeptuell zu 05): k-means auf [var,coh,energy]-Features eines Kodak-Subsets -> t_coh/t_flat/t_tex_energy als JSON; von SegConfig ladbar.",
      "unit_tests": "test_segmentation.py (pytest, nutzt verfahren/testdata/kodim*.png + synthetische Bilder):\n\nKORREKTHEIT / MATHE:\n1. test_integral_variance_exact: zufaelliges f64-Bild, box_var via Integral == np.var fuer 5 Rechtecke, atol 1e-6. (Bereits verifiziert: 2.5e-11.)\n2. test_plane_fit_ramp: reine x-Rampe -> _plane_cost D ~ 0 (atol 1e-3); reines Rauschen -> D gross.\n3. test_parseval_dct: _dct_cost D == sum((B-idct(Cq))^2) (Parseval), rel err < 1e-4. (Verifiziert 6e-7.)\n4. test_palette_roundtrip: Block mit <=K distinkten Werten -> pal[searchsorted(pal,B)]==B bit-exakt; N>K -> _palette_cost==inf.\n\nKLASSIFIKATION:\n5. test_coherence_separates_edge_texture: synthetische vertikale Kante -> coh>0.9 & cls in {EDGE}; isotropes Rauschen gleicher Varianz -> coh<0.3 & cls in {TEXTURE,DCT}. (Verifiziert: 1.0 vs 0.20.)\n6. test_theta_orientation: vertikale Kante -> theta_sector entspricht ~0 deg; diagonale Kante -> Sektor ~45 deg (Toleranz 1 Sektor).\n7. test_smooth_flat: konstanter Block -> cls==SMOOTH, var==0.\n8. test_palette_class_on_synthetic_logo: Bild aus 8 Farben (Logo-artig) -> ueberwiegend cls==PALETTE.\n\nRDO-VERHALTEN (Kernkontrakt):\n9. test_lambda_monotone_blockcount: segment auf kodim05 mit lam in [klein,mittel,gross] -> Anzahl Blaetter MONOTON fallend mit lam (grobe Partition bei hoher Bitrate-Strafe). (Idee [3] berichtete 3579->3354->3225.)\n10. test_merge_never_worse: fuer Stichprobe Quadrupel gilt nach Merge J_parent <= sum(J_children)+lam*R_split (Lagrange-Bedingung erfuellt).\n11. test_smooth_gradient_not_oversplit: groSse Helligkeitsrampe (1 Objekt) -> wird zu wenigen grossen SMOOTH-Bloecken, NICHT zu vielen 8er-Bloecken (Kontrast zu Varianzschwelle).\n\nSTRUKTUR / ROUNDTRIP:\n12. test_partition_bit_exact_roundtrip: segment_channel -> encode_split_bits -> decode_split_bits -> decode_partition rekonstruiert IDENTISCHE (y,x,bs)-Menge wie das Original-leaves.\n13. test_class_map_covers_image: class_map hat orig_hw, keine ungesetzten Pixel (alle in {0..4}); Summe der Blockflaechen == H_pad*W_pad.\n14. test_blocksizes_valid: alle bs in {8,16,32,64}, alle Bloecke innerhalb pad_hw, keine Ueberlappung (per Belegungs-Maske).\n\nPERFORMANCE (Smoke):\n15. test_speed_512: segment_channel auf 512x512 < 500ms (CPU); precompute_stats < 50ms. (Precompute verifiziert 17ms.)\n\nINTEGRATION (Stub):\n16. test_segment_image_all_channels: dict mit Y/Co/Cg -> SegResult pro Kanal; Chroma nutzt skaliertes lambda (weniger Blaetter bei chroma_lambda_scale>1).",
      "backup_ideas": "Zuruckgestellt (orthogonal nachruestbar oder dominiert):\n- [9] CSF/Luminanz-Maskierung: perzeptuelle Split-Gewichtung. NICHT in der RD-Kernschleife, sondern spaeter als lambda-Modulation pro Block (lambda_local = lambda * w_lum*w_tex) einfuegbar — saubere Erweiterung ohne Architekturbruch. Stark fuer bpp/SSIM, aber erst nach validierter RD-Basis.\n- [12] SLIC-Superpixel-Prior: objekt-/konturtreue Partition. Zurueckgestellt, weil nicht-quadratische Regionen die festen DCT-Blockgroessen in 03 brechen und SLIC teurer ist; als optionaler Prior fuer die Merge-Entscheidung (block_is_pure) nachruestbar.\n- [16] GMM/EM-Klassifikation + MDL: maechtiger als k-means-Schwellen, aber komplexer und instabiler beim Fit. k-means-Tripel ([2][14]) reicht; GMM als Drop-in-Ersatz der Schwellenlogik moeglich, wenn Klassen-Trennung unscharf.\n- [7] FlatTree-PredTag (Lossless-Praediktor-Residualentropie als Split-Kriterium): eigener (near-)lossless-Modus. Der pred_id-Slot in leaves ist bereits reserviert; ein zweites Kostenmodell (MED/JPEG-LS-Praediktor-Bits statt DCT) aktiviert den verlustfreien Pfad parallel — starke near-lossless/Graphik-Achse, aber separater Encode-Modus.\n- [6] vektorisierte Statistik-Pyramide + Boolean-Merge: reine Geschwindigkeitsoptimierung des Bottom-up-Schritts (Impl-Schritt 13). Funktional aequivalent zur Schleifenversion; als Performance-Pass nach Korrektheit.\n- [2] quantisierte Richtungsannotation als 16er-Codebook (statt nur Schwellen): feinere theta-Quantisierung (16 statt 8 Sektoren) wenn 03 mehr directionale Achsen unterstuetzt.\n- [11]/[18] explizite anisotrope Richtungsenergie aus kausalen Diffs (d0/d45/d90/d135) als zusaetzliches Split-Feature: redundant mit Strukturtensor-coh, aber billiger; als Fallback wenn Sobel-Tensor zu grob.\n- Reines Top-down-Varianz-Quadtree [13] als schnelle Baseline/Vergleichsimplementierung fuer 10_evaluation_tests (Ablation: RDO vs Varianzschwelle auf derselben RD-Kurve)."
    },
    {
      "area": "03_transform — Pro-Klasse-Repraesentation/Transform (Block -> Koeffizienten/Tokens + exakte Inverse)",
      "chosen_design": "MARC-T: eine klassen-geroutete Transform-Bank mit EINHEITLICHEM Token-Schema. Bereich 02 liefert pro Quadtree-Block (Groessen 8/16/32) ein Klassenlabel; 03 waehlt deterministisch (KEINE Such-Signalisierung noetig, Decoder kennt dieselbe Klassenkarte) die Repraesentation. Vier Pfade, alle batched ueber Bloecke gleicher (Groesse,Pfad) via einer einzigen Matrixformel:\n\nPFAD A = PLANE (Klasse 'glatt'): affine Ebene B_hat=a+b*x_n+c*y_n via geschlossener, gitter-invarianter LSQ-Projektion P=pinv([1,x_n,y_n]) (pro Groesse EINMAL gecacht; zentriertes Gitter -> Normalgleichungen diagonal). Residuum R=B-Plane wird per Batch-DCT transformiert; nur die K oberen-linken Koeffizienten behalten (K aus RDO/Bereich 04), Rest=0. Token=(a,b,c quantisiert + bis zu K Restkoeffizienten). VERIFIZIERT: auf Gradientenblock sinkt Residuum-std von 20.7 auf 1.6, exakte Rekonstruktion (err=0). Das ist der Headline-Differenzierer: parametrische Ebene statt Spektrum auf grossen Bloecken -> kein DC-Blockbanding.\n\nPFAD B = DCT (Klasse 'Kante'/'Detail', Default-/Fallback-Pfad): separable orthonormale DCT-II als Batch-GEMM C=D@B@D.T (D pro Groesse gecacht). VERIFIZIERT bit-identisch zu cv2.dct (err 3.5e-15) UND ~5x schneller im Batch (0.0044s vs 0.0222s/4000 Bloecke). Inverse D.T@C@D.\n\nPFAD C = KLT (optional, Klasse mit gelernter Basis aus Bereich 05): separable Pro-Klasse-PCA-Basis U,V statt D; identische Batch-Mechanik T=U.T@(B-mean)@V. 1-Bit RDO-Flag KLT-vs-DCT pro Block als Fallback-Sicherung. Faellt auf DCT zurueck wenn Basis fehlt.\n\nPFAD D = VQ-TOKEN (optional, Klasse 'Textur', aus Bereich 05): Gain-Shape-VQ auf plane-entferntem Residuum -> Token=(plane a,b,c, gain g, shape_idx). NN-Suche = argmax(Shapes@Codebook.T). Nur aktiv wenn Codebook geladen; sonst Textur -> DCT.\n\nAlle Pfade emittieren EIN gemeinsames Token-Dataclass mit Feld 'kind' und einem float32-Koeff-Array, sodass 04/06 uniform weiterarbeiten. Directionale/rotierte Transform-Pfade (Shear/remap) gehen NICHT in den MVP (siehe rationale/backup).",
      "rationale": "Empirisch verifiziert in dieser Umgebung (Python 3.14, NumPy 2.4.6, cv2 4.13):\n\n1) Die DCT-Matrixformel ist bit-identisch zu cv2.dct (max err 3.6e-15) und als Batch-einsum ~5x schneller -> Idee[5] ist ein kostenloser Speed-Gewinn ohne Ratenaenderung. Batching ist nur moeglich, weil 02 ohnehin nach Groesse/Klasse gruppiert.\n\n2) Plane-Fit ist die robusteste, hoechstwirksame Idee: geschlossene LSQ (Ideen 2/4/6/13/18), exakte Rekonstruktion (err=0), Residuum-Energie auf glatten Bloecken um >10x reduziert. Grosse glatte Flaechen (Himmel/Haut/Hintergrund) dominieren die Pixelzahl -> 3..wenige Zahlen pro 32x32-Block statt Dutzende Koeffizienten = direkter bpp-Gewinn UND kein Banding (hoeheres SSIM bei niedriger Rate). Das ist zugleich der geforderte Struktur-Differenzierer 'je Region andere Repraesentation' in der risikoaermsten Form.\n\n3) KRITISCHER MESSBEFUND gegen die directionalen Ideen [1,3,9,11,14,15,17,19] im MVP: Ich habe die naiven Integer-Operationen getestet. Plain-DCT kompaktiert einen orientierten Ramp bereits auf 2 Koeffizienten und eine 30-Grad-Anti-Alias-Kante auf 4 (95/90% Energie). Der naive np.roll-Shear VERSCHLECHTERT das (2->14) und auch die symmetrische Anti-Diagonal-Praediktion verschlechterte einen 45-Grad-Block (21->35 Koeff), weil zyklischer Wrap bzw. fehlender Nachbarkontext neue Diskontinuitaeten erzeugt. Echte Gewinne erfordern nicht-zyklisches Resampling mit echten Nachbarpixeln (cv2.remap, float, Randbehandlung) und sind dann nicht mehr block-lokal exakt invertierbar - hohes Integrationsrisiko fuer unsicheren Gewinn. Deshalb: directional NUR als RDO-gegated Erweiterung nach dem MVP, damit es nie schaden kann. Diese ehrliche Einordnung deckt sich mit der Spec-Vorgabe 'kein Over-Promising'.\n\n4) Die VQ/parametrische-Textur-Ideen [12,16,18,20] sind wertvoll, haengen aber an Bereich 05 (Offline-Training) -> als optionale, sauber abschaltbare Pfade gebaut, damit der MVP ohne sie lauffaehig bleibt.",
      "novelty_contribution": "Kern-Differenzierer von MARC realisiert in 03: PRO REGION eine ANDERE Repraesentation, deterministisch aus der Klassenkarte geroutet (0 Signalisierungsbits fuer die Pfadwahl, da Decoder dieselbe Karte hat) - strukturell verschieden von JPEG/WebP (EIN globaler 8x8-DCT-Pfad) und von JPEG XL (variable Bloecke, aber EIN Transform-Paradigma). Konkret neu: (a) parametrische affine Ebene mit geschlossener gitter-invarianter LSQ auf grossen Quadtree-Bloecken als eigener Pfad NEBEN exakten DCT-Bloecken (statt glatte Verlaeufe via DC+AC zu treppen -> bandingfrei); (b) Residuum-DCT nur fuer den Ebenen-Rest (Transform auf das, was die parametrische Repraesentation uebrig laesst); (c) klassengeroutete KLT-Bank, die EINE feste Transform pro Klasse statt global nutzt; (d) Gain-Shape-VQ auf plane-/gain-normiertem Residuum, sodass ein winziges Codebook helligkeits-/neigungsinvariant generalisiert. Bewusst KEIN trainiertes NN, alles deterministisch und interpretierbar (lineare Algebra + NumPy/cv2).",
      "interface": "Datei: verfahren/03_transform/transform.py (reines NumPy + cv2). Alle Arrays float32, DC-zentriert NICHT vorausgesetzt (Pfade managen mean selbst).\n\n# --- Konstanten/Enums ---\nKIND_PLANE=0; KIND_DCT=1; KIND_KLT=2; KIND_VQ=3\nSUPPORTED_SIZES=(8,16,32)\n\n# --- Token (uniform, dataclass) ---\n@dataclass\nclass BlockToken:\n    kind:int                 # KIND_*\n    size:int                 # 8|16|32 (Block-Kantenlaenge N)\n    coeffs:np.ndarray        # float32; PLANE/DCT/KLT: shape(N,N) (PLANE: Ebene-Params in coeffs[0,0:3], Rest=Residuum-Koeff); VQ: leer shape(0,)\n    params:np.ndarray        # float32 1D Seiteninfo: PLANE->[a,b,c]; VQ->[a,b,c,gain]; DCT->[] ; KLT->[]\n    side:int                 # Seiteninfo-Index: KLT-Basis-Id / VQ-shape_idx / DCT->0\n\n# --- Encoder-Kernfunktionen (batched, eine pro Pfad) ---\ndef make_dct_matrix(N:int)->np.ndarray   # (N,N) float64 orthonormale DCT-II; gecacht\ndef plane_projection(N:int)->tuple[np.ndarray,np.ndarray]  # (A:(N*N,3), P:(3,N*N)); gecacht\n\ndef forward_plane(blocks:np.ndarray)->tuple[np.ndarray,np.ndarray]:\n    # blocks:(M,N,N) f32 -> (params:(M,3) [a,b,c] f32, residual:(M,N,N) f32 = blocks-plane)\ndef forward_dct(blocks:np.ndarray)->np.ndarray:\n    # (M,N,N) f32 -> coeffs (M,N,N) f32  (C=D@B@D.T via einsum)\ndef forward_klt(blocks:np.ndarray, U:np.ndarray, V:np.ndarray)->tuple[np.ndarray,np.ndarray]:\n    # -> (means:(M,) f32, coeffs:(M,N,N) f32)\ndef forward_vq(residual:np.ndarray, codebook:np.ndarray)->tuple[np.ndarray,np.ndarray]:\n    # residual:(M,N,N), codebook:(K,N*N) L2-normiert -> (gain:(M,) f32, idx:(M,) int32)\n\n# --- Top-level Routing (vom Integrationscode 09 genutzt) ---\ndef transform_blocks(blocks_by_group:dict[tuple[int,int],np.ndarray],\n                     class_by_group:dict[tuple[int,int],int],\n                     models:'Models|None'=None,\n                     rdo:'RDOConfig|None'=None) -> list[BlockToken]:\n    # blocks_by_group: key=(size,group_id) -> (M,N,N) f32; class_by_group: key->KIND_* default\n    # Liefert flache Liste BlockToken in stabiler Reihenfolge.\n\n# --- Inverse (Decoder) ---\ndef inverse_dct(coeffs:np.ndarray)->np.ndarray            # (M,N,N)->(M,N,N)\ndef inverse_plane(params:np.ndarray, residual:np.ndarray, N:int)->np.ndarray  # ->(M,N,N)\ndef inverse_klt(means, coeffs, U, V)->np.ndarray\ndef inverse_vq(params, idx, codebook, N)->np.ndarray\ndef reconstruct_blocks(tokens:list[BlockToken], models:'Models|None'=None)->dict[tuple[int,int],np.ndarray]\n    # Spiegel von transform_blocks: Tokens -> rekonstruierte Bloecke (M,N,N) je Gruppe.\n\n# Models: schlanker Container {klt:dict[class->(U,V)], vq:dict[(class,N)->codebook]}; None erlaubt (MVP).\n# RDOConfig: {keep_k:dict[size->int]|None, allow_klt:bool, allow_vq:bool}.\n# WICHTIG: 03 quantisiert NICHT (das ist 04). forward_* liefert float-Koeffizienten;\n# 04 quantisiert coeffs/params, 03.inverse_* nimmt dequantisierte Werte entgegen.",
      "depends_on": "EINGANG von Bereich 02 (segmentation_analysis): pro Quadtree-Block N in {8,16,32} + Klassenlabel (glatt/Kante/Textur/Detail). Erwartete Datenstruktur: Bloecke nach (Groesse,Klasse) gruppiert als (M,N,N)-float32-Arrays + eine deterministische Klassenkarte, die der Decoder rekonstruiert (Routing-Vertrag: KEINE separate Pfad-Signalisierung). MUSS mit 02 gelockt werden: Mapping Klasse->KIND_* (Default: glatt->PLANE, Kante/Detail->DCT, Textur->VQ-falls-Modell-sonst-DCT).\nAUSGANG an Bereich 04 (quantization): float32-Koeffizienten/params je Token; 04 liefert dequantisierte Werte zurueck an 03.inverse_*. Trennung: 03 transformiert, 04 quantisiert - keine Quantisierung in 03.\nMODELLE von Bereich 05 (dictionary_learning): optionale KLT-Basen (U,V je Klasse/Groesse, separabel) und Gain-Shape-VQ-Codebooks (L2-normiert, K Atome). 03 MUSS ohne diese lauffaehig sein (PLANE+DCT-MVP).\nNACHGELAGERT 06 (context_entropy): nutzt token.kind + Koeff-Statistik fuer Kontextwahl - 03 garantiert stabile Token-Reihenfolge. 09 verdrahtet, 07 setzt keep_k/RDO via 04.",
      "impl_steps": "1) Caches: make_dct_matrix(N) und plane_projection(N) mit functools.lru_cache fuer N in (8,16,32). Gitter zentriert auf [-1,1] -> Normalgleichungen diagonal (a=mean, b,c je eine vektorisierte Reduktion); pinv als robuster Default.\n2) forward_dct/inverse_dct: einsum('ij,bjk,lk->bil', D, B, D.T) in float64 intern, Ausgabe float32. Unit-getestet gegen cv2.dct.\n3) forward_plane/inverse_plane: params=(P@flat.T).T; plane=(params@A.T).reshape(M,N,N); residual=blocks-plane. Inverse exakt: plane+residual.\n4) Residuum-Truncation-Helper keep_topleft(coeffs,K): Maske der K obersten-linken Zickzack-Positionen -> 0-Rest (eigentliche Wahl von K via 04/07; 03 stellt nur die Maske bereit).\n5) forward_klt/inverse_klt: U,V aus 05; mean abspalten; T=U.T@(B-mean)@V batched per einsum; Inverse U@T@V.T+mean.\n6) forward_vq/inverse_vq: plane abspalten (Pfad A reuse), gain=||res||, shape=res/gain; idx=argmax(shapes_flat@codebook.T); Rekon gain*codebook[idx].\n7) transform_blocks: iteriere Gruppen, route per class_by_group auf den Pfad; bei fehlendem Modell Fallback DCT; baue BlockTokens mit stabiler (size,group_id)-Sortierung. reconstruct_blocks spiegelbildlich.\n8) Robustheit: leere Gruppen, nicht unterstuetzte Groessen -> klare ValueError; float32-Konsistenz; alle Operationen vektorisiert (keine Python-Schleife ueber einzelne Bloecke).\n9) Defensive Numerik: gain==0 (flacher Block) -> shape=0, idx=0; pinv statt solve fuer Singularitaetssicherheit.\n10) Erst NACH gruenem MVP (PLANE+DCT): KLT/VQ-Pfade aktivieren; directionale Pfade nur als separates, RDO-gegated Modul (siehe backup).",
      "unit_tests": "tests/test_transform.py (pytest, nur numpy/cv2):\nT1 dct_matrix_orthonormal: |D@D.T - I| < 1e-12 fuer N in (8,16,32).\nT2 dct_matches_cv2: random (50,N,N); |forward_dct - cv2.dct je Block| < 1e-5.\nT3 dct_roundtrip: inverse_dct(forward_dct(B)) == B (atol 1e-4).\nT4 plane_exact_on_affine: B=a+b*x+c*y exakt -> residual ~ 0 (max abs < 1e-4) und recovered (a,b,c)~ Ground-Truth.\nT5 plane_roundtrip: inverse_plane(params, residual) == B (atol 1e-4) fuer random Bloecke.\nT6 plane_reduces_energy: auf Gradient+Rauschen-Block ist residual.std < 0.15*blocks.std (verifiziert ~0.08).\nT7 plane_batched_equiv: Batch-Ergebnis == Schleifenergebnis je Block (atol 1e-5).\nT8 klt_roundtrip: mit zufaelliger orthonormaler U,V ist inverse_klt(forward_klt(B)) == B (atol 1e-4).\nT9 vq_roundtrip_normalized: codebook = orthonormalisierte Zeilen; residual exakt als gain*atom konstruiert -> idx trifft das Atom, Rekon err < 1e-4.\nT10 routing_no_models: transform_blocks mit models=None routet Textur->DCT (Fallback) und reconstruct_blocks roundtrip (atol 1e-4) ueber gemischte Gruppen/Groessen.\nT11 token_order_stable: zweimaliger Aufruf liefert identische Token-Reihenfolge (Determinismus-Vertrag fuer 06).\nT12 unsupported_size_raises: N=7 -> ValueError.\nT13 speed_smoke (nicht-bindend): batched DCT 4000 Bloecke < 50ms (Regressionswaechter, in dieser Umgebung 4.4ms gemessen).\nIntegrationsstub: gegen ein 64x64-Schachbrett+Gradient-Bild Bloecke aus echtem 02-Mock fuettern, E2E reconstruct -> PSNR(rekonstruiert ohne Quant, original) = inf.",
      "backup_ideas": "NACH dem MVP, in Prioritaetsreihenfolge:\n\n1) KLT-Bank (Idee[20]) und Gain-Shape-VQ-Textur (Ideen[12,16,18]) - schon im Interface vorgesehen, brauchen nur Bereich-05-Modelle. Geringes Risiko, klarer Ratengewinn auf Textur/spezialisierten Klassen.\n\n2) Directionale Transforms als RDO-gegate Erweiterung fuer die Kanten-Klasse - NUR aktivieren wenn RDO-Score besser als plain DCT, sodass nie Schaden entsteht. Reihenfolge nach Risiko: (a) Idee[1] directional-Lifting-Predict (exakt invertierbar via Predict/Update) als reversible Pre-Praediktion; (b) Idee[7] klassengesteuerte MED/GAP-Praediktorbank fuer den verlustfreien Pfad (0 Signalisierungsbits, gut fuer lossless-Achse); (c) Ideen[3,9,15,19] remap-/rotation-basierte DDCT mit cv2.remap und 2px-Ueberlapp - hoeheres Risiko (float, Randbehandlung, nicht block-lokal exakt invertierbar), nur mit Ueberlapp-Mittelung. ACHTUNG: meine Messung zeigt, dass naive np.roll-Shear (Ideen[14,17]) und symmetrische Anti-Diagonal-Praediktion die Kompaktion VERSCHLECHTERN koennen (zyklischer Wrap / fehlender Nachbarkontext) - daher zwingend mit echtem Nachbarkontext + nicht-zyklischem Resampling implementieren und immer RDO-gegated.\n\n3) Idee[8] reversibler 5/3-LeGall-Integer-Lifting-Refinement-Layer auf dem Lossy-Residuum (klassen-gegated) fuer near-lossless/lossless-Skalierbarkeit - eigener optionaler Layer, beruehrt das MVP-Interface nicht.\n\n4) Idee[10] CSF-gewichtete perzeptuelle Quant-Matrix + Aktivitaets-Masking - gehoert primaer in Bereich 04, aber 03 kann pro Klasse eine Basis-Gewichtsmatrix W bereitstellen (Kante: HF-tolerant entlang Kante; glatt: aggressiv lowpass).\n\n5) Idee[11] echte Ridgelet/Slant (1D-DCT entlang Bruchlinie) - nur falls remap-DDCT unzureichend; hoechster Implementierungsaufwand, daher zuletzt."
    },
    {
      "area": "04_quantization — RDO-Quantisierung (skalar + Vektor) für MARC",
      "chosen_design": "EIN einheitlicher, region-adaptiver RD-Quantisierer mit zwei Pfaden, der pro Block (gesteuert durch die Klassenkarte aus 02 und λ aus 07) skalar ODER vektoriell quantisiert, voll vektorisiert in reinem NumPy.\n\nKERN-IDEE (Synthese): \"Je Region andere Repräsentation\" auf Quant-Ebene = skalarer RDOQ-Pfad für glatt/Kante/Detail, Gain-Shape-RD-VQ-Pfad für Textur, beide an dasselbe Entropiemodell (06) und dasselbe λ (07) gekoppelt, plus datengelernte Rekonstruktion (05).\n\nA) SKALARER PFAD (Ideen 1+3+6+13+15+17 verschmolzen, ohne echten Viterbi):\n  1. Klassen-/bandabhängige Quant-Matrix Q[klasse] (B*B) aus 05, optional perzeptuell skaliert (CSF·Aktivitäts·Luminanz-Maskierung pro Block, Idee 9) zu effektivem step[block,pos].\n  2. Vektorisierte 2-Kandidaten-Entscheidung pro Koeffizient: a=|c|/step; Kandidaten floor(a),floor(a)+1; pro Kandidat D=(c-L·step)² und R=rate_lut[level|kontext] (echte −log2 p aus 06, sonst Surrogat log2(|L|+1)+1); elementweise argmin von J=D+λ·R via np.where. KEINE Python-Schleife über Koeffizienten/Blöcke.\n  3. EOB/Last-Position-Trim als Suffix-Scan in Zickzack-Ordnung (np.cumsum von hinten): k* = argmin(prefix_keep + suffix_zero), Trailing-Run wird genullt wenn das J senkt. Verifiziert: 6.4% Rate gespart, niedrigeres J als Hartquant.\n  4. Dead-Zone z[klasse,band] (breiter für glatt/Textur) und Lloyd-Max-Rekonstruktionsoffset δ[klasse,band,|idx|] (Idee 4+16) aus 05: Dequant = sign(idx)·centroid_lut statt idx·step. Verifiziert: positiver PSNR-Gewinn bei IDENTISCHER Bitrate.\n\nB) VEKTOR-PFAD (Ideen 2+5+14+20 verschmolzen) NUR für Textur-Blöcke:\n  Gain-Shape: x→g=||x||, s=x/g. Shape gegen gelerntes einheitsnorm-Codebuch C[K,D] (05) via Matmul-Trick (Idee 5): idx=argmin(cb_norm−2·X@C.T) bzw. ratenbewusst J=(cb_norm−2·X@C.T)+λ·code_cost[k]. Verifiziert: bit-identisch zu naiv, 20x schneller. Gain skalar auf Log-Grid quantisiert. Pro Block RD-Stichwahl Skalar-vs-VQ (J_vq vs J_scalar), 1-Bit-Flag oder klassen-implizit.\n\nBeide Pfade liefern Integer-Indizes + Side-Channel an 06; inverse Rekonstruktion exakt spiegelbar. Optionaler Refinement-Layer (Idee 7) als embedded Rest für near-/lossless wird als OPTION im Interface vorgesehen, aber nicht im MVP-Kern.",
      "rationale": "Warum diese Auswahl die stärkste kohärente Spec ist:\n\n1. KONVERGENZ: 8 der 20 Ideen (1,3,6,12,13,15,17,19) beschreiben dasselbe — RD-getriebene skalare Quantisierung mit run/EOB-Bewusstsein. Ihr gemeinsamer, CPU-tauglicher Nenner ist die VOLL VEKTORISIERTE 2-Kandidaten-Form + Suffix-Scan-EOB-Trim (Idee 6/1), NICHT der echte Viterbi-Trellis (3/15/17). In reinem NumPy ist ein Per-Block-Per-Koeffizient-DP über 64 Positionen × N Blöcke zwar als (N×states)-Matrix machbar, aber die 2-Kandidaten+Suffix-Variante holt empirisch fast den ganzen RD-Gewinn (verifiziert 6.4% Rate, 1.1% J) bei einem Bruchteil der Komplexität und Risiko. Trellis bleibt als backup_idea für v1.\n\n2. ORTHOGONALE GRATIS-GEWINNE: Lloyd-Max-Centroid-Recon (16) + Dead-Zone (4) ändern die Indizes NICHT, nur die Decoder-LUT → +PSNR bei null Mehrbits, null Encoder-Kosten, kompatibel mit jedem Pfad. Verifiziert. Das ist der billigste Hebel überhaupt und MUSS rein.\n\n3. MARC-DIFFERENZIERER: Der VQ-Pfad (2,5,14,20) ist das Quant-Gegenstück zum Kern-Alleinstellungsmerkmal (\"je Region andere Repräsentation\"). Gain-Shape (2) entkoppelt Kontrast/Struktur und ist extrem NumPy-freundlich; der Matmul-Trick (5) macht ihn überhaupt praktikabel (20x verifiziert). Reine Distanz-VQ ohne Rate (klassisch) wird durch ratenbewusste Indexwahl ersetzt.\n\n4. ENTROPIE-KOPPLUNG: Alle Raten-Terme ziehen aus den ECHTEN 06-Wahrscheinlichkeiten (nicht JPEG-Huffman-Heuristik) → der im Quantisierer minimierte Bitaufwand IST der real codierte. Das ist nur möglich, weil MARC Quant+Entropie gemeinsam entwirft, und genau der strukturelle Vorteil gegenüber formatgebundenen Codecs.\n\n5. VERWORFEN/VERSCHOBEN: Lattice-D4-VQ (18) ist elegant aber riskanter (Conway/Sloane-Korrektheit, Companding-Tuning) → backup. Praediktiver MED-Pfad (8) gehört konzeptuell in 03 (Repräsentation), nicht 04 (Quant). Reiner Trellis (3/15/17) → v1-Upgrade. SSIM-Surrogat (11) wird als perzeptuelle Gewichtung (9/10) integriert, aber konservativ (MSE-Default), da die exakte SSIM-Kopplung Tuning braucht.\n\nAlle drei load-bearing Zahlen wurden im Ziel-Environment (NumPy 2.4, Python 3.14) verifiziert, nicht nur behauptet.",
      "novelty_contribution": "MARCs Quant-Stufe unterscheidet sich strukturell von JPEG/WebP/JXL durch DREI gekoppelte Neuerungen, die kein Standardcodec in dieser Kombination hat:\n\n1. PRO-BLOCK-REPRÄSENTATIONSWAHL auf Quant-Ebene: skalarer RDOQ-Pfad ODER Gain-Shape-RD-VQ-Pfad, RD-entschieden je Block nach Inhaltsklasse (02). JPEG/WebP/JXL sind rein skalar; klassische VQ-Codecs haben keinen skalaren Konkurrenzpfad und keine Ratenbewusstheit. Das ist die Quant-seitige Ausprägung des MARC-Multi-Repräsentations-Kerns.\n\n2. ENTROPIECODER-KONSISTENTE RDO: λ und Ratentabellen kommen klassenspezifisch aus demselben Codec (06/07), nicht aus generischen VLC-Tabellen. Der Drop-/Null-Schwellwert ist exakt der, bei dem es im realen rANS-Bitstream lohnt — kein Schätzfehler. Möglich nur durch gemeinsames Quant+Entropie-Design.\n\n3. DATENGELERNTE REKONSTRUKTIONSGEOMETRIE pro Klasse×Subband: Dead-Zone-Breite und Lloyd-Max-Centroid-Offsets aus der empirischen Koeffizientenverteilung (05) statt fester Bin-Center-Dequant. \"Je Region andere Quantisierungs-/Rekonstruktionsgeometrie.\" Verifiziert als Gratis-PSNR ohne Ratenanstieg.\n\nZusatz-Novelty: voll vektorisierte 2-Kandidaten-RDOQ + Suffix-Scan-EOB-Trim macht RDOQ in reinem CPU-NumPy schnell genug (kein Per-Koeffizient-Viterbi-Loop); ratenbewusste Gain-Shape-VQ via Matmul-Trick.",
      "interface": "Modul: verfahren/04_quantization/quantize.py — reines NumPy/OpenCV, RGB-uint8-Konvention wie common/metrics.py.\n\n# ---------- Datentypen ----------\n# coeffs:     np.ndarray float32/float64, shape (N, B*B)  — N Blöcke, Koeffizienten in ZICKZACK-Reihenfolge (skalarer Pfad)\n#             ODER (N, D) Patch-/Residuenvektoren (VQ-Pfad). B=8 (Detail/Kante) oder 16 (glatt).\n# class_map:  np.ndarray int32, shape (N,)             — Inhaltsklasse je Block aus 02 (0=glatt,1=Kante,2=Detail,3=Textur)\n# QuantModel: dataclass (aus 05, mit Codec ausgeliefert), Felder:\n#   Q          : dict[int, np.ndarray float32 (B*B,)]   per-Klasse Quant-Matrix (zigzag)\n#   deadzone   : dict[int, np.ndarray float32 (B*B,)]   per-Klasse,band Dead-Zone-Halbbreite in step-Einheiten [0..1]\n#   centroid   : dict[int, np.ndarray float32 (B*B, Lmax+1)] Lloyd-Max recon |idx|->|wert|/step\n#   rate_lut   : callable(level:int_array, ctx:int_array)->bits float32  (Adapter zu 06; Surrogat-Default vorhanden)\n#   vq_codebook: np.ndarray float32 (K, D)  einheitsnorm-Shapes (Textur)\n#   vq_codecost: np.ndarray float32 (K,)    -log2 p(k) je Codewort (aus 06)\n#   gain_grid  : np.ndarray float32 (G,)    Log-Grid der Gains\n#   perceptual : bool                        CSF/Aktivität/Luminanz-Maskierung an/aus\n# QResult: dataclass, Felder:\n#   idx        : np.ndarray int32 (N, B*B)   skalare Indizes (0 für VQ-Blöcke)\n#   path       : np.ndarray uint8 (N,)       0=skalar, 1=VQ\n#   vq_idx     : np.ndarray int32 (N,)       Codewort-Index (−1 wenn skalar)\n#   vq_gain    : np.ndarray int32 (N,)       Gain-Grid-Index (−1 wenn skalar)\n#   side       : dict                        EOB-Positionen, Flags für 06/08\n\n# ---------- Hauptfunktionen ----------\ndef quantize_blocks(coeffs: np.ndarray, class_map: np.ndarray, model: 'QuantModel',\n                    lam: float, perceptual_ctx: dict | None = None) -> 'QResult':\n    \\\"\\\"\\\"Encoder. RD-quantisiert alle Blöcke (skalar+VQ), vektorisiert. lam aus 07.\n       perceptual_ctx optional: {'dc': (N,), 'ac_var': (N,)} für Maskierung.\\\"\\\"\\\"\n\ndef dequantize_blocks(qr: 'QResult', class_map: np.ndarray, model: 'QuantModel') -> np.ndarray:\n    \\\"\\\"\\\"Decoder. -> coeffs_hat float32 (N, B*B). Exakt spiegelbar zu quantize_blocks\n       (gleicher idx/path/model => gleiche Rekonstruktion).\\\"\\\"\\\"\n\n# ---------- Vektorisierte Bausteine (intern, testbar) ----------\ndef rdoq_scalar(coeffs: np.ndarray, step: np.ndarray, lam: float,\n                rate_lut, deadzone: np.ndarray) -> np.ndarray:\n    \\\"\\\"\\\"2-Kandidaten-RDOQ pro Koeffizient. step,deadzone broadcastbar (B*B,) oder (N,B*B).\n       -> idx int32 (N, B*B). Keine Python-Schleife über Koeffizienten.\\\"\\\"\\\"\n\ndef eob_trim(idx: np.ndarray, coeffs: np.ndarray, step: np.ndarray,\n             lam: float, rate_lut) -> tuple[np.ndarray, np.ndarray]:\n    \\\"\\\"\\\"Suffix-Scan Trailing-Zero-Trim in Zickzack. -> (idx_getrimmt int32, kstar int32 (N,)).\\\"\\\"\\\"\n\ndef vq_gain_shape(x: np.ndarray, codebook: np.ndarray, codecost: np.ndarray,\n                  gain_grid: np.ndarray, lam: float) -> tuple[np.ndarray, np.ndarray, np.ndarray]:\n    \\\"\\\"\\\"Matmul-VQ (||x-c||²=−2x·c+||c||², ratenbewusst). -> (vq_idx (M,), gain_idx (M,), dist (M,)).\\\"\\\"\\\"\n\ndef reconstruct_scalar(idx: np.ndarray, step: np.ndarray,\n                       centroid: np.ndarray) -> np.ndarray:\n    \\\"\\\"\\\"Lloyd-Max-Dequant: sign(idx)*centroid[|idx|]*step. -> coeffs_hat float32.\\\"\\\"\\\"",
      "depends_on": "EINGANG (was 04 konsumiert):\n- 03_transform: liefert coeffs (N, B*B) in Zickzack pro Block; muss Blockgröße B und Zickzack-Reihenfolge festlegen. KRITISCH: orthonormale DCT/KLT, damit D im Koeffizientenraum = Pixel-MSE (Parseval). 04 nimmt int/float-Koeffizienten entgegen.\n- 02_segmentation_analysis: class_map (N,) int32; Klassen-Enum muss zwischen 02/03/04/05 GELOCKT sein (0=glatt,1=Kante,2=Detail,3=Textur).\n- 05_dictionary_learning: QuantModel-Inhalt — per-Klasse Q-Matrizen, deadzone[klasse,band], centroid-LUTs (Lloyd-Max aus empirischer Verteilung), vq_codebook (spherical k-means, einheitsnorm), vq_codecost, gain_grid. Offline trainiert, mit Codec ausgeliefert.\n- 07_rate_control: λ (skalar oder per-Klasse-Vektor) für J=D+λ·R; steuert Ziel-bpp.\n- 06_context_entropy: rate_lut-Adapter (level,ctx -> bits = −log2 p). Bei Bootstrap Surrogat log2(|L|+1)+1.\n\nAUSGANG (wer 04 konsumiert):\n- 06_context_entropy: QResult.idx, vq_idx, vq_gain, path als Symbolströme; side (EOB-Positionen) als Kontext.\n- 08_bitstream_format: serialisiert QResult-Felder.\n- 09_integration_codec: ruft quantize_blocks/dequantize_blocks im encode/decode-Pfad.\n\nGELOCKTE KONVENTIONEN: float32 intern, int32 Indizes, Zickzack-Reihenfolge, Klassen-Enum, B in {8,16}. rate_lut-Signatur ist die Bruchstelle zu 06 — als callable abstrahiert, damit 04 vor 06 testbar ist (Surrogat-Default).",
      "impl_steps": "1. Skelett + Datentypen: QuantModel/QResult dataclasses, Surrogat-rate_lut (log2(|L|+1)+1), Surrogat-QuantModel-Builder für Tests (synthetische Q/deadzone/centroid aus Laplace-Fit). Zickzack-Index-LUT für B=8 und B=16 (einmal np.array).\n\n2. reconstruct_scalar + Lloyd-Max-LUT: Dequant sign(idx)·centroid[|idx|]·step. Centroid-LUT offline: pro Klasse×band b=mean(|c|) (Laplace-MLE), Zell-Erwartungswert E[|c| | Zelle] analytisch (truncated Laplace) oder empirisch. Test gegen Bin-Center (muss MSE senken, Rate gleich).\n\n3. rdoq_scalar: a=|c|/step, 2 Kandidaten floor/floor+1, D=(c−L·step)², R=rate_lut, np.where(J_hi<J_lo). Dead-Zone integriert (kleine a→L=0). Voll broadcastbar (N,B*B). Test gegen Hartquant: J darf nicht steigen.\n\n4. eob_trim: prefix_keep=cumsum(J_keep)−J_keep; suf_zero=cumsum(J_zero rückwärts); kstar=argmin(prefix+suf); use_trunc wenn < full; trim_mask=(pos>=kstar)&use_trunc → idx[mask]=0. Test: J↓, Rate↓, nz↓ (verifiziert 6.4%).\n\n5. vq_gain_shape: g=norm(x), s=x/g; G=X@C.T; J=(cb_norm−2G)+λ·codecost; vq_idx=argmin; gain_idx=Snap auf log_grid via |g−grid|.argmin. Decode-LUT g·C[k]. Test: idx bit-identisch zu naivem ((x−c)²).sum.argmin bei λ=0; Speed.\n\n6. quantize_blocks Dispatcher: skalare Blöcke → rdoq_scalar+eob_trim; Textur-Blöcke → vq_gain_shape; pro Textur-Block RD-Stichwahl J_vq vs J_scalar, path-Flag setzen. perceptual_ctx optional → step·m_act·m_lum·CSF.\n\n7. dequantize_blocks: path-abhängig reconstruct_scalar bzw. g·C[k]; zusammensetzen (N,B*B). Roundtrip-Assertion: dequant(quant) deterministisch reproduzierbar.\n\n8. Perzeptuelle Maskierung (optional, Idee 9): Q_csf 8x8 vorberechnet, m_act=(1+a·var/ref)^p, m_lum=(DC/mid)^0.7; step_eff. Default aus (perceptual=False) für MVP, Flag im Model.\n\n9. Integrationstest mit 03/05-Stubs auf testdata/kodim*.png: end-to-end coeffs→idx→coeffs_hat, RD gegen Hartquant-Baseline.",
      "unit_tests": "verfahren/04_quantization/tests/test_quantize.py (pytest, nur NumPy):\n\nT1 roundtrip_deterministic: dequantize_blocks(quantize_blocks(c)) zweimal aufgerufen → bit-identisch. Sicherstellt Encoder/Decoder-Spiegelung.\n\nT2 rdoq_never_worse_than_hard: für zufällige Laplace-coeffs J_rdoq(idx) <= J_hard(round). Über mehrere λ (0, 10, 30, 100). λ=0 ⇒ rdoq == round-to-nearest (innerhalb Dead-Zone-Toleranz).\n\nT3 eob_trim_reduces_J_and_rate: nach eob_trim gilt total_J <= total_J(vor trim) UND nonzero-count <= vorher. Auf block-strukturierten coeffs mit HF-Schwänzen (verifiziert: 6.4% Rate).\n\nT4 vq_matmul_bit_identical: vq_gain_shape mit λ=0 liefert vq_idx == naives ((s[:,None,:]-C)**2).sum(-1).argmin(1). (i_naive==i_fast).all()==True (verifiziert).\n\nT5 lloyd_max_lowers_mse_at_equal_rate: reconstruct_scalar mit centroid-LUT vs Bin-Center (idx·step) auf Laplace-Quelle → MSE_centroid < MSE_center, idx IDENTISCH (Rate unverändert). (verifiziert +PSNR).\n\nT6 deadzone_increases_zeros: größere deadzone[klasse] ⇒ mehr Null-Indizes, monoton.\n\nT7 path_selection_picks_min_J: für Textur-Block mit bekanntem J_vq<J_scalar muss path==1 (VQ) gewählt werden, und umgekehrt.\n\nT8 shapes_and_dtypes: idx int32, path uint8, coeffs_hat float32; shapes (N,B*B). class_map-Längen-Mismatch → ValueError.\n\nT9 parseval_consistency: für orthonormale DCT-Basis (Stub) ist Σ(c−c_hat)² im Koeffraum == Σ(pixel−pixel_hat)² (bis Toleranz 1e-6) → bestätigt D-Term-Korrektheit.\n\nT10 vectorization_no_python_loop (perf-smoke): quantize_blocks auf N=16384 Blöcken < 200ms (kein Per-Block-Loop); reine Funktionsausführung, nicht streng asserted aber geloggt.\n\nT11 lossless_refinement_optional (falls Layer impl.): coeffs == dequant + refinement exakt (np.array_equal) bei tau=0.",
      "backup_ideas": "Geordnet nach erwartetem Mehrwert/Risiko, für v1+ nach MVP:\n\n1. ECHTER TRELLIS (Ideen 3/15/17): wenn 2-Kandidaten-RDOQ+EOB-Trim die JPEG-RD-Kurve nicht klar genug dominiert, Upgrade auf Forward-DP mit Zustand=Nullen-Run (S=2..17), vektorisiert als (N×states)-min-add über 64 Zickzack-Schritte. Holt die letzten %-Punkte run-gekoppelter Rate. Höheres Risiko/Komplexität, daher nach MVP.\n\n2. EMBEDDED REFINEMENT-LAYER (Idee 7): Rest resid=c−rec als zweiter Symbolstrom → derselbe Encoder bedient lossy/near-lossless/lossless über Bitplane-Truncation (Linf<=2^(s−1)). Billig, exakt verifizierbar (np.array_equal). Im Interface bereits vorgesehen, Implementierung nach MVP.\n\n3. EXAKTES SSIM-SURROGAT (Idee 11): Distortion-Gewicht w_p=1/(σ_local²+C2) aus cv2.GaussianBlur, an genau die metrics.py-SSIM gekoppelt, in den DCT-Raum gezogen (mean-w pro Block × CSF-Matrix). Direkter SSIM/bpp-Gewinn, aber Tuning-bedürftig; startet als perceptual-Flag-Erweiterung der Maskierung (Idee 9).\n\n4. LATTICE-COMPANDING-VQ D4 (Idee 18): codebuchfreie O(1)-VQ auf D4-Gitter mit PCA-Whitening + Potenz-Companding; ~0.37dB Packungsgewinn, kein Codebuch-RAM. Ersetzt/ergänzt k-means-VQ wenn Codebuchgröße/Speicher zum Problem wird. Conway/Sloane-D4-Quantisierung (~10 Zeilen) braucht eigene Korrektheitstests.\n\n5. PRODUKT-VQ (Idee 2): Shape in 2 Subvektoren à D/2 mit kleineren Codebüchern → K² effektive Einträge bei 2K Speicher. Skaliert VQ-Qualität ohne Speicherexplosion.\n\n6. MEHRSTUFIGE RESIDUUM-VQ (Idee 14/20): zweites kleines Codebook auf x−C[idx] für near-lossless-VQ. \n\n7. RICHTUNGS-Q-PROFILE für Kante-Klasse (Idee 19): richtungsbewusste Q-Tabelle (Diagonalen weniger quantisiert) statt isotroper Matrix — SSIM-Gewinn an Kanten."
    },
    {
      "area": "05_dictionary_learning — Gelernte Modelle (offline, reines NumPy/OpenCV). Erzeugt EIN auslieferbares Modell-Artefakt (marc_model.npz) mit drei gekoppelten Modellfamilien, die [03] Transform, [04] Quantisierung und [06] Entropie speisen: (A) klassen-konditionierte KLT-Basenbank, (B) Gain-Shape-Textur-VQ-Codebuecher, (C) kontext-konditionierte Entropie-Tabellen (KLT-Koeffizienten + VQ-Index-Markov + verlustfreie/near-lossless MED-Residuen). Trainiert wird ausschliesslich offline aus einem kleinen Bildset (Kodak-Teilmenge), deterministisch (fester Seed), CPU-only.",
      "chosen_design": "SYNTHESE — eine Pipeline, drei Artefaktfamilien, ein .npz.\n\nGewaehlt (staerkste Ideen, fusioniert):\n- KLT-Bank: Idee 13 als Kern (reine eigh-KLT, deterministisch, kein Tuning) + Klassenschema/Richtungsbins aus 1/17/19 + eigenwert-getriebene Quant-Step-Ableitung (Reverse-Water-Filling) aus 15/9. CSF-Modulation (9) als optionaler perzeptueller Gewichtungsfaktor, default AUS fuer v0 (klare Baseline, PSNR-zentriert), per Flag aktivierbar.\n- Textur-VQ: Idee 20/18 (Gain-Shape-Split: DC abziehen, gain=||r||, shape=r/g) als Kern, da beleuchtungsinvariant -> generalisiert von 24 Bildern weg. k-means++ Init + feste Lloyd-Iterationen mit Leer-Cluster-Reseed (14). Optional 2-stufiges RVQ (4/18) als Qualitaetsschalter. Frequency-sort der Indizes + ko-trainierte Markov-Index-Tabelle (12) fuer [06].\n- Entropie-Tabellen: parametrisch-geometrisch fuer KLT-Koeffizienten (16, winziger Footprint: p0+rho je Kontext) MIT Voll-Histogramm-Fallback; MED/GAP-Kontexttabellen fuer den verlustfreien Pfad (7) + tau/klassen-spezifische near-lossless Bins mit Bias-Korrektur (8).\n- Laufzeit-Beschleuniger als reine Modell-Felder mitgeliefert: vorberechnete Halb-Normen half=0.5*||c||^2 (5) und gefaltete Decode-Basis B_fused=B*step (6).\n\nTrainingspipeline (NumPy/OpenCV):\n1. Bildset laden -> [01] YCoCg (Y, Co, Cg getrennt). Blockextraktion via stride-tricks: nicht-ueberlappende 8x8 (KLT) bzw. 4x4 (VQ), reshape zu N x 64 / N x 16.\n2. Jeden Block mit DEMSELBEN billigen Klassifikator labeln, den [02] zur Encode-Zeit nutzt (Structure-Tensor: gx,gy via cv2.Sobel; Aktivitaet = mittlere Gradientenmagnitude; Orientierung = 0.5*atan2(sum mag*sin2a, sum mag*cos2a)). Klassen v0: SMOOTH, EDGE (4 Richtungsbins), TEXTURE. -> identische Klassen-IDs encoder-/trainerseitig (Single-Source-of-Truth-Funktion in [02], hier importiert).\n3. KLT je (Klasse, Kanal): mu = mean(X); Xc = X - mu; C = Xc.T@Xc / N (float64 Akku); w,V = np.linalg.eigh(C); Spalten absteigend nach w sortiert -> B (DxD). Energie-Cutoff m_k(QP)-Kurve aus w vortabelliert. Quant-Steps step[k] ∝ 1/sqrt(w_k), normiert auf QP-Grid aus [07]; B_fused=B*step[:,None].\n4. VQ je Textur-(Sub)klasse: Gain-Shape auf 4x4-Residuen (DC weg). Spherical-kmeans auf shapes (K=256). Frequency-sort. Optional RVQ-Stufe 2 (K2=64). Gain: Lloyd-Max 16 Level. half=0.5*(C*C).sum(1).\n5. Entropie: KLT-Koeffizienten je (Klasse,Kanal,Subband,Aktivitaet) -> (p0, rho) + Voll-Histogramm. VQ-Index -> Markov P(idx|coarse(left),coarse(up)). MED-Residuen je Gradienten-Kontext (729->~365 gefaltet) -> normierte freq (Summe=2^14, jedes Symbol>=1, add-1-Laplace) + cum-cdf im rANS-Format aus [06].\n6. Serialisieren als EIN marc_model.npz mit Versions-/Schema-Header + Manifest. Loader gibt ein eingefrorenes Dataclass-/dict-Modellobjekt zurueck, das [03]/[04]/[06] read-only konsumieren.\n\nStrikte Trennung Encode/Decode: B (forward) und B_fused (decode) beide gespeichert; Trainer liefert NIE Online-Schaetzung im Codec-Hotpath. Fallback-Robustheit: bei degenerierter Klassenstatistik (zu wenige Bloecke N<N_min) faellt die Basis auf DCT zurueck (orthonormale 2D-DCT-Matrix), die Entropie-Tabelle auf uniform/Laplace-Default — Codec bleibt immer lauffaehig.",
      "rationale": "Warum diese Synthese und nicht eine Einzelidee:\n\n1. Multi-Repraesentations-Kern bedient: Die drei Familien sind exakt die drei Repraesentationspfade von MARC (SMOOTH->Praediktion/Lossless-Entropie, EDGE->klassen-KLT, TEXTURE->VQ-Token). Eine Einzelidee deckt nur einen Pfad ab; [05] MUSS alle drei speisen, sonst hat [03]/[04]/[06] nichts gelerntes.\n\n2. KLT via reiner eigh (13) ist die robusteste Variante: deterministisch, keine Konvergenzschleife, keine Lernrate, eigh auf 64x64 verifiziert ~1.5ms. Die theoretische Energiekompaktierungs-Optimalitaet der KLT fuer die Klassenstatistik liefert direkt das Primaerziel (weniger signifikante Koeffizienten -> niedrigere bpp bei gleichem PSNR). Eigenwerte w sind GRATIS-Nebenprodukt und geben via Reverse-Water-Filling (15) die RDO-optimale Quant-Step-Tabelle OHNE handgetunte JPEG-Matrix.\n\n3. Gain-Shape-VQ (20/18) statt Roh-Block-VQ (14): kritisch fuer Generalisierung. 24 Kodak-Bilder reichen nicht, um Roh-Bloecke ueber alle Helligkeits-/Kontraststufen abzudecken; durch DC-Abzug + Norm-Normierung lernt das Codebuch NUR Mikrostruktur -> winziges, generalisierendes Dictionary. Verifiziert: VQ-Assign 50k x256 ~80ms, voll vektorisierbar.\n\n4. Parametrische Entropie (16) als Default mit Histogramm-Fallback: winzige Modelldatei (2 floats/Kontext statt KByte-Histogramme), kommt nahe an H(X|context). Risiko Modell-Mismatch wird durch optionalen Voll-Histogramm-Pfad pro Kontext abgesichert -> [06] kann je Kontext waehlen.\n\n5. Fusion-Felder (5,6) sind quasi-gratis (Offline-Vorberechnung, exakt identische Ergebnisse) und beschleunigen Encode (half) und Decode (B_fused) — opportunistische Sekundaerachse Geschwindigkeit ohne Algorithmusaenderung.\n\n6. CSF (9/10) default AUS: haelt v0-Baseline sauber PSNR-vergleichbar; perzeptuelle Modulation ist ein klar abgegrenzter Schalter fuer spaeter (SSIM-Achse), nicht im kritischen Pfad.\n\nAbgrenzung zu JPEG/WebP/JXL/NN: EINE feste DCT (JPEG) bzw. fester Transform-Satz (WebP/AVIF) vs. MARCs pro-Region datengelernte Basis; feste Huffman-Tabellen (JPEG) vs. kontext-konditionierte, offline-gelernte rANS-Tabellen; rein lineare Algebra + k-means, kein Netz/GPU (vs. VQ-VAE/Neuralcodecs).",
      "novelty_contribution": "Der zentrale, mit dem Codec ausgelieferte Differenzierer: EIN offline gelerntes Modell, das je Inhaltsklasse eine ANDERE Repraesentation bereitstellt — datengelernte KLT-Basen pro Klasse/Richtung (statt einer globalen DCT), gain-shape-separierte Textur-VQ-Codebuecher (statt Transform fuer alles), und kontext-konditionierte parametrische Entropie-Modelle (statt fester Huffman-Tabellen). Neu gegenueber allen Mainstream-Codecs ist die KOPPLUNG: dieselbe billige Structure-Tensor-Klassifikation, die [02] zur Laufzeit nutzt, waehlt offline die Trainingspartition UND zur Laufzeit Basis/Codebuch/Entropie-Kontext — die Basiswahl wird Teil der Regionsklassifikation. Zusaetzlich: Quantisierung NICHT handgetunt sondern reverse-water-filling-optimal aus dem gelernten Eigenwertspektrum abgeleitet, und Codebuch-Indizes werden so umsortiert (frequency-sort), dass die ko-trainierte raeumliche Markov-Index-Tabelle maximale Praediktion liefert. Alles deterministisch, reines NumPy/OpenCV, ~0.6-2 MB Modell, mit dem Codec auslieferbar.",
      "interface": "Modul: verfahren/05_dictionary_learning/model.py (Loader + Schema) und train.py (Offline-Trainer). Alle Arrays NumPy; Klassen-IDs als int aus [02].\n\n# ---- Datentypen (eingefrorene Modell-Container) ----\n# KLTBasis: pro (klasse, kanal)\n#   B:        np.ndarray (D,D) float32   # forward-Basis, Spalten=Eigenvektoren absteigend nach Eigenwert\n#   B_fused:  np.ndarray (D,D) float32   # = B * step[:,None], fuer fused dequant+inverse beim Decode (Idee 6)\n#   mu:       np.ndarray (D,)  float32   # Klassen-Mittel (DC-Praediktor)\n#   eigval:   np.ndarray (D,)  float32   # Koeffizientenvarianzen w_k (absteigend)\n#   step_grid:np.ndarray (Q,D) float32   # Quant-Step je QP-Index q und Koeffizient k (reverse-water-filling)\n#   m_grid:   np.ndarray (Q,)  int16     # energie-getrimmte Koeffizientenzahl je QP (Cutoff)\n#   D = 64 (8x8). float32 in-memory; auf Platte fp16 (Idee 5/Modellgroesse), Loader castet.\n#\n# VQCodebook: pro (textur_subklasse)\n#   shape_cb:  np.ndarray (K,Dv)  float32  # Einheitsnorm-Shape-Codewoerter (Dv=16 fuer 4x4)\n#   half:      np.ndarray (K,)    float32  # 0.5*||shape_cb||^2 (Idee 5; bei Einheitsnorm ~0.5, aber exakt gespeichert)\n#   shape_cb2: np.ndarray (K2,Dv) float32 | None  # optionale RVQ-Stufe-2\n#   half2:     np.ndarray (K2,)   float32 | None\n#   gain_levels: np.ndarray (Lg,) float32  # Lloyd-Max Gain-Quantisierungsstufen\n#   index_freqsort: np.ndarray (K,) int32  # Permutation: alter->neuer Index (frequency-sort)\n#   K=256, K2=64, Lg=16\n#\n# EntropyTables:\n#   klt_geom:  dict[ctx_id -> (p0:float32, rho:float32)]            # parametrisch (Idee 16)\n#   klt_hist:  dict[ctx_id -> freq:np.ndarray (A,) uint16]          # Voll-Histogramm-Fallback, Summe=2^14\n#   vq_markov: np.ndarray (Nctx, K) uint16                          # P(idx|coarse(left),coarse(up)), Summe=2^14/Zeile\n#   med_ctx:   {'freqs': np.ndarray (Kctx,A) uint16,               # MED/GAP-Residuen (Idee 7)\n#               'cum':   np.ndarray (Kctx,A+1) uint32,             # kumulativ, rANS-Format aus [06]\n#               'thresholds': np.ndarray int16, 'A': int}\n#   nll_bins:  dict[(tau:int, klasse:int) -> {'freqs':uint16(Atau,), 'bias':int16(Kctx,)}]  # near-lossless (Idee 8)\n#   ctx_layout: dict  # wie ctx_id aus (klasse,kanal,subband_bin,aktivitaet_bin) berechnet wird\n#\n# MarcModel (Top-Level, read-only):\n#   version:int; class_scheme:dict; channels:tuple[str]\n#   klt: dict[(klasse:int, kanal:int) -> KLTBasis]\n#   vq:  dict[textur_subklasse:int -> VQCodebook]\n#   entropy: EntropyTables\n#\n# ---- Trainer (offline) ----\ndef train_model(\n    image_paths: list[str],\n    *,\n    block_klt: int = 8,           # 8x8\n    block_vq: int = 4,            # 4x4\n    n_classes_edge_dirs: int = 4, # Richtungsbins\n    vq_K: int = 256, vq_K2: int | None = 64, gain_levels: int = 16,\n    qp_grid: np.ndarray = None,   # QP-Stuetzstellen, default aus [07]\n    use_csf: bool = False,        # perzeptuelle Modulation (Idee 9/10), default aus\n    rans_total_log2: int = 14,    # Summe der freq = 2^14 (Format [06])\n    n_min_block: int = 2000,      # < n_min -> DCT/Default-Fallback fuer diese Klasse\n    seed: int = 0,\n) -> \"MarcModel\": ...\n# bestimmt Klassen via SHARED Klassifikator aus [02], baut KLT/VQ/Entropie, gibt MarcModel.\n\ndef save_model(model: \"MarcModel\", path: str) -> None: ...   # -> marc_model.npz (fp16 wo erlaubt, +Manifest)\ndef load_model(path: str) -> \"MarcModel\": ...                # castet fp16->float32, validiert Schema/Version\n\n# ---- Read-only Zugriffs-API fuer [03]/[04]/[06] (keine Mutation) ----\ndef get_klt(model, klasse:int, kanal:int) -> KLTBasis: ...   # garantiert nie None (Fallback-DCT)\ndef get_vq(model, textur_subklasse:int) -> VQCodebook: ...\ndef klt_ctx_id(model, klasse:int, kanal:int, subband_bin:int, activity_bin:int) -> int: ...\n\n# ---- Bequemlichkeits-Operatoren (vektorisiert, fuer [03]/[04]; rein funktional) ----\ndef klt_forward(B: np.ndarray, mu: np.ndarray, blocks: np.ndarray) -> np.ndarray:\n    # blocks (N,D) -> coeff (N,D):  (blocks - mu) @ B\n    ...\ndef klt_inverse_fused(B_fused: np.ndarray, mu: np.ndarray, qcoeff: np.ndarray) -> np.ndarray:\n    # qcoeff (N,D) int/float Indizes -> blocks (N,D):  qcoeff @ B_fused.T + mu   (Idee 6)\n    ...\ndef vq_encode(shape_cb: np.ndarray, half: np.ndarray, shapes: np.ndarray) -> np.ndarray:\n    # shapes (N,Dv) -> idx (N,) int32:  argmax(shapes @ shape_cb.T - half)   (Idee 5, exakt = NN)\n    ...\ndef vq_decode(shape_cb: np.ndarray, idx: np.ndarray) -> np.ndarray:\n    # idx (N,) -> shapes (N,Dv): shape_cb[idx]\n    ...",
      "depends_on": "- [01] color_preprocess: Trainer braucht die identische RGB->YCoCg-Funktion (Kanaltrennung), damit gelernte Statistik = Encode-Statistik. HART: gleiche Transform, gleiche Rundung.\n- [02] segmentation_analysis: liefert die SINGLE-SOURCE Klassifikator-Funktion classify_block(features)->class_id (Structure-Tensor: Aktivitaet + Orientierungsbin). Trainer importiert exakt diese Funktion; sonst Klassendrift. Definiert die Klassen-IDs/das Klassenschema. HART.\n- [04] quantization: konsumiert step_grid/B_fused/eigval (RDO) und vq_encode/vq_decode. Vereinbart QP<->q-Index-Mapping und Quant-Konvention (Dead-Zone, Vorzeichen).\n- [06] context_entropy: definiert das rANS-Tabellenformat (Summe=2^rans_total_log2, jedes Symbol>=1, cum-cdf uint32, Symbol-Alphabet A, Escape-Konvention). [05] muss EXAKT dieses Format exportieren. HART.\n- [07] rate_control: liefert das qp_grid und das lambda/QP->theta-Mapping fuer Reverse-Water-Filling und m_grid-Cutoff.\n- common/metrics.py: nur fuer Trainer-Validierung (PSNR der KLT/VQ-Rekonstruktion), nicht im Modell selbst.\n- Externe Libs: nur numpy + cv2 (Sobel, optional kmeans). KEIN scipy/torch/skimage.\n\nReihenfolge: [01]+[02] Klassifikator-Stub muss vor [05]-Training existieren (oder gemockt). [06]-Tabellenformat muss vor Entropie-Export gelockt sein. [05] liefert dann statisches marc_model.npz, das [03]/[04]/[06] zur Laufzeit nur lesen.",
      "impl_steps": "1. Schema/Loader zuerst (model.py): MarcModel/KLTBasis/VQCodebook/EntropyTables als frozen dataclasses; save_model/load_model mit np.savez_compressed + JSON-Manifest (Versions-Tag, Schluessel-Liste, dtype-Map). load_model castet fp16->float32, validiert gegen Schema, faellt bei fehlenden Klassen auf Default zurueck. -> Erlaubt allen anderen Bereichen, sofort gegen ein (leeres/Default-)Modell zu programmieren.\n2. Default/Fallback-Generator: build_default_model() erzeugt orthonormale 2D-DCT-Basis als KLTBasis (B=B_fused mit Einheits-step), uniforme/Laplace-Entropie, leeres VQ. Garantiert lauffaehigen Codec ohne Trainingslauf.\n3. Block-Extraktion: extract_blocks(channel, size) via np.lib.stride_tricks (nicht-ueberlappende Kacheln, Rand via cv2.copyMakeBorder reflect). Vektorisiert N x D.\n4. Klassen-Labeling: classify_blocks(blocks) ruft die [02]-Funktion vektorisiert; Rueckgabe class_id-Array. Bis [02] fertig: lokaler Stub mit Structure-Tensor (Sobel) + dokumentierter TODO-Sync.\n5. KLT-Training (train_klt): pro (Klasse,Kanal) C=Xc.T@Xc/N (float64), eigh, sortieren, B/mu/eigval. step_grid aus eigval via reverse-water-filling ueber qp_grid (step_k=clip(base_q(QP)/sqrt(w_k/w_ref),1,255)); m_grid via Energie-Cutoff. B_fused=B*step. N<n_min -> DCT-Fallback. fp16-Quant der gespeicherten B.\n6. VQ-Training (train_vq): Textur-4x4 sammeln, DC weg, gain=norm, shape=r/g (eps-Schutz fuer g~0). spherical k-means: k-means++ Init (distanzgewichtet), feste 25 Lloyd-Iter, assign=argmax(shape@C.T) (Einheitsnorm), Update=renormierter Cluster-Mittel, Leer-Cluster-Reseed auf entferntesten Punkt. Optional RVQ-Stufe2 auf shape-cb[idx]-Residuum. gain Lloyd-Max 16 Level. frequency-sort + Permutation. half vorberechnen. np.random.seed(seed) -> deterministisch.\n7. Entropie-Training (train_entropy): (a) KLT-Koeffizienten quantisieren (mit step_grid bei Referenz-QP), je Kontext p0=P(=0), rho=mu/(mu+1) aus E[|k|-1 | !=0]; parallel Voll-Histogramm -> uint16 normiert auf 2^14 (add-1, jedes>=1). (b) VQ-Index-Markov: P(idx|coarse(left),coarse(up)), coarse=idx>>4, Laplace-Glaettung. (c) MED/GAP: kausaler MED-Praediktor, Kontext-Quantisierung (Gradienten-Schwellen, Vorzeichen-Faltung 729->~365), zigzag-Mapping, Histogramm->freqs+cum (rANS-Format). (d) near-lossless: je (tau,Klasse) quantisierte Residuen-Bins + bias[ctx]=round(mean(r|ctx)).\n8. Verdrahtung train_model(): 3-7 orchestrieren, MarcModel zusammensetzen, optional Trainer-Selbsttest (KLT-Rekonstruktions-PSNR, VQ-Distortion) loggen.\n9. CLI: python train.py --images <glob> --out marc_model.npz [--use-csf] [--no-rvq]. Deterministisch, reproduzierbar.\n10. Operatoren klt_forward/klt_inverse_fused/vq_encode/vq_decode als duenne, voll vektorisierte, rein funktionale Helfer (kein State), damit [03]/[04] sie direkt batchen.",
      "unit_tests": "verfahren/05_dictionary_learning/tests/ (pytest, nur numpy/cv2):\n\n1. test_klt_orthonormal: fuer jede gelernte/Default-Basis B: ||B.T@B - I||_max < 1e-3 (fp16-toleranz). Sichert exakte Invertierbarkeit.\n2. test_klt_roundtrip_lossless: ohne Quantisierung klt_inverse(klt_forward(blocks)) == blocks bis 1e-4 (float32). Garantiert linearer Pfad korrekt.\n3. test_klt_fused_equiv: klt_inverse_fused(B_fused,...,q) == (q*step)@B.T+mu (zweistufig) bis 1e-5. Sichert Idee-6-Fusion exakt.\n4. test_klt_energy_compaction: auf synthetischer AR(1)-Quelle (rho=0.95) packt gelernte KLT >= DCT-Energie in die ersten m Koeffizienten (cumulative eigval-Fraktion >= DCT-Fraktion). Belegt Primaerziel.\n5. test_klt_eigval_sorted: eigval streng absteigend; step_grid monoton fallend in eigval (groessere Varianz -> feinerer Step).\n6. test_vq_assign_equals_nn: vq_encode (argmax(X@C.T-half)) == bruteforce argmin ||x-c||^2 fuer Zufallsdaten (100% Index-Match). Sichert Idee-5-Identitaet.\n7. test_vq_shapes_unit_norm: alle shape_cb-Zeilen ||.||=1 bis 1e-5 (spherical). half == 0.5*||row||^2.\n8. test_vq_decode_reconstruct: v_hat = dc + gain*shape_cb[idx]; auf Trainings-Textur mittlere L2 < L2 des DC-only-Baseline (VQ bringt echten Gewinn).\n9. test_vq_deterministic: train_vq mit seed=0 zweimal -> identische Codebuecher (bitgleich). Reproduzierbarkeit.\n10. test_vq_empty_cluster_reseed: kuenstlich leerer Cluster wird reseedet, kein NaN/Inf im Codebuch.\n11. test_entropy_freq_format: alle freq-Tabellen Summe == 2^rans_total_log2, jedes Symbol >=1, cum monoton steigend, cum[-1]==Summe. Sichert [06]-Kompatibilitaet (parametrisiert ueber [06]-Konstante).\n12. test_entropy_geom_matches_hist: gerendertes geometrisches Modell (p0,rho) vs Voll-Histogramm: KL-Divergenz < Schwelle auf Trainingsdaten (Modell-Mismatch begrenzt).\n13. test_med_context_causal: MED-Kontext aus rekonstruierten Nachbarn im Decoder == Encoder-Kontext (kausale Synchronitaet) auf Zufallsbild.\n14. test_nll_linf_guarantee: near-lossless Quant/Dequant mit tau -> max|x-x_hat| <= tau pixelweise (harte L-inf-Schranke).\n15. test_save_load_roundtrip: save_model->load_model: alle Arrays gleich (float32 nach fp16-cast bis 1e-3), Schema/Version erhalten, Dict-Schluessel identisch.\n16. test_default_model_runs: build_default_model() liefert vollstaendiges MarcModel; get_klt/get_vq nie None; Default-KLT == 2D-DCT (gegen cv2.dct verifiziert).\n17. test_fallback_small_class: Klasse mit N<n_min -> DCT-Fallback-Basis (kein Crash, orthonormal).\n18. test_train_smoke_kodak: train_model auf 2 Kodak-Bildern laeuft < 30s, erzeugt KLT fuer alle Klassen + nichtleeres VQ; gespeichertes .npz < 4 MB.\n19. test_model_readonly: get_klt/get_vq geben Objekte, deren Mutation das Modell nicht veraendert (frozen / copy-Semantik), damit [03]/[04]/[06] sich nicht gegenseitig korrumpieren.",
      "backup_ideas": "Bewusst NACHGEORDNET (klar, aber nicht im v0-kritischen Pfad), in Prioritaet:\n\n- CSF-/Masking-gewichtete Quant-Matrizen und VQ-Distanz (Ideen 9, 10): perzeptuelle Achse (SSIM). Als Flag use_csf vorgesehen, default aus; aktivieren sobald PSNR-Baseline steht und SSIM-Optimierung gewuenscht. Risiko: verfaelscht PSNR-Vergleich, daher nicht v0.\n- Directional-Orientation-Cluster via Gradienten-HISTOGRAMM + k-means-Router (Idee 11): feinere Richtungsadaption als die festen 4 Sobel-Richtungsbins (17/13). Mehrwert auf Diagonalkanten, aber zusaetzliche Router-Side-Info-Bits und Komplexitaet; erst nach Messung des Gewinns der festen Bins.\n- 2-stufiges RVQ als Default (Ideen 4, 18): in v0 als OPTIONALER Schalter (vq_K2) drin, aber default einstufig fuer minimale Bitrate/Komplexitaet; auf RVQ hochschalten nur wo Qualitaet bei niedriger bpp es rechtfertigt.\n- Plane-Praediktion (DC+lineare Steigung per LS) vor Gain-Shape-VQ (Idee 18): entfernt Beleuchtungsgradienten, die VQ schlecht packt. Lohnt v.a. auf groesseren 8x8-Texturbloecken; bei 4x4 ist DC-Abzug meist ausreichend. Nachruesten falls 4x4-VQ-Residuen sichtbaren Gradienten-Restfehler zeigen.\n- Mini-Batch-/Online-k-means (Idee 14-Variante) und cv2.kmeans statt eigener Lloyd: nur falls Trainingszeit auf groesserem Set zum Problem wird; eigene vektorisierte Lloyd ist deterministischer.\n- Pro-Klasse SEPARATE 4x4-KLT zusaetzlich zu 8x8 (Idee 3): mehr Granularitaet, mehr Modellgroesse; erst wenn Quadtree haeufig auf 4x4 splittet.\n- GAP-Praediktor (CALIC) als Alternative zu MED (Idee 7): potentiell schaerfere Residuen-Verteilung auf Kanten; MED zuerst (einfacher), GAP als A/B-Test.\n- fp16-Codebuch zusaetzlich zu fp16-Basis (Idee 5): weitere Modellgroessen-Halbierung; nur falls Modell >2 MB stoert (Test 18 grenzt schon auf <4MB)."
    },
    {
      "area": "06_context_entropy — Kontextmodell & Entropiecodierung (Indizes ↔ Bytes, verlustfrei & exakt invertierbar)",
      "chosen_design": "KERN: Ein klassen-konditionierter, kontextadaptiver Interleaved-rANS-Coder mit Significance/Sign/Magnitude-Splitting und statisch-gelernten Tabellen (+ optionalem header-freiem Online-Fallback). Synthese der staerksten Ideen:\n\n1) STATISCHE rANS-TABELLENBANK mit 3D-Kontext (Ideen 1,13,17,19): Kontextindex ctx = (class c) x (band) x (neighbor-bucket). Pro ctx eine offline gelernte (Bereich 05), auf TOTAL=2^12=4096 normalisierte uint16-Frequenztabelle. class kommt GRATIS aus der MARC-Klassenkarte (Bereich 02) — das ist der MARC-Differenzierer, in die Entropieschicht durchgezogen.\n\n2) SIGNIFICANCE/SIGN/MAGNITUDE-SPLITTING (Ideen 2A,3,16,20): Der Indexstrom wird in drei rANS-Substroeme zerlegt. (a) Block-Significance-Plane: pro Quadtree-Blatt 1 Bit all-zero?, kausaler 4-Kontext (links/oben all-zero). (b) Coefficient-Significance: binaer pro Koeffizient, ctx aus (class, band, links_nz, oben_nz). (c) Magnitude+Sign nur fuer signifikante Positionen, klassen-konditionierte Tabelle, |idx|-1 mit Escape→Exp-Golomb-Bypass-Bits.\n\n3) INTERLEAVED SIMD-LANE rANS (Ideen 5,6) als EINZIGE Coder-Engine: N=16 Lanes, State als uint64-Array (N,), 32-bit-State pro Lane (RANS_L=1<<23), byteweise Renorm, Encode rueckwaerts/Decode vorwaerts. Slot→Symbol per vorberechneter 2D-LUT slot2sym[ctx,slot] (ein Fancy-Index-Gather, KEINE Suche). Per-Lane-Bytestreams, N Endstates im Header.\n\n4) PER-NODE MODEL-SWITCH (Idee 18) als RDO-Option: pro Quadtree-Knoten 1 Selektorbit rANS-Tabelle ODER adaptiver Golomb-Rice (JPEG-LS k-Regel); es gewinnt der billigere → nie schlechter als der bessere globale Coder.\n\n5) ONLINE-FALLBACK-SCHIENE (Ideen 8,14): header-freier adaptiver Range/binary-rANS-Pfad (Counts starten uniform, deterministisches kausales Update Enc==Dec) als Bootstrap, wenn Bereich-05-Tabellen fehlen oder Bild stark von Trainingsstatistik abweicht. Per-Bild 1-Flag waehlt static vs adaptive.\n\nPrototyp in NumPy verifiziert: kontextadaptiver Interleaved-rANS roundtrippt exakt (4000xN=16 Symbole, 4 Kontexte), erreicht 17693 B vs 17666 B Ideal-Entropie (+0.15% Lane-Overhead); GR-Kostenwahl, Zigzag-Invertierbarkeit und Significance-Bit-Mathematik bestaetigt.",
      "rationale": "WARUM diese Auswahl: (a) rANS statt Huffman schliesst die fraktionale-Codelaengen-Luecke (~0.25-0.5 bit/Symbol). (b) Klassen-Konditionierung ist der EINZIGE Hebel, der den MARC-Kern ('je Region andere Repraesentation') in die Entropieschicht verlaengert und in keinem Standardcodec existiert — gemessen in den Quell-Notizen -9.4% Koeffizienten-Entropie allein durch die vorhandene Seiteninfo. (c) Significance-Splitting holt den groessten Gewinn genau dort, wo MARC-Bilder bei niedriger bpp am sparsesten sind (>80% Nullen); H(sig|class,nbr) schwankt 0.02..0.97 — ein globales Modell verschenkt diese Spreizung. (d) Interleaved-SIMD-rANS ist die EINZIGE Art, rANS in reinem NumPy ohne C-Extension auf brauchbaren Durchsatz zu bringen (1 Array-Op pro Zeitschritt statt 1 Python-Loop pro Symbol) — Pflicht unter dem CPU/NumPy-Constraint. (e) Statische Tabellen sind deterministisch, winziger Header, vektorisierbares Setup; der Online-Fallback entkoppelt Bereich 06 von der Fertigstellung Bereich 05 (Integrationsrisiko sinkt) und schuetzt gegen Domain-Shift.\n\nABGELEHNT/zurueckgestellt: Voll-adaptives MIX2/CABAC pro Symbol (Idee 4,7,15) — zu teuer im Python-Hotloop, nicht batchbar; als segmentweise-statische Variante (Idee 2B) optional nachruestbar. NPC k-means-Kontexte (Idee 12) — staerker, aber Komplexitaet > MVP-Nutzen; als backup. PEL Layer-Trunkierung (Idee 10) gehoert konzeptuell zu Bereich 07/08. PCMQ-CSF-Tabellenpraezision (Idee 9) ist ein billiges Add-on auf der Magnitude-Tabelle, spaeter.",
      "novelty_contribution": "Der Headline-Differenzierer dieser Schicht: Die semantische MARC-Region-KLASSE (glatt/Kante/Textur aus Bereich 02) ist die PRIMAERE Kontextachse des Entropiecoders. Glatte, Kanten- und Texturregionen bekommen voellig getrennte rANS-Wahrscheinlichkeitstabellen, obwohl ihre Symbole im selben interleaved Strom liegen. Kein Standardcodec (JPEG=statisches Huffman ohne Raumkontext; WebP=Bool-Coder ohne Inhaltsklasse; JPEG XL=MA-Baum auf einheitlichen Koeffizienten) konditioniert den Entropiecoder auf eine vorgelagerte semantische Regionsklasse. Zweite Neuheit: getrennte rANS-Substroeme PRO REPRAESENTATION (DCT/Plane/VQ) — MARCs Heterogenitaet wird zum Entropie-Vorteil statt zum verschmierten Misch-Strom. Dritte (orthogonale) Neuheit: der Wahrscheinlichkeits-Coder selbst (rANS-Tabelle vs Golomb-Rice) ist pro Quadtree-Knoten regionenadaptiv waehlbar.",
      "interface": "Modul: verfahren/06_context_entropy/entropy.py — reines NumPy. PROB_BITS=12, TOTAL=4096, RANS_L=1<<23, NLANES=16 (Konstanten). Alle Arrays C-contiguous.\n\n# ---- Datentypen ----\n# SymbolStream (dataclass-artiges dict), Ausgabe von Bereich 04, Eingabe hier:\n#   {'class_map': int8[H_blk,W_blk]        # Region-Klasse 0..K-1 je Block\n#    'blocks': list[BlockTokens]}          # je Quadtree-Blatt in Scan-Reihenfolge\n# BlockTokens: {'repr': int (0=DCT,1=PLANE,2=VQ), 'pos':(by,bx), 'size':int,\n#   'indices': int32[n]   # quantisierte Indizes (DC+AC zigzag-skaniert) ODER VQ-IDs+Residuen\n#   'band_id': int8[n]}   # 0=DC,1=lowAC,2=midAC,3=highAC (aus Position abgeleitet)\n\n# ---- Tabellen-Modell (aus Bereich 05 geladen, oder None -> adaptiver Fallback) ----\nclass EntropyModel:\n    # freq:  uint16[n_ctx, n_sym]  (jede Zeile summiert exakt zu TOTAL)\n    # cum:   uint32[n_ctx, n_sym+1]\n    # slot2sym: uint16[n_ctx, TOTAL]\n    # sig_p1: uint16[n_sig_ctx]     # P(signifikant) in /4096 fuer binaeren rANS\n    # n_classes:int, n_bands:int, n_nbr_buckets:int, esc_sym:int\n    @classmethod\n    def load(cls, path: str) -> 'EntropyModel': ...\n    @classmethod\n    def from_histograms(cls, hist: dict) -> 'EntropyModel': ...   # offline Build (Bereich 05)\n    def save(self, path: str) -> None: ...\n\n# ---- Haupt-API (exakt invertierbares Paar) ----\ndef encode_symbols(stream: dict, model: EntropyModel | None,\n                   adaptive: bool = False) -> bytes:\n    \\\"\\\"\\\"Indizes -> Byte-Blob. adaptive=True nutzt header-freien Online-Coder.\n    Liefert self-contained Bytes inkl. Mini-Header (flags, NLANES Endstates,\n    Substream-Laengen). Deterministisch.\\\"\\\"\\\"\n\ndef decode_symbols(blob: bytes, model: EntropyModel | None,\n                   class_map: 'int8[H_blk,W_blk]',\n                   block_layout: list[dict]) -> dict:\n    \\\"\\\"\\\"Byte-Blob -> identische 'stream'-Struktur. block_layout (repr,pos,size,\n    n_indices,band_id je Block) kommt aus Bereich 08-Header, der VOR dem\n    Koeff-Strom steht. Rekonstruiert indices exakt.\\\"\\\"\\\"\n\n# ---- Low-Level rANS-Engine (wiederverwendbar, getestet isoliert) ----\ndef rans_encode(symbols: 'int32[T]', ctx: 'int32[T]', freq: 'u64[C,S]',\n                cum: 'u64[C,S+1]', n_lanes: int = NLANES\n               ) -> tuple[bytes, 'u64[n_lanes]']:\n    \\\"\\\"\\\"Interleaved Encode (rueckwaerts). Liefert (lane_bytes_concat, endstates).\\\"\\\"\\\"\ndef rans_decode(blob: bytes, endstates: 'u64[n_lanes]', ctx: 'int32[T]',\n                freq: 'u64[C,S]', cum: 'u64[C,S+1]', slot2sym: 'u16[C,TOTAL]'\n               ) -> 'int32[T]':\n    \\\"\\\"\\\"ctx[t] MUSS beim Decode kausal bekannt sein (entweder vorab bekannt oder\n    aus bereits dekodierten Symbolen berechenbar). Liefert symbols.\\\"\\\"\\\"\n\n# ---- Hilfen ----\ndef normalize_freqs(hist: 'int[S]', total: int = TOTAL) -> 'uint32[S]':  # sum==total, jede occ. freq>=1\ndef build_slot2sym(cum: 'uint32[S+1]', total: int = TOTAL) -> 'uint16[total]':\ndef zigzag(v: 'int32[n]') -> 'int32[n]':   # signed->unsigned, exakt invertierbar\ndef inv_zigzag(z: 'int32[n]') -> 'int32[n]':\ndef gr_cost(u: 'int32[n]') -> tuple[int,int]:  # (best_bits, best_k) fuer Model-Switch\ndef make_ctx(class_id:int, band:'int8[n]', nbr:'int32[n]',\n             n_bands:int, n_nbr:int) -> 'int32[n]':  # ctx = (class*n_bands+band)*n_nbr+bucket(nbr)\n\nINVARIANTE: decode_symbols(encode_symbols(s, m), m, s['class_map'], layout) == s  (bit-exakt, alle indices).",
      "depends_on": "- Bereich 02 (segmentation): liefert class_map int8[H_blk,W_blk] (Region-Klasse je Block) + Quadtree-Blatt-Layout (pos,size). HARTE Abhaengigkeit — Kontextachse.\n- Bereich 04 (quantization): liefert die quantisierten Indizes (signed int32) je Block + repr-Typ; Symbol-Alphabet-Definition + Zigzag-Mapping muss mit 04 abgestimmt sein (Escape-Schwelle).\n- Bereich 05 (dictionary_learning): liefert offline gelernte Frequenz-/Significance-Tabellen (EntropyModel). OPTIONAL via adaptivem Fallback entkoppelt.\n- Bereich 08 (bitstream_format): schreibt class_map + block_layout (repr,pos,size,n_indices,band_id) VOR den Koeff-Strom, damit der Decoder ctx kausal bilden kann; konsumiert die bytes aus encode_symbols. Liefert beim Decode block_layout an decode_symbols.\n- Bereich 07 (rate_control): orthogonal; bei aktivierter PEL-Erweiterung (backup) Layer-Trunkierung.\nLIEFERT AN: 08 (Bytes), 09 (Integration ruft encode/decode_symbols).",
      "impl_steps": "1. rans_core: normalize_freqs, build_slot2sym, zigzag/inv_zigzag implementieren + Mikro-Tests (Invertierbarkeit, sum==TOTAL, occ-freq>=1). [Prototyp bereits verifiziert]\n2. Interleaved-Engine: rans_encode (rueckwaerts, per-Lane bytearray, vektorisierte Renorm-while-.any()-Schleife) + rans_decode (vorwaerts, 2D-Gather slot2sym[ctx,slot]). Roundtrip-Test mit Zufallssymbolen + Zufallskontext. [Prototyp verifiziert: exakt, +0.15% vs Ideal]\n3. make_ctx + Band-Ableitung aus Zigzag-Position; neighbor-bucket via np.searchsorted([0,1,2,4,8,16,32,64]).\n4. Significance-Splitting: split_streams(stream) -> (block_sig_bits, coef_sig_bits, sign_bits, magnitudes). Binaerer rANS als 2-Symbol-Spezialfall der Engine. Kausaler nbr-Significance-Kontext via inkrementell aufgebaute 2D-Maske.\n5. EntropyModel: from_histograms (np.bincount je ctx, normalize_freqs, min-clamp), save/load (npz). build_slot2sym cachen.\n6. encode_symbols/decode_symbols: Substroeme verketten, Mini-Header (magic, version, flags, NLANES endstates, substream-Laengen als varint). repr-getrennte Substroeme (CAZE) ueber argsort nach (repr,ctx).\n7. Golomb-Rice-Pfad + per-Knoten Model-Switch (gr_cost vs rANS-Kosten, 1 Selektorbit), in Bypass-Buffer.\n8. Adaptiver Fallback (header-frei): adaptive Counts, deterministisches kausales Update, periodisches Rescale; binaerer Range/rANS. Flag-gesteuert.\n9. Integration-Glue + E2E-Roundtrip ueber synthetische 'stream'-Struktur.\nVektorisierung: alle ctx/sym/band-Arrays VORAB vollvektorisiert berechnen; nur die rANS-Renorm-while-Schleife bleibt im (kurzen, <=~4 Iter) Loop ueber Lanes.",
      "unit_tests": "test_entropy.py (pytest, reines NumPy):\n1. test_normalize_freqs_sum: normalize_freqs(beliebiges hist) summiert exakt zu TOTAL; jede in hist>0 vorkommende Freq >=1; keine negative.\n2. test_zigzag_roundtrip: inv_zigzag(zigzag(v))==v fuer v in [-10000..10000] inkl. 0,+-1.\n3. test_slot2sym_consistency: fuer jede slot in [0,TOTAL): cum[slot2sym[slot]]<=slot<cum[slot2sym[slot]+1].\n4. test_rans_single_ctx_roundtrip: 50k Zufallssymbole, 1 Kontext -> rans_decode(rans_encode())==input.\n5. test_rans_multi_ctx_roundtrip: 4 Kontexte, Zufallssymbole+Zufallskontext (beidseitig bekannt) -> exakt. [Prototyp: PASS]\n6. test_rans_size_near_entropy: encodierte Bytes <= ideal_model_bits/8 * 1.01 + 4*NLANES (Lane-Overhead). [Prototyp: +0.15%]\n7. test_empty_and_singlesym: leerer Strom, Strom aus 1 Symbol, Strom aus nur-Nullen -> roundtrip + plausible Groesse.\n8. test_significance_split_roundtrip: stream mit 90% Nullindizes -> split/merge exakt; coded size deutlich < dense-Coding.\n9. test_class_conditioning_helps: synthetischer Strom mit 2 Klassen unterschiedlicher Verteilung -> klassen-konditionierte Bytes < globale-Tabelle-Bytes (Entropie-Argument).\n10. test_full_roundtrip_exact: decode_symbols(encode_symbols(stream,model),...)==stream bit-exakt (alle indices, class_map, repr).\n11. test_adaptive_fallback_roundtrip: model=None, adaptive=True -> header-frei, exakt invertierbar, Enc/Dec deterministisch identisch.\n12. test_gr_vs_rans_switch: Knoten mit kleinen Residuen -> GR gewinnt; Knoten mit schiefer Verteilung -> rANS gewinnt; Selektorbit korrekt rekonstruiert.\n13. test_escape_symbol: Indizes ausserhalb Alphabet (Ausreisser) -> Escape + Bypass-Bits exakt rekonstruiert.\n14. test_determinism: encode_symbols zweimal -> identische Bytes (keine Nichtdeterminismus durch dict-Order o.ae.).",
      "backup_ideas": "- MIX2 / online-adaptierte 2-Komponenten-Mischung (gelernter Prior + Counts) in Festkomma (Ideen 4,15): naeher an bedingter Entropie bei Domain-Shift; als segmentweise-statisches Update (Idee 2B, 4096-Symbol-Segmente) batchbar nachruestbar ohne Seitenkanal.\n- NPC k-means-Kontext-Vokabular (Idee 12): Kontextauswahl als VQ des kausalen Nachbar-Featureraums statt Handbuckets; nutzt dieselbe k-means-Maschine wie Bereich 05; schaerfere Verteilungen, aber Komplexitaet > MVP.\n- PCMQ perzeptuelle Tabellen-Praezision (Idee 9): CSF-/Maskierungs-skaliertes min-count-Floor pro Sichtbarkeits-Bucket auf der Magnitude-Tabelle — billiges bpp@SSIM-Add-on, verlustfrei bzgl. Indizes.\n- PEL Sichtbarkeits-priorisierte Layer-Trunkierung (Idee 10): mehrere rANS-Substroeme in CSF-Reihenfolge fuer graceful Truncation / single-encode-multi-bitrate — gehoert mit Bereich 07/08 abgestimmt.\n- Voll-adaptiver Range-Coder mit Carry-Handling (Idee 8,14) als alternativer Engine-Pfad fuer Residuenstroeme unbekannter Statistik / Lossless-Schiene (MED/Paeth-Residuen).\n- CAZE pfad-getrennte Substroeme mit Cross-Repraesentations-Switch-Praediktor (Idee 11): Pfad der Nachbarbloecke als Kontext fuer den repr-Selektor-Strom (Pfade clustern raeumlich -> billig); im MVP bereits als repr-Substream-Trennung enthalten, raeumliche Praediktion als Erweiterung."
    },
    {
      "area": "07_rate_control — Ratensteuerung & Bit-Allokation (Ziel-bpp/Qualitaet -> lambda/QP je Block/Klasse), inkl. optionalem Praezisions-Refinement und near-lossless-Pfad.",
      "chosen_design": "KERN: \"CRSA\" — Class-Relative Slope Allocation. Eine zweistufige Pipeline, die die staerksten Ideen (1, 13, 14/17, 2/16, 8) zu EINEM kohaerenten Allokator verschmilzt, der MARCs Multi-Repraesentations-Kern direkt auf die Raten-Achse hebt: ein globales Lagrange-lambda fuer das ganze Bild, aber je Region/Klasse ein eigener Arbeitspunkt.\n\nSTUFE A (Default, encode-frei, ~1ms): Geschlossene varianz-basierte Constant-Slope-Allokation (Ideen 14+17+1+13).\n- Pro Quadtree-Blatt b: Aktivitaets-/Energieskalar sigma2_b (Luma- bzw. Residuen-Varianz aus [02]/[03]).\n- Perzeptuelles Klassen+Maskierungs-Gewicht w_b (Ideen 2/16/9/19): w_b = CLASS_W[cls_b] * clip((sigma2_b/sig2_med)**0.5, 0.7, 2.2) * lum_lift(mu_b). CLASS_W protegiert glatt (z.B. 1.6), straft Textur (0.7); Maskierung erhoeht effektives Quant in Textur, Luminanz-Lift schuetzt dunkle Regionen. mean(w)=1-normiert.\n- Effektive Varianz sig2w_b = sigma2_b / w_b.\n- Hochraten-RD-Modell pro Block (geschlossen, KEINE Probe-Encodes): bei konstantem Slope lambda gilt optimal D*_b = min(sig2w_b, theta) mit theta=lambda*ln2/2; q_step_b = sqrt(12*D*_b); R_b = 0.5*log2(max(sig2w_b/D*_b,1)) [bits/Koeff] * n_coeff_b. Bloecke mit sig2w_b<theta fallen automatisch auf DC/flat (R_b=0) -> Water-Filling.\n- KLASSEN/PFAD-SPEZIFISCHE Modellkonstanten (Idee 11/15): rate_scale[cls], dist_scale[cls] kalibrieren die DCT- vs Plane-vs-VQ-Kurvenform getrennt (aus [05], mit Codec ausgeliefert). Das ist der strukturelle Differenzierer: heterogene Codepfade teilen EIN lambda, aber haben verschiedene R-D-Kurven.\n- Ziel-bpp-Treffer: Bisektion auf SKALAR lambda (40 Iter, jede = 1 vektorisierte NumPy-Auswertung ueber alle Bloecke). R(lambda) ist beweisbar monoton fallend (im Prototyp verifiziert) -> garantierte Konvergenz, exakter bpp-Treffer.\n- Min-Quality-Floor: q_step_b zusaetzlich auf qmax_cls gedeckelt, damit glatte Bloecke bei sehr niedriger bpp nicht voellig verhungern (verhindert Banding).\n\nSTUFE B (optional, opt-in via mode='accurate'/'near_lossless'): Entropie-Proxy-Refinement (Ideen 8+6+20).\n- Nach STUFE A liefert [04] echte quantisierte Indizes; ein billiger Shannon-Proxy bpp_pred = -sum p*log2 p auf den Index-Histogrammen (np.bincount, vektorisiert, KEIN rANS) korrigiert lambda per Sekanten-Schritt: lambda_neu = lambda * (bpp_ist/bpp_target)**(1/b_eff). Trifft Ziel-bpp typ. <1-3% in 1-2 Zusatz-Schritten statt 5-8 Voll-Encodes. (Prototyp: 8-Schritt-Bisektion auf log2-Offset s konvergiert auf ~0.5% Ziel.)\n- NEAR-LOSSLESS-PFAD (Idee 7+8): statt QP -> Deadzone-tau pro Block. Geschlossene PSNR->tau-Inversion: tau_b = round((sqrt(12*MSE_target)-1)/2) mit MSE_target = 255^2/10^(P/10), klassen-moduliert tau_b *= g(cls). Globaler Skalar s per Bisektion auf Entropie-Proxy fuer Ziel-bpp.\n\nOUTPUT: AllocResult mit qp_map (int16, ein q_step je Block), lambda* (float), optional tau_map (int16) und Diagnostik. Geht 1:1 an [04].",
      "rationale": "WARUM DIESE WAHL: (1) Primaerachse Rate: Constant-Slope (gleiche RD-Steigung -dD/dR=lambda fuer alle Bloecke) ist das Shoham-Gersho/KKT-Optimum der Bit-Allokation — beweisbar besser als JPEGs EINE globale Quant-Tabelle*Skalar. Bits wandern dorthin, wo sie pro dB am meisten bringen. (2) Geschwindigkeit (kritisch bei reinem Python/CPU): STUFE A braucht KEINE Probe-Encodes — nur Varianzen + eine Skalar-Bisektion ueber ein vorberechnetes NumPy-Array. Im Prototyp <1ms fuer ein Kodak-Bild; ersetzt die teuerste RDO-Komponente (Lambda-Suche per Trial-Encode) durch reine Arithmetik. (3) Struktureller Differenzierer: Die klassen/pfad-spezifischen RD-Modellkonstanten (DCT vs Plane vs VQ) sind NUR in einem Multi-Repraesentations-Codec definiert — kein Single-Path-Codec kann das. Das gemeinsame lambda klammert die heterogenen Repraesentationen sauber. (4) SSIM/Perzeption: das w_b-Gewicht koppelt die Allokation an die SSIM-Sichtbarkeit (Kontrast-/Luminanzmaskierung) und nutzt GENAU die Statistiken (var, mu), die [02] ohnehin erzeugt — Null Zusatzkosten. (5) Robustheit/Praezision: STUFE B faengt die Faelle ab, in denen das Hochraten-Modell bei sehr niedriger bpp (0.2-0.5, MARCs Zielzone) ungenau wird, ohne die Default-Geschwindigkeit zu opfern. VERWORFEN als Default: reine RDO-Trial-Encodes (zu langsam in Python); reine LUT-Tabellen ohne Bildanpassung (Idee 5 — weniger praezise pro Bild); separate Blockweise-Lambda-Karten mit teurem Cache (Idee 6 — nur fuer progressiv noetig). Prototyp auf kodim05 bestaetigt: exakter bpp-Treffer, monotone R(lambda), plausible Klassen-Bit-Shares (Textur 0.65 bpp / glatt 0.01 bpp bei 1.0 bpp Gesamt).",
      "novelty_contribution": "Drei strukturell neue Punkte gegenueber JPEG/WebP/JPEG XL: (A) EIN globales lambda, aber PRO REPRAESENTATIONSPFAD eigene, offline kalibrierte R-D-Modellkonstanten — Cross-Representation-Slope-Ausgleich ueber physisch unterschiedliche Codepfade (DCT/Plane/VQ). Existiert in keinem Standardcodec, weil dort alle Bloecke denselben Pfad/dieselbe Kurvenform teilen. (B) Geschlossene, encode-freie varianz-basierte Water-Filling-Allokation auf VARIABLEN Quadtree-Blockgroessen (n_coeff_b variiert) — flache Bloecke werden analytisch auf 0 Bits / DC-only gesetzt, was eine feste Zickzack-Quanttabelle nicht kann. (C) Perzeptuelles Gewicht w_b ist KLASSENBEDINGT (Maskierung nur in Textur, Anti-Banding-Cap nur in glatt, Luminanz-Lift global) und an die Repraesentationswahl gekoppelt — eine klassen-aware perzeptuelle Allokation, die die Block-DCT-Codecs strukturell nicht haben. Zusatz: bpp-Treffer ohne teure Multi-Pass-Suche (Skalar-Bisektion + optionaler Entropie-Proxy-Sekantenschritt statt N Voll-Encodes).",
      "interface": "Datei: verfahren/07_rate_control/rate_control.py. Reine NumPy/OpenCV, keine scipy/torch.\n\n# --- Eingabe-Datentypen (aus [02]/[03], als dataclass/Dict) ---\n# BlockStats: parallele NumPy-Arrays gleicher Laenge n_blocks (struct-of-arrays, vektorisierbar)\n#   sigma2:   np.ndarray float64 (n,)  # Aktivitaets-/Energievarianz je Block (Luma od. AC-Residuen)\n#   mu:       np.ndarray float64 (n,)  # mittlere Luma je Block (fuer Luminanz-Lift)\n#   cls:      np.ndarray int8    (n,)  # Inhaltsklasse 0=SMOOTH,1=EDGE,2=TEXTURE (erweiterbar)\n#   path:     np.ndarray int8    (n,)  # Repraesentationspfad 0=DCT,1=PLANE,2=VQ\n#   n_coeff:  np.ndarray int32   (n,)  # Anzahl quantisierter Koeffizienten je Block (~Pixelzahl)\n#   size:     np.ndarray int16   (n,)  # Blockkantenlaenge {8,16,32}\n# CalibTable (aus [05], mit Codec ausgeliefert; sinnvolle Defaults eingebaut):\n#   class_w:    np.ndarray float64 (n_cls,)        # perzeptuelle Sensitivitaet, Default [1.6,0.9,0.7]\n#   rate_scale: np.ndarray float64 (n_path,)       # Pfad-R-Modell-Skalierung, Default [1,1,1]\n#   dist_scale: np.ndarray float64 (n_path,)       # Pfad-D-Modell-Skalierung, Default [1,1,1]\n#   qmin/qmax:  float                              # q_step-Clamp, Default 1.0 / 255.0\n#   mask_p:float=0.5; mask_lo:float=0.7; mask_hi:float=2.2; lum_gamma:float=0.4\n\n# --- AUSGABE ---\n# @dataclass AllocResult:\n#   q_step:   np.ndarray float64 (n,)   # Quantisierungsschritt je Block  -> direkt an [04]\n#   lam:      float                     # globales Lagrange-lambda (Header/Diagnostik)\n#   est_bpp:  float                     # geschaetzte Bitrate (Modell bzw. Proxy)\n#   tau:      np.ndarray int16 (n,) | None  # near-lossless Deadzone je Block (nur near_lossless-Modus)\n#   diag:     dict                      # {'iters':int,'class_bpp':np.ndarray,'converged':bool}\n\n# --- HAUPT-API ---\ndef allocate(stats: BlockStats, target_bpp: float, total_pixels: int,\n             calib: CalibTable | None = None,\n             mode: str = \"fast\") -> AllocResult:\n    '''mode in {\"fast\",\"accurate\",\"near_lossless\"}. \"fast\"=Stufe A geschlossen.\n       \"accurate\"=A + Entropie-Proxy-Sekantenkorrektur (Callback noetig).\n       \"near_lossless\"=tau-Pfad. Trifft target_bpp; bei mode!=\"fast\" optional refine_cb.'''\n\ndef allocate_quality(stats: BlockStats, quality: float, total_pixels: int,\n                     calib: CalibTable | None = None) -> AllocResult:\n    '''quality in [1..100] (JPEG-aehnlich) -> lambda via offline gefitteter q_base=a*lam**b Tabelle.\n       Bequemer Qualitaets-Knopf ohne bpp-Ziel.'''\n\n# --- BAUSTEINE (einzeln testbar, alle vektorisiert) ---\ndef perceptual_weight(stats, calib) -> np.ndarray:   # (n,) w_b, mean(w)==1\ndef model_rate_dist(sig2w, n_coeff, path, lam, calib): # -> (R_bits (n,), q_step (n,)) geschlossen\ndef total_bpp(sig2w, n_coeff, path, lam, calib, total_pixels) -> float  # Modellrate\ndef solve_lambda(sig2w, n_coeff, path, calib, target_bpp, total_pixels,\n                 n_iter=40) -> tuple[float,bool]    # log-Bisektion, (lam, converged)\ndef lambda_to_qmap(stats, lam, calib) -> np.ndarray  # (n,) q_step, mit qmin/qmax-Clamp+Floor\n\n# --- Entropie-Proxy (Stufe B, [06]-frei) ---\ndef shannon_bpp(indices: np.ndarray, total_pixels: int) -> float  # -sum p log2 p, np.bincount\ndef refine_lambda(lam0, measured_bpp, target_bpp, b_eff=0.5) -> float  # Sekanten-Update\n\n# --- near-lossless ---\ndef psnr_to_tau(target_psnr: float, max_val=255.0) -> int           # geschlossen\ndef tau_map(stats, target_psnr, calib) -> np.ndarray                # (n,) int16, klassen-moduliert",
      "depends_on": "EINGANG (lesend): [02] segmentation_analysis liefert BlockStats-Felder cls, size, mu und die Quadtree-Blattliste; sigma2 kommt entweder aus [02] (Luma-Varianz) ODER aus [03] transform (AC-Koeffizienten-Energie/Residuenvarianz) — Vereinbarung: [03] fuellt sigma2 und n_coeff, [02] fuellt cls/mu/size. path wird von [02]/[03] gesetzt (welche Repraesentation je Block gewaehlt). [05] dictionary_learning liefert die CalibTable (class_w, rate_scale, dist_scale, a/b fuer quality-Mapping) — offline aus dem Kodak-Trainingsset gefittet; bis dahin greifen die eingebauten Defaults. AUSGANG (schreibend): [04] quantization konsumiert q_step (und tau im near-lossless) direkt als Pro-Block-Quantisierungsschritt; lam wandert in den [08]-Header. Fuer mode='accurate' braucht allocate() einen Callback refine_cb(q_step)->indices, der [04] einmal aufruft und die quantisierten Indizes fuer shannon_bpp zurueckgibt (lose Kopplung, keine harte Importabhaengigkeit). [09] integration verdrahtet den Aufruf. KEINE Abhaengigkeit auf [06] rANS (Entropie-Proxy ersetzt ihn in der Suchschleife). metrics.py-Konventionen (BT.601-Luma, MSE-PSNR, SSIM C2) werden fuer psnr_to_tau und die w_b-Herleitung gespiegelt.",
      "impl_steps": "1. Modul-Skelett verfahren/07_rate_control/rate_control.py + dataclasses BlockStats, CalibTable, AllocResult; Default-CalibTable als Modulkonstante (class_w=[1.6,0.9,0.7], rate_scale/dist_scale=[1,1,1], qmin=1, qmax=255).\n2. perceptual_weight(): w = class_w[cls] * clip((sigma2/median(sigma2))**mask_p, mask_lo, mask_hi) * (mu/128)**(-lum_gamma)-Lift, dann global auf mean==1 normieren (damit lambda-Skala stabil). Nur in TEXTURE die Maskierung voll wirken lassen, in SMOOTH per min(.,1.0) cappen (Anti-Banding) -> klassenbedingt.\n3. model_rate_dist(): theta=lam*ln2/2; sig2w=sigma2/w; D*=clip(min(sig2w*dist_scale[path], theta), (qmin^2/12)); q_step=sqrt(12*D*); R=0.5*log2(max(sig2w/D*,1))*rate_scale[path]; bits=R*n_coeff. Voll vektorisiert ueber alle Bloecke.\n4. total_bpp() + solve_lambda(): log-Raum-Bisektion lo=1e-4,hi=1e6, 40 Iter, mid=sqrt(lo*hi); converged-Flag wenn |bpp-target|/target<1e-3. (Monotonie im Prototyp verifiziert.)\n5. lambda_to_qmap(): q_step aus model_rate_dist, dann clip(qmin,qmax) UND klassenabhaengiger Quality-Floor qmax_cls (SMOOTH strenger gedeckelt).\n6. allocate(mode='fast'): solve_lambda -> lambda_to_qmap -> AllocResult mit diag (class_bpp via np.add.at ueber cls).\n7. shannon_bpp() + refine_lambda(): np.bincount auf (indices-min), p=hist/N, H=-sum p log2 p; Sekanten-Update. allocate(mode='accurate') ruft refine_cb fuer 1-2 Korrekturschritte.\n8. psnr_to_tau()/tau_map(): geschlossene Inversion + g(cls)-Modulation; allocate(mode='near_lossless') liefert tau statt q_step.\n9. allocate_quality(): a*lam**b-Inversion (a,b Default aus 1 Kalibrierbild; spaeter aus [05]).\n10. Vektorisierungs-Audit: keine Python-Schleife ueber Bloecke ausser der 40-Iter-Skalar-Bisektion. Edge-Cases: leere Klasse, sigma2=0 (-> q_step=qmin, R=0), target_bpp unerreichbar (clamp an [bpp(hi),bpp(lo)] + converged=False).\n11. tests/ schreiben (s.u.), gegen kodim05/kodim01 laufen lassen.",
      "unit_tests": "Datei: verfahren/07_rate_control/tests/test_rate_control.py (pytest, nutzt testdata/kodim*.png + common/metrics).\nT1 monotonicity: total_bpp(lam) streng monoton fallend ueber lam in [1e-3,1e6] auf Zufalls-sigma2 (1000 Bloecke) -> assert np.all(np.diff(bpps)<=0).\nT2 bisection_hits_target: fuer target in {0.25,0.5,1.0,2.0} auf kodim05-BlockStats: |allocate.est_bpp - target| < 1e-3 und diag['converged'] True.\nT3 waterfilling_smooth_starved_low_rate: bei target=0.25 ist mean(q_step[cls==SMOOTH]) hoeher (groeber, ausser Floor greift) und class_bpp[TEXTURE] > class_bpp[SMOOTH] -> Bits folgen Aktivitaet.\nT4 perceptual_weight_props: mean(perceptual_weight)≈1 (±1e-6); w[SMOOTH] > w[TEXTURE] bei gleicher sigma2 (Schutz glatter Regionen); w monoton in mu fallend (Dunkel-Schutz).\nT5 quality_floor: kein q_step > qmax und keiner < qmin; SMOOTH q_step <= qmax_cls (Anti-Banding-Cap haelt).\nT6 path_separation: zwei Bloecke gleicher sigma2 aber path=DCT vs path=PLANE mit unterschiedlichem rate_scale -> verschiedene R_bits (Cross-Representation-Modell aktiv).\nT7 shannon_proxy_monotone: shannon_bpp(round(coeff/q)) faellt monoton in q (8x8-DCT eines Testbilds); proxy >= 0.\nT8 refine_convergence: synthetischer Index-Generator mit bekannter Rate; refine_lambda + 2 Iterationen bringt |bpp-target|/target < 0.05.\nT9 psnr_to_tau_closed: rekonstruiere Deadzone-Quant mit tau=psnr_to_tau(P) auf kodim01, gemessene PSNR (metrics.psnr) liegt innerhalb ±1.5 dB von P fuer P in {40,45,50}.\nT10 vectorized_no_loops: allocate() auf 24-Block- und 4000-Block-Eingabe liefert konsistente Shapes; Laufzeit < 50ms (Smoke-Perf, nicht hart).\nT11 determinism: zweimal allocate() mit gleichem Input -> bitidentische q_step.\nT12 degenerate: sigma2 all-zero -> q_step==qmin, est_bpp==0, kein NaN/inf; leere TEXTURE-Klasse bricht class_bpp nicht.",
      "backup_ideas": "Falls CRSA-Stufe-A-Modell die bpp zu ungenau trifft (z.B. stark nicht-gauss'sche Bloecke): (R1) Idee 5/15 RD-LUT pro Klasse als Fallback-Tabelle statt geschlossenem Modell — robuster, aber weniger bildadaptiv; mit Codec ausliefern. (R2) Idee 18 RHO-LOCK: nonzero-Count-Proxy (rho-Domaene) statt Shannon-Entropie fuer die Refinement-Schleife — noch billiger, gut fuer 0.2-0.7 bpp. (R3) Idee 6 Pro-Block-RD-Cache fuer den progressiven/2-Pass-Modus (Cache der vollen RD-Kurve, beliebig oft lambda-Auswertung ohne Re-Encode) — nur wenn progressiver Layer-Aufbau gewuenscht. (R4) Idee 3 implizite, NICHT signalisierte QP-Ableitung aus der Grob-Rekonstruktion — spart QP-Seiteninfo bei sehr niedriger bpp (Header dominiert), Decoder rekonstruiert qp_map deterministisch; integrierbar als Header-Sparmodus. (R5) Idee 4 geschlossene Power-Law-Inversion r_c(lam)=k_c*lam**-rho_c als Newton-Start fuer solve_lambda (schnellerer Bisektions-Startwert). (R6) Idee 9/CSF-Bandgewicht (DCT-Mittenfrequenz-Energie) als zusaetzlicher w_b-Faktor, falls SSIM-Gewinn zu klein. (R7) Idee 10 SSIM-Linearisierung w_b=1/(C2+2*sigma^2) als alternative w_b-Form (direkt aus SSIM-Nenner), falls die multiplikative Maskierung perzeptuell schlechter abschneidet. Erweiterbarkeit: n_cls/n_path sind parametrisch, weitere Klassen (Haut, Banding-anfaellig) und Pfade (KLT, directional) ohne Interface-Bruch ergaenzbar."
    },
    {
      "area": "08_bitstream_format — .marc Container, Header, Quadtree-/Klassen-Serialisierung, Block-Payloads, Versionierung (round-trip-exakt)",
      "chosen_design": "SYNTHESE: \"Sectioned Struct-of-Streams Container\" — ein TLV/Chunk-Rahmen (PNG/RIFF-artig) mit fixem 32-Byte-Header, dessen PAYLOAD aber NICHT blockverschachtelt ist (kein Array-of-Structs wie JPEG/WebP), sondern klassen-/feldweise in homogene Spalten-Streams demultiplext und gruppiert. Das ist der Headline-Differenzierer dieses Bereichs und genau auf MARCs Multi-Repraesentations-Kern zugeschnitten.\n\nGewaehlte Ideen (kombiniert):\n- [8]+[13] PHYSISCHES LAYOUT: fixer 32-Byte-Header (MAGIC b'MARC', version_major/minor, profile, flags, W, H, max_depth, min_block, num_classes, color_space) + Folge typisierter Sektionen. Jede Sektion = section_type(u8) + length(LEB128 uvarint) + payload + crc32(u32 LE, zlib.crc32 ueber payload). Unbekannte Sektionen werden length-gesteuert uebersprungen -> Vorwaerts-Kompatibilitaet. Footer-Sektion 0xFF LOSSLESS_VERIFY mit crc32 des Originalbildes fuer den verlustfreien Beweis.\n- [3]+[6] STRUKTUR-SEKTION (0x02 QUADTREE_CLASS): EIN gemeinsamer Symbolstrom ueber Pre-order-DFS, Alphabet {SPLIT, LEAF_c0..LEAF_c(K-1)}. Damit ist das Split-Bit selbst entropiefaehig und Split/Klasse-Korrelation genutzt. MVP serialisiert byte-effizient via np.packbits (Split-Bits 1 bit, Klassen 2 bit/Leaf); die context-rANS-Codierung mit ctx=(depth,parent_symbol) ist optionaler Pfad ueber Bereich 06 (flag-gated), sodass MVP ohne 06-Abhaengigkeit lauffaehig ist.\n- [1]+[11]+[18]+[19] PAYLOAD-ORGANISATION (Headline): Block-Payloads werden in pro-Klasse gruppierte, homogene Sub-Streams demultiplext. Sektion 0x05 BLOCK_PAYLOADS enthaelt je Klasse einen laengen-praefixierten Sub-Stream (Konkatenation aller Block-Bytes der Klasse k in DFS-Reihenfolge). Zuordnung Leaf->Position IMPLIZIT ueber DFS-Reihenfolge + per-Class-Zaehler -> keine expliziten Block-IDs/Offsets noetig. So sieht der rANS aus Bereich 06 pro Sub-Stream eine stationaere, klassenreine Verteilung statt einer gemischten.\n- [2]+[4] SIDE-INFO-SEKTION (0x06 BLOCK_SIDEINFO): QP-Delta, Mode-Flags, DC/Mittelwert glatter Bloecke praediktiv/delta-kodiert. DC glatter Bloecke via MED-Praediktor (links+oben Block-DC) entlang Morton-Order der Leaves; QP als Delta zum Morton-Vorgaenger; Mode-Flags run-length-vorverarbeitet. Streams getrennt, jeder homogen.\n- [5] SoA-OFFSET-INDEX innerhalb jeder Payload-Sektion: pro Sub-Stream EIN uint32-lengths-Array + EIN Single-Blob; Decode rekonstruiert Grenzen per einzelnem np.cumsum (O(1) Random-Access, kein byteweises Traversieren).\n\nKlare Schichtung: Bereich 08 ist der reine (De)Serialisierer. Er bekommt fertige Symbol-/Byte-Streams (vom Encoder-Frontend ueber 02/03/04) bzw. fertige rANS-Bytes (von 06) und packt/entpackt sie in das .marc-Layout. 08 ruft 06 NICHT selbst auf (Entkopplung); die rANS-Bytes werden von 09 durchgereicht. Reihenfolge der Sektionen fix: HEADER(0x01) -> QUADTREE_CLASS(0x02) -> MODEL_REFS(0x04) -> RATE_META(0x07) -> BLOCK_SIDEINFO(0x06) -> BLOCK_PAYLOADS(0x05) -> [LOSSLESS_VERIFY(0xFF)].",
      "rationale": "Warum diese Auswahl die staerkste ist:\n\n1. KOHAERENZ MIT MARC-KERN: Die 20 Ideen zerfallen in drei Cluster — (A) TLV/Container-Rahmen, (B) Struct-of-Streams/klassen-homogene Gruppierung, (C) praediktive Strukturkodierung. Cluster B (Ideen 1,4,11,18,19) ist die eigentliche Neuheit, die NUR fuer einen Multi-Repraesentations-Codec existieren kann und gleichzeitig die Primaerachse RATE bedient: homogene Sub-Streams geben dem rANS in 06 scharfe Modelle statt gemischter Statistik (typisch 5-15% Payload-Ersparnis laut den Ideen). Das ist der groesste, billigste Rate-Hebel und strukturell different zu JPEG/WebP/JXL (alle Array-of-Structs/MCU-interleaved).\n\n2. RATE-DOMINANZ BEI NIEDRIGER bpp: Bei Zielraten 0.15-0.5 bpp ist der Header-/Side-Info-Anteil relativ am groessten. Die joint (Split,Class)-Symbolcodierung [3]/[6] + delta-kodierte Side-Info [2]/[4] druecken genau diesen Overhead. Bei glatten Bildern kollabieren Split-Bits (~0.1 bit) und glatte-Block-DCs (MED-Residuen ~0) — Gratis-Gewinn auch fuer near-lossless.\n\n3. KORREKTHEIT/VERSIONIERUNG HART GARANTIERT: TLV+per-Section-CRC32 [8]/[13] gibt fruehe Fehlererkennung, skip-unknown gibt Vorwaerts-Kompatibilitaet, der LOSSLESS_VERIFY-Footer [8] liefert einen eingebauten Round-Trip-Beweis — direkt auf das Erfolgskriterium 'exakt round-trip-faehig'. Bei einem neuen Dateiformat ist Selbstbeschreibung + Integritaet nicht optional.\n\n4. GESCHWINDIGKEIT QUASI GRATIS: SoA-Blob+cumsum [5] und packbits-Strukturserialisierung [6] sind voll NumPy-vektorisiert (kein Per-Block-Python-Loop), passen zur CPU-only/NumPy-Randbedingung und ermoeglichen spaeter Multi-Core-Block-Decode.\n\n5. ENTKOPPLUNG: 08 ruft 06 nicht selbst — verhindert Interface-Drift (laut Master-Spec das Hauptrisiko). 08 ist ein reiner Byte-Layouter mit deterministischem, testbarem Roundtrip.\n\nVerworfen fuer MVP, weil orthogonal/teurer: perzeptuelle Truncation [9], JND-Side-Channel [10], self-describing Opcode-Registry [12], inline-Modell-RDO [20] — wertvoll, aber Komplexitaet/Abhaengigkeiten ueberwiegen den MVP-Nutzen; landen in backup_ideas.",
      "novelty_contribution": "Der Kern-Differenzierer dieses Bereichs gegenueber ALLEN Baselines: Das .marc-Format ist ein STRUCT-OF-STREAMS-Container, der heterogene Per-Region-Block-Payloads physisch nach Repraesentationsklasse in homogene Spalten-Streams demultiplext, mit IMPLIZITER Positionszuordnung ueber die DFS-Quadtree-Reihenfolge (null Block-Pointer). JPEG (MCU-interleaved), WebP/VP8 (feste Block-Syntax) und JPEG XL (variable Bloecke, aber kontext-interleavtes Layout) sind im Kern Array-of-Structs und kennen keine je-Region-Repraesentationsklasse — sie KOENNEN diese Gruppierung strukturell nicht vornehmen. Aus MARCs Multi-Repraesentation (die normalerweise Container-Komplexitaet ADDIERT) wird so ein Entropie-VORTEIL: jeder Sub-Stream hat klassenreine Statistik fuer scharfe rANS-Modelle.\n\nZweite Neuheit: gemeinsame (Split,Klasse)-Verbund-Symbolcodierung des Quadtree mit tiefen-/parent-Kontext — kein klassischer Codec koppelt Quadtree-Topologie mit einer Repraesentations-Klassenwahl in einem Symbolstrom. Dritte: der eingebaute LOSSLESS_VERIFY-Footer (Original-CRC32) als selbstpruefender Korrektheitsbeweis, den kein Mainstream-Bildcontainer hat.",
      "interface": "Modul: verfahren/08_bitstream_format/container.py  (reines NumPy + stdlib struct/zlib; KEINE Laufzeit-Abhaengigkeit auf cv2/Bereich 06)\n\n# ---------- Datencontainer (dataclasses) ----------\n@dataclass\nclass MarcHeader:\n    version_major:int; version_minor:int      # u8,u8\n    profile:int            # u8: 0=lossy,1=near_lossless,2=lossless\n    flags:int              # u8 bitfield: bit0=context_coded_structure, bit1=has_verify_footer, bit2=chroma_subsampled\n    width:int; height:int  # u32,u32\n    max_depth:int; min_block:int   # u8,u8 (log2)\n    num_classes:int        # u8 (K, 1..8)\n    color_space:int        # u8: 0=YCoCg,1=RGB\n\n@dataclass\nclass QuadtreeStruct:\n    split_flags:np.ndarray   # uint8(0/1), pre-order-DFS, ein Eintrag pro besuchtem Knoten\n    leaf_classes:np.ndarray  # uint8(0..K-1), ein Eintrag pro Blatt in DFS-Reihenfolge\n\n@dataclass\nclass BlockSideInfo:\n    qp:np.ndarray            # int16, ein Wert pro Blatt (DFS-Reihenfolge)\n    modes:np.ndarray         # uint8, ein Wert pro Blatt\n    smooth_dc:np.ndarray     # int16, DC nur fuer glatte Bloecke (len=#smooth-leaves)\n\n@dataclass\nclass BlockPayloads:\n    per_class:list[list[bytes]]   # len==num_classes; je Block ein bytes-Objekt (DFS-geordnet)\n\n@dataclass\nclass MarcContainer:\n    header:MarcHeader; quadtree:QuadtreeStruct; sideinfo:BlockSideInfo\n    payloads:BlockPayloads; model_refs:np.ndarray   # uint16, evtl leer\n    rate_meta:dict   # {'lambda':float,'qp_base':int}\n\n# ---------- Top-level ----------\ndef write_marc(container:MarcContainer, original_rgb:np.ndarray|None=None) -> bytes\n    # serialisiert; bei profile>=1 + original_rgb gegeben: haengt LOSSLESS_VERIFY(crc32(original.tobytes())) an. Deterministisch.\ndef read_marc(blob:bytes) -> MarcContainer\n    # parst; validiert MAGIC/version/alle Section-CRC32; skip unbekannte Sektionen; wirft MarcFormatError.\ndef verify_lossless(blob:bytes, reconstructed_rgb:np.ndarray) -> bool\n    # liest Verify-Footer, vergleicht crc32; False wenn Footer fehlt.\n\n# ---------- Bausteine (einzeln getestet, vektorisiert) ----------\ndef pack_quadtree(qt:QuadtreeStruct, num_classes:int) -> bytes\ndef unpack_quadtree(payload:bytes, num_classes:int) -> QuadtreeStruct\ndef dfs_leaf_coords(split_flags:np.ndarray, W:int, H:int, root_size:int) -> list[tuple[int,int,int]]\n    # kanonischer DFS-Replay (Kinder NW,NE,SW,SE) -> (y,x,size) je Blatt. Geometrie-Wahrheit auch fuer Bereich 02.\ndef encode_sideinfo(si:BlockSideInfo, leaf_coords, smooth_mask:np.ndarray) -> bytes\ndef decode_sideinfo(payload:bytes, leaf_coords, smooth_mask:np.ndarray) -> BlockSideInfo\ndef pack_payload_section(payloads:BlockPayloads) -> bytes\ndef unpack_payload_section(payload:bytes, num_classes:int) -> BlockPayloads\n\n# ---------- Low-level ----------\ndef write_uvarint(v:int) -> bytes\ndef read_uvarint(buf:memoryview, pos:int) -> tuple[int,int]   # (wert, neue_pos)\ndef write_section(stype:int, payload:bytes) -> bytes          # type ++ uvarint(len) ++ payload ++ crc32\ndef iter_sections(blob:bytes, pos:int) -> Iterator[tuple[int,memoryview]]   # CRC-geprueft\n\n# Konstanten:\nMAGIC=b'MARC'; VERSION=(0,1)\nSEC_HEADER=0x01; SEC_QUADTREE_CLASS=0x02; SEC_MODEL_REFS=0x04; SEC_BLOCK_PAYLOADS=0x05\nSEC_BLOCK_SIDEINFO=0x06; SEC_RATE_META=0x07; SEC_VERIFY=0xFF\nHEADER_STRUCT='<4sBBBBIIBBBB'  # +pad auf 32 Byte; Little-Endian ueberall\n\n# Invariante: read_marc(write_marc(c))==c feldweise; write_marc deterministisch (bytegleich bei gleichem Input).",
      "depends_on": "- Bereich 02 (segmentation_analysis): liefert QuadtreeStruct (split_flags in DFS, leaf_classes). DFS-Reihenfolge + Kind-Reihenfolge GELOCKT: pre-order DFS, Kinder NW,NE,SW,SE. 08 stellt dfs_leaf_coords als kanonische Referenz bereit, die 02 mitbenutzt.\n- Bereich 03/04 (transform/quantization): liefern die fertigen Block-Byte-Payloads pro Block (bereits quantisiert). 08 behandelt sie als opake bytes.\n- Bereich 06 (context_entropy): OPTIONAL und entkoppelt. Die finale Entropiecodierung der Sub-Streams (und optional die context-codierte Struktur bei flag bit0) macht NICHT 08, sondern 09 ruft 06; 08 erhaelt/liefert nur fertige rANS-Bytes. MVP funktioniert mit rohen packbits-Streams ohne 06.\n- Bereich 05 (dictionary_learning): liefert nur Modell-IDs (uint16) fuer SEC_MODEL_REFS; 08 bettet keine Modelle ein (Default-ID-Referenz).\n- Bereich 07 (rate_control): liefert rate_meta (lambda/qp_base) fuer SEC_RATE_META.\n- Bereich 09 (integration_codec): Orchestrator; ruft write_marc/read_marc, fuettert original_rgb fuer Verify-Footer, durchschleust 06-Bytes.\n- common/metrics.py: nur in Tests (bpp), keine Laufzeitabhaengigkeit.\n- stdlib: struct, zlib (crc32), dataclasses, typing. Extern nur numpy. KEIN cv2, KEIN scipy.\n\nGelockte Konventionen (gegen Drift): (a) Little-Endian ueberall. (b) DFS pre-order, Kinder NW,NE,SW,SE. (c) Klassen-IDs 0..K-1, 0=glatt/smooth. (d) Morton-Order der Leaves aus leaf_coords (y,x) via Bit-Interleave. (e) LEB128 uvarint fuer variable Laengen/kleine Header-Zahlen.",
      "impl_steps": "1. Primitive: write_uvarint/read_uvarint (LEB128), write_section/iter_sections (type+uvarint-len+payload+crc32 via zlib.crc32). Isoliert testen.\n2. Header: struct.pack(HEADER_STRUCT,...) auf feste 32 Byte (Padding). MAGIC- und Version-Major-Check in read_marc.\n3. dfs_leaf_coords: iterativer DFS (expliziter Stack, kein Rekursionslimit) ueber split_flags -> leaf_coords. Kanonische Geometrie-Wahrheit (auch Bereich 02). Test gegen handgerechnete kleine Baeume.\n4. pack_quadtree/unpack_quadtree: split_flags via np.packbits/unpackbits (mit n_flags-LEB128-Praefix fuers Trimmen); leaf_classes 2-bit-gepackt (reshape(-1,4)+Bit-Shift) bei K<=4, sonst uint8-roh. Vollvektorisiert, keine Python-Bit-Schleife.\n5. encode_sideinfo/decode_sideinfo: (a) smooth_mask aus leaf_classes==0; (b) smooth_dc per MED-Praediktor (links/oben/oben-links Block-DC via Morton-Nachbar-Lookup) -> Residuen, zigzag-gefaltet; (c) QP-Delta zum Morton-Vorgaenger; (d) Mode-RLE (mode,run)-Paare. Drei laengen-praefixierte Sub-Streams. Decode spiegelt bit-exakt (ganzzahlig).\n6. pack_payload_section/unpack_payload_section: pro Klasse uint32-lengths(.astype('<u4').tobytes) ++ b''.join(blocks); Sektion = uvarint(K) ++ je Klasse [uvarint(n), lengths_bytes, blob]. Decode: np.frombuffer lengths, offsets=cumsum, memoryview-Slices (zero-copy).\n7. write_marc: Sektionen in fixer Reihenfolge bauen, optional SEC_VERIFY(crc32(original_rgb.tobytes())). Deterministisch.\n8. read_marc: iter_sections, dispatch per type, unbekannte skippen, CRC validieren, Container bauen. verify_lossless separat.\n9. MarcFormatError-Exception + defensive Checks (Laenge, CRC, MAGIC, Version-Major).\n10. Optionaler Pfad (flag bit0): liefert 09 context-codierte Struktur-Bytes (von 06), legt write_marc sie statt packbits in SEC_QUADTREE_CLASS; read_marc reicht sie zurueck. 08 codiert selbst nicht.\n11. Modulkopf mit Byte-Layout-Diagramm; design.md mit Sektionstabelle.",
      "unit_tests": "tests/test_container.py (pytest, nur numpy):\n\nROUNDTRIP (Kern):\n- test_uvarint_roundtrip: Werte 0,1,127,128,16383,16384,2**31; read(write(v))==(v, korrekte pos).\n- test_section_roundtrip_and_crc: write_section/iter_sections payload bytegleich; ein payload-Byte flippen -> iter_sections wirft (CRC-Mismatch).\n- test_header_roundtrip: zufaellige MarcHeader -> pack/unpack feldgleich; fixe 32-Byte-Laenge.\n- test_dfs_leaf_coords_known_tree: handgerechneter Baum (root 64x64, ein Split) -> 4 Bloecke 32x32 NW,NE,SW,SE; tiefer Baum -> Summe Blattflaechen == W*H, Pixel-Abdeckungsmaske voll, keine Ueberlappung.\n- test_quadtree_pack_roundtrip: random split_flags+leaf_classes (K=2,3,4) -> pack/unpack array_equal.\n- test_sideinfo_roundtrip_exact: random qp/modes/smooth_dc -> encode/decode bit-exakt; MED-Invertierbarkeit.\n- test_payload_section_roundtrip: per_class mit variabel langen bytes (auch leere Bloecke, leere Klasse) -> identische bytes; cumsum-Offsets korrekt.\n- test_full_container_roundtrip: vollstaendiger Container -> write_marc -> read_marc feldgleich (array_equal+bytes); zweimal write == bytegleich.\n\nKORREKTHEIT/EDGE:\n- test_verify_footer_lossless: write_marc(c,original) -> verify_lossless True; verfaelschtes rec -> False; ohne Footer -> False.\n- test_bad_magic_raises / test_version_mismatch_raises / test_truncated_blob_raises: MarcFormatError.\n- test_skip_unknown_section: eingeschobene Sektion type=0x7E wird uebersprungen, Rest parst.\n- test_single_leaf_image: split_flags leer/[0] -> ein Blatt, Roundtrip ok.\n- test_empty_class: Klasse ohne Bloecke -> per_class[k]==[] roundtrippt.\n\nRATE-SANITY:\n- test_structure_overhead_small: 256x256 glattes Bild, flacher Quadtree -> Struktur-Sektion < 0.05 bpp.\n- test_packbits_vs_raw: gepackte Struktur < 1-Byte-pro-Symbol-Variante.\n- test_class_grouping_homogeneity: synthetische Payloads zweier Klassen verschiedener Byte-Verteilung -> Sub-Streams kontiguierlich + korrekt segmentiert (kein Cross-Class-Leak).\n\nPERFORMANCE (smoke):\n- test_decode_random_access_no_loop: 10k Bloecke -> offsets ein einzelner cumsum-Call (<~5ms), Random-Access auf Block i korrekt.",
      "backup_ideas": "NACHRANGIG (wertvoll, additiv via neue Sektionstypen/Profil-Flags andockbar dank TLV+skip-unknown):\n\n- [9] Perzeptuell sortierter, byte-truncierbarer Container (Saliency/foveal-Layer + layer_offset_table + DC-erst): starker Sekundaerachsen-Gewinn (progressive Vorschau/Streaming-Truncation), braucht DC/AC-Trennung + Saliency aus 01/02. Als optionales Profil-Flag nachruestbar.\n- [10] Per-Region JND-/Dead-Zone-/Dither-Byte (1 Byte/Leaf perzeptueller Side-Channel): echter Rate-bei-Qualitaet-Hebel, koppelt eng an Quantisierer 04 + Decoder-Deblocking; als zusaetzliche SEC_PERCEPTUAL-Sektion spaeter.\n- [12] Self-describing Repr-Opcode-Registry (TLV-Capability + datengetriebener Dispatch): elegant fuer Erweiterbarkeit ueber Format-Bumps, aber Overkill fuer 2-4 feste MVP-Klassen.\n- [20] Per-Klasse Modell-Manifest mit Default-ID-vs-Inline-RDO-Switch + FNV-Hash: Default-ID-Referenz ist schon drin (SEC_MODEL_REFS); Inline-Custom-Dict-Pfad + RDO haengt an 05/09, nur als optionaler Inline-Blob-Typ vorgesehen.\n- [16] Klassen-konditionierte Exp-Golomb/rANS-Laengenkodierung statt fixer uint32-lengths: senkt Laengen-Overhead weiter (~16->~8 bit/Block), braucht 06-Kopplung; das gewaehlte SoA-uint32-lengths [5] ist der einfachere, vektorisierte MVP-Schritt (komprimiert sich homogen ohnehin gut). Drop-in-Ersatz spaeter.\n- [17] Klassen-Vererbungs-Bit (Kinder erben Elternklasse) zusaetzlich zur joint (Split,Class)-Codierung: weiterer Strukturbit-Spar-Hebel bei tiefen Baeumen (30-60% Klassenbits); orthogonal zu [3], als Encoder-Vorverarbeitung nachruestbar.\n- [14]/[15] separate Split-Bitmap+RLE-Klassen bzw. context-konditionierte getrennte Streams: von der gewaehlten [3]-Joint-Codierung subsumiert; [15]s 2D-Nachbar-Kontext (c_left,c_up) ist guter Upgrade-Pfad fuer die context-coded-structure-Variante via 06.\n\nBegruendung: alle obigen erhoehen entweder die Kopplung (06/04/05) oder zielen auf Sekundaerachsen. Der gewaehlte Kern (Sectioned Struct-of-Streams + joint Quadtree + delta-SideInfo + SoA-Index + Verify-Footer) liefert den groessten Rate-Gewinn bei minimaler Abhaengigkeit und voller Round-Trip-Garantie — exakt der MVP-Auftrag."
    },
    {
      "area": "09_integration_codec — End-to-End-Verdrahtung: encode()/decode(), Fehlerbehandlung, Lossy/Lossless/Near-Lossless, Rate-Targeting-Orchestrierung",
      "chosen_design": "SYNTHESE: Vier zusammengeschmolzene Ideen bilden den Integrationskern; die restlichen 16 sind Verfeinerungen einzelner Stufen (gehoeren in 03/04/06/07) oder Backups.\n\nKERN-DIFFERENZIERER (Headline): \"Cached-Frontend Two-Pass RDO mit Whole-Image-Arbiter\" = Ideen [1]+[3]+[15]+[16]+[2] verschmolzen. Der teure, lambda-UNABHAENGIGE Pipeline-Vorderteil (01 Farbtransform -> 02 Quadtree+Klassenkarte -> 03 Transform/Tokens) laeuft GENAU EINMAL und wird in einer 'plan'-Datenstruktur gecacht. Der billige, lambda-ABHAENGIGE Hinterteil (04 Quant-Roundtrip -> 06 Bit-Schaetzung) laeuft in einer aeusseren Bisektionsschleife ueber log(lambda) fuer ~6-7 Kandidaten, um eine target_bpp punktgenau zu treffen, OHNE Re-Transform. Pro Block J = D(lambda) + lambda*R; D = SSE des reinen NumPy-Quant-Roundtrips, R = Shannon-/Tabellen-Bitschaetzung. Erst nach Konvergenz wird der reale Entropiecoder (06) + Serialisierung (08) EINMAL ausgefuehrt. Zusaetzlich baut der Encoder einen schlanken Ganzbild-Globalpfad (Kandidat G) als RD-Vergleichsanker und schreibt ein 1-Byte-Flag, welcher Pfad gewann -> garantierte untere Schranke (nie schlechter als simpler Globalcodec).\n\nSTRUKTURBACKBONE: \"Plan-Dict Stage-Contract + Roundtrip-Self-Check\" = Idee [13]+[14]. EINE zentrale Datenstruktur 'plan: dict' fliesst durch alle Bereiche. encode() ist eine feste Reduce-Kette plan=m01.encode(...); plan=m02.encode(...); ...; bytes=m08.serialize(plan). Jeder Bereich hat die harte Signatur encode_stage(plan,cfg)->plan / decode_stage(plan,cfg)->plan. Ein _STAGE_CONTRACT-Dict (requires/provides Key-Mengen) wird bei jedem Stufenuebergang per set-Differenz geprueft (MarcContractError bei Drift) -> macht das Spec-Risiko #1 (Interface-Drift) zur Laufzeit sichtbar statt zu stiller Korruption. Lossy/Lossless ist EINE Pipeline, zwei Configs (Idee 14): Lossless erzwingt CLASS_RAW + reversible YCoCg-R + QP=1 + MED-Praediktor; KEINE zweite Codebasis.\n\nLOSSLESS/NEAR-LOSSLESS-HUELLE: \"Verify-or-Fallback + L_inf Top-up\" = Idee [8]+[14]. encode() ruft im Lossless/Near-Lossless-Modus IMMER intern decode() auf (recon=_decode_internal(payload)), bildet err=img-recon, und (a) near_lossless: korrigiert nur den ueber [-eps,+eps] hinausschiessenden Teil -> harte per-Pixel L_inf<=eps Garantie; (b) lossless: codiert das volle Sparse-Residuum nach. Schlaegt der Roundtrip-Check fehl, faellt der Codec hart auf einen zlib-ueber-MED-Residuen Notfallpfad zurueck (Header-Flag fallback=1) -> Lossless kann NIE eine korrupte Datei erzeugen.\n\nCONTAINER/FEHLERBEHANDLUNG: TLV-Box-Handoff an 08 = Idee [18] (reduziert). encode() uebergibt 08 eine geordnete Box-Liste (MhdR, TREE, BODY, optional TOPUP); decode() liest Boxen tag-laengen-gefuehrt -> unbekannte/korrupte Boxen ueberspringbar, truncated .marc crasht nicht. (Der eingebettete JPEG-BASE-Praediktor aus [18] ist Backup, nicht MVP.)\n\nAUSDRUECKLICH NICHT im Integrationskern (delegiert): per-Block Mode-Competition [12][17][19], perzeptuelle Lambda-Karte [9][10], Klassen-Batching [5], Skip-Fastpath [6], Pass-1-Perzentil-Kalibrierung [20], Bytecode-VM [11] -> Backups bzw. gehoeren in 02/03/04. Die Integration STELLT die Naht bereit (plan traegt 'mode_id'/'w_block'/'repr_id' optional), erzwingt sie aber nicht im MVP.",
      "rationale": "WARUM DIESE WAHL (gegen die Alternativen):\n\n1) Plan-Dict-Backbone schlaegt die Bytecode-VM [11]: Beide loesen Interface-Drift, aber [13] ist sofort implementierbar, debugbar (Stufe-fuer-Stufe) und braucht keinen Opcode-Interpreter, dessen Korrektheit selbst wieder Testaufwand kostet. [11] ist elegant fuer Vorwaertskompatibilitaet, aber das ist ein v2-Luxus; v0 braucht Korrektheit JETZT. Der Stage-Contract-Check adressiert das im Spec explizit benannte Top-Risiko ('Integrations-/Interface-Drift') direkt und mit ~20 Zeilen Code.\n\n2) Cached-Frontend-Bisektion [1/3] ist der billigste Weg zu echtem RDO + target_bpp: Der teuerste Teil (DCT/Quadtree) laeuft 1x statt 6-7x -> ~70% Zeitersparnis ggue. naivem Trial-Encode, und gleichzeitig der Primaerachsen-Gewinn (Arbeitspunkt auf der konvexen RD-Huelle statt fixer Tabellen). target_bpp als First-Class-Signatur ist ein Feature, das JPEG/WebP-Pillow gar nicht bieten -> sofort messbarer USP. Die volle per-Block-Mode-Competition [12/17/19] ist staerker fuer die Rate, aber teuer und gehoert konzeptionell in 03/04; die Integration muss nur die GECACHTE (R,D)-Tabelle bereitstellen, ueber die ein spaeteres argmin laufen kann -> ich baue die Naht, nicht die Politik.\n\n3) Whole-Image-Arbiter [2] gibt eine GARANTIE: MARCs eingebaute Achillesferse ist Quadtree+Klassenkarten-Overhead auf glatten Bildern. Ohne Arbiter koennte MARC auf manchen Kodak-Bildern SCHLECHTER als JPEG sein -> Erfolgskriterium v0 ('dominiert JPEG auf Mehrzahl') gefaehrdet. Der Arbiter (1 Byte) macht den Worst-Case nie schlechter als ein simpler Globalpfad. Billig, weil beide Kandidaten ihre Bits/SSE aus der gecachten Pipeline fast gratis liefern.\n\n4) Verify-or-Fallback [8/14] ist Robustheit fuer fast gratis: Der Decoder ist im Encoder ohnehin vorhanden (fuer den Arbiter und die Bisektion). Ihn als Korrektheits-Kritiker zu nutzen, macht Lossless bug-resistent und liefert near-lossless mit harter L_inf-Garantie aus EINEM Codepfad -> deckt die Sekundaerachse 'near-lossless' ab, ohne zweite Codebasis. JPEG/WebP bieten keine per-Pixel L_inf-Garantie.\n\nVerworfen fuer v0-Kern: [9/10] perzeptuelle Karten (Gewinn real, aber encoderseitige Verfeinerung von 04/07, nicht Verdrahtung), [5/6] Geschwindigkeits-Batching (orthogonal, bit-identisch -> spaeter ohne Risiko nachruestbar), [20] Perzentil-Kalibrierung (verbessert Mode-Trennung, aber Komplexitaet vor Korrektheit).",
      "novelty_contribution": "Der strukturelle Neuheits-Beitrag der INTEGRATIONSSCHICHT (ueber die Unterbereiche hinaus):\n\n(A) RDO-getriebenes target_bpp-Targeting OHNE Re-Transform: Klassische Codecs (JPEG/WebP via Pillow) haben nur einen Quality-Slider; der Arbeitspunkt liegt fast nie auf der konvexen RD-Huelle und nie exakt auf einer Ziel-bpp. MARC trennt die Pipeline strukturell in einen lambda-unabhaengigen (gecachten) und einen lambda-abhaengigen Teil und macht bpp zur First-Class-Signatur. Das ist nur moeglich, weil MARC Segmentierung (parameterunabhaengig) von Quantisierung (lambda-abhaengig) trennt - klassische Codecs verschmelzen Block-Split und Quant pro 8x8-Block.\n\n(B) Pro-Region gewichtete RD-Cost an der Integrationsnaht: Die Cost-Funktion J=D+lambda*R wird PRO REGION mit ihrer regionsspezifischen Repraesentation und ihrem regionsspezifischen R-Modell ausgewertet (Plane-Bloecke anderes R-Modell als DCT-Bloecke). Ein globaler Codec kann das strukturell nicht. Die Integration ist der Ort, wo die heterogenen Repraesentationen unter EINEM Lagrange-Funktional zusammenlaufen.\n\n(C) Garantierte untere Schranke via Whole-Image-Arbiter: Ein eingebauter Globalpfad-Anker + 1-Byte-Flag garantiert, dass der Multi-Repraesentations-Codec im Worst Case nie schlechter ist als ein simpler Globalcodec - eine Eigenschaft, die monolithische Codecs nicht haben (sie sind IMMER ihr eine Pfad).\n\n(D) Closed-Loop Verify-or-Fallback: Der eigene Decoder als Korrektheits-Kritiker im Encoder + deterministischer L_inf-Top-up gibt eine einstellbare Qualitaetsleiter (eps=0 lossless / eps=n near-lossless / keine TOPUP-Box = lossy) aus EINEM Codepfad mit harter per-Pixel-Garantie - bei JPEG/WebP unueblich, hier billig integrierbar.\n\n(E) Laufzeit-erzwungener Stage-Contract: Die Pipeline-Topologie ist ein deklarativer Kontrakt ueber ein Dict; Interface-Drift zwischen den 10 Bereichen wird zur Laufzeit als Exception sichtbar statt als stille Bitstrom-Korruption - direkt auf MARCs lose gekoppelte Multi-Repraesentations-Architektur zugeschnitten.",
      "interface": "PYTHON 3.14 / NumPy 2.4 / OpenCV 4.13 / Pillow 12.2. Modul: verfahren/09_integration_codec/marc_codec.py\n\n# ---- Oeffentliche API ----\ndef encode(img_rgb: np.ndarray,        # HxWx3 uint8 (oder HxW Graustufe -> intern zu HxWx3 expandiert)\n           *,\n           target_bpp: float | None = None,   # exakte Zielrate; gegenseitig exklusiv mit quality\n           quality: int | None = None,        # 1..100 (JPEG-aehnlich); Default 75 falls beide None\n           mode: str = 'lossy',               # 'lossy' | 'lossless' | 'near_lossless'\n           eps: int = 0,                       # nur near_lossless: max per-Pixel L_inf (1..255)\n           lambda_search_iters: int = 6,       # Bisektions-Iterationen fuer target_bpp\n           models: 'MarcModels | None' = None, # gelernte Tabellen aus [05]; None -> Default/Builtin\n           verify: bool = False,               # erzwingt internen Roundtrip-Check (immer True bei lossless)\n           cfg: dict | None = None             # Override-Hook fuer Stufen-Parameter + 'self_check'\n           ) -> bytes:                          # serialisierter .marc-Bytestream\n    \"\"\"Wirft MarcEncodeError bei ungueltiger Eingabe; faellt bei lossless-Roundtrip-Fehler\n       auf zlib-MED-Notfallpfad zurueck (Header fallback=1) statt zu crashen.\"\"\"\n\ndef decode(buf: bytes,\n           *,\n           models: 'MarcModels | None' = None\n           ) -> np.ndarray:                     # HxWx3 uint8 (Graustufe wird als HxWx3 zurueckgegeben)\n    \"\"\"Wirft MarcDecodeError nur bei Magic/Version-Mismatch; unbekannte TLV-Boxen werden\n       uebersprungen; truncated Stream liefert best-effort Rekonstruktion + Warnung.\"\"\"\n\ndef encode_to_file(path: str, img_rgb, **kw) -> int   # gibt Byteanzahl zurueck\ndef decode_from_file(path: str, **kw) -> np.ndarray\n\n# ---- Modellcontainer (von [05] geliefert/geladen) ----\n@dataclass(frozen=True)\nclass MarcModels:\n    vq_dicts: dict[int, np.ndarray]      # class_id -> (K, S*S) float32 Codebook\n    klt_bases: dict[int, np.ndarray]     # class_id -> (S*S, S*S) float32 Basis\n    entropy_tables: dict[str, np.ndarray]# ctx_name -> Wahrscheinlichkeits-LUT (float32, summe=1)\n    version: int\n\n# ---- Zentrale Plan-Datenstruktur (fliesst durch alle Bereiche) ----\n# plan: dict mit garantierten Keys je Stufe (vom Stage-Contract geprueft):\n#  nach 01: 'channels': list[np.ndarray]  (Y,Co,Cg als HxW float32; reversibel-int16 bei lossless)\n#           'color_meta': dict {'transform':'ycocg'|'ycocg_r','subsample':(sy,sx),'shape':(H,W)}\n#  nach 02: 'blocks': np.ndarray structured dtype=[('y',u2),('x',u2),('size',u1),('cls',u1)]\n#           'classmap': np.ndarray (H_b x W_b) uint8  (Block-Aufloesung)\n#           'tree_bits': np.ndarray uint8 (gepackte Quadtree-Split-Flags, Praeorder)\n#  nach 03: 'coeffs': dict[int, np.ndarray]   block_idx -> float32 Koeff/Tokens\n#  nach 04: 'qindices': dict[int, np.ndarray] block_idx -> int16\n#           'qtables': dict | float (QP/lambda-Spur)\n#  nach 06: 'payload': bytes (rANS-codierter Hauptkoerper)\n#           'sideinfo': bytes (delta-codierte Klassenkarte+QP, eigener Kontext)  [Idee 2]\n#  Container-Stufe 08 konsumiert: 'payload','sideinfo','tree_bits','color_meta','header'\n\n# ---- Stage-Contract (im Integrationsmodul, hart gelockt) ----\n_STAGE_CONTRACT: dict[str, dict[str, set[str]]]\n#  z.B. {'02': {'requires': {'channels'}, 'provides': {'blocks','classmap','tree_bits'}}, ...}\ndef _assert_contract(stage_id: str, before: dict, after: dict) -> None  # wirft MarcContractError\n\n# ---- Header / Box-Schema (an [08] uebergeben) ----\n# MARC_MAGIC = b'MARC'; VERSION = 1\n# Header-Felder (in 'MhdR'-Box): magic, version, H:u16, W:u16, mode:u8,\n#   eps:u8, color_transform:u8, subsample:u8, arbiter_flag:u8 (0=Region,1=Global),\n#   post_profile:u8 (Idee 4, Default 0), fallback:u8, payload_len:u32\n# Boxen: 'MhdR'|'TREE'|'SIDE'|'BODY'|'TOPUP'(optional)|'BASE'(optional, Backup)\n\n# ---- Interner Roundtrip & Arbiter (privat) ----\ndef _decode_internal(plan: dict, cfg: dict) -> np.ndarray            # spiegelt encode-Pipeline\ndef _rdo_lambda_bisect(cached_front: dict, target_bpp: float,\n                       iters: int) -> tuple[float, dict]             # (lambda*, billing_cache)\ndef _build_global_candidate(channels, color_meta, lam) -> tuple[bytes, float, float]  # (bytes,bpp,sse)\ndef _topup_residual(img, recon, mode, eps) -> bytes                  # L_inf-Garantie-Schicht\n\n# ---- Exceptions ----\nclass MarcError(Exception): ...\nclass MarcEncodeError(MarcError): ...\nclass MarcDecodeError(MarcError): ...\nclass MarcContractError(MarcError): ...\nclass MarcRoundtripError(MarcError): ...\n\n# Stubs/Adapter fuer noch fehlende Bereiche: jeder m0X-Aufruf laeuft ueber einen duennen\n# Adapter im Integrationsmodul (z.B. _stage_01_color(plan,cfg)), der die echte Bereichs-\n# Implementierung kapselt -> Integration testbar mit Identitaets-/Dummy-Stages.",
      "depends_on": "HARTE Abhaengigkeiten (Integration ruft diese; Interfaces muessen vorab gelockt sein):\n- [01] color_preprocess: encode_color(img_rgb,cfg)->(channels,color_meta); decode_color(channels,color_meta)->img_rgb. Lossless braucht reversibles YCoCg-R (int-lifting). MUSS: roundtrip-exakt im reversiblen Modus.\n- [02] segmentation_analysis: segment(channels,cfg)->(blocks,classmap,tree_bits); reconstruct_tree(tree_bits,shape)->blocks. MUSS: CLASS_RAW als Klasse unterstuetzen (fuer Lossless, Idee 14); Quadtree-Serialisierung deterministisch in Praeorder.\n- [03] transform: forward(block,cls,models)->coeffs; inverse(coeffs,cls,models)->block. MUSS: pro Klasse trennbar; getrennt vom Quant.\n- [04] quantization: quantize(coeffs,qp/lambda,cls)->qindices; dequantize(qindices,...)->coeffs; roundtrip_sse(coeffs,qp)->float (BILLIG, ohne Re-Transform) fuer die Bisektion.\n- [06] context_entropy: estimate_bits(qindices,ctx)->float (billige R-Schaetzung, Pass2); encode_symbols(...)->bytes; decode_symbols(bytes,...)->qindices. MUSS: estimate_bits konsistent mit echtem encode (sonst Bisektion zielt daneben).\n- [07] rate_control: bpp_to_lambda_init(target_bpp)->lambda0, lambda_bounds()->(lo,hi). Liefert Startintervall fuer die Bisektion (Integration fuehrt die Bisektion selbst).\n- [08] bitstream_format: serialize(boxes:list[(tag,bytes)])->bytes; parse(bytes)->dict{tag:bytes}; MUSS: TLV mit 4-Byte-Tag + u32-Length, unbekannte Boxen ueberspringbar.\n\nWEICHE/Optionale Abhaengigkeiten:\n- [05] dictionary_learning: liefert MarcModels; Integration funktioniert mit models=None (Builtin-Defaults).\n- common/metrics.py: psnr/ssim/bpp fuer self_check + post_profile-Entscheidung (Idee 4). VORHANDEN.\n- [10] evaluation_tests: konsumiert encode/decode (umgekehrte Richtung, keine Abhaengigkeit).\n\nBAUREIHENFOLGE-EMPFEHLUNG: Integration kann SOFORT mit Dummy/Identitaets-Adaptern fuer 01-08 starten (Plan-Dict + Stage-Contract + Box-Container testbar ohne echte Bereiche). Realer E2E-Test sobald 01,02,03,04,06,08 minimal stehen.",
      "impl_steps": "1. SKELETT + CONTRACT: marc_codec.py anlegen. Exceptions, MarcModels-Dataclass, _STAGE_CONTRACT-Dict (requires/provides je Bereich), _assert_contract() per set-Differenz. Duenne Stage-Adapter _stage_01_color..._stage_08_serialize, die zunaechst Identitaets-/Dummy-Logik enthalten (z.B. 01 = passthrough, 04 = round(x), 06 = pickle/zlib), damit die Verdrahtung VOR den echten Bereichen testbar ist.\n\n2. PLAN-REDUCE-KETTE: encode() als feste Kette plan={}; plan=_stage_01(...); _assert_contract('01',...); plan=_stage_02(...); ... bauen. decode() als gespiegelte Kette ueber dieselben Keys rueckwaerts. Eingabevalidierung (dtype uint8, ndim 2/3, Graustufe->HxWx3-Expansion, leere/1x1-Bilder).\n\n3. CONTAINER-HANDOFF: Header-Encoding (struct.pack: magic,version,H,W,mode,eps,...) + Box-Liste an _stage_08_serialize; decode() liest Boxen tag-laengen-gefuehrt, ueberspringt unbekannte Tags, faengt truncated Stream ab (best-effort). Magic/Version-Check -> MarcDecodeError nur hier.\n\n4. CACHED-FRONTEND-SPLIT: encode() teilt in _run_frontend(img,cfg)->cached_front (laeuft 01->02->03 GENAU EINMAL, cached coeffs+blocks+classmap) und _run_backend(cached_front,lambda)->(qindices,est_bits,sse) (nur 04->06-Schaetzung). Schluessel: Backend darf KEINEN Transform aufrufen.\n\n5. RDO-BISEKTION: _rdo_lambda_bisect(): lo,hi aus [07]; ~6-7x mid=sqrt(lo*hi); _run_backend(mid); bpp=8*sum(est_bits)/(H*W); ist bpp>target -> lo=mid (mehr quantisieren) sonst hi=mid; Abbruch |bpp-target|/target<0.03. Gewinner-lambda -> EINMAL realer 06-encode + 08-serialize. Bei quality-Modus statt target_bpp: festes lambda aus quality-Mapping, keine Schleife.\n\n6. WHOLE-IMAGE-ARBITER (Idee 2): _build_global_candidate() (schlanker Ganzbild-DCT/Plane ohne Quadtree). RD-Vergleich J=D+lambda*R Region vs. Global; 1-Byte arbiter_flag in MhdR; decode() waehlt Rekonstruktionspfad nach Flag. SIDE-Box: delta-praedizierte Klassenkarte + QP-Deltas (Idee 2).\n\n7. LOSSLESS-CONFIG (Idee 14): mode=='lossless' setzt cfg-Defaults {color:'ycocg_r', force_class:RAW, qp:1, predictor:'med'}; gleicher _run-Pfad. mode=='near_lossless' nutzt Lossy-Pfad + Top-up.\n\n8. VERIFY-OR-FALLBACK + TOPUP (Idee 8): nach Encode bei mode in {lossless,near_lossless} oder verify=True: recon=_decode_internal(plan). lossless: assert array_equal sonst Notfall-zlib-MED-Pfad (fallback=1). near_lossless: err=img-recon; over=err-clip(err,-eps,eps); _topup_residual() codiert nur over als Sparse-Map -> TOPUP-Box; decode() addiert sie. self_check (cfg) ruft optional decode(bytes) + psnr/array_equal.\n\n9. (Optional, Idee 4) POST_PROFILE: Encoder testet decode mit/ohne Postfilter via ssim() aus metrics.py, setzt post_profile-Byte; decode() wendet klassenkartengesteuerten bilateral/median nur an wenn Flag=1. Hinter cfg-Flag, default aus.\n\n10. ROBUSTHEIT/EDGE-CASES: leere Modelle, Bild kleiner als min-Blockgroesse, alle-gleich-Pixel (DC-only), Graustufe, sehr grosse Bilder (Streaming nicht noetig, aber Scratch-Buffer-Wiederverwendung als Backup Idee 6). Deterministische Seeds.",
      "unit_tests": "Datei: verfahren/09_integration_codec/tests/test_integration.py (pytest, nur NumPy/OpenCV/Pillow).\n\nROUNDTRIP & KORREKTHEIT:\n- test_lossless_exact: fuer jedes Kodak-Testbild (testdata/kodim*.png) gilt np.array_equal(img, decode(encode(img, mode='lossless'))). HARTE Bedingung.\n- test_lossless_grayscale: HxW-Graustufenbild -> lossless exakt (als HxWx3 zurueck, alle Kanaele gleich).\n- test_near_lossless_linf: fuer eps in [1,4,8]: recon=decode(encode(img,mode='near_lossless',eps=eps)); assert np.max(np.abs(img.astype(int)-recon.astype(int))) <= eps. HARTE per-Pixel L_inf-Garantie.\n- test_lossy_roundtrip_shape_dtype: decode(encode(img)).shape==img.shape and dtype==uint8; PSNR>25 dB bei quality=75.\n\nRATE-TARGETING (Kern-Differenzierer):\n- test_target_bpp_hit: fuer target in [0.25,0.5,1.0,2.0]: buf=encode(img,target_bpp=target); achieved=bpp(len(buf),H,W); assert abs(achieved-target)/target < 0.10 (Toleranz 10%, Bisektion garantiert ~3%).\n- test_monotone_rate: hoeheres target_bpp -> >= Bytes (Monotonie der Bisektion).\n- test_frontend_called_once: Monkeypatch/Counter auf _run_frontend; bei target_bpp mit 6 Iter -> Frontend-Aufrufzaehler==1, Backend-Zaehler<=7 (beweist Caching).\n\nARBITER / UNTERE SCHRANKE:\n- test_arbiter_never_worse: auf einem synthetischen glatten Gradientenbild (Region-Overhead-Worst-Case) ist len(encode(img,target_bpp=0.3)) <= len(global_only_encode(img,...)) + Header-Slack. Beweist garantierte untere Schranke.\n- test_arbiter_flag_roundtrip: erzwinge beide Pfade (cfg-Override), decode() rekonstruiert korrekt je Flag.\n\nSTAGE-CONTRACT / ROBUSTHEIT:\n- test_contract_violation_raises: Adapter, der einen 'provides'-Key absichtlich nicht setzt -> MarcContractError mit Bereichsname.\n- test_missing_requires_raises: Stufe ohne vorausgesetzten Key -> MarcContractError.\n- test_truncated_stream: decode(encode(img)[:len//2]) crasht nicht, liefert Array oder definierte MarcDecodeError (kein ungefangener Exception-Typ).\n- test_unknown_box_skipped: kuenstlich eingefuegte Fremd-TLV-Box -> decode ignoriert sie, Bild unveraendert.\n- test_corrupt_magic: decode(b'XXXX'+rest) -> MarcDecodeError.\n- test_verify_fallback: Monkeypatch der Lossy-Stufe auf bewusst falsche Rekonstruktion + mode='lossless' -> Ausgabe ist trotzdem exakt (fallback=1 gesetzt), array_equal haelt.\n\nEINGABE-VALIDIERUNG:\n- test_invalid_dtype: float-Bild -> MarcEncodeError. test_target_and_quality_both: beide gesetzt -> MarcEncodeError.\n- test_1x1_and_tiny: 1x1, 3x3, nicht-2er-Potenz-Dimensionen (z.B. 37x53) -> roundtrip funktioniert.\n- test_solid_color: konstantes Bild -> sehr kleine Datei, lossless exakt.\n\nDETERMINISMUS:\n- test_deterministic: encode(img) zweimal -> identische Bytes (keine Zufallsabhaengigkeit ohne Seed).\n\nE2E-SMOKE (sobald echte Bereiche stehen, sonst mit Dummy-Adaptern uebersprungen via pytest.importorskip):\n- test_e2e_beats_jpeg_at_psnr: auf >=3 Kodak-Bildern bei gematchtem PSNR ist MARC-bpp <= JPEG-bpp (Erfolgskriterium v0; mit baselines.jpeg_rd verglichen).",
      "backup_ideas": "PRIORISIERTE BACKUPS (wenn Kern steht / falls Zeit):\n\n1. [4] Decode-Postfilter-Flag (post_profile, 1 Byte): klassenkartengesteuertes bilateral/median im decode(); Encoder waehlt per ssim() aus metrics.py. Gewinn rein im Decoder-Postprocessing -> RD-Kurve nach oben OHNE Bits. Naht ist im Header bereits reserviert (post_profile-Byte). NIEDRIGES Risiko, hoher Low-bpp-SSIM-Gewinn -> erster Nachzug.\n\n2. [16] Wasserfuellung der Restbits: nach Bisektion verbleibendes delta_bits per Grenznutzen dD/dR auf steilste Regionen verteilen (gleiche-Steigung-Bedingung). Verfeinert die Rate-Praezision; baut direkt auf der gecachten (R,D)-Tabelle der Bisektion auf.\n\n3. [5]+[6] Geschwindigkeit: Klassen-gebatchtes Block-Dispatch (np.lexsort nach (size,cls), batched matmul-DCT) + Skip/Flat-Fastpath (packbits-Bitset + DPCM-DC am Entropie-Seam) + wiederverwendete Scratch-Buffer (out=-Parameter). Bit-IDENTISCH -> risikolos nachruestbar, rein Sekundaerachse Geschwindigkeit.\n\n4. [12]/[17]/[19] Per-Block Mode-Competition (Try-Both + 1-Bit repr_id + lossless-Escape): Klasse aus [02] wird zu Prior/Kandidatenfilter; echtes per-Block RDO-Tournament zwischen DCT/Plane/VQ/RAW. STAERKSTER Rate-Hebel, aber teuer und konzeptionell in 03/04 -> die Integration haelt schon 'mode_id'/'repr_id' im plan + Bitstream-Naht bereit. Aktivierbar via cfg 'best'-Flag (Idee 12: 'fast'=reiner Klassen-Dispatch).\n\n5. [9]+[10] Perzeptuelle RDO-Lambda-Karte (CSF/Luminanz/Kontrast/Kanten-Masking -> w_block, 0 Bit, encoderseitig) + Closed-Loop SSIM-Patch-Refinement mit Banding-Detektor. bpp runter bei gleichem SSIM. Naht: plan traegt optional 'w_block'.\n\n6. [20] Pass-1-Perzentil-Kalibrierung: bildspezifische Varianz-Schwellen (statt Magic Numbers) + lambda*-Startwert aus aggregierter R-Schaetzung; ~6-10 Byte im Header. Bessere Mode-Trennung pro Bild.\n\n7. [18] Eingebetteter JPEG-BASE-Layer (BASE-Box): 2-3KB progressives Thumbnail via Pillow als deterministischer Low-Freq-Praediktor (glatte Bloecke codieren nur Residuum gegen bilinear-hochskaliertes BASE) + Sofort-Preview + degenerierter Mini-Datei-Fallback. Box-Schema ist bereits vorgesehen.\n\n8. [11] Plan-as-Bytecode-VM: Pipeline-Topologie als serialisiertes Opcode-Programm im Header -> Vorwaerts/Rueckwaerts-Kompatibilitaet fuer neue Repraesentationen ohne Decoder-Umbau. v2-Feature; der Plan-Dict-Backbone [13] ist die pragmatische v0-Vorstufe."
    },
    {
      "area": "10_evaluation_tests — Evaluierung, Baselines & Tests",
      "chosen_design": "HEADLINE-ENTSCHEIDUNG: Die Headline-Kennzahl des Bereichs ist die BD-Rate (Bjontegaard-Delta-Rate) — die EINE Zahl \"% weniger Bits bei gleicher Qualitaet vs JPEG/WebP\". Sie quantifiziert das v0-Erfolgskriterium (Spec Abschnitt 6: \"RD-Kurve dominiert JPEG\") erstmals belastbar. Darum herum eine modulare, prozess-parallele Eval-Suite, die MARCs Kern-Differenzierer (je Region andere Repraesentation) sichtbar und attribuierbar macht, plus ein hartes Roundtrip/E2E-Testgeruest.\n\nIch synthetisiere die staerksten Ideen zu 6 neuen Modulen + 2 Erweiterungen bestehender Dateien. Gewaehlt: [1/13/15/17] (BD-Rate, fusioniert), [3/9/20-Metrik] (MS-SSIM + Chroma-dE + int32-PSNR Speedup), [7/8] (near-lossless MED-Baseline + Lossless-Referenzen + Roundtrip-Helfer), [2/11/19] (klassen-stratifizierte RD-Tomographie, fusioniert), [5/6] (Prozess-Parallelisierung + Metrik-Microopt), [4] (Iso-bpp-Harness), [14] (synthetischer Klassen-Testkorpus). [10/12/16/18/20-Orakel] kommen nach backup_ideas.\n\nGESAMTARCHITEKTUR (Dateien unter verfahren/10_evaluation_tests/ und verfahren/common/):\n- common/metrics.py  ERWEITERT: MS-SSIM, Chroma-dE (Lab a*b*), max_abs_error, pct_within, ssim_map (Map-Variante), int32-PSNR-Pfad, 5-Kanal-gestapeltes GaussianBlur in ssim.\n- bd_rate.py  NEU: Bjontegaard-Delta-Rate/PSNR in reinem NumPy mit monotonem Hull-Filter (aus [17]).\n- baselines.py  ERWEITERT: png_lossless, webp_lossless, webp_near_lossless, jpeg_lowbpp (Downscale-Anker), assert_roundtrip.\n- nll_baseline.py  NEU: MED/LOCO-I-Praediktor + NEAR-Quantisierung + Order-0-Entropie-bpp (near-lossless Referenzlinie).\n- region_rd.py  NEU: klassen-stratifizierte RD-Tomographie (Blockklassifikator glatt/Kante/Textur, per-Klasse PSNR/MS-SSIM/bpp, optionale class_map aus Bereich 02, PSNR-Diff-Heatmap-PNG).\n- rd_compare.py  NEU: Iso-bpp-Harness (Bisektion auf Ziel-bpp), worst-region-Crop-Galerie.\n- run_baselines.py  ERWEITERT: ProcessPoolExecutor-Parallelisierung + per-path Ladecache; ruft am Ende bd_rate auf und schreibt BD-Rate-Summary.\n- rd_summary.py  NEU: liest baseline_results.json + (spaeter) marc_results.json, aggregiert BD-Rate ueber Kodak-Set, exportiert RD-Kurven als matplotlib-freies SVG.\n- tests/: test_metrics.py, test_bd_rate.py, test_baselines_roundtrip.py, test_nll.py, test_region_rd.py, make_corpus.py + test_corpus_roundtrip.py (synthetischer Klassen-Korpus aus [14]).\n\nDATENVERTRAEGE (hart gelockt, damit andere Bereiche andocken koennen):\n- Bilder: RGB uint8, HxWx3, C-contiguous.\n- Codec-encode-fn-Signatur (bestehend, beibehalten): fn(img_rgb:uint8[H,W,3], quality:int) -> (num_bytes:int, dec_rgb:uint8[H,W,3]). MARCs encode/decode aus Bereich 09 wird ueber einen duennen Adapter in dieselbe Signatur gebracht.\n- RD-Punkt-Schema (baseline_results.json / marc_results.json): list[dict] mit Schluesseln {image:str, codec:str, quality:int, bpp:float, psnr:float, ssim:float, bytes:int}; optionale Felder {msssim:float, chroma_de:float}.\n- class_map (von Bereich 02, optional): uint8[H,W] mit 0=glatt,1=Kante,2=Textur. Wenn None: interner Sobel/Varianz-Proxy auf 16x16-Raster.\n- per-block-bits (von Bereich 08, optional): dict oder uint32[H//B,W//B] Byte-Budget je Block; wenn None: Rate-Stratifizierung wird uebersprungen, nur Distortion stratifiziert.",
      "rationale": "WARUM DIESE WAHL:\n1) BD-Rate ist viermal vorgeschlagen worden ([1][13][15][17]) — starkes Signal, dass dies die fehlende Kern-Kennzahl ist. Heute liefert run_baselines.py nur eine 60-Zeilen-Punktwolke; ein fairer Vergleich ist unmoeglich, weil JPEG/WebP/MARC nie exakt dieselbe bpp oder denselben PSNR treffen. BD-Rate loest genau das (Industriestandard HEVC/AV1/JPEG-XL) und macht das v0-Erfolgskriterium messbar statt visuell behauptet. Ich fusioniere die vier Vorschlaege und nehme den robustesten Twist aus [17] (monotoner Hull-Filter via np.maximum.accumulate vor dem polyfit), weil MARCs experimentelle RDO lokal nicht-konvexe Punkte erzeugen kann, die rohes Bjontegaard-polyfit zum Oszillieren bringen. Verifiziert: meine BD-Rate-Implementierung liefert fuer eine uniforme 0.8x-bpp-Verschiebung exakt -20.0% (Lehrbuchwert).\n\n2) Klassen-stratifizierte RD-Tomographie ([2][11][19]) ist die EINZIGE Eval, die MARCs Kernthese (\"je Region andere Repraesentation lohnt\") belegen oder widerlegen kann. Eine globale PSNR-Zahl verschleiert, OB Adaptivitaet Bits spart. Diese Module sind exakt auf MARCs Andersartigkeit zugeschnitten — kein Standard-Benchmark (JPEG/WebP/CompressAI) macht das. Hoher diagnostischer Hebel fuer das RDO-Tuning in Bereich 04/07.\n\n3) MS-SSIM + Chroma-dE ([3][9][20]): metrics.py hat aktuell nur Single-Scale-SSIM auf Luma (chroma-blind, global gemittelt). MS-SSIM korreliert nachweislich besser mit Wahrnehmung bei niedriger Bitrate (Quelle 20, Wang 2003); Lab-Chroma-dE deckt YCoCg-Chroma-Shifts ab (Bereich 01), die Luma-PSNR komplett uebersieht. Perzeptuell ehrlicheres Gate.\n\n4) Near-lossless + Lossless-Referenzen ([7][8]): Spec Abschnitt 6 fordert hart \"lossless exakt rekonstruierbar; definierte Toleranz im Lossy-Modus\". Aktuell gibt es KEINE Lossless-Referenz und KEINEN wiederverwendbaren Roundtrip-Pruefer. Der MED/LOCO-I-Praediktor passt strukturell zu MARCs Glatt-Pfad (Praediktion statt Transform) und liefert eine Order-0-bpp-Untergrenze, gegen die Bereich 06 (rANS) gemessen werden kann.\n\n5) Parallelisierung + int32-PSNR ([5][6]): 60 unabhaengige encode/decode/metrik-Tasks single-threaded; ProcessPoolExecutor gibt ~5-7x auf 8 Kernen, der per-path-Ladecache eliminiert die 12-fache Mehrfachdekodierung. Reine Harness-Optimierung, kein Codec-Pfad beruehrt. int32-PSNR ist verifiziert bit-aequivalent zu f64 auf uint8.\n\n6) Iso-bpp-Harness ([4]) + synthetischer Korpus ([14]): faire harte Baseline statt geschoenter quality-Vergleich; deterministische Klassen-Patches testen jede Routing-Entscheidung isoliert in Sekunden.\n\nABGELEHNT/VERSCHOBEN: BBDM [12], per-Klasse-Orakel [16], perzeptueller Artefakt-Report [10], Headroom-Orakel [20] sind exzellent, aber teurer und brauchen mehr Bereich-08/02-Kopplung — nach v0. Region-Heatmap-Bits [2b] braucht per-block-bits aus Bereich 08, das beim Eval-Bau evtl. noch fehlt -> als optionaler Pfad implementiert.",
      "novelty_contribution": "Was diesen Eval-Bereich strukturell von Standard-Codec-Benchmarks unterscheidet (passend zu MARCs Andersartigkeit):\n\n1) KLASSEN-STRATIFIZIERTE RD-TOMOGRAPHIE: Standard-Benchmarks (JPEG/WebP/JXL-Vergleiche, CompressAI) berichten EINEN globalen RD-Punkt pro Bild. Hier wird Rate UND Distortion gemeinsam nach Inhaltsklasse (glatt/Kante/Textur) aufgeschluesselt — drei separate RD-Kurven pro Bild. Damit wird MARCs Kern-Differenzierer (\"je Region andere Repraesentation\") erstmals direkt messbar: \"auf glatten Regionen schlaegt MARC JPEG um X bpp, auf Textur um Y\" — eine Aussage, die kein inhalts-agnostischer Codec-Benchmark treffen kann, weil dort keine Per-Region-Repraesentation existiert.\n\n2) BD-RATE MIT MONOTONEM HULL-FILTER: Reine-NumPy-Bjontegaard-Implementierung (kein scipy) ist schon ungewoehnlich; der Hull-Filter (np.maximum.accumulate vor polyfit) ist der Robustheits-Twist speziell fuer einen EXPERIMENTELLEN Codec, dessen RDO nicht-konvexe Punkte erzeugen kann — generischer Eval-Code macht das nicht.\n\n3) ROUTING-VALIDIERENDER SYNTHETISCHER KORPUS: deterministische 64x64-Klassen-Patches (flat/gradient/edge/texture/mixed), die jede Repraesentations-/Routing-Entscheidung ISOLIERT pruefen, statt nur auf 5 Naturbildern E2E zu testen. Direkt auf den Multi-Repraesentations-Kern zugeschnitten.\n\n4) MED-PRAEDIKTOR ALS near-lossless-REFERENZ + Order-0-ENTROPIE-UNTERGRENZE: liefert die theoretische bpp-Schranke, gegen die MARCs realer rANS-Coder (Bereich 06) gemessen wird — trennt Transform-/Quantisierungs-Ineffizienz von Entropiecoder-Ineffizienz.\n\n5) cv2-spezifische 5-Kanal-GaussianBlur-Mikrooptimierung in SSIM (moeglich, weil skimage/scipy bewusst NICHT benutzt werden) und verifiziert bit-aequivalenter int32-PSNR-Pfad fuer garantiert-uint8-Eingaben.",
      "interface": "PRAEZISE SIGNATUREN (alle rein NumPy/cv2/Pillow, CPU):\n\n# --- verfahren/common/metrics.py (ERWEITERT, bestehende psnr/ssim/bpp bleiben) ---\npsnr(a, b, max_val=255.0) -> float   # int32-Pfad fuer uint8, sonst f64; inf bei mse<=1e-12\nssim(a, b, max_val=255.0) -> float   # 5-Kanal-gestapeltes GaussianBlur (mu_x,mu_y,xx,yy,xy in EINEM Call)\nssim_map(a, b, max_val=255.0) -> np.ndarray[float32, HxW]   # num/den OHNE Mittelung\nms_ssim(a, b, max_val=255.0) -> float   # 5 Skalen, W=[0.0448,0.2856,0.3001,0.2363,0.1333], cv2.pyrDown; prod(mean(cs_j)^W_j, j=1..4)*mean(ssim_5)^W_5; gibt nan-sicheren float\nchroma_de(a, b) -> dict{mean:float, p95:float}   # cv2.cvtColor RGB2Lab, euklid. Distanz nur a*,b*\nmax_abs_error(a, b) -> int            # max |int16-Diff| ueber alle Kanaele\npct_within(a, b, tau:int) -> float    # Anteil Pixel mit |Diff|<=tau, in [0,1]\n\n# --- verfahren/10_evaluation_tests/bd_rate.py (NEU) ---\n_rd_hull(bpp:np.ndarray, metric:np.ndarray) -> (bpp_h, metric_h)   # monotoner Pareto-Filter via np.maximum.accumulate\nbd_rate(rd_ref:list[dict], rd_test:list[dict], metric='psnr') -> float\n    # IN: zwei RD-Punktlisten (dicts mit 'bpp' + metric-key) FUER EIN Bild/Codec-Paar\n    # OUT: BD-Rate in % (negativ = test spart Bits bei gleicher Qualitaet). nan bei leerem Overlap.\n    # intern: x=log10(bpp), y=metric; deg-3 polyfit (deg-2 Fallback <4 Pkt), np.polyint analytisch\nbd_psnr(rd_ref, rd_test, metric='psnr') -> float   # Achsen vertauscht: dB-Gewinn bei gleicher bpp\naggregate_bd(per_image:dict[str,float]) -> dict{mean:float, median:float, std:float, n:int, n_wins:int}\n\n# --- verfahren/10_evaluation_tests/baselines.py (ERWEITERT) ---\npng_lossless(img_rgb) -> (bytes:int, dec:uint8[H,W,3])\nwebp_lossless(img_rgb) -> (bytes:int, dec)\nwebp_near_lossless(img_rgb, near:int) -> (bytes, dec)   # near in {0..3} -> Pillow-Stufe {100,80,60,40}\njpeg_lowbpp(img_rgb, quality:int, scale:float) -> (bytes, dec)   # INTER_AREA down, INTER_CUBIC up\nassert_roundtrip(encode_fn, decode_fn, img_rgb, near:int=0) -> None   # raise AssertionError mit Pixelkoord des max-Fehlers\n\n# --- verfahren/10_evaluation_tests/nll_baseline.py (NEU) ---\nmed_predict(channel:uint8[H,W]) -> int16[H,W]      # vektorisierter LOCO-I/MED Praediktor\nnll_encode_rate(img_rgb, near:int) -> dict{bpp:float, max_err:int, recon:uint8[H,W,3]}\n    # NEAR-Quantisierung q=round(res/(2*near+1)); recon=pred+q*(2*near+1); garantiert max_err<=near\n    # bpp = Order-0-Shannon-Entropie der zigzag-gemappten q-Symbole * kanaele / pixel\norder0_entropy_bpp(symbols:np.ndarray, n_pixels:int, n_channels:int) -> float\n\n# --- verfahren/10_evaluation_tests/region_rd.py (NEU) ---\nclassify_blocks(img_rgb, block:int=16) -> uint8[H//block, W//block]   # 0 glatt,1 Kante,2 Textur (Sobel/Varianz)\nregion_rd(orig, dec, class_map=None, block:int=16, block_bits=None) -> dict\n    # OUT: {'psnr':{0:..,1:..,2:..}, 'msssim':{...}, 'frac':{...}, 'bpp':{...}|None, 'global_psnr':float}\npsnr_diff_heatmap(orig, dec_ref, dec_test, block:int=16, out_png:str=None) -> uint8[H,W,3]   # cv2.applyColorMap\n\n# --- verfahren/10_evaluation_tests/rd_compare.py (NEU) ---\nfind_quality_for_bpp(encode_fn, img_rgb, target_bpp:float, tol=0.01, qlo=1, qhi=95, max_iter=10) -> (q:int, bpp:float, dec)\niso_bitrate_table(img_rgb, codecs:dict[str,callable], targets=(0.10,0.15,0.20,0.30)) -> list[dict]\nworst_region_crops(img, dec_ref, dec_test, k=3, crop=64, out_png:str=None) -> list[tuple]\n\n# --- verfahren/10_evaluation_tests/rd_summary.py (NEU) ---\nload_points(json_path:str) -> list[dict]\ngroup_rd(points, by=('image','codec')) -> dict[tuple, list[dict]]\nsummarize_bd(ref_json:str, test_json:str, ref_codec='JPEG', metric='psnr') -> dict[str, dict]   # pro Bild + aggregate\nexport_rd_svg(groups:dict, out_svg:str) -> None   # matplotlib-frei, Python-f-string-Polylines\n\n# --- verfahren/10_evaluation_tests/tests/make_corpus.py (NEU) ---\nmake_corpus(seed:int=0, size:int=64) -> dict[str, uint8[size,size,3]]   # keys: flat,gradient,edge,texture,mixed\nclass_descriptors(patch) -> dict{var:float, grad:float}   # Routing-Deskriptoren wie Bereich 02",
      "depends_on": "HARTE ABHAENGIGKEITEN (vorhanden, verifiziert):\n- NumPy 2.4.6 (polyfit/polyint/polyval/trapezoid vorhanden), OpenCV 4.13.0, Pillow 12.2.0 mit WebP+near_lossless. Alles bestaetigt lauffaehig.\n- Bestehende Dateien: verfahren/common/metrics.py, verfahren/10_evaluation_tests/{baselines.py, run_baselines.py, baseline_results.json}, verfahren/testdata/{kodim01,05,07,19,23}.png.\n- Python concurrent.futures.ProcessPoolExecutor (stdlib) fuer Parallelisierung.\n\nWEICHE ABHAENGIGKEITEN (optional, Eval funktioniert ohne sie via Fallback):\n- Bereich 02 (segmentation_analysis): liefert class_map uint8[H,W] (0/1/2). Fallback: interner classify_blocks-Proxy. INTERFACE-LOCK: 0=glatt,1=Kante,2=Textur muss bereichsuebergreifend identisch sein.\n- Bereich 08 (bitstream_format): liefert per-block-Byte-Budget fuer Rate-Stratifizierung in region_rd. Fallback: bpp-Stratifizierung wird uebersprungen (Rueckgabe None), Distortion-Stratifizierung laeuft trotzdem.\n- Bereich 09 (integration_codec): liefert marc_encode(img,quality)->(bytes,dec) und marc_decode. Ein duenner Adapter in run_baselines/rd_compare bringt MARC in dieselbe (bytes,dec)-Signatur wie jpeg_rd/webp_rd. Solange Bereich 09 fehlt, laeuft die Suite nur auf JPEG/WebP/Lossless-Baselines (voll testbar).\n\nNACHGELAGERTE NUTZER dieses Bereichs:\n- Bereich 04 (quantization) + 07 (rate_control): nutzen region_rd + bd_rate als Optimierungsziel.\n- Bereich 06 (context_entropy): misst rANS-bpp gegen order0_entropy_bpp-Untergrenze aus nll_baseline.\n- Integrations-Agent: nutzt aggregate_bd als Go/No-Go-Gate fuer v0 (\"BD-Rate < 0 auf Mehrzahl der Bilder\").\n\nKEINE zirkulaere Abhaengigkeit: Eval haengt von 02/08/09 nur OPTIONAL ab; die Kernmetriken/BD-Rate/Baselines/Tests sind sofort und eigenstaendig lauffaehig.",
      "impl_steps": "BAUREIHENFOLGE (jeder Schritt eigenstaendig testbar, keine Wartezeit auf andere Bereiche):\n\nSCHRITT 1 — metrics.py erweitern (Fundament, alle anderen bauen darauf):\n1a. int32-PSNR-Pfad: wenn a,b uint8 -> d=a.astype(int32)-b.astype(int32); mse=mean(d*d). Sonst f64. Verifiziert bit-aequivalent.\n1b. ssim umbauen auf 5-Kanal-Stack: np.stack([x,y,x*x,y*y,x*y],axis=-1) als float32, EIN cv2.GaussianBlur((11,11),1.5), Ebenen entstapeln. Ergebnis numerisch identisch (Test gegen Alt-Impl, atol=1e-4).\n1c. ssim_map: wie ssim aber Rueckgabe num/den als HxW-Array (kein mean).\n1d. ms_ssim: Schleife ueber 5 Skalen, cv2.pyrDown zwischen Skalen, cs-Term je Skala aus ssim_map-Bausteinen, gewichtetes Produkt. Clamp negatives cs auf 0 vor Potenz (nan-Schutz).\n1e. chroma_de, max_abs_error, pct_within: kurze vektorisierte Funktionen.\n\nSCHRITT 2 — bd_rate.py (Headline-Metrik):\n2a. _rd_hull: nach bpp sortieren, np.maximum.accumulate auf metric, Maske metric>running_max behalten (monotone Huelle).\n2b. bd_rate: Hull-Filter -> x=log10(bpp), y=metric -> overlap [max(min_y_a,min_y_b), min(max_y_a,max_y_b)]; leer -> nan + Warnung. deg=min(3, n_pts-1) polyfit y->x; np.polyint, an Grenzen auswerten, /(hi-lo); (10^avg-1)*100.\n2c. bd_psnr: x/y tauschen. aggregate_bd: mean/median/std/n/n_wins.\n2d. Sanity-Test: synthetische 0.8x-bpp-Kurve -> exakt -20.0% (verifiziert).\n\nSCHRITT 3 — baselines.py erweitern: png_lossless, webp_lossless, webp_near_lossless, jpeg_lowbpp (cv2.resize INTER_AREA/INTER_CUBIC), assert_roundtrip (np.unravel_index(argmax) fuer Fehlerkoord).\n\nSCHRITT 4 — nll_baseline.py: med_predict (shift-Arrays a=links,b=oben,c=oben-links; vektorisierte MED-Regel), NEAR-Quantisierung, order0_entropy_bpp (np.unique counts, -sum p*log2 p). Test: max_err<=near garantiert; near=0 -> exakter Roundtrip.\n\nSCHRITT 5 — region_rd.py: classify_blocks (cv2.boxFilter Varianz + cv2.Sobel Gradientenergie auf 16x16, Terzil-Schwellen), region_rd (reshape (H//B,B,W//B,B) fuer per-Block-MSE, ssim_map-Pooling, np.kron-Maske je Klasse), psnr_diff_heatmap (cv2.applyColorMap).\n\nSCHRITT 6 — rd_compare.py: find_quality_for_bpp (Bisektion ueber quality, 10 Iter), iso_bitrate_table, worst_region_crops (Block-MSE-Argsort, cv2-Kachelmontage).\n\nSCHRITT 7 — run_baselines.py parallelisieren: Tasks (path,codec,q) materialisieren, top-level worker() mit lru_cache(load), ProcessPoolExecutor(os.cpu_count()), executor.map; danach rd_summary.summarize_bd aufrufen, BD-Rate-Block nach stdout + baseline_results.json beibehalten (Schema unveraendert, msssim/chroma_de als optionale Felder ergaenzen).\n\nSCHRITT 8 — rd_summary.py: load_points/group_rd/summarize_bd/export_rd_svg (handgebautes SVG, lineare Achsenskalierung in f-strings).\n\nSCHRITT 9 — tests/make_corpus.py + alle Unit-Tests (siehe unit_tests). Suite-Laufzeit-Ziel <5s ohne Naturbild-E2E.\n\nReihenfolge-Begruendung: 1 ist Fundament; 2 ist die Headline und nur von 1 abhaengig; 3/4 sind eigenstaendige Baselines; 5/6 brauchen 1; 7/8 verdrahten alles; 9 sichert ab. MARC-Adapter (Bereich 09) wird erst in 7 optional eingehaengt — bis dahin laeuft alles auf JPEG/WebP.",
      "unit_tests": "KONKRETE UNIT-TESTS (pytest-Stil, deterministisch, seeded; Laufzeit-Ziel <5s):\n\ntest_metrics.py:\n- test_psnr_identical: psnr(x,x)==inf.\n- test_psnr_int32_equals_f64: psnr(uint8 a,b) gleich (atol=1e-9) zu Referenz-f64-Berechnung. (verifiziert: int32 mse bit-gleich)\n- test_psnr_known_mse: konstanter Offset von 1 ueberall -> mse=1 -> psnr=48.13 dB (atol=1e-2).\n- test_ssim_identical: ssim(x,x)==1.0 (atol=1e-5).\n- test_ssim_stack_matches_naive: neue 5-Kanal-ssim == alte 5-Call-ssim (atol=1e-4) auf Zufallsbild.\n- test_ms_ssim_range: 0<=ms_ssim(a,b)<=1; ms_ssim(x,x)==1 (atol=1e-4); ms_ssim monoton fallend mit zunehmendem Rauschen.\n- test_ssim_map_mean_equals_ssim: ssim_map(a,b).mean() ~ ssim(a,b) (atol=1e-4).\n- test_chroma_de_zero: chroma_de(x,x)['mean']==0; positiv bei Farbverschiebung.\n- test_max_abs_error: bekannter max-Fehler an gesetztem Pixel exakt zurueck; pct_within(x,x,0)==1.0.\n\ntest_bd_rate.py:\n- test_bd_uniform_shift: rd_test = rd_ref mit bpp*0.8 -> bd_rate ~ -20.0% (atol=0.5). (VERIFIZIERT exakt -20.0)\n- test_bd_identical_zero: rd_ref==rd_test -> bd_rate ~ 0 (atol=1e-6).\n- test_bd_sign: schlechtere Kurve (bpp*1.25) -> bd_rate > 0.\n- test_bd_empty_overlap_nan: disjunkte PSNR-Bereiche -> np.isnan(bd_rate).\n- test_bd_few_points: 3 Punkte -> deg-2-Fallback, kein Crash, endlicher Wert.\n- test_hull_filter_monotone: nicht-monotone Punktwolke -> _rd_hull liefert streng steigende metric.\n- test_aggregate_bd: n_wins zaehlt negative BD-Werte korrekt.\n\ntest_baselines_roundtrip.py:\n- test_png_lossless_exact: np.array_equal(dec, img) fuer png_lossless auf jedem Korpus-Patch.\n- test_webp_lossless_exact: np.array_equal fuer webp_lossless.\n- test_webp_near_lossless_bound: max_abs_error(img,dec) <= erwartete Schranke je near-Stufe.\n- test_assert_roundtrip_raises: kuenstlich verfaelschter Decode -> AssertionError mit korrekter Pixelkoord.\n- test_jpeg_lowbpp_smaller: jpeg_lowbpp(scale=0.5) liefert weniger bytes als jpeg_rd bei gleicher quality.\n\ntest_nll.py:\n- test_med_near0_lossless: nll_encode_rate(img,0)['max_err']==0 und recon exakt.\n- test_med_near_bound: fuer near in {1,2,4,7}: max_err<=near (HART, ueber alle Korpus-Patches + Kodak-Crop).\n- test_order0_entropy_bounds: konstantes Bild -> bpp~0; Zufallsrauschen -> bpp nahe 8*kanaele/... (Obergrenze).\n- test_bpp_monotone_in_near: bpp(near=0) >= bpp(near=2) >= bpp(near=7).\n\ntest_region_rd.py:\n- test_classify_flat_smooth: flat-Patch -> alle Bloecke Klasse 0.\n- test_classify_texture: Rausch-Patch -> Klasse 2.\n- test_region_rd_keys: Rueckgabe-dict hat psnr/msssim/frac fuer alle vorkommenden Klassen; frac summiert zu ~1.0.\n- test_region_rd_no_bits: block_bits=None -> bpp ist None, Rest gefuellt.\n- test_heatmap_shape: psnr_diff_heatmap liefert uint8[H,W,3].\n\ntest_corpus_roundtrip.py (E2E-Geruest, MARC-Adapter optional via skip):\n- test_corpus_deterministic: make_corpus(seed=0) zweimal -> identische Arrays.\n- test_descriptors_separable: flat var~0/grad~0; edge hohe var/niedrige grad-Energie; texture hohe grad-Energie (Schwellen sauber trennbar).\n- test_marc_lossless_roundtrip [skipif Bereich09 fehlt]: assert_roundtrip(marc_enc,marc_dec,patch,near=0) je Klasse.\n- test_marc_lossy_tolerance [skipif]: psnr(patch,marc_dec(marc_enc(patch,q))) >= klassen-Schwelle (flat>=50,texture>=28).\n- test_marc_deterministic [skipif]: encode-bytes zweimal identisch (sha1-Snapshot); bpp waechst monoton mit quality.\n\ntest_rd_compare.py:\n- test_bisection_hits_target: find_quality_for_bpp trifft Ziel-bpp innerhalb tol auf JPEG.\n- test_iso_table_schema: iso_bitrate_table liefert pro Ziel-bpp eine Zeile je Codec mit erreichter bpp/psnr/msssim.",
      "backup_ideas": "NACH v0 (exzellent, aber teurer oder mehr Bereich-Kopplung noetig):\n\n[12] BBDM (Block-Boundary-Discontinuity-Metric): MARC-spezifischer Nahtdetektor an Quadtree-Grenzen, wo verschiedene Repraesentationen aufeinandertreffen — ein Artefakttyp, den NUR ein Multi-Repraesentations-Codec erzeugt und den PSNR/SSIM wegmitteln. Braucht aber die echten variablen Blockgrenzen aus Bereich 08/02. Hoher Wert sobald der Quadtree steht; ideal als Abnahmekriterium (\"keine sichtbare Naht\" = BBDM<Schwelle).\n\n[16] Per-Klasse-RD-Orakel (Shannon-Untergrenze): zerlegt die RD-Luecke in Transform-Wahl vs Quantisierung vs Entropiecodierung, pro Inhaltsklasse. Steuert direkt Design-Entscheidungen in 03/04/06. Teurer (cv2.dct pro 8x8-Block + Entropie); kommt wenn der Transform-Pfad steht.\n\n[10] Per-Region perzeptueller Artefakt-Report (blockingness/banding/texture-loss): bindet jede Artefaktsignatur an die Klassenkarte. Sehr nuetzlich fuers Lambda-Tuning, aber braucht stabile class_map + Decode-Pfad.\n\n[20] Adaptivitaets-Headroom-Orakel (Intra-Bild-Heterogenitaetsindex H_img=p90/p10 der Blockvarianz) + Block-adaptives-JPEG-Orakel als HAERTERE Baseline: verhindert, dass MARC nur gegen kuenstlich schwaches global-q-JPEG gewinnt; testet die Kernthese kausal (Korrelation Heterogenitaet<->Ersparnis). Sehr starker fairer-Baseline-Hebel, aber rechenintensiv (Block-adaptives JPEG-Orakel sweept q pro Block).\n\n[18] MS-SSIM-Map x Klassenkarte mit Bit-Attribution: region-attribuierte Distortion+Bits in einem. Ueberschneidet sich stark mit region_rd ([2/11/19]) das ich gewaehlt habe; die Map-Bit-Attribution-Variante ist die natuerliche Erweiterung sobald per-block-bits aus Bereich 08 verfuegbar sind.\n\n[6a-alt] Separierbarer Box-statt-Gauss-SSIM-Pfad: noch schneller, aber leicht anderes Ergebnis als die Gauss-Referenz — nur als optionaler fast-mode, nicht als Default (Vergleichbarkeit mit Literatur wahren).\n\nDiese fuenf bilden die natuerliche Bereich-10-Roadmap nach dem JPEG-schlagenden v0-Kern."
    }
  ]
}