{"version":3,"file":"normalization.cjs","names":[],"sources":["../../src/vision/normalization.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Which preprocessing a classifier expects its input to have had.\n *\n * A classifier is trained on a specific tensor, and feeding it a differently\n * prepared one degrades it silently — no exception, no warning, just worse\n * predictions. The two families this SDK sees most disagree completely:\n * torchvision-style models want the ImageNet mean and deviation subtracted and\n * divided out, while an Ultralytics classification head consumes raw `[0, 1]`.\n *\n * Guessing wrong is not detectable from the outside, but it is not a guess: an\n * Ultralytics export stamps `author` and `task` into its own metadata, and every\n * task in this SDK already reads that map for the class names.\n *\n * Mirrors `ort_vision_sdk.normalization` in the Python SDK; the two must agree,\n * because the same model file is driven by both.\n */\n\n/**\n * Which preprocessing the classifier expects its input to have had.\n *\n * - `\"auto\"` (the default wherever it is accepted) reads the model's own export\n *   metadata and picks. An Ultralytics classification head gets\n *   `\"ultralytics\"`; anything else gets `\"imagenet\"`.\n * - `\"imagenet\"` subtracts the ImageNet mean and divides by the ImageNet\n *   deviation — the torchvision convention.\n * - `\"ultralytics\"` leaves the image in `[0, 1]`. Ultralytics' own classifier\n *   applies no mean/std at all, so anything else feeds it images it never saw\n *   in training.\n * - `\"none\"` is the same arithmetic as `\"ultralytics\"` — identity — under a\n *   name that says \"this model wants raw `[0, 1]`\" rather than naming a vendor.\n */\nexport type Normalization = \"auto\" | \"imagenet\" | \"ultralytics\" | \"none\";\n\n/** Per-channel RGB mean of ImageNet, the torchvision preprocessing convention. */\nexport const IMAGENET_MEAN: readonly [number, number, number] = [0.485, 0.456, 0.406];\n\n/** Per-channel RGB standard deviation of ImageNet. */\nexport const IMAGENET_STD: readonly [number, number, number] = [0.229, 0.224, 0.225];\n\n/** Mean that leaves an image untouched — what an Ultralytics classifier expects. */\nexport const IDENTITY_MEAN: readonly [number, number, number] = [0, 0, 0];\n\n/** Deviation that leaves an image untouched — what an Ultralytics classifier expects. */\nexport const IDENTITY_STD: readonly [number, number, number] = [1, 1, 1];\n\n/** Name reported when the caller supplied `mean`/`std` directly. */\nexport const CUSTOM_NORMALIZATION = \"custom\";\n\nconst PRESETS: Readonly<\n    Record<string, readonly [readonly [number, number, number], readonly [number, number, number]]>\n> = {\n    imagenet: [IMAGENET_MEAN, IMAGENET_STD],\n    ultralytics: [IDENTITY_MEAN, IDENTITY_STD],\n    none: [IDENTITY_MEAN, IDENTITY_STD],\n};\n\n/** What {@link resolveNormalization} settled on. */\nexport interface ResolvedNormalization {\n    /** The preset name, or `\"custom\"` when the caller supplied the values. */\n    readonly name: string;\n    /** Per-channel mean to subtract. */\n    readonly mean: readonly [number, number, number];\n    /** Per-channel deviation to divide by. */\n    readonly std: readonly [number, number, number];\n}\n\n/**\n * Whether a metadata map came out of `YOLO(...).export(format=\"onnx\")`.\n *\n * Every Ultralytics export stamps `author` and `task` into its metadata, and the\n * pair is unambiguous: `\"Ultralytics\"` plus `\"classify\"` is a classification head\n * from that codebase and nothing else.\n *\n * @param metadata The model's custom metadata map.\n */\nexport function isUltralyticsClassifier(metadata: Readonly<Record<string, string>>): boolean {\n    return (\n        (metadata.author ?? \"\").trim().toLowerCase() === \"ultralytics\" &&\n        (metadata.task ?? \"\").trim().toLowerCase() === \"classify\"\n    );\n}\n\n/**\n * Settle which `mean`/`std` to apply, and what to call the choice.\n *\n * Explicit `mean`/`std` always win — they are the escape hatch for a model whose\n * preprocessing neither preset describes. Anything they leave open falls back to\n * the preset, so passing only a `mean` does not silently reset the deviation\n * to 1.\n *\n * Warns (via `console.warn`) when the model is an Ultralytics export and the\n * supplied values are not the identity it was trained with. Nothing fails in\n * that case: the prediction has the right shape and is simply less accurate,\n * which is exactly why it is worth saying out loud.\n *\n * @param metadata The model's custom metadata map, read to detect the family.\n * @param options The preset asked for, plus any explicit `mean`/`std`.\n * @throws {RangeError} If `normalization` is not a known preset, or names one\n *   while `mean`/`std` are also supplied — two answers to the same question.\n */\nexport function resolveNormalization(\n    metadata: Readonly<Record<string, string>>,\n    options: {\n        readonly normalization?: Normalization;\n        readonly mean?: readonly [number, number, number];\n        readonly std?: readonly [number, number, number];\n    } = {},\n): ResolvedNormalization {\n    const normalization = options.normalization ?? \"auto\";\n    const explicit = options.mean !== undefined || options.std !== undefined;\n\n    if (normalization !== \"auto\" && PRESETS[normalization] === undefined) {\n        throw new RangeError(\n            `normalization must be one of 'auto', 'imagenet', 'ultralytics', 'none'; ` +\n                `got ${JSON.stringify(normalization)}.`,\n        );\n    }\n    if (explicit && normalization !== \"auto\") {\n        throw new RangeError(\n            `Pass either normalization=${JSON.stringify(normalization)} or explicit mean/std, not both.`,\n        );\n    }\n\n    const ultralytics = isUltralyticsClassifier(metadata);\n    const preset =\n        normalization === \"auto\" ? (ultralytics ? \"ultralytics\" : \"imagenet\") : normalization;\n    const [presetMean, presetStd] = PRESETS[preset] as readonly [\n        readonly [number, number, number],\n        readonly [number, number, number],\n    ];\n\n    if (!explicit) {\n        return { name: preset, mean: presetMean, std: presetStd };\n    }\n\n    const mean = options.mean ?? presetMean;\n    const std = options.std ?? presetStd;\n    if (ultralytics && !(isIdentity(mean, IDENTITY_MEAN) && isIdentity(std, IDENTITY_STD))) {\n        console.warn(\n            \"The classifier is an Ultralytics export, whose classification head is trained on raw \" +\n                `[0, 1] images, but mean=${JSON.stringify(mean)} / std=${JSON.stringify(std)} was ` +\n                \"requested. It will be fed images normalized in a way it never saw in training, which \" +\n                \"degrades accuracy without raising anything. Drop mean/std to let normalization='auto' \" +\n                \"pick the identity.\",\n        );\n    }\n    return { name: CUSTOM_NORMALIZATION, mean, std };\n}\n\n/**\n * Whether two channel triples are equal.\n *\n * @param value The triple to check.\n * @param reference The triple to compare against.\n */\nfunction isIdentity(\n    value: readonly [number, number, number],\n    reference: readonly [number, number, number],\n): boolean {\n    return value.every((entry, index) => entry === reference[index]);\n}\n"],"mappings":"AAmCA,IAAa,EAAmD,CAAC,KAAO,KAAO,IAAK,EAGvE,EAAkD,CAAC,KAAO,KAAO,IAAK,EAGtE,EAAmD,CAAC,EAAG,EAAG,CAAC,EAG3D,EAAkD,CAAC,EAAG,EAAG,CAAC,EAG1D,EAAuB,SAE9B,EAEF,CACA,SAAU,CAAC,EAAe,CAAY,EACtC,YAAa,CAAC,EAAe,CAAY,EACzC,KAAM,CAAC,EAAe,CAAY,CACtC,EAqBA,SAAgB,EAAwB,EAAqD,CACzF,OACK,EAAS,QAAU,GAAA,CAAI,KAAK,CAAC,CAAC,YAAY,IAAM,gBAChD,EAAS,MAAQ,GAAA,CAAI,KAAK,CAAC,CAAC,YAAY,IAAM,UAEvD,CAoBA,SAAgB,EACZ,EACA,EAII,CAAC,EACgB,CACrB,IAAM,EAAgB,EAAQ,eAAiB,OACzC,EAAW,EAAQ,OAAS,IAAA,IAAa,EAAQ,MAAQ,IAAA,GAE/D,GAAI,IAAkB,QAAU,EAAQ,KAAmB,IAAA,GACvD,MAAU,WACN,+EACW,KAAK,UAAU,CAAa,EAAE,EAC7C,EAEJ,GAAI,GAAY,IAAkB,OAC9B,MAAU,WACN,6BAA6B,KAAK,UAAU,CAAa,EAAE,iCAC/D,EAGJ,IAAM,EAAc,EAAwB,CAAQ,EAC9C,EACF,IAAkB,OAAU,EAAc,cAAgB,WAAc,EACtE,CAAC,EAAY,GAAa,EAAQ,GAKxC,GAAI,CAAC,EACD,MAAO,CAAE,KAAM,EAAQ,KAAM,EAAY,IAAK,CAAU,EAG5D,IAAM,EAAO,EAAQ,MAAQ,EACvB,EAAM,EAAQ,KAAO,EAU3B,OATI,GAAe,EAAE,EAAW,EAAM,CAAa,GAAK,EAAW,EAAK,CAAY,IAChF,QAAQ,KACJ,gHAC+B,KAAK,UAAU,CAAI,EAAE,SAAS,KAAK,UAAU,CAAG,EAAE,mMAIrF,EAEG,CAAE,KAAM,EAAsB,OAAM,KAAI,CACnD,CAQA,SAAS,EACL,EACA,EACO,CACP,OAAO,EAAM,OAAO,EAAO,IAAU,IAAU,EAAU,EAAM,CACnE"}