@photivra/engine 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +24 -0
- package/dist/camera/lens-projection.d.ts +5 -0
- package/dist/camera/lens-projection.js +10 -0
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +227 -0
- package/dist/equipment/generic-lens-catalogue.js +243 -0
- package/dist/equipment/generic-lens-catalogue.js.map +1 -0
- package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
- package/dist/facade/defocus-bokeh-zones.js +53 -0
- package/dist/facade/defocus-bokeh-zones.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.d.ts +9 -0
- package/dist/facade/defocus-image-space-provider.js +96 -7
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +29 -0
- package/dist/facade/diffraction-provider.js +117 -11
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +4 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +10 -0
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +30 -0
- package/dist/facade/display-view-lens-signal.js +427 -0
- package/dist/facade/display-view-lens-signal.js.map +1 -0
- package/dist/facade/expected-signal-setup.d.ts +7 -0
- package/dist/facade/expected-signal-setup.js +12 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +8 -0
- package/dist/facade/expected-signal.js +150 -0
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +6 -0
- package/dist/facade/generic-lens-capture.js +8 -0
- package/dist/facade/generic-lens-capture.js.map +1 -0
- package/dist/facade/large-defocus-lattice.d.ts +39 -0
- package/dist/facade/large-defocus-lattice.js +64 -0
- package/dist/facade/large-defocus-lattice.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +5 -0
- package/dist/facade/scene-radiance.js +5 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +38 -5
- package/dist/facade/simulate-capture.js +306 -49
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +22 -16
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +0 -9
- package/dist/gpu/display-view-quality-gpu.js +6 -0
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.js +2 -0
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
- package/dist/optics/defocus-bokeh-kernel.js +380 -0
- package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
- package/dist/optics/defocus-disc-kernel.d.ts +41 -19
- package/dist/optics/defocus-disc-kernel.js +44 -23
- package/dist/optics/defocus-disc-kernel.js.map +1 -1
- package/dist/optics/generic-lens-psf.d.ts +68 -0
- package/dist/optics/generic-lens-psf.js +337 -0
- package/dist/optics/generic-lens-psf.js.map +1 -0
- package/dist/optics/illumination-vignetting.d.ts +6 -0
- package/dist/optics/illumination-vignetting.js +24 -7
- package/dist/optics/illumination-vignetting.js.map +1 -1
- package/dist/optics/sensor-environment-query.d.ts +6 -0
- package/dist/optics/sensor-environment-query.js +13 -6
- package/dist/optics/sensor-environment-query.js.map +1 -1
- package/dist/optics/site-kernel-runs.d.ts +77 -0
- package/dist/optics/site-kernel-runs.js +200 -0
- package/dist/optics/site-kernel-runs.js.map +1 -0
- package/dist/output/generic-lens-corrections.d.ts +24 -0
- package/dist/output/generic-lens-corrections.js +51 -0
- package/dist/output/generic-lens-corrections.js.map +1 -0
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +49 -1
- package/docs/QUICK_START.md +257 -17
- package/docs/USAGE.md +57 -5
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +281 -0
- package/docs/api/exports.json +137 -2
- package/docs/api/facade-simulate-capture.md +23 -2
- package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
- package/docs/api/optics-defocus-disc-kernel.md +22 -15
- package/docs/api/optics-sensor-environment-query.md +6 -0
- package/docs/api/output-generic-lens-corrections.md +39 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defocus-bokeh-kernel.js","sourceRoot":"","sources":["../../src/optics/defocus-bokeh-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Bokeh character (#553): the defocus kernel of `lens.defocus` shaped by the lens, instead of the uniform disc of an ideal\n * circular pupil. Three opt-in parts, each off by default (without them `lens.defocus` keeps the exact disc of\n * `createDefocusDiscSiteKernel`, byte for byte):\n *\n * - **Aperture blades** (`blades`): the opening is a regular polygon of `count` straight blades\n * (`calculateIdealApertureGeometry`'s vertex convention, `firstBladeEdgeAngleDegrees`), or with `curvature` κ in (0, 1]\n * rounded blades: each edge an arc of radius 1/κ (circumradius units) through the edge's two vertices, so κ = 1 is the\n * circumscribed circle. At or below `circularAtOrBelowFNumber` the blades are open and the opening is the circle.\n * The opening is scaled to the area of the circle of diameter f/N (equal-area convention): the f-number keeps its\n * meaning for light throughput, which the capture applies elsewhere (π/(4·N_w²)), and the kernel's support has the\n * disc's area.\n * - **Cat's eye** (`catsEye`): mechanical vignetting, the model of `calculateMechanicalVignetting` (Greivenkamp, OPTI-502\n * course notes, University of Arizona, sec. 10, free; also see Kingslake, Lens Design Fundamentals, sec. 6.4). Each\n * clipping aperture (a barrel or element rim referred to the entrance-pupil plane) is a circle of `radiusMm`, shifted by\n * `shiftPerTanFieldMm`·tan θ toward the field point; the open pupil is the opening intersected with every circle. It\n * depends on the field point, so the capture applies it per field zone (see `defocus-image-space-provider.ts`). The light\n * it removes is not applied by the kernel (the kernel has unit sum, a shape only): declare the illumination falloff with\n * `lens.vignetting`, as for any field throughput; `openAreaRatio` reports the open fraction.\n * - **Spherical aberration** (`sphericalAberration`): primary (Seidel) spherical aberration W = W040·ρ⁴ of the working\n * pupil (ρ the pupil radius normalized to the pupil of f/N), given as W040 in µm at a reference f-number N₀ and scaled\n * to the working f-number as W040·(N₀/N)⁴ (the pupil coordinate of a fixed lens shrinks with the stop). Its transverse\n * ray aberration at the image is ε(ρ) = −(R/r_p)·∂W/∂ρ = −8·N·W040·ρ³ for R/r_p = 2N (Wyant & Creath 1992, \"Basic\n * Wavefront Aberration Theory for Optical Metrology\", Applied Optics and Optical Engineering XI, §II.A and eq. 31, free).\n *\n * The ray map. A geometric (ray-optics) defocused image of a point is the pupil projected onto the sensor: the ray through\n * pupil point P = ρ·p̂ (pupil-radius units) lands at displacement\n * y(P) = (σ·c/2 + b·ρ²)·P, b = 8·N·W040(N)/pitch (sites),\n * from the chief ray, with c the thin-lens blur diameter in sites (`calculateDefocusCircle`, the marginal ray of the\n * aberration-free lens) and σ = +1 for a scene point behind the focus plane, −1 in front of it. Rays from behind the\n * focus plane cross before the sensor, those from in front have not crossed yet, so the two blurs are the same pupil\n * turned through 180° relative to each other, and spherical aberration adds to the defocus on one side and subtracts on\n * the other: with undercorrected spherical aberration (W040 > 0) the blur behind the focus plane is soft-edged and the\n * blur in front bright-rimmed (H. H. Nasse, \"Depth of Field and Bokeh\", Carl Zeiss Camera Lens Division, 2010, free; the\n * ray density follows from the map: for a circular pupil the image intensity at r = |σc/2·ρ + bρ³| is\n * proportional to 1/(|σc/2 + bρ²|·|σc/2 + 3bρ²|)). The convention of this module, used for `firstBladeEdgeAngleDegrees` and\n * the cat's-eye shift: shapes are declared as the blur of a point behind the focus plane appears in the output image\n * (+X right, +Y up), and the field tangent (tan θ along the field azimuth) is in the same frame.\n *\n * Sampling on the site lattice. As for the disc, a site's weight is the fraction of the blur's light inside that site's\n * square cell [i − ½, i + ½] × [j − ½, j + ½].\n * - Without spherical aberration the blur is uniform over the projected open pupil, a convex polygon (circles and blade\n * arcs are polygons of 1024 sides, scaled to their true area; the boundary then lies within 5e-6 of the radius of the true\n * curve), and each weight is the exact area of the polygon inside the cell (convex clipping and the shoelace formula),\n * divided by the polygon's area: method `\"exact-polygon-cell-area\"`.\n * - With spherical aberration the intensity is not uniform (and has a caustic when |b| > c/6 on the bright-rimmed side), so\n * the pupil is sampled on a square grid of step h ≤ 1/64, fine enough that neighbouring rays land at most 1/8 site apart,\n * each grid cell's row segment weighted by its exact overlap with the open pupil's chord, and every ray binned into the\n * site cell it lands in: method `\"pupil-ray-binning\"`, a quadrature with an error of the order of the ray spacing at the\n * cells the blur's edge or caustic crosses.\n * Every step uses correctly rounded operations and the engine's portable trigonometry, so the weights are the same bits in\n * every JavaScript engine, and they are normalized as the disc's are (exact sum 1 within half an ulp of the largest weight,\n * which takes up the rounding instead of the centre: off axis the cat's eye can leave the centre cell empty).\n *\n * What it is: a geometric approximation (no diffraction of the defocused wave, an ideal thin lens with an aberration-free\n * pupil apart from the declared primary spherical aberration, referred to the paraxial focus, and an exit pupil of the same\n * shape and orientation as the entrance pupil). Not modelled: onion rings (aspherical-element texture), higher-order or\n * field-dependent aberrations, colour (the kernel is the same at every wavelength) and the depth-dependent change of the\n * cat's eye with the subject distance.\n */\n\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { portableAsin, portableCosTwoPi, portableSinTwoPi } from \"../core/portable-math.js\";\nimport { requireAllowlistedRecord, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, requirePositiveFinite } from \"../core/validation.js\";\nimport { DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES, defocusDiscLimitMessage, normalizeSiteKernel, resolveDefocusDisc, type DefocusDiscSiteKernel } from \"./defocus-disc-kernel.js\";\n\n/** Largest blade count of `lens.defocus.blades` (and of {@link DefocusBokehBlades}). */\nexport const DEFOCUS_BOKEH_MAXIMUM_BLADES = 32;\n/** Largest number of field zones across the frame's long side for the cat's eye (`catsEye.fieldZonesAcrossLongSide`). */\nexport const DEFOCUS_BOKEH_MAXIMUM_FIELD_ZONES = 32;\n/** Default field zones across the long side. */\nexport const DEFOCUS_BOKEH_DEFAULT_FIELD_ZONES = 8;\n/** Sides of the polygon that stands for a circle (and of all blade arcs together). */\nconst CURVE_SIDES = 1024;\n/** Largest number of clipping apertures (as `calculateMechanicalVignetting`). */\nconst MAXIMUM_CLIPPING_APERTURES = 8;\n\n/** Aperture blades: a regular polygon, optionally with rounded blades and open at wide f-numbers. */\nexport interface DefocusBokehBlades {\n /** Number of diaphragm blades, 3 to {@link DEFOCUS_BOKEH_MAXIMUM_BLADES}. */\n count: number;\n /** Rotation of the first blade edge, degrees counter-clockwise (`calculateIdealApertureGeometry`'s convention). Default 0. */\n firstBladeEdgeAngleDegrees?: number;\n /** Blade roundness κ in [0, 1]: each edge is an arc of radius 1/κ circumradii (0: straight blades, 1: a circle). Default 0. */\n curvature?: number;\n /** At or below this f-number the blades are open and the opening is circular (the lens's widest aperture). Default: never. */\n circularAtOrBelowFNumber?: number;\n}\n\n/** Primary (Seidel) spherical aberration W = W040·ρ⁴, given at a reference f-number. */\nexport interface DefocusBokehSphericalAberration {\n /** W040 in µm of optical path at the pupil edge of `atFNumber`; positive = undercorrected (marginal rays focus short). */\n w040Micrometers: number;\n /** The f-number at which `w040Micrometers` is given; the working value is W040·(atFNumber/N)⁴. */\n atFNumber: number;\n}\n\n/** One clipping aperture of the cat's eye, as `MechanicalVignettingAperture` of `calculateMechanicalVignetting`. */\nexport interface DefocusBokehClippingAperture {\n /** Aperture radius referred to the entrance-pupil plane, mm. */\n radiusMm: number;\n /** Shift of its projected centre per unit tan(field angle), mm, toward the field point when positive. */\n shiftPerTanFieldMm: number;\n}\n\n/** Mechanical vignetting (cat's eye): the open pupil clipped by each aperture at the field point. */\nexport interface DefocusBokehCatsEye {\n /** One through eight clipping apertures. */\n apertures: readonly DefocusBokehClippingAperture[];\n /**\n * `lens.defocus` only: zones across the frame's long side (1 to {@link DEFOCUS_BOKEH_MAXIMUM_FIELD_ZONES}, default\n * {@link DEFOCUS_BOKEH_DEFAULT_FIELD_ZONES}); square zones, each with the kernel of its centre's field point.\n */\n fieldZonesAcrossLongSide?: number;\n}\n\n/** The bokeh options of `lens.defocus` (#553), each off when omitted; a lens catalogue entry can carry them as they are. */\nexport interface DefocusBokehOptions {\n blades?: DefocusBokehBlades;\n sphericalAberration?: DefocusBokehSphericalAberration;\n catsEye?: DefocusBokehCatsEye;\n}\n\n/** Validated {@link DefocusBokehOptions}: a copy with defaults filled in. */\nexport interface ResolvedDefocusBokehOptions {\n blades?: Required<Omit<DefocusBokehBlades, \"circularAtOrBelowFNumber\">> & { circularAtOrBelowFNumber: number | null };\n sphericalAberration?: DefocusBokehSphericalAberration;\n catsEye?: { apertures: readonly DefocusBokehClippingAperture[]; fieldZonesAcrossLongSide: number };\n}\n\nfunction finiteIn(name: string, value: unknown, low: number, high: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < low || value > high)\n throw new InvalidScientificInputError(name + \" must be a finite number from \" + low + \" to \" + high + \".\");\n return value;\n}\nfunction integerIn(name: string, value: unknown, low: number, high: number): number {\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < low || value > high)\n throw new InvalidScientificInputError(name + \" must be an integer from \" + low + \" to \" + high + \".\");\n return value;\n}\n\n/**\n * Validates bokeh options (unknown fields: `InvalidConfigurationError`; out-of-range values: `InvalidScientificInputError`).\n * `label` prefixes the messages (for example `\"lens.defocus\"`).\n */\nexport function resolveDefocusBokehOptions(value: unknown, label: string): ResolvedDefocusBokehOptions {\n const record = requireAllowlistedRecord(value, [\"blades\", \"sphericalAberration\", \"catsEye\"], label + \" bokeh options must be an object.\");\n const out: ResolvedDefocusBokehOptions = {};\n if (record.blades !== undefined) {\n const b = requireAllowlistedRecord(record.blades, [\"count\", \"firstBladeEdgeAngleDegrees\", \"curvature\", \"circularAtOrBelowFNumber\"], label + \".blades must be an object.\");\n out.blades = {\n count: integerIn(label + \".blades.count\", b.count, 3, DEFOCUS_BOKEH_MAXIMUM_BLADES),\n firstBladeEdgeAngleDegrees: b.firstBladeEdgeAngleDegrees === undefined ? 0 : finiteIn(label + \".blades.firstBladeEdgeAngleDegrees\", b.firstBladeEdgeAngleDegrees, -360, 360),\n curvature: b.curvature === undefined ? 0 : finiteIn(label + \".blades.curvature\", b.curvature, 0, 1),\n circularAtOrBelowFNumber: b.circularAtOrBelowFNumber === undefined ? null : finiteIn(label + \".blades.circularAtOrBelowFNumber\", b.circularAtOrBelowFNumber, 0.5, 1024)\n };\n }\n if (record.sphericalAberration !== undefined) {\n const s = requireAllowlistedRecord(record.sphericalAberration, [\"w040Micrometers\", \"atFNumber\"], label + \".sphericalAberration must be an object.\");\n out.sphericalAberration = { w040Micrometers: finiteIn(label + \".sphericalAberration.w040Micrometers\", s.w040Micrometers, -100, 100),\n atFNumber: finiteIn(label + \".sphericalAberration.atFNumber\", s.atFNumber, 0.5, 1024) };\n }\n if (record.catsEye !== undefined) {\n const c = requireAllowlistedRecord(record.catsEye, [\"apertures\", \"fieldZonesAcrossLongSide\"], label + \".catsEye must be an object.\");\n if (!Array.isArray(c.apertures) || c.apertures.length < 1 || c.apertures.length > MAXIMUM_CLIPPING_APERTURES)\n throw new InvalidScientificInputError(label + \".catsEye.apertures must list 1 to \" + MAXIMUM_CLIPPING_APERTURES + \" clipping apertures.\");\n const apertures = (c.apertures as unknown[]).map((a, k) => {\n const r = requireAllowlistedRecord(a, [\"radiusMm\", \"shiftPerTanFieldMm\"], label + \".catsEye.apertures[\" + k + \"] must be an object.\");\n return { radiusMm: finiteIn(label + \".catsEye.apertures[\" + k + \"].radiusMm\", r.radiusMm, 1e-6, 1e4),\n shiftPerTanFieldMm: finiteIn(label + \".catsEye.apertures[\" + k + \"].shiftPerTanFieldMm\", r.shiftPerTanFieldMm, -1e4, 1e4) };\n });\n out.catsEye = { apertures, fieldZonesAcrossLongSide: c.fieldZonesAcrossLongSide === undefined ? DEFOCUS_BOKEH_DEFAULT_FIELD_ZONES :\n integerIn(label + \".catsEye.fieldZonesAcrossLongSide\", c.fieldZonesAcrossLongSide, 1, DEFOCUS_BOKEH_MAXIMUM_FIELD_ZONES) };\n }\n return out;\n}\n\n// ---- Convex polygons, as flat [x0, y0, x1, y1, …] arrays, counter-clockwise. ----\n\ntype Polygon = number[];\n\nfunction polygonArea(p: Polygon): number {\n let twice = 0;\n for (let k = 0, n = p.length; k < n; k += 2) {\n const k2 = k + 2 === n ? 0 : k + 2;\n twice += p[k]! * p[k2 + 1]! - p[k2]! * p[k + 1]!;\n }\n return Math.abs(twice) / 2;\n}\n\n/** Sutherland–Hodgman: the part of `p` where a·x + b·y ≤ c (one half-plane). */\nfunction clipHalfPlane(p: Polygon, a: number, b: number, c: number): Polygon {\n const out: Polygon = [], n = p.length;\n if (n === 0) return out;\n let px = p[n - 2]!, py = p[n - 1]!, pd = a * px + b * py - c;\n for (let k = 0; k < n; k += 2) {\n const x = p[k]!, y = p[k + 1]!, d = a * x + b * y - c;\n if (d <= 0) {\n if (pd > 0) { const t = pd / (pd - d); out.push(px + t * (x - px), py + t * (y - py)); }\n out.push(x, y);\n } else if (pd <= 0) { const t = pd / (pd - d); out.push(px + t * (x - px), py + t * (y - py)); }\n px = x; py = y; pd = d;\n }\n return out;\n}\n\n/** The part of convex `p` inside convex counter-clockwise `clip` (each clip edge a half-plane). */\nfunction clipConvex(p: Polygon, clip: Polygon): Polygon {\n let out = p;\n for (let k = 0, n = clip.length; k < n && out.length > 0; k += 2) {\n const k2 = k + 2 === n ? 0 : k + 2, ax = clip[k]!, ay = clip[k + 1]!, ex = clip[k2]! - ax, ey = clip[k2 + 1]! - ay;\n // Inside (left of the edge): ex·(y − ay) − ey·(x − ax) ≥ 0, i.e. ey·x − ex·y ≤ ey·ax − ex·ay.\n out = clipHalfPlane(out, ey, -ex, ey * ax - ex * ay);\n }\n return out;\n}\n\n/** The unit circle as a regular polygon of {@link CURVE_SIDES} sides, scaled to the circle's area π (built once). */\nlet unitCircle: Polygon | undefined;\nfunction unitCirclePolygon(): Polygon {\n if (unitCircle) return unitCircle;\n const p: Polygon = [];\n for (let k = 0; k < CURVE_SIDES; k++) p.push(portableCosTwoPi(k / CURVE_SIDES), portableSinTwoPi(k / CURVE_SIDES));\n unitCircle = scaled(p, Math.sqrt(Math.PI / polygonArea(p)));\n return unitCircle;\n}\nfunction scaled(p: Polygon, s: number): Polygon { return p.map((v) => v * s); }\n\n/**\n * The aperture opening in pupil-radius units (the circle of f/N has radius 1), scaled to area π: a circle, a regular\n * polygon (vertex k at turn ¼·(firstEdge/90 − 1) + (k + ½)/n, `calculateIdealApertureGeometry`'s convention) or rounded blades.\n */\nfunction openingPolygon(blades: ResolvedDefocusBokehOptions[\"blades\"], fNumber: number): Polygon {\n if (!blades || blades.curvature === 1 || (blades.circularAtOrBelowFNumber !== null && fNumber <= blades.circularAtOrBelowFNumber)) return unitCirclePolygon();\n const n = blades.count, first = blades.firstBladeEdgeAngleDegrees / 360 - 0.25 + 0.5 / n, p: Polygon = [];\n if (blades.curvature === 0) {\n for (let k = 0; k < n; k++) p.push(portableCosTwoPi(first + k / n), portableSinTwoPi(first + k / n));\n } else {\n // Edge k runs from vertex k to vertex k + 1; its normal is at turn first + (k + ½)/n. The arc of radius ρ_b = 1/κ through both\n // vertices has its centre on the normal at hc − √(ρ_b² − hs²) (hc = cos(π/n), hs = sin(π/n)) and spans ±α, sin α = hs·κ.\n const rb = 1 / blades.curvature, hs = portableSinTwoPi(0.5 / n), hc = portableCosTwoPi(0.5 / n);\n const centre = hc - Math.sqrt((rb - hs) * (rb + hs)), alphaTurns = portableAsin(Math.min(1, hs * blades.curvature)) / (2 * Math.PI);\n const perEdge = Math.max(2, Math.ceil(CURVE_SIDES / n));\n for (let k = 0; k < n; k++) {\n const normal = first + (k + 0.5) / n, cx = centre * portableCosTwoPi(normal), cy = centre * portableSinTwoPi(normal);\n for (let m = 0; m < perEdge; m++) {\n const u = normal - alphaTurns + 2 * alphaTurns * (m / perEdge);\n p.push(cx + rb * portableCosTwoPi(u), cy + rb * portableSinTwoPi(u));\n }\n }\n }\n return scaled(p, Math.sqrt(Math.PI / polygonArea(p)));\n}\n\n/** The open pupil: the opening clipped by each cat's-eye circle at field tangent t (pupil-radius units). */\nfunction openPupil(options: ResolvedDefocusBokehOptions, fNumber: number, pupilRadiusMm: number, field: { x: number; y: number }): { polygon: Polygon; openAreaRatio: number } {\n const opening = openingPolygon(options.blades, fNumber), full = polygonArea(opening);\n let open = opening;\n if (options.catsEye) for (const a of options.catsEye.apertures) {\n const r = a.radiusMm / pupilRadiusMm, s = a.shiftPerTanFieldMm / pupilRadiusMm, circle = unitCirclePolygon(), cx = s * field.x, cy = s * field.y;\n const c: Polygon = [];\n for (let k = 0; k < circle.length; k += 2) c.push(cx + r * circle[k]!, cy + r * circle[k + 1]!);\n open = clipConvex(open, c);\n }\n return { polygon: open, openAreaRatio: open === opening ? 1 : polygonArea(open) / full };\n}\n\n/** Internal: the open-area ratio of the pupil at a field point (1 without a cat's eye), without building a kernel. */\nexport function openPupilAreaRatio(options: ResolvedDefocusBokehOptions, fNumber: number, focalLengthMm: number, field: { x: number; y: number }): number {\n return openPupil(options, fNumber, focalLengthMm / fNumber / 2, field).openAreaRatio;\n}\n\n/** Exact area of convex `p` (native site units, +Y down) inside each cell of the (2R + 1)² window. */\nfunction polygonCellAreas(p: Polygon, R: number): Float64Array {\n const width = 2 * R + 1, out = new Float64Array(width * width);\n for (let j = -R; j <= R; j++) {\n const strip = clipHalfPlane(clipHalfPlane(p, 0, -1, -(j - 0.5)), 0, 1, j + 0.5);\n if (strip.length < 6) continue;\n let xmin = Infinity, xmax = -Infinity;\n for (let k = 0; k < strip.length; k += 2) { xmin = Math.min(xmin, strip[k]!); xmax = Math.max(xmax, strip[k]!); }\n const i0 = Math.max(-R, Math.ceil(xmin - 0.5)), i1 = Math.min(R, Math.floor(xmax + 0.5));\n for (let i = i0; i <= i1; i++) {\n const cell = clipHalfPlane(clipHalfPlane(strip, -1, 0, -(i - 0.5)), 1, 0, i + 0.5);\n if (cell.length >= 6) out[(j + R) * width + i + R] = polygonArea(cell);\n }\n }\n return out;\n}\n\n/** The index of the largest weight (the first on ties), which takes up the normalization's rounding: the centre may be empty off axis. */\nfunction largestIndex(weights: Float64Array): number {\n let best = 0;\n for (let k = 1; k < weights.length; k++) if (weights[k]! > weights[best]!) best = k;\n return best;\n}\n\nfunction radiusOfExtent(extent: number): number { return extent <= 0.5 ? 0 : Math.ceil(extent - 0.5); }\n\n/** The kernel radius of a blur reaching `extent` sites from the centre, refused past the cap with the disc's short message (#564). */\nfunction requireRadius(extent: number, pitchMicrometers: number): number {\n const radius = radiusOfExtent(extent);\n if (radius > DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES) throw new InvalidScientificInputError(defocusDiscLimitMessage(2 * extent, 2 * extent * pitchMicrometers));\n return radius;\n}\n\n/** The shaped kernel on the site lattice, with what shaped it. */\nexport interface DefocusBokehSiteKernel extends DefocusDiscSiteKernel {\n /** Open pupil area / unclipped opening area at this field point (1 without a cat's eye). */\n openAreaRatio: number;\n /** \"exact-polygon-cell-area\" without spherical aberration, \"pupil-ray-binning\" with it. */\n method: \"exact-polygon-cell-area\" | \"pupil-ray-binning\";\n /** Rays binned (pupil-ray-binning), else 0. */\n raySamples: number;\n /** b = 8·N·W040(N) / pitch, the transverse spherical aberration of the marginal ray in sites (0 without it). */\n sphericalAberrationSites: number;\n}\n\nexport interface DefocusBokehKernelParameters {\n /** Thin-lens blur diameter c in sites (> 0). */\n diameterSites: number;\n /** +1: scene plane behind the focus plane (subject farther than focus); −1: in front of it. */\n side: 1 | -1;\n fNumber: number;\n focalLengthMm: number;\n pitchMicrometers: number;\n options: ResolvedDefocusBokehOptions;\n /** Field tangent tan θ·(cos φ, sin φ) in output-image axes (+X right, +Y up); only the cat's eye reads it. Default on axis. */\n field?: { x: number; y: number };\n}\n\n/** Internal: the shaped defocus kernel for one field point (see the module comment). Fails past the radius cap or when the pupil closes. */\nexport function createDefocusBokehSiteKernel(input: DefocusBokehKernelParameters): DefocusBokehSiteKernel {\n const { diameterSites, side, fNumber, options } = input;\n const a = diameterSites / 2, pupilRadiusMm = input.focalLengthMm / fNumber / 2, field = input.field ?? { x: 0, y: 0 };\n const sa = options.sphericalAberration;\n const q = sa ? sa.atFNumber / fNumber : 0, w040 = sa ? sa.w040Micrometers * ((q * q) * (q * q)) : 0, b = 8 * fNumber * w040 / input.pitchMicrometers;\n const { polygon, openAreaRatio } = openPupil(options, fNumber, pupilRadiusMm, field);\n if (polygon.length < 6 || openAreaRatio <= 0)\n throw new InvalidScientificInputError(\"lens.defocus: the cat's-eye apertures close the pupil at field tangent (\" + field.x + \", \" + field.y + \").\");\n const s = side * a;\n if (b === 0) {\n // Uniform blur: the open pupil scaled by σ·c/2, +Y up to native +Y down.\n const image: Polygon = [];\n let extent = 0;\n for (let k = 0; k < polygon.length; k += 2) {\n const x = s * polygon[k]!, y = -s * polygon[k + 1]!;\n image.push(x, y);\n extent = Math.max(extent, Math.abs(x), Math.abs(y));\n }\n const radius = requireRadius(extent, input.pitchMicrometers);\n const width = 2 * radius + 1, weights = polygonCellAreas(image, radius);\n normalizeSiteKernel(weights, largestIndex(weights));\n return { diameterSites, radius, width, weights, openAreaRatio, method: \"exact-polygon-cell-area\", raySamples: 0, sphericalAberrationSites: 0 };\n }\n // Spherical aberration: y(P) = (σc/2 + bρ²)·P, binned. The extent bound: max over ρ ≤ ρmax of |s·ρ + b·ρ³|.\n let rhoMax2 = 0, xmin = Infinity, xmax = -Infinity, ymin = Infinity, ymax = -Infinity;\n for (let k = 0; k < polygon.length; k += 2) {\n const x = polygon[k]!, y = polygon[k + 1]!;\n rhoMax2 = Math.max(rhoMax2, x * x + y * y);\n xmin = Math.min(xmin, x); xmax = Math.max(xmax, x); ymin = Math.min(ymin, y); ymax = Math.max(ymax, y);\n }\n const rhoMax = Math.sqrt(rhoMax2);\n let extent = Math.abs(s * rhoMax + b * rhoMax * rhoMax2);\n const stationary2 = -s / (3 * b);\n if (stationary2 > 0 && stationary2 < rhoMax2) { const r = Math.sqrt(stationary2); extent = Math.max(extent, Math.abs(s * r + b * r * stationary2)); }\n const radius = requireRadius(extent, input.pitchMicrometers);\n const width = 2 * radius + 1, weights = new Float64Array(width * width);\n // Pupil step: rays at most 1/8 site apart where the map is steepest (|s| + 3|b|ρ²), and at least 64 steps per pupil radius.\n const h = Math.min(1 / 64, 1 / (8 * (Math.abs(s) + 3 * Math.abs(b) * rhoMax2)));\n const rows = Math.ceil((ymax - ymin) / h), columns = Math.ceil((xmax - xmin) / h), n = polygon.length;\n let rays = 0;\n for (let r = 0; r < rows; r++) {\n const y = ymin + (r + 0.5) * h;\n let left = Infinity, right = -Infinity;\n for (let k = 0; k < n; k += 2) {\n const k2 = k + 2 === n ? 0 : k + 2, ay = polygon[k + 1]!, by = polygon[k2 + 1]!;\n if ((ay <= y && y < by) || (by <= y && y < ay)) {\n const ax = polygon[k]!, x = ax + (y - ay) * (polygon[k2]! - ax) / (by - ay);\n left = Math.min(left, x); right = Math.max(right, x);\n }\n }\n if (!(left < right)) continue;\n const m0 = Math.max(0, Math.floor((left - xmin) / h)), m1 = Math.min(columns - 1, Math.floor((right - xmin) / h));\n for (let m = m0; m <= m1; m++) {\n const x0 = xmin + m * h, x = x0 + 0.5 * h, coverage = Math.min(x0 + h, right) - Math.max(x0, left);\n if (!(coverage > 0)) continue;\n const f = s + b * (x * x + y * y);\n let i = Math.round(f * x), j = Math.round(-f * y);\n if (i > radius) i = radius; else if (i < -radius) i = -radius;\n if (j > radius) j = radius; else if (j < -radius) j = -radius;\n const index = (j + radius) * width + i + radius;\n weights[index] = weights[index]! + coverage;\n rays++;\n }\n }\n normalizeSiteKernel(weights, largestIndex(weights));\n return { diameterSites, radius, width, weights, openAreaRatio, method: \"pupil-ray-binning\", raySamples: rays, sphericalAberrationSites: b };\n}\n\nexport interface CalculateDefocusBokehKernelInput {\n /** Lens focal length, mm. */\n focalLengthMm: number;\n /** Working f-number. */\n aperture: number;\n /** Focus-plane distance, m. */\n focusDistanceM: number;\n /** Scene-plane distance, m. */\n subjectDistanceM: number;\n /** Sensor site pitch, µm: the lattice the kernel is sampled on. */\n pixelPitchMicrometers: number;\n /** The bokeh options, as `lens.defocus` takes them (blades, sphericalAberration, catsEye). */\n bokeh: DefocusBokehOptions;\n /** Field tangent tan θ·(cos φ, sin φ) in output-image axes (+X right, +Y up); only the cat's eye reads it. Default on axis. */\n fieldTangent?: { x: number; y: number };\n}\n\nexport interface DefocusBokehKernel {\n /** \"behind-focus\" (subject farther than focus), \"in-front-of-focus\", or \"in-focus\" (no kernel). */\n side: \"behind-focus\" | \"in-front-of-focus\" | \"in-focus\";\n /** Thin-lens blur diameter c, µm and sites (`calculateDefocusCircle`, thin-lens model 1.1.0). */\n diameterMicrometers: number;\n diameterSites: number;\n /** Kernel radius R and window side 2R + 1 (0 and 1 in focus). */\n kernelRadiusSites: number;\n width: number;\n /** Row-major weights, index (j + R)·width + (i + R) for site offset (i right, j down) from the chief ray; null in focus. */\n weights: Float64Array | null;\n /** Open pupil area / unclipped opening area at the field point (the cat's eye's open fraction; 1 without one). */\n openAreaRatio: number;\n method: \"exact-polygon-cell-area\" | \"pupil-ray-binning\" | \"none\";\n /** Transverse spherical aberration of the marginal ray at the working f-number, sites (8·N·W040(N)/pitch). */\n sphericalAberrationSites: number;\n raySamples: number;\n}\n\n/**\n * The defocus kernel of `lens.defocus` with bokeh options (#553), on the site lattice, for one field point: the same kernel\n * `simulateCapture` and the display view apply (on the field zone's centre with a cat's eye). Pure; for tests, previews and\n * lens catalogues. Fails with `InvalidScientificInputError` past {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (spherical\n * aberration can widen the blur beyond the disc) or when the cat's eye closes the pupil, `InvalidConfigurationError` for\n * unknown fields. A geometric approximation: see the module comment of `defocus-bokeh-kernel.ts` and docs/QUICK_START.md,\n * \"Bokeh character\".\n *\n * @param input Lens, f-number, distances, pitch, bokeh options and an optional field point.\n * @returns The kernel, its radius, the open-area ratio, the method and the spherical-aberration size in sites.\n */\nexport function calculateDefocusBokehKernel(input: CalculateDefocusBokehKernelInput): CalculationResult<DefocusBokehKernel> {\n requireInputKeys(input, \"calculateDefocusBokehKernel\", [\"focalLengthMm\", \"aperture\", \"focusDistanceM\", \"subjectDistanceM\", \"pixelPitchMicrometers\", \"bokeh\", \"fieldTangent\"]);\n requirePositiveFinite(\"pixelPitchMicrometers\", input.pixelPitchMicrometers);\n const options = resolveDefocusBokehOptions(input.bokeh, \"calculateDefocusBokehKernel\");\n let field = { x: 0, y: 0 };\n if (input.fieldTangent !== undefined) {\n const f = requireAllowlistedRecord(input.fieldTangent, [\"x\", \"y\"], \"fieldTangent must be { x, y }.\");\n if (typeof f.x !== \"number\" || typeof f.y !== \"number\" || !Number.isFinite(f.x) || !Number.isFinite(f.y)) throw new InvalidConfigurationError(\"fieldTangent x and y must be finite numbers.\");\n field = { x: f.x, y: f.y };\n }\n const disc = resolveDefocusDisc({ focalLengthMm: input.focalLengthMm, aperture: input.aperture, focusDistanceM: input.focusDistanceM,\n subjectDistanceM: input.subjectDistanceM, pitchMicrometers: input.pixelPitchMicrometers });\n const assumptions = [\"Geometric defocus kernel shaped by the lens (#553), an approximation: thin-lens blur circle, projected open pupil, primary spherical aberration by its ray map\",\n \"No diffraction, onion rings, higher-order or field-dependent aberrations, or colour\"];\n if (disc.diameterSites === 0) return calculatedResult({ side: \"in-focus\" as const, diameterMicrometers: 0, diameterSites: 0, kernelRadiusSites: 0, width: 1, weights: null,\n openAreaRatio: 1, method: \"none\" as const, sphericalAberrationSites: 0, raySamples: 0 }, \"defocus-bokeh-kernel\", \"1.0.0\", assumptions);\n const side = input.subjectDistanceM > input.focusDistanceM ? 1 : -1;\n const kernel = createDefocusBokehSiteKernel({ diameterSites: disc.diameterSites, side, fNumber: input.aperture, focalLengthMm: input.focalLengthMm,\n pitchMicrometers: input.pixelPitchMicrometers, options, field });\n return calculatedResult({ side: side === 1 ? \"behind-focus\" as const : \"in-front-of-focus\" as const, diameterMicrometers: disc.diameterMicrometers, diameterSites: disc.diameterSites,\n kernelRadiusSites: kernel.radius, width: kernel.width, weights: kernel.weights, openAreaRatio: kernel.openAreaRatio, method: kernel.method,\n sphericalAberrationSites: kernel.sphericalAberrationSites, raySamples: kernel.raySamples }, \"defocus-bokeh-kernel\", \"1.0.0\", assumptions);\n}\n"]}
|
|
@@ -29,22 +29,24 @@
|
|
|
29
29
|
* takes up the remaining rounding, so their exact sum is 1 within half a unit in the last place of the centre weight.
|
|
30
30
|
*
|
|
31
31
|
* Limits. The window is (2R + 1)² sites with R = ⌈r − ½⌉ (0 when r ≤ ½: a disc inside one cell is the identity, a
|
|
32
|
-
* valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}; past it the call
|
|
33
|
-
*
|
|
32
|
+
* valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (512 since 2.17.0, #564); past it the call
|
|
33
|
+
* fails with a short message instead of clipping the disc. Up to 64 sites the renderers convolve the kernel directly, as
|
|
34
|
+
* in 2.16.0; above it they use the run convolution of `site-kernel-runs.ts`, the same sums regrouped.
|
|
34
35
|
*/
|
|
35
36
|
import { type CalculationResult } from "../core/calculation-result.js";
|
|
36
37
|
/**
|
|
37
|
-
* Largest kernel radius in sites of `lens.defocus: { model: "geometric-disc" }`, R ≤ 64: a disc of
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* request first with {@link calculateDefocusDiscKernelRadius}.
|
|
38
|
+
* Largest kernel radius in sites of `lens.defocus: { model: "geometric-disc" }`, R ≤ 512 (#564, 2.17.0; 64 before): a disc of
|
|
39
|
+
* at most 1025 sites across (6.15 mm at 6 µm). `simulateCapture`, `simulateDisplayView` and `prepareDisplayView` refuse a
|
|
40
|
+
* request whose disc needs more, and never clip the disc; check a request first with {@link calculateDefocusDiscKernelRadius}.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Up to 64 sites the disc is convolved directly ((2R + 1)² taps per site,
|
|
43
|
+
* byte-identical to 2.16.0). Above it the renderers use the exact run convolution (`site-kernel-runs.ts`: one prefix-sum
|
|
44
|
+
* difference per kernel row plus the cells the circle crosses, about 12·R reads per site), whose cost grows with R instead of
|
|
45
|
+
* R². 512 bounds that cost (6,145 reads per site, below the 16,641 taps of the direct gather at 64), the kernel's own
|
|
46
|
+
* memory ((2R + 1)² doubles, 8.4 MB) and each tile's source ((64 + 2R)² doubles per field, 9.5 MB): measured in
|
|
47
|
+
* docs/QUICK_START.md, "Optics".
|
|
46
48
|
*/
|
|
47
|
-
export declare const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES =
|
|
49
|
+
export declare const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES = 512;
|
|
48
50
|
/** The disc sampled on the site lattice. */
|
|
49
51
|
export interface DefocusDiscSiteKernel {
|
|
50
52
|
/** Disc diameter in sites (c / pitch). */
|
|
@@ -59,16 +61,30 @@ export interface DefocusDiscSiteKernel {
|
|
|
59
61
|
/** Kernel radius in sites for a disc of `diameterSites`: the cells [i − ½, i + ½] the disc reaches with positive area. */
|
|
60
62
|
export declare function defocusDiscKernelRadius(diameterSites: number): number;
|
|
61
63
|
/**
|
|
62
|
-
* {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError`
|
|
63
|
-
*
|
|
64
|
+
* {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError` when it exceeds
|
|
65
|
+
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, with a short message: the disc and the largest disc in sites, rounded.
|
|
66
|
+
* The disc is never clipped to fit.
|
|
64
67
|
*/
|
|
65
68
|
export declare function requireDefocusDiscKernelRadius(diameterSites: number): number;
|
|
69
|
+
/**
|
|
70
|
+
* The short refusal: "The blur circle is 6.42 mm (1,070 sites) across; the engine renders at most 1,025 sites
|
|
71
|
+
* (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES)." The size is rounded to 3 significant digits (µm below 1 mm) and the sites up,
|
|
72
|
+
* so a disc just past the limit never reads as equal to it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function defocusDiscLimitMessage(diameterSites: number, diameterMicrometers?: number): string;
|
|
66
75
|
/**
|
|
67
76
|
* The defocus disc of `diameterSites` (c / pitch) sampled on the site lattice, or null for a zero diameter (in focus:
|
|
68
77
|
* callers apply no kernel). Fails with `InvalidScientificInputError` for a negative or non-finite diameter, and when
|
|
69
78
|
* the kernel radius exceeds {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}.
|
|
70
79
|
*/
|
|
71
80
|
export declare function createDefocusDiscSiteKernel(diameterSites: number): DefocusDiscSiteKernel | null;
|
|
81
|
+
/**
|
|
82
|
+
* Divides by the float64 sum, then sets the centre weight to 1 minus the exact sum of the others (accumulated as a
|
|
83
|
+
* double-double with Knuth's TwoSum), rounded once. The exact real sum of the weights is then 1 within half a unit in
|
|
84
|
+
* the last place of the centre weight (internal; also used by the shaped kernels of #553). (No float64 summation order can promise exactly 1: a one-ulp change of one weight
|
|
85
|
+
* can move a rounded sum from just below 1 to just above it.)
|
|
86
|
+
*/
|
|
87
|
+
export declare function normalizeSiteKernel(weights: Float64Array, centre: number): void;
|
|
72
88
|
/** The geometric defocus disc of one request, in micrometres and in sites. */
|
|
73
89
|
export interface ResolvedDefocusDisc {
|
|
74
90
|
/** Blur-circle diameter c at the sensor, µm (`calculateDefocusCircle`, thin-lens model 1.1.0). */
|
|
@@ -108,26 +124,32 @@ export interface DefocusDiscKernelRadius {
|
|
|
108
124
|
diameterSites: number;
|
|
109
125
|
/** Kernel radius R in sites the renderer needs for this disc, ⌈c / (2·pitch) − ½⌉ (0 for a disc inside one site, and in focus). */
|
|
110
126
|
kernelRadiusSites: number;
|
|
111
|
-
/** {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, 64. */
|
|
127
|
+
/** {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, 512 (64 before 2.17.0). */
|
|
112
128
|
maximumKernelRadiusSites: number;
|
|
113
|
-
/** Largest disc diameter in sites within the limit, 2·
|
|
129
|
+
/** Largest disc diameter in sites within the limit, 2·512 + 1 = 1025: within the limit exactly when `diameterSites` ≤ 1025. */
|
|
114
130
|
maximumDiameterSites: number;
|
|
115
131
|
/** True when `kernelRadiusSites` ≤ `maximumKernelRadiusSites`: `lens.defocus` renders the request; false: it refuses it. */
|
|
116
132
|
withinLimit: boolean;
|
|
117
|
-
/** How far past the limit the kernel radius is, max(0, R −
|
|
133
|
+
/** How far past the limit the kernel radius is, max(0, R − 512) sites; 0 when within the limit. */
|
|
118
134
|
excessKernelRadiusSites: number;
|
|
135
|
+
/**
|
|
136
|
+
* How the renderers convolve this disc (#564): `"direct"` for a kernel radius up to 64 sites (as in 2.16.0; also in focus),
|
|
137
|
+
* `"runs"` above it (the exact run convolution, cost about proportional to R per site), null past the limit.
|
|
138
|
+
*/
|
|
139
|
+
convolution: "direct" | "runs" | null;
|
|
119
140
|
}
|
|
120
141
|
/**
|
|
121
142
|
* Checks, before rendering, whether `lens.defocus: { model: "geometric-disc" }` can render a request: the geometric
|
|
122
143
|
* defocus disc's diameter on the site lattice, the kernel radius it needs, and whether that radius is within
|
|
123
|
-
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532
|
|
144
|
+
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532; 512 sites since 2.17.0, #564), and which convolution the renderers
|
|
145
|
+
* would use. Pure and cheap: no kernel is built and nothing is rendered.
|
|
124
146
|
*
|
|
125
147
|
* It runs the same resolution and the same limit test as `simulateCapture`, `simulateDisplayView` and `prepareDisplayView`
|
|
126
148
|
* (the blur circle c = f²·|s − d| / (N·d·(s − f)) of `calculateDefocusCircle`, thin-lens model 1.1.0, divided by the pitch),
|
|
127
149
|
* so for the same focal length, f-number, distances and pitch a request they refuse for its defocus size has
|
|
128
150
|
* `withinLimit: false`, and one they accept has `withinLimit: true`. Pass the values the capture uses: in an automatic
|
|
129
|
-
* exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the
|
|
130
|
-
* kernel (
|
|
151
|
+
* exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the diffraction
|
|
152
|
+
* kernel (at most 32 sites) is applied with it and has no further limit. Other reasons a capture
|
|
131
153
|
* refuses `lens.defocus` out of focus (pupil sampling, a caller PSF, camera rotation with a rolling shutter, a vignetting or
|
|
132
154
|
* distortion profile that does not reach the kernel's radius beyond the frame, and in the display view a projection,
|
|
133
155
|
* distortion, vignetting or rotation) are not checked here.
|
|
@@ -30,26 +30,29 @@
|
|
|
30
30
|
* takes up the remaining rounding, so their exact sum is 1 within half a unit in the last place of the centre weight.
|
|
31
31
|
*
|
|
32
32
|
* Limits. The window is (2R + 1)² sites with R = ⌈r − ½⌉ (0 when r ≤ ½: a disc inside one cell is the identity, a
|
|
33
|
-
* valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}; past it the call
|
|
34
|
-
*
|
|
33
|
+
* valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (512 since 2.17.0, #564); past it the call
|
|
34
|
+
* fails with a short message instead of clipping the disc. Up to 64 sites the renderers convolve the kernel directly, as
|
|
35
|
+
* in 2.16.0; above it they use the run convolution of `site-kernel-runs.ts`, the same sums regrouped.
|
|
35
36
|
*/
|
|
36
37
|
import { calculatedResult } from "../core/calculation-result.js";
|
|
37
38
|
import { portableAsin } from "../core/portable-math.js";
|
|
38
39
|
import { requireInputKeys } from "../core/record-validation.js";
|
|
39
40
|
import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
|
|
40
41
|
import { calculateDefocusCircle } from "./depth-of-field.js";
|
|
42
|
+
import { DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES } from "./site-kernel-runs.js";
|
|
41
43
|
/**
|
|
42
|
-
* Largest kernel radius in sites of `lens.defocus: { model: "geometric-disc" }`, R ≤ 64: a disc of
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* request first with {@link calculateDefocusDiscKernelRadius}.
|
|
44
|
+
* Largest kernel radius in sites of `lens.defocus: { model: "geometric-disc" }`, R ≤ 512 (#564, 2.17.0; 64 before): a disc of
|
|
45
|
+
* at most 1025 sites across (6.15 mm at 6 µm). `simulateCapture`, `simulateDisplayView` and `prepareDisplayView` refuse a
|
|
46
|
+
* request whose disc needs more, and never clip the disc; check a request first with {@link calculateDefocusDiscKernelRadius}.
|
|
46
47
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Up to 64 sites the disc is convolved directly ((2R + 1)² taps per site,
|
|
49
|
+
* byte-identical to 2.16.0). Above it the renderers use the exact run convolution (`site-kernel-runs.ts`: one prefix-sum
|
|
50
|
+
* difference per kernel row plus the cells the circle crosses, about 12·R reads per site), whose cost grows with R instead of
|
|
51
|
+
* R². 512 bounds that cost (6,145 reads per site, below the 16,641 taps of the direct gather at 64), the kernel's own
|
|
52
|
+
* memory ((2R + 1)² doubles, 8.4 MB) and each tile's source ((64 + 2R)² doubles per field, 9.5 MB): measured in
|
|
53
|
+
* docs/QUICK_START.md, "Optics".
|
|
51
54
|
*/
|
|
52
|
-
export const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES =
|
|
55
|
+
export const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES = 512;
|
|
53
56
|
/** Whether a kernel radius is within {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}: the one test the renderer and the pre-check share. */
|
|
54
57
|
function isWithinDefocusDiscKernelLimit(radius) {
|
|
55
58
|
return radius <= DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES;
|
|
@@ -61,16 +64,32 @@ export function defocusDiscKernelRadius(diameterSites) {
|
|
|
61
64
|
return r <= 0.5 ? 0 : Math.ceil(r - 0.5);
|
|
62
65
|
}
|
|
63
66
|
/**
|
|
64
|
-
* {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError`
|
|
65
|
-
*
|
|
67
|
+
* {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError` when it exceeds
|
|
68
|
+
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, with a short message: the disc and the largest disc in sites, rounded.
|
|
69
|
+
* The disc is never clipped to fit.
|
|
66
70
|
*/
|
|
67
71
|
export function requireDefocusDiscKernelRadius(diameterSites) {
|
|
68
72
|
const radius = defocusDiscKernelRadius(diameterSites);
|
|
69
73
|
if (!isWithinDefocusDiscKernelLimit(radius))
|
|
70
|
-
throw new InvalidScientificInputError(
|
|
71
|
-
" sites; the limit is " + DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + " sites (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).");
|
|
74
|
+
throw new InvalidScientificInputError(defocusDiscLimitMessage(diameterSites));
|
|
72
75
|
return radius;
|
|
73
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The short refusal: "The blur circle is 6.42 mm (1,070 sites) across; the engine renders at most 1,025 sites
|
|
79
|
+
* (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES)." The size is rounded to 3 significant digits (µm below 1 mm) and the sites up,
|
|
80
|
+
* so a disc just past the limit never reads as equal to it.
|
|
81
|
+
*/
|
|
82
|
+
export function defocusDiscLimitMessage(diameterSites, diameterMicrometers) {
|
|
83
|
+
const sites = groupedInteger(Math.ceil(diameterSites)) + " sites", largest = groupedInteger(2 * DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + 1);
|
|
84
|
+
const size = diameterMicrometers === undefined ? sites : (diameterMicrometers >= 1000 ? Number((diameterMicrometers / 1000).toPrecision(3)) + " mm" :
|
|
85
|
+
Number(diameterMicrometers.toPrecision(3)) + " µm") + " (" + sites + ")";
|
|
86
|
+
return "The blur circle is " + size + " across; the engine renders at most " + largest + " sites (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).";
|
|
87
|
+
}
|
|
88
|
+
/** A non-negative integer with comma thousands separators, the same in every engine (no locale data). */
|
|
89
|
+
function groupedInteger(value) {
|
|
90
|
+
const digits = String(value);
|
|
91
|
+
return digits.length <= 3 ? digits : digits.replace(/\B(?=(\d{3})+$)/gu, ",");
|
|
92
|
+
}
|
|
74
93
|
function requireDiameter(diameterSites) {
|
|
75
94
|
if (typeof diameterSites !== "number" || !Number.isFinite(diameterSites) || diameterSites < 0)
|
|
76
95
|
throw new InvalidScientificInputError("The defocus disc diameter must be a finite number of sites, zero or more.");
|
|
@@ -125,16 +144,16 @@ export function createDefocusDiscSiteKernel(diameterSites) {
|
|
|
125
144
|
for (const sv of v === 0 ? [1] : [1, -1])
|
|
126
145
|
weights[(sv * v + radius) * width + su * u + radius] = a;
|
|
127
146
|
}
|
|
128
|
-
|
|
147
|
+
normalizeSiteKernel(weights, (width * width - 1) / 2);
|
|
129
148
|
return { diameterSites, radius, width, weights };
|
|
130
149
|
}
|
|
131
150
|
/**
|
|
132
151
|
* Divides by the float64 sum, then sets the centre weight to 1 minus the exact sum of the others (accumulated as a
|
|
133
152
|
* double-double with Knuth's TwoSum), rounded once. The exact real sum of the weights is then 1 within half a unit in
|
|
134
|
-
* the last place of the centre weight. (No float64 summation order can promise exactly 1: a one-ulp change of one weight
|
|
153
|
+
* the last place of the centre weight (internal; also used by the shaped kernels of #553). (No float64 summation order can promise exactly 1: a one-ulp change of one weight
|
|
135
154
|
* can move a rounded sum from just below 1 to just above it.)
|
|
136
155
|
*/
|
|
137
|
-
function
|
|
156
|
+
export function normalizeSiteKernel(weights, centre) {
|
|
138
157
|
let total = 0;
|
|
139
158
|
for (let k = 0; k < weights.length; k++)
|
|
140
159
|
total += weights[k];
|
|
@@ -167,14 +186,15 @@ export function resolveDefocusDisc(input) {
|
|
|
167
186
|
/**
|
|
168
187
|
* Checks, before rendering, whether `lens.defocus: { model: "geometric-disc" }` can render a request: the geometric
|
|
169
188
|
* defocus disc's diameter on the site lattice, the kernel radius it needs, and whether that radius is within
|
|
170
|
-
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532
|
|
189
|
+
* {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532; 512 sites since 2.17.0, #564), and which convolution the renderers
|
|
190
|
+
* would use. Pure and cheap: no kernel is built and nothing is rendered.
|
|
171
191
|
*
|
|
172
192
|
* It runs the same resolution and the same limit test as `simulateCapture`, `simulateDisplayView` and `prepareDisplayView`
|
|
173
193
|
* (the blur circle c = f²·|s − d| / (N·d·(s − f)) of `calculateDefocusCircle`, thin-lens model 1.1.0, divided by the pitch),
|
|
174
194
|
* so for the same focal length, f-number, distances and pitch a request they refuse for its defocus size has
|
|
175
195
|
* `withinLimit: false`, and one they accept has `withinLimit: true`. Pass the values the capture uses: in an automatic
|
|
176
|
-
* exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the
|
|
177
|
-
* kernel (
|
|
196
|
+
* exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the diffraction
|
|
197
|
+
* kernel (at most 32 sites) is applied with it and has no further limit. Other reasons a capture
|
|
178
198
|
* refuses `lens.defocus` out of focus (pupil sampling, a caller PSF, camera rotation with a rolling shutter, a vignetting or
|
|
179
199
|
* distortion profile that does not reach the kernel's radius beyond the frame, and in the display view a projection,
|
|
180
200
|
* distortion, vignetting or rotation) are not checked here.
|
|
@@ -199,8 +219,9 @@ export function calculateDefocusDiscKernelRadius(input) {
|
|
|
199
219
|
maximumKernelRadiusSites: DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES,
|
|
200
220
|
maximumDiameterSites: 2 * DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + 1,
|
|
201
221
|
withinLimit,
|
|
202
|
-
excessKernelRadiusSites: withinLimit ? 0 : disc.kernelRadiusSites - DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES
|
|
203
|
-
|
|
222
|
+
excessKernelRadiusSites: withinLimit ? 0 : disc.kernelRadiusSites - DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES,
|
|
223
|
+
convolution: !withinLimit ? null : disc.kernelRadiusSites > DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES ? "runs" : "direct"
|
|
224
|
+
}, "defocus-disc-kernel-radius", "1.1.0", [
|
|
204
225
|
"Geometric thin-lens blur circle of an ideal uniform circular pupil (thin-lens-defocus-circle 1.1.0), an approximation",
|
|
205
226
|
"The disc and limit test lens.defocus { model: \"geometric-disc\" } applies in simulateCapture and the display view",
|
|
206
227
|
"Only the defocus size limit is checked"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defocus-disc-kernel.js","sourceRoot":"","sources":["../../src/optics/defocus-disc-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The geometric defocus disc of a flat scene, sampled on the square site lattice, for\n * `lens.defocus: { model: \"geometric-disc\" }` (#492). Internal except for the radius cap\n * {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} and the pre-check {@link calculateDefocusDiscKernelRadius}, which are\n * root exports (#532), so a caller can check a request against the cap before rendering.\n *\n * Model. For a scene plane at the subject distance d and a thin lens focused at s, an ideal uniform circular pupil\n * spreads every in-focus image point into a uniform disc of diameter c = f²·|s − d| / (N·d·(s − f)) on the sensor\n * (`calculateDefocusCircle`, thin-lens model 1.1.0; geometric optics, no diffraction or aberrations). The defocused\n * image of the plane is therefore the in-focus image convolved with that disc: one fixed, wavelength-independent\n * kernel. It is an approximation (geometric optics, ideal uniform circular pupil, no vignetting of the pupil).\n *\n * Sampling. The disc of diameter c/p sites (p the site pitch) is centred on a site centre. The weight of the site at\n * offset (i, j) is the exact area of the disc inside that site's square cell [i − ½, i + ½] × [j − ½, j + ½], divided\n * by the area of the disc. The area of a disc inside a rectangle is a signed sum over the rectangle's four corners of\n * the area Q(x, y) of the disc inside the quadrant [0, x] × [0, y] (inclusion–exclusion), and for x, y ≥ 0 with\n * x, y clamped to the radius r\n * Q = x·y when x² + y² ≤ r²,\n * Q = ½·(x_a·y + x·y_a + r²·(arcsin(x/r) − arcsin(x_a/r))) otherwise,\n * with x_a = √(r² − y²) and y_a = √(r² − x²) the arc's crossings of the quadrant's edges: the rectangle left of the\n * crossing plus the area under the arc, ∫ √(r² − t²) dt = ½·(t·√(r² − t²) + r²·arcsin(t/r)) (elementary geometry\n * and calculus; NIST DLMF 4.24.1 for the arcsine, free). The arcsine difference is the angle between the two arc\n * points, evaluated as arcsin((x·y − x_a·y_a)/r²) (the sine of a difference). Q is extended to negative x or y as an\n * odd function.\n *\n * Determinism. Only correctly rounded operations and `portableAsin` are used, so the weights are the same bits in\n * every JavaScript engine. One octant (0 ≤ j ≤ i) is computed and mirrored, so the kernel has the exact 8-fold\n * symmetry of the disc and the square lattice. The weights are divided by their float64 sum, then the centre weight\n * takes up the remaining rounding, so their exact sum is 1 within half a unit in the last place of the centre weight.\n *\n * Limits. The window is (2R + 1)² sites with R = ⌈r − ½⌉ (0 when r ≤ ½: a disc inside one cell is the identity, a\n * valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}; past it the call fails, naming the\n * cap and the radius asked for, instead of clipping the disc.\n */\n\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { portableAsin } from \"../core/portable-math.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, requirePositiveFinite } from \"../core/validation.js\";\nimport { calculateDefocusCircle } from \"./depth-of-field.js\";\n\n/**\n * Largest kernel radius in sites of `lens.defocus: { model: \"geometric-disc\" }`, R ≤ 64: a disc of at most 129 sites\n * across (a 129 × 129 window, 16 641 taps per site; the diffraction kernels stop at 32). `simulateCapture`,\n * `simulateDisplayView` and `prepareDisplayView` refuse a request whose disc needs more, and never clip the disc; check a\n * request first with {@link calculateDefocusDiscKernelRadius}.\n *\n * Set from the cost measured in the capture (#492, step 2a; docs/QUICK_START.md, \"Optics\"): the capture's time grows with\n * R² at every site the disc sees an edge from, and a 0.7 MP Siemens star took 0.37 s in focus, 2.9 s at R = 32, 4.7 s at\n * R = 41 and 11.6 s at R = 64 (Node, one thread). 64 keeps the issue's 1.4 µm example (50 mm f/2.8, subject 2 m, focus\n * 1.6 m: R = 41) with room for larger blurs, at a cost the caller chooses and the docs state; past it the call fails.\n */\nexport const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES = 64;\n\n/** Whether a kernel radius is within {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}: the one test the renderer and the pre-check share. */\nfunction isWithinDefocusDiscKernelLimit(radius: number): boolean {\n return radius <= DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES;\n}\n\n/** The disc sampled on the site lattice. */\nexport interface DefocusDiscSiteKernel {\n /** Disc diameter in sites (c / pitch). */\n readonly diameterSites: number;\n /** Kernel radius R in sites: the window is (2R + 1) × (2R + 1). */\n readonly radius: number;\n /** Window side, 2R + 1. */\n readonly width: number;\n /** Row-major weights, index (j + R)·width + (i + R) for the site offset (i, j); their row-major float64 sum is 1. */\n readonly weights: Float64Array;\n}\n\n/** Kernel radius in sites for a disc of `diameterSites`: the cells [i − ½, i + ½] the disc reaches with positive area. */\nexport function defocusDiscKernelRadius(diameterSites: number): number {\n requireDiameter(diameterSites);\n const r = diameterSites / 2;\n return r <= 0.5 ? 0 : Math.ceil(r - 0.5);\n}\n\n/**\n * {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError` (naming the cap and the radius asked for)\n * when it exceeds {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}. The disc is never clipped to fit.\n */\nexport function requireDefocusDiscKernelRadius(diameterSites: number): number {\n const radius = defocusDiscKernelRadius(diameterSites);\n if (!isWithinDefocusDiscKernelLimit(radius))\n throw new InvalidScientificInputError(\"The defocus disc of \" + diameterSites + \" sites needs a kernel radius of \" + radius +\n \" sites; the limit is \" + DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + \" sites (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).\");\n return radius;\n}\n\nfunction requireDiameter(diameterSites: number): void {\n if (typeof diameterSites !== \"number\" || !Number.isFinite(diameterSites) || diameterSites < 0)\n throw new InvalidScientificInputError(\"The defocus disc diameter must be a finite number of sites, zero or more.\");\n}\n\n/** Area of the disc of radius r (centred at the origin) inside [0, x] × [0, y], for x, y ≥ 0. */\nfunction quadrantArea(x: number, y: number, r: number): number {\n if (x > r) x = r;\n if (y > r) y = r;\n if (x * x + y * y <= r * r) return x * y;\n const xa = Math.sqrt((r - y) * (r + y)), ya = Math.sqrt((r - x) * (r + x));\n // arcsin(x/r) − arcsin(x_a/r) is the angle between the arc points (x_a, y) and (x, y_a), whose sine is (x·y − x_a·y_a)/r².\n return 0.5 * (xa * y + x * ya + r * r * portableAsin(Math.min(1, (x * y - xa * ya) / (r * r))));\n}\n/** {@link quadrantArea} extended as an odd function of x and of y. */\nfunction signedQuadrantArea(x: number, y: number, r: number): number {\n const q = quadrantArea(x < 0 ? -x : x, y < 0 ? -y : y, r);\n return (x < 0) !== (y < 0) ? -q : q;\n}\n/**\n * Area of the disc inside the cell [i − ½, i + ½] × [j − ½, j + ½], for i ≥ j ≥ 0. Exactly 0 when the cell's point nearest\n * the centre lies on or outside the circle. Otherwise the four corner terms are up to r² in size, so the difference\n * carries an absolute rounding error of a few r²·2^-53 (below 10^-11 at the radius cap, under 10^-15 of the disc's area);\n * a cell that barely touches the disc can come out a rounding error below zero and is set to 0.\n */\nfunction cellArea(i: number, j: number, r: number): number {\n const x0 = i - 0.5, x1 = i + 0.5, y0 = j - 0.5, y1 = j + 0.5;\n const nx = i === 0 ? 0 : x0, ny = j === 0 ? 0 : y0;\n if (nx * nx + ny * ny >= r * r) return 0;\n const area = (signedQuadrantArea(x1, y1, r) - signedQuadrantArea(x0, y1, r)) - (signedQuadrantArea(x1, y0, r) - signedQuadrantArea(x0, y0, r));\n return area > 0 ? area : 0;\n}\n\n/**\n * The defocus disc of `diameterSites` (c / pitch) sampled on the site lattice, or null for a zero diameter (in focus:\n * callers apply no kernel). Fails with `InvalidScientificInputError` for a negative or non-finite diameter, and when\n * the kernel radius exceeds {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}.\n */\nexport function createDefocusDiscSiteKernel(diameterSites: number): DefocusDiscSiteKernel | null {\n const radius = requireDefocusDiscKernelRadius(diameterSites);\n if (diameterSites === 0) return null;\n const r = diameterSites / 2, width = 2 * radius + 1, weights = new Float64Array(width * width);\n // One octant, mirrored into the eight symmetric cells.\n for (let i = 0; i <= radius; i++) for (let j = 0; j <= i; j++) {\n const a = cellArea(i, j, r);\n for (const [u, v] of [[i, j], [j, i]] as const) for (const su of u === 0 ? [1] : [1, -1]) for (const sv of v === 0 ? [1] : [1, -1])\n weights[(sv * v + radius) * width + su * u + radius] = a;\n }\n normalize(weights, (width * width - 1) / 2);\n return { diameterSites, radius, width, weights };\n}\n\n/**\n * Divides by the float64 sum, then sets the centre weight to 1 minus the exact sum of the others (accumulated as a\n * double-double with Knuth's TwoSum), rounded once. The exact real sum of the weights is then 1 within half a unit in\n * the last place of the centre weight. (No float64 summation order can promise exactly 1: a one-ulp change of one weight\n * can move a rounded sum from just below 1 to just above it.)\n */\nfunction normalize(weights: Float64Array, centre: number): void {\n let total = 0;\n for (let k = 0; k < weights.length; k++) total += weights[k]!;\n for (let k = 0; k < weights.length; k++) weights[k]! /= total;\n let hi = 0, lo = 0;\n for (let k = 0; k < weights.length; k++) {\n if (k === centre) continue;\n const w = weights[k]!, s = hi + w, b = s - hi;\n lo += (hi - (s - b)) + (w - b);\n hi = s;\n }\n // 1 − (hi + lo): TwoSum of 1 and −hi, then the low parts.\n const s = 1 - hi, b = s - 1, e = (1 - (s - b)) + (-hi - b);\n weights[centre] = s + (e - lo);\n}\n\n/** The geometric defocus disc of one request, in micrometres and in sites. */\nexport interface ResolvedDefocusDisc {\n /** Blur-circle diameter c at the sensor, µm (`calculateDefocusCircle`, thin-lens model 1.1.0). */\n readonly diameterMicrometers: number;\n /** c / pitch. */\n readonly diameterSites: number;\n /** Kernel radius R in sites (0 when c = 0). */\n readonly kernelRadiusSites: number;\n}\n\n/**\n * Resolves the defocus disc for a thin lens focused at `focusDistanceM` imaging a plane at `subjectDistanceM`, on a\n * lattice of `pitchMicrometers`. Exactly zero when the two distances are equal. It does not check the radius cap.\n */\nexport function resolveDefocusDisc(input: { focalLengthMm: number; aperture: number; focusDistanceM: number; subjectDistanceM: number;\n pitchMicrometers: number }): ResolvedDefocusDisc {\n const { pitchMicrometers, ...circle } = input;\n if (!Number.isFinite(pitchMicrometers) || pitchMicrometers <= 0) throw new InvalidScientificInputError(\"pitchMicrometers must be a positive finite number.\");\n const diameterMicrometers = calculateDefocusCircle(circle).value.diameterMm * 1000;\n const diameterSites = diameterMicrometers / pitchMicrometers;\n return { diameterMicrometers, diameterSites, kernelRadiusSites: defocusDiscKernelRadius(diameterSites) };\n}\n\nexport interface CalculateDefocusDiscKernelRadiusInput {\n /** Lens focal length in millimetres (`lens.focalLengthMm`). */\n focalLengthMm: number;\n /** F-number the capture uses: `lens.fNumber`, or in an automatic exposure mode the resolved `exposure.settings.fNumber`. */\n aperture: number;\n /** Focus-plane distance from the lens plane in metres (`lens.focusDistanceM`; `simulateCapture` defaults it to the subject distance). */\n focusDistanceM: number;\n /** Scene-plane distance from the lens plane in metres (`subjectDistanceM`). */\n subjectDistanceM: number;\n /** Sensor site pitch in micrometres (`sensor.pixelPitchMicrometers`), the lattice the disc is sampled on. */\n pixelPitchMicrometers: number;\n}\n\nexport interface DefocusDiscKernelRadius {\n /** Geometric blur-circle diameter c at the sensor in micrometres (`calculateDefocusCircle`, thin-lens model 1.1.0); 0 in focus. */\n diameterMicrometers: number;\n /** c / pixel pitch: the disc's diameter in sites. */\n diameterSites: number;\n /** Kernel radius R in sites the renderer needs for this disc, ⌈c / (2·pitch) − ½⌉ (0 for a disc inside one site, and in focus). */\n kernelRadiusSites: number;\n /** {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, 64. */\n maximumKernelRadiusSites: number;\n /** Largest disc diameter in sites within the limit, 2·64 + 1 = 129: within the limit exactly when `diameterSites` ≤ 129. */\n maximumDiameterSites: number;\n /** True when `kernelRadiusSites` ≤ `maximumKernelRadiusSites`: `lens.defocus` renders the request; false: it refuses it. */\n withinLimit: boolean;\n /** How far past the limit the kernel radius is, max(0, R − 64) sites; 0 when within the limit. */\n excessKernelRadiusSites: number;\n}\n\n/**\n * Checks, before rendering, whether `lens.defocus: { model: \"geometric-disc\" }` can render a request: the geometric\n * defocus disc's diameter on the site lattice, the kernel radius it needs, and whether that radius is within\n * {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532). Pure and cheap: no kernel is built and nothing is rendered.\n *\n * It runs the same resolution and the same limit test as `simulateCapture`, `simulateDisplayView` and `prepareDisplayView`\n * (the blur circle c = f²·|s − d| / (N·d·(s − f)) of `calculateDefocusCircle`, thin-lens model 1.1.0, divided by the pitch),\n * so for the same focal length, f-number, distances and pitch a request they refuse for its defocus size has\n * `withinLimit: false`, and one they accept has `withinLimit: true`. Pass the values the capture uses: in an automatic\n * exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the combined\n * kernel (diffraction radius plus defocus radius, at most 32 + 64 sites) has no further limit. Other reasons a capture\n * refuses `lens.defocus` out of focus (pupil sampling, a caller PSF, camera rotation with a rolling shutter, a vignetting or\n * distortion profile that does not reach the kernel's radius beyond the frame, and in the display view a projection,\n * distortion, vignetting or rotation) are not checked here.\n *\n * The blur circle is a geometric thin-lens approximation for an ideal uniform circular pupil (no diffraction, aberrations\n * or pupil vignetting). Inputs follow `calculateDefocusCircle`: all positive and finite, both distances beyond the focal\n * length; invalid values fail with `InvalidScientificInputError`, unknown fields with `InvalidConfigurationError`.\n *\n * @param input Lens, f-number, focus and subject distances, and the sensor's site pitch.\n * @returns The disc and its kernel radius, the limit, and whether the request is within it and by how much it is not.\n */\nexport function calculateDefocusDiscKernelRadius(input: CalculateDefocusDiscKernelRadiusInput): CalculationResult<DefocusDiscKernelRadius> {\n requireInputKeys(input, \"calculateDefocusDiscKernelRadius\", [\"focalLengthMm\", \"aperture\", \"focusDistanceM\", \"subjectDistanceM\", \"pixelPitchMicrometers\"]);\n requirePositiveFinite(\"pixelPitchMicrometers\", input.pixelPitchMicrometers);\n const disc = resolveDefocusDisc({ focalLengthMm: input.focalLengthMm, aperture: input.aperture, focusDistanceM: input.focusDistanceM,\n subjectDistanceM: input.subjectDistanceM, pitchMicrometers: input.pixelPitchMicrometers });\n const withinLimit = isWithinDefocusDiscKernelLimit(disc.kernelRadiusSites);\n return calculatedResult({\n diameterMicrometers: disc.diameterMicrometers,\n diameterSites: disc.diameterSites,\n kernelRadiusSites: disc.kernelRadiusSites,\n maximumKernelRadiusSites: DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES,\n maximumDiameterSites: 2 * DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + 1,\n withinLimit,\n excessKernelRadiusSites: withinLimit ? 0 : disc.kernelRadiusSites - DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES\n }, \"defocus-disc-kernel-radius\", \"1.0.0\", [\n \"Geometric thin-lens blur circle of an ideal uniform circular pupil (thin-lens-defocus-circle 1.1.0), an approximation\",\n \"The disc and limit test lens.defocus { model: \\\"geometric-disc\\\" } applies in simulateCapture and the display view\",\n \"Only the defocus size limit is checked\"\n ]);\n}\n\n/**\n * The full 2-D convolution of two square site kernels (row-major, radii `radiusA` and `radiusB`): a kernel of radius\n * radiusA + radiusB with out(t) = Σ_s a(s)·b(t − s), each output summed over the taps of `a` in row-major order (zero taps\n * of either kernel skipped, which adds exact zeros only). Used for lens.defocus with lens.diffraction (#492): each spectral\n * node's diffraction kernel convolved with the defocus disc. That combination is an approximation: the defocused wave PSF\n * of a circular pupil is not exactly the in-focus Airy pattern convolved with the geometric disc. Both inputs here are\n * centred and non-negative, so the result is too; its sum is the product of the inputs' sums up to rounding.\n */\nexport function convolveSiteKernels(a: Float64Array, radiusA: number, b: Float64Array, radiusB: number): Float64Array {\n const wa = 2 * radiusA + 1, wb = 2 * radiusB + 1, radius = radiusA + radiusB, width = 2 * radius + 1;\n if (a.length !== wa * wa || b.length !== wb * wb) throw new InvalidScientificInputError(\"Site kernels must be (2R + 1)² taps.\");\n const out = new Float64Array(width * width);\n // Non-zero taps of b, as (row, column, weight).\n const bRows: number[] = [], bColumns: number[] = [], bWeights: number[] = [];\n for (let j = 0; j < wb; j++) for (let i = 0; i < wb; i++) { const w = b[j * wb + i]!; if (w !== 0) { bRows.push(j); bColumns.push(i); bWeights.push(w); } }\n for (let sj = 0; sj < wa; sj++) for (let si = 0; si < wa; si++) {\n const w = a[sj * wa + si]!;\n if (w === 0) continue;\n for (let k = 0; k < bWeights.length; k++) {\n const index = (sj + bRows[k]!) * width + si + bColumns[k]!;\n out[index] = out[index]! + w * bWeights[k]!;\n }\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"defocus-disc-kernel.js","sourceRoot":"","sources":["../../src/optics/defocus-disc-kernel.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,gBAAgB,EAA0B,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,0CAA0C,EAAE,MAAM,uBAAuB,CAAC;AAEnF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,wCAAwC,GAAG,GAAG,CAAC;AAE5D,6IAA6I;AAC7I,SAAS,8BAA8B,CAAC,MAAc;IACpD,OAAO,MAAM,IAAI,wCAAwC,CAAC;AAC5D,CAAC;AAcD,0HAA0H;AAC1H,MAAM,UAAU,uBAAuB,CAAC,aAAqB;IAC3D,eAAe,CAAC,aAAa,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,aAAqB;IAClE,MAAM,MAAM,GAAG,uBAAuB,CAAC,aAAa,CAAC,CAAC;IACtD,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC;QACzC,MAAM,IAAI,2BAA2B,CAAC,uBAAuB,CAAC,aAAa,CAAC,CAAC,CAAC;IAChF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,aAAqB,EAAE,mBAA4B;IACzF,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,QAAQ,EAAE,OAAO,GAAG,cAAc,CAAC,CAAC,GAAG,wCAAwC,GAAG,CAAC,CAAC,CAAC;IAC9I,MAAM,IAAI,GAAG,mBAAmB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;QACnJ,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG,GAAG,CAAC;IAC3E,OAAO,qBAAqB,GAAG,IAAI,GAAG,sCAAsC,GAAG,OAAO,GAAG,oDAAoD,CAAC;AAChJ,CAAC;AAED,yGAAyG;AACzG,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,eAAe,CAAC,aAAqB;IAC5C,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC;QAC3F,MAAM,IAAI,2BAA2B,CAAC,2EAA2E,CAAC,CAAC;AACvH,CAAC;AAED,iGAAiG;AACjG,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACnD,IAAI,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC;IACjB,IAAI,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC;IACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3E,2HAA2H;IAC3H,OAAO,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClG,CAAC;AACD,sEAAsE;AACtE,SAAS,kBAAkB,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACzD,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AACD;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IAC/C,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;IAC7D,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/I,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAC/D,MAAM,MAAM,GAAG,8BAA8B,CAAC,aAAa,CAAC,CAAC;IAC7D,IAAI,aAAa,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,CAAC,GAAG,aAAa,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,YAAY,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IAC/F,uDAAuD;IACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9D,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAU;gBAAE,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAAE,KAAK,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wBAChI,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IACD,mBAAmB,CAAC,OAAO,EAAE,CAAC,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAE,MAAc;IACvE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC;IAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC,CAAE,IAAI,KAAK,CAAC;IAC9D,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,MAAM;YAAE,SAAS;QAC3B,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAC9C,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,EAAE,GAAG,CAAC,CAAC;IACT,CAAC;IACD,0DAA0D;IAC1D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AACjC,CAAC;AAYD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KACP;IAC1B,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,gBAAgB,IAAI,CAAC;QAAE,MAAM,IAAI,2BAA2B,CAAC,oDAAoD,CAAC,CAAC;IAC7J,MAAM,mBAAmB,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;IACnF,MAAM,aAAa,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;IAC7D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,aAAa,CAAC,EAAE,CAAC;AAC3G,CAAC;AAqCD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,gCAAgC,CAAC,KAA4C;IAC3F,gBAAgB,CAAC,KAAK,EAAE,kCAAkC,EAAE,CAAC,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAC1J,qBAAqB,CAAC,uBAAuB,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc;QAClI,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,CAAC,qBAAqB,EAAE,CAAC,CAAC;IAC7F,MAAM,WAAW,GAAG,8BAA8B,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC3E,OAAO,gBAAgB,CAAC;QACtB,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;QAC7C,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;QACzC,wBAAwB,EAAE,wCAAwC;QAClE,oBAAoB,EAAE,CAAC,GAAG,wCAAwC,GAAG,CAAC;QACtE,WAAW;QACX,uBAAuB,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,GAAG,wCAAwC;QAC5G,WAAW,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,GAAG,0CAA0C,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;KAC3H,EAAE,4BAA4B,EAAE,OAAO,EAAE;QACxC,uHAAuH;QACvH,oHAAoH;QACpH,wCAAwC;KACzC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAe,EAAE,OAAe,EAAE,CAAe,EAAE,OAAe;IACpG,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IACrG,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,GAAG,EAAE;QAAE,MAAM,IAAI,2BAA2B,CAAC,sCAAsC,CAAC,CAAC;IAChI,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IAC5C,gDAAgD;IAChD,MAAM,KAAK,GAAa,EAAE,EAAE,QAAQ,GAAa,EAAE,EAAE,QAAQ,GAAa,EAAE,CAAC;IAC7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;QAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAE,CAAC;YAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,CAAC;QAAC,CAAC;IAC3J,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE;QAAE,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;YAC/D,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,MAAM,KAAK,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;gBAC3D,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;YAC9C,CAAC;QACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The geometric defocus disc of a flat scene, sampled on the square site lattice, for\n * `lens.defocus: { model: \"geometric-disc\" }` (#492). Internal except for the radius cap\n * {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} and the pre-check {@link calculateDefocusDiscKernelRadius}, which are\n * root exports (#532), so a caller can check a request against the cap before rendering.\n *\n * Model. For a scene plane at the subject distance d and a thin lens focused at s, an ideal uniform circular pupil\n * spreads every in-focus image point into a uniform disc of diameter c = f²·|s − d| / (N·d·(s − f)) on the sensor\n * (`calculateDefocusCircle`, thin-lens model 1.1.0; geometric optics, no diffraction or aberrations). The defocused\n * image of the plane is therefore the in-focus image convolved with that disc: one fixed, wavelength-independent\n * kernel. It is an approximation (geometric optics, ideal uniform circular pupil, no vignetting of the pupil).\n *\n * Sampling. The disc of diameter c/p sites (p the site pitch) is centred on a site centre. The weight of the site at\n * offset (i, j) is the exact area of the disc inside that site's square cell [i − ½, i + ½] × [j − ½, j + ½], divided\n * by the area of the disc. The area of a disc inside a rectangle is a signed sum over the rectangle's four corners of\n * the area Q(x, y) of the disc inside the quadrant [0, x] × [0, y] (inclusion–exclusion), and for x, y ≥ 0 with\n * x, y clamped to the radius r\n * Q = x·y when x² + y² ≤ r²,\n * Q = ½·(x_a·y + x·y_a + r²·(arcsin(x/r) − arcsin(x_a/r))) otherwise,\n * with x_a = √(r² − y²) and y_a = √(r² − x²) the arc's crossings of the quadrant's edges: the rectangle left of the\n * crossing plus the area under the arc, ∫ √(r² − t²) dt = ½·(t·√(r² − t²) + r²·arcsin(t/r)) (elementary geometry\n * and calculus; NIST DLMF 4.24.1 for the arcsine, free). The arcsine difference is the angle between the two arc\n * points, evaluated as arcsin((x·y − x_a·y_a)/r²) (the sine of a difference). Q is extended to negative x or y as an\n * odd function.\n *\n * Determinism. Only correctly rounded operations and `portableAsin` are used, so the weights are the same bits in\n * every JavaScript engine. One octant (0 ≤ j ≤ i) is computed and mirrored, so the kernel has the exact 8-fold\n * symmetry of the disc and the square lattice. The weights are divided by their float64 sum, then the centre weight\n * takes up the remaining rounding, so their exact sum is 1 within half a unit in the last place of the centre weight.\n *\n * Limits. The window is (2R + 1)² sites with R = ⌈r − ½⌉ (0 when r ≤ ½: a disc inside one cell is the identity, a\n * valid kernel). R is capped at {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (512 since 2.17.0, #564); past it the call\n * fails with a short message instead of clipping the disc. Up to 64 sites the renderers convolve the kernel directly, as\n * in 2.16.0; above it they use the run convolution of `site-kernel-runs.ts`, the same sums regrouped.\n */\n\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { portableAsin } from \"../core/portable-math.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, requirePositiveFinite } from \"../core/validation.js\";\nimport { calculateDefocusCircle } from \"./depth-of-field.js\";\nimport { DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES } from \"./site-kernel-runs.js\";\n\n/**\n * Largest kernel radius in sites of `lens.defocus: { model: \"geometric-disc\" }`, R ≤ 512 (#564, 2.17.0; 64 before): a disc of\n * at most 1025 sites across (6.15 mm at 6 µm). `simulateCapture`, `simulateDisplayView` and `prepareDisplayView` refuse a\n * request whose disc needs more, and never clip the disc; check a request first with {@link calculateDefocusDiscKernelRadius}.\n *\n * Up to 64 sites the disc is convolved directly ((2R + 1)² taps per site,\n * byte-identical to 2.16.0). Above it the renderers use the exact run convolution (`site-kernel-runs.ts`: one prefix-sum\n * difference per kernel row plus the cells the circle crosses, about 12·R reads per site), whose cost grows with R instead of\n * R². 512 bounds that cost (6,145 reads per site, below the 16,641 taps of the direct gather at 64), the kernel's own\n * memory ((2R + 1)² doubles, 8.4 MB) and each tile's source ((64 + 2R)² doubles per field, 9.5 MB): measured in\n * docs/QUICK_START.md, \"Optics\".\n */\nexport const DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES = 512;\n\n/** Whether a kernel radius is within {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}: the one test the renderer and the pre-check share. */\nfunction isWithinDefocusDiscKernelLimit(radius: number): boolean {\n return radius <= DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES;\n}\n\n/** The disc sampled on the site lattice. */\nexport interface DefocusDiscSiteKernel {\n /** Disc diameter in sites (c / pitch). */\n readonly diameterSites: number;\n /** Kernel radius R in sites: the window is (2R + 1) × (2R + 1). */\n readonly radius: number;\n /** Window side, 2R + 1. */\n readonly width: number;\n /** Row-major weights, index (j + R)·width + (i + R) for the site offset (i, j); their row-major float64 sum is 1. */\n readonly weights: Float64Array;\n}\n\n/** Kernel radius in sites for a disc of `diameterSites`: the cells [i − ½, i + ½] the disc reaches with positive area. */\nexport function defocusDiscKernelRadius(diameterSites: number): number {\n requireDiameter(diameterSites);\n const r = diameterSites / 2;\n return r <= 0.5 ? 0 : Math.ceil(r - 0.5);\n}\n\n/**\n * {@link defocusDiscKernelRadius}, failing with `InvalidScientificInputError` when it exceeds\n * {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, with a short message: the disc and the largest disc in sites, rounded.\n * The disc is never clipped to fit.\n */\nexport function requireDefocusDiscKernelRadius(diameterSites: number): number {\n const radius = defocusDiscKernelRadius(diameterSites);\n if (!isWithinDefocusDiscKernelLimit(radius))\n throw new InvalidScientificInputError(defocusDiscLimitMessage(diameterSites));\n return radius;\n}\n\n/**\n * The short refusal: \"The blur circle is 6.42 mm (1,070 sites) across; the engine renders at most 1,025 sites\n * (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).\" The size is rounded to 3 significant digits (µm below 1 mm) and the sites up,\n * so a disc just past the limit never reads as equal to it.\n */\nexport function defocusDiscLimitMessage(diameterSites: number, diameterMicrometers?: number): string {\n const sites = groupedInteger(Math.ceil(diameterSites)) + \" sites\", largest = groupedInteger(2 * DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + 1);\n const size = diameterMicrometers === undefined ? sites : (diameterMicrometers >= 1000 ? Number((diameterMicrometers / 1000).toPrecision(3)) + \" mm\" :\n Number(diameterMicrometers.toPrecision(3)) + \" µm\") + \" (\" + sites + \")\";\n return \"The blur circle is \" + size + \" across; the engine renders at most \" + largest + \" sites (DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).\";\n}\n\n/** A non-negative integer with comma thousands separators, the same in every engine (no locale data). */\nfunction groupedInteger(value: number): string {\n const digits = String(value);\n return digits.length <= 3 ? digits : digits.replace(/\\B(?=(\\d{3})+$)/gu, \",\");\n}\n\nfunction requireDiameter(diameterSites: number): void {\n if (typeof diameterSites !== \"number\" || !Number.isFinite(diameterSites) || diameterSites < 0)\n throw new InvalidScientificInputError(\"The defocus disc diameter must be a finite number of sites, zero or more.\");\n}\n\n/** Area of the disc of radius r (centred at the origin) inside [0, x] × [0, y], for x, y ≥ 0. */\nfunction quadrantArea(x: number, y: number, r: number): number {\n if (x > r) x = r;\n if (y > r) y = r;\n if (x * x + y * y <= r * r) return x * y;\n const xa = Math.sqrt((r - y) * (r + y)), ya = Math.sqrt((r - x) * (r + x));\n // arcsin(x/r) − arcsin(x_a/r) is the angle between the arc points (x_a, y) and (x, y_a), whose sine is (x·y − x_a·y_a)/r².\n return 0.5 * (xa * y + x * ya + r * r * portableAsin(Math.min(1, (x * y - xa * ya) / (r * r))));\n}\n/** {@link quadrantArea} extended as an odd function of x and of y. */\nfunction signedQuadrantArea(x: number, y: number, r: number): number {\n const q = quadrantArea(x < 0 ? -x : x, y < 0 ? -y : y, r);\n return (x < 0) !== (y < 0) ? -q : q;\n}\n/**\n * Area of the disc inside the cell [i − ½, i + ½] × [j − ½, j + ½], for i ≥ j ≥ 0. Exactly 0 when the cell's point nearest\n * the centre lies on or outside the circle. Otherwise the four corner terms are up to r² in size, so the difference\n * carries an absolute rounding error of a few r²·2^-53 (below 10^-11 at the radius cap, under 10^-15 of the disc's area);\n * a cell that barely touches the disc can come out a rounding error below zero and is set to 0.\n */\nfunction cellArea(i: number, j: number, r: number): number {\n const x0 = i - 0.5, x1 = i + 0.5, y0 = j - 0.5, y1 = j + 0.5;\n const nx = i === 0 ? 0 : x0, ny = j === 0 ? 0 : y0;\n if (nx * nx + ny * ny >= r * r) return 0;\n const area = (signedQuadrantArea(x1, y1, r) - signedQuadrantArea(x0, y1, r)) - (signedQuadrantArea(x1, y0, r) - signedQuadrantArea(x0, y0, r));\n return area > 0 ? area : 0;\n}\n\n/**\n * The defocus disc of `diameterSites` (c / pitch) sampled on the site lattice, or null for a zero diameter (in focus:\n * callers apply no kernel). Fails with `InvalidScientificInputError` for a negative or non-finite diameter, and when\n * the kernel radius exceeds {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}.\n */\nexport function createDefocusDiscSiteKernel(diameterSites: number): DefocusDiscSiteKernel | null {\n const radius = requireDefocusDiscKernelRadius(diameterSites);\n if (diameterSites === 0) return null;\n const r = diameterSites / 2, width = 2 * radius + 1, weights = new Float64Array(width * width);\n // One octant, mirrored into the eight symmetric cells.\n for (let i = 0; i <= radius; i++) for (let j = 0; j <= i; j++) {\n const a = cellArea(i, j, r);\n for (const [u, v] of [[i, j], [j, i]] as const) for (const su of u === 0 ? [1] : [1, -1]) for (const sv of v === 0 ? [1] : [1, -1])\n weights[(sv * v + radius) * width + su * u + radius] = a;\n }\n normalizeSiteKernel(weights, (width * width - 1) / 2);\n return { diameterSites, radius, width, weights };\n}\n\n/**\n * Divides by the float64 sum, then sets the centre weight to 1 minus the exact sum of the others (accumulated as a\n * double-double with Knuth's TwoSum), rounded once. The exact real sum of the weights is then 1 within half a unit in\n * the last place of the centre weight (internal; also used by the shaped kernels of #553). (No float64 summation order can promise exactly 1: a one-ulp change of one weight\n * can move a rounded sum from just below 1 to just above it.)\n */\nexport function normalizeSiteKernel(weights: Float64Array, centre: number): void {\n let total = 0;\n for (let k = 0; k < weights.length; k++) total += weights[k]!;\n for (let k = 0; k < weights.length; k++) weights[k]! /= total;\n let hi = 0, lo = 0;\n for (let k = 0; k < weights.length; k++) {\n if (k === centre) continue;\n const w = weights[k]!, s = hi + w, b = s - hi;\n lo += (hi - (s - b)) + (w - b);\n hi = s;\n }\n // 1 − (hi + lo): TwoSum of 1 and −hi, then the low parts.\n const s = 1 - hi, b = s - 1, e = (1 - (s - b)) + (-hi - b);\n weights[centre] = s + (e - lo);\n}\n\n/** The geometric defocus disc of one request, in micrometres and in sites. */\nexport interface ResolvedDefocusDisc {\n /** Blur-circle diameter c at the sensor, µm (`calculateDefocusCircle`, thin-lens model 1.1.0). */\n readonly diameterMicrometers: number;\n /** c / pitch. */\n readonly diameterSites: number;\n /** Kernel radius R in sites (0 when c = 0). */\n readonly kernelRadiusSites: number;\n}\n\n/**\n * Resolves the defocus disc for a thin lens focused at `focusDistanceM` imaging a plane at `subjectDistanceM`, on a\n * lattice of `pitchMicrometers`. Exactly zero when the two distances are equal. It does not check the radius cap.\n */\nexport function resolveDefocusDisc(input: { focalLengthMm: number; aperture: number; focusDistanceM: number; subjectDistanceM: number;\n pitchMicrometers: number }): ResolvedDefocusDisc {\n const { pitchMicrometers, ...circle } = input;\n if (!Number.isFinite(pitchMicrometers) || pitchMicrometers <= 0) throw new InvalidScientificInputError(\"pitchMicrometers must be a positive finite number.\");\n const diameterMicrometers = calculateDefocusCircle(circle).value.diameterMm * 1000;\n const diameterSites = diameterMicrometers / pitchMicrometers;\n return { diameterMicrometers, diameterSites, kernelRadiusSites: defocusDiscKernelRadius(diameterSites) };\n}\n\nexport interface CalculateDefocusDiscKernelRadiusInput {\n /** Lens focal length in millimetres (`lens.focalLengthMm`). */\n focalLengthMm: number;\n /** F-number the capture uses: `lens.fNumber`, or in an automatic exposure mode the resolved `exposure.settings.fNumber`. */\n aperture: number;\n /** Focus-plane distance from the lens plane in metres (`lens.focusDistanceM`; `simulateCapture` defaults it to the subject distance). */\n focusDistanceM: number;\n /** Scene-plane distance from the lens plane in metres (`subjectDistanceM`). */\n subjectDistanceM: number;\n /** Sensor site pitch in micrometres (`sensor.pixelPitchMicrometers`), the lattice the disc is sampled on. */\n pixelPitchMicrometers: number;\n}\n\nexport interface DefocusDiscKernelRadius {\n /** Geometric blur-circle diameter c at the sensor in micrometres (`calculateDefocusCircle`, thin-lens model 1.1.0); 0 in focus. */\n diameterMicrometers: number;\n /** c / pixel pitch: the disc's diameter in sites. */\n diameterSites: number;\n /** Kernel radius R in sites the renderer needs for this disc, ⌈c / (2·pitch) − ½⌉ (0 for a disc inside one site, and in focus). */\n kernelRadiusSites: number;\n /** {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES}, 512 (64 before 2.17.0). */\n maximumKernelRadiusSites: number;\n /** Largest disc diameter in sites within the limit, 2·512 + 1 = 1025: within the limit exactly when `diameterSites` ≤ 1025. */\n maximumDiameterSites: number;\n /** True when `kernelRadiusSites` ≤ `maximumKernelRadiusSites`: `lens.defocus` renders the request; false: it refuses it. */\n withinLimit: boolean;\n /** How far past the limit the kernel radius is, max(0, R − 512) sites; 0 when within the limit. */\n excessKernelRadiusSites: number;\n /**\n * How the renderers convolve this disc (#564): `\"direct\"` for a kernel radius up to 64 sites (as in 2.16.0; also in focus),\n * `\"runs\"` above it (the exact run convolution, cost about proportional to R per site), null past the limit.\n */\n convolution: \"direct\" | \"runs\" | null;\n}\n\n/**\n * Checks, before rendering, whether `lens.defocus: { model: \"geometric-disc\" }` can render a request: the geometric\n * defocus disc's diameter on the site lattice, the kernel radius it needs, and whether that radius is within\n * {@link DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES} (#532; 512 sites since 2.17.0, #564), and which convolution the renderers\n * would use. Pure and cheap: no kernel is built and nothing is rendered.\n *\n * It runs the same resolution and the same limit test as `simulateCapture`, `simulateDisplayView` and `prepareDisplayView`\n * (the blur circle c = f²·|s − d| / (N·d·(s − f)) of `calculateDefocusCircle`, thin-lens model 1.1.0, divided by the pitch),\n * so for the same focal length, f-number, distances and pitch a request they refuse for its defocus size has\n * `withinLimit: false`, and one they accept has `withinLimit: true`. Pass the values the capture uses: in an automatic\n * exposure mode that is the resolved f-number. The limit applies to the disc alone; with `lens.diffraction` the diffraction\n * kernel (at most 32 sites) is applied with it and has no further limit. Other reasons a capture\n * refuses `lens.defocus` out of focus (pupil sampling, a caller PSF, camera rotation with a rolling shutter, a vignetting or\n * distortion profile that does not reach the kernel's radius beyond the frame, and in the display view a projection,\n * distortion, vignetting or rotation) are not checked here.\n *\n * The blur circle is a geometric thin-lens approximation for an ideal uniform circular pupil (no diffraction, aberrations\n * or pupil vignetting). Inputs follow `calculateDefocusCircle`: all positive and finite, both distances beyond the focal\n * length; invalid values fail with `InvalidScientificInputError`, unknown fields with `InvalidConfigurationError`.\n *\n * @param input Lens, f-number, focus and subject distances, and the sensor's site pitch.\n * @returns The disc and its kernel radius, the limit, and whether the request is within it and by how much it is not.\n */\nexport function calculateDefocusDiscKernelRadius(input: CalculateDefocusDiscKernelRadiusInput): CalculationResult<DefocusDiscKernelRadius> {\n requireInputKeys(input, \"calculateDefocusDiscKernelRadius\", [\"focalLengthMm\", \"aperture\", \"focusDistanceM\", \"subjectDistanceM\", \"pixelPitchMicrometers\"]);\n requirePositiveFinite(\"pixelPitchMicrometers\", input.pixelPitchMicrometers);\n const disc = resolveDefocusDisc({ focalLengthMm: input.focalLengthMm, aperture: input.aperture, focusDistanceM: input.focusDistanceM,\n subjectDistanceM: input.subjectDistanceM, pitchMicrometers: input.pixelPitchMicrometers });\n const withinLimit = isWithinDefocusDiscKernelLimit(disc.kernelRadiusSites);\n return calculatedResult({\n diameterMicrometers: disc.diameterMicrometers,\n diameterSites: disc.diameterSites,\n kernelRadiusSites: disc.kernelRadiusSites,\n maximumKernelRadiusSites: DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES,\n maximumDiameterSites: 2 * DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES + 1,\n withinLimit,\n excessKernelRadiusSites: withinLimit ? 0 : disc.kernelRadiusSites - DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES,\n convolution: !withinLimit ? null : disc.kernelRadiusSites > DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES ? \"runs\" : \"direct\"\n }, \"defocus-disc-kernel-radius\", \"1.1.0\", [\n \"Geometric thin-lens blur circle of an ideal uniform circular pupil (thin-lens-defocus-circle 1.1.0), an approximation\",\n \"The disc and limit test lens.defocus { model: \\\"geometric-disc\\\" } applies in simulateCapture and the display view\",\n \"Only the defocus size limit is checked\"\n ]);\n}\n\n/**\n * The full 2-D convolution of two square site kernels (row-major, radii `radiusA` and `radiusB`): a kernel of radius\n * radiusA + radiusB with out(t) = Σ_s a(s)·b(t − s), each output summed over the taps of `a` in row-major order (zero taps\n * of either kernel skipped, which adds exact zeros only). Used for lens.defocus with lens.diffraction (#492): each spectral\n * node's diffraction kernel convolved with the defocus disc. That combination is an approximation: the defocused wave PSF\n * of a circular pupil is not exactly the in-focus Airy pattern convolved with the geometric disc. Both inputs here are\n * centred and non-negative, so the result is too; its sum is the product of the inputs' sums up to rounding.\n */\nexport function convolveSiteKernels(a: Float64Array, radiusA: number, b: Float64Array, radiusB: number): Float64Array {\n const wa = 2 * radiusA + 1, wb = 2 * radiusB + 1, radius = radiusA + radiusB, width = 2 * radius + 1;\n if (a.length !== wa * wa || b.length !== wb * wb) throw new InvalidScientificInputError(\"Site kernels must be (2R + 1)² taps.\");\n const out = new Float64Array(width * width);\n // Non-zero taps of b, as (row, column, weight).\n const bRows: number[] = [], bColumns: number[] = [], bWeights: number[] = [];\n for (let j = 0; j < wb; j++) for (let i = 0; i < wb; i++) { const w = b[j * wb + i]!; if (w !== 0) { bRows.push(j); bColumns.push(i); bWeights.push(w); } }\n for (let sj = 0; sj < wa; sj++) for (let si = 0; si < wa; si++) {\n const w = a[sj * wa + si]!;\n if (w === 0) continue;\n for (let k = 0; k < bWeights.length; k++) {\n const index = (sj + bRows[k]!) * width + si + bColumns[k]!;\n out[index] = out[index]! + w * bWeights[k]!;\n }\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { EvidenceProvenance } from "../core/evidence-provenance.js";
|
|
2
|
+
import { type ResolvedGenericLens } from "../equipment/generic-lens-catalogue.js";
|
|
3
|
+
import { type LensSampledPsfProfile } from "./lens-psf-profile.js";
|
|
4
|
+
/** Wavelength nodes of a catalogue PSF profile, nm (they span the facade's 380–780 nm quadrature). */
|
|
5
|
+
export declare const GENERIC_LENS_PSF_WAVELENGTHS_NM: readonly [380, 450, 550, 650, 780];
|
|
6
|
+
/** Largest kernel radius of the aberration PSF in sites (before any defocus disc is convolved in). */
|
|
7
|
+
export declare const GENERIC_LENS_PSF_MAXIMUM_KERNEL_RADIUS_SITES = 12;
|
|
8
|
+
/** Largest kernel radius in sites of the catalogue PSF with a defocus disc convolved in (`simulateCapture` refuses more: cost). */
|
|
9
|
+
export declare const GENERIC_LENS_PSF_MAXIMUM_COMBINED_RADIUS_SITES = 16;
|
|
10
|
+
/** Energy fraction a node may leave outside the chosen kernel window (it is put back by renormalisation and reported). */
|
|
11
|
+
export declare const GENERIC_LENS_PSF_WINDOW_TRUNCATION = 0.03;
|
|
12
|
+
export interface GenericLensPsfInput {
|
|
13
|
+
/** The lens resolved at the capture's f-number and focus distance (`resolveGenericLens`). */
|
|
14
|
+
lens: ResolvedGenericLens;
|
|
15
|
+
/** Finite focus distance, m (the thin-lens image distance sets the propagation distance). */
|
|
16
|
+
focusDistanceM: number;
|
|
17
|
+
sitePitchMicrometers: number;
|
|
18
|
+
/** Half extent of the field the profile must cover, mm (the frame's half width and height plus the PSF and query reach). */
|
|
19
|
+
fieldHalfExtentMm: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
};
|
|
23
|
+
/** The frame's half width and height, mm (the inner field nodes sit at half of them). */
|
|
24
|
+
frameHalfMm: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
};
|
|
28
|
+
/** Optional site kernel (2R_d + 1)² to convolve into every node (the `lens.defocus` disc). */
|
|
29
|
+
extraKernel?: {
|
|
30
|
+
weights: Float64Array;
|
|
31
|
+
radius: number;
|
|
32
|
+
};
|
|
33
|
+
evidence: readonly EvidenceProvenance[];
|
|
34
|
+
}
|
|
35
|
+
export interface GenericLensPsf {
|
|
36
|
+
profile: LensSampledPsfProfile;
|
|
37
|
+
/** Radius of the aberration PSF window, sites, and of the applied kernel (with `extraKernel`). */
|
|
38
|
+
aberrationKernelRadiusSites: number;
|
|
39
|
+
kernelRadiusSites: number;
|
|
40
|
+
/** Largest energy fraction outside the aberration window over the nodes (put back by renormalisation). */
|
|
41
|
+
maximumTruncatedEnergyFraction: number;
|
|
42
|
+
/** FFT sizes used (smallest, largest) and the largest pupil phase step, waves. */
|
|
43
|
+
transformSize: {
|
|
44
|
+
minimum: number;
|
|
45
|
+
maximum: number;
|
|
46
|
+
};
|
|
47
|
+
maximumPhaseStepWaves: number;
|
|
48
|
+
/** Field node coordinates, mm, and wavelengths, nm. */
|
|
49
|
+
fieldXmm: readonly number[];
|
|
50
|
+
fieldYmm: readonly number[];
|
|
51
|
+
wavelengthsNm: readonly number[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* An upper bound of the aberration PSF's kernel radius in sites (the window is then narrowed to keep at most
|
|
55
|
+
* {@link GENERIC_LENS_PSF_WINDOW_TRUNCATION} of every node's energy outside): the largest transverse ray aberration 2·N_w·|∂W/∂ρ| over the pupil at the frame
|
|
56
|
+
* corner and every wavelength node, plus the lateral-colour shift, plus the Airy third dark ring 3.24·λ·N_w at 780 nm, rounded up
|
|
57
|
+
* to whole sites (at least 1, at most {@link GENERIC_LENS_PSF_MAXIMUM_KERNEL_RADIUS_SITES}).
|
|
58
|
+
*/
|
|
59
|
+
export declare function genericLensPsfKernelRadius(input: {
|
|
60
|
+
lens: ResolvedGenericLens;
|
|
61
|
+
focusDistanceM: number;
|
|
62
|
+
sitePitchMicrometers: number;
|
|
63
|
+
}): number;
|
|
64
|
+
/**
|
|
65
|
+
* Builds the catalogue lens's sampled PSF profile for the environment PSF quadrature: 5 × 5 field nodes × 5 wavelengths, each
|
|
66
|
+
* kernel on the site lattice. Engine-owned (parsed once). Deterministic: the same input gives the same profile.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createGenericLensPsfProfile(input: GenericLensPsfInput): GenericLensPsf;
|