@photivra/engine 2.20.0 → 2.22.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 +295 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +18 -0
- package/dist/capture/c2pa-embed.d.ts +116 -0
- package/dist/capture/c2pa-embed.js +493 -0
- package/dist/capture/c2pa-embed.js.map +1 -0
- package/dist/capture/dng-import.d.ts +73 -8
- package/dist/capture/dng-import.js +315 -80
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/dng-lens-correction-opcodes.d.ts +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
- package/dist/capture/dng-opcodes.d.ts +96 -0
- package/dist/capture/dng-opcodes.js +391 -0
- package/dist/capture/dng-opcodes.js.map +1 -0
- package/dist/capture/dng-profile-maps.d.ts +99 -0
- package/dist/capture/dng-profile-maps.js +381 -0
- package/dist/capture/dng-profile-maps.js.map +1 -0
- package/dist/capture/dng-stage-image.d.ts +23 -0
- package/dist/capture/dng-stage-image.js +65 -0
- package/dist/capture/dng-stage-image.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
- package/dist/capture/jpeg-xl-tiles.js +151 -0
- package/dist/capture/jpeg-xl-tiles.js.map +1 -0
- package/dist/capture/lossy-jpeg.d.ts +17 -0
- package/dist/capture/lossy-jpeg.js +369 -0
- package/dist/capture/lossy-jpeg.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +41 -73
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-development.d.ts +2 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +12 -3
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
- package/dist/color/neighbor-highlight-reconstruction.js +234 -0
- package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
- package/dist/color/sensor-color-development.d.ts +5 -0
- package/dist/color/sensor-color-development.js +7 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/color/spectral-spikiness.d.ts +11 -0
- package/dist/color/spectral-spikiness.js +23 -0
- package/dist/color/spectral-spikiness.js.map +1 -0
- package/dist/core/execution-error.d.ts +21 -0
- package/dist/core/execution-error.js +12 -0
- package/dist/core/execution-error.js.map +1 -0
- 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 +3 -2
- package/dist/equipment/generic-lens-catalogue.js +18 -5
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +16 -11
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +28 -3
- package/dist/facade/catalogue-lens-signal.js +83 -78
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +1 -1
- package/dist/facade/diffraction-provider.js +4 -4
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +39 -5
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +19 -6
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.js +25 -23
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view-normal.d.ts +15 -0
- package/dist/facade/display-view-normal.js +44 -0
- package/dist/facade/display-view-normal.js.map +1 -0
- package/dist/facade/display-view-rendering.d.ts +97 -0
- package/dist/facade/display-view-rendering.js +598 -0
- package/dist/facade/display-view-rendering.js.map +1 -0
- package/dist/facade/display-view.d.ts +37 -65
- package/dist/facade/display-view.js +152 -499
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +3 -3
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +1 -1
- package/dist/facade/fast-methods.js +46 -9
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +3 -3
- package/dist/facade/generic-lens-capture.js +3 -3
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +384 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/on-demand-kits.d.ts +14 -0
- package/dist/facade/on-demand-kits.js +36 -0
- package/dist/facade/on-demand-kits.js.map +1 -0
- package/dist/facade/opt-in-kit.d.ts +9 -0
- package/dist/facade/opt-in-kit.js +11 -0
- package/dist/facade/opt-in-kit.js.map +1 -0
- package/dist/facade/quality-kit.d.ts +21 -0
- package/dist/facade/quality-kit.js +23 -0
- package/dist/facade/quality-kit.js.map +1 -0
- package/dist/facade/quality-paths.d.ts +22 -0
- package/dist/facade/quality-paths.js +44 -0
- package/dist/facade/quality-paths.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +145 -4
- package/dist/facade/scene-radiance.js +50 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +68 -20
- package/dist/facade/simulate-capture.js +399 -146
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +10 -12
- package/dist/facade/simulate-display-view.js +70 -53
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
- package/dist/gpu/display-view-gpu-renderer.js +134 -0
- package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
- package/dist/gpu/display-view-image-material-module.d.ts +87 -0
- package/dist/gpu/display-view-image-material-module.js +361 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +87 -0
- package/dist/gpu/display-view-image-quality.js +625 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +80 -65
- package/dist/gpu/display-view-quality-gpu.js +187 -445
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +67 -19
- package/dist/gpu/display-view-speed-gpu.js +181 -105
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/image-kernel-execution.d.ts +2 -13
- package/dist/gpu/image-kernel-execution.js +2 -10
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
- package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
- package/dist/gpu/wgsl-display-view-coupled.js +353 -0
- package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +33 -24
- package/dist/gpu/wgsl-display-view-quality-kernel.js +166 -146
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +19 -44
- package/dist/gpu/wgsl-display-view-speed-kernel.js +116 -18
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +22 -6
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.js +54 -46
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/output/camera-channel-response.d.ts +50 -0
- package/dist/output/camera-channel-response.js +69 -0
- package/dist/output/camera-channel-response.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +28 -9
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -5
- package/dist/output/generic-lens-corrections.js +52 -8
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/native-capture-sdr.js +5 -2
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/sdr-rendering.d.ts +18 -1
- package/dist/output/sdr-rendering.js +39 -2
- package/dist/output/sdr-rendering.js.map +1 -1
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/native-sensor-report.d.ts +10 -0
- package/dist/sensor/native-sensor-report.js +16 -0
- package/dist/sensor/native-sensor-report.js.map +1 -0
- package/dist/sensor/sensor-characteristics.d.ts +91 -13
- package/dist/sensor/sensor-characteristics.js +30 -15
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +214 -4
- package/docs/QUICK_START.md +162 -8
- package/docs/USAGE.md +195 -22
- package/docs/api/capture-c2pa-embed.md +177 -0
- package/docs/api/capture-dng-import.md +86 -10
- package/docs/api/capture-dng-opcodes.md +93 -0
- package/docs/api/capture-dng-profile-maps.md +141 -0
- package/docs/api/capture-dng-stage-image.md +43 -0
- package/docs/api/capture-jpeg-xl-tiles.md +33 -0
- package/docs/api/capture-lossy-jpeg.md +41 -0
- package/docs/api/capture-native-raw-development.md +2 -1
- package/docs/api/core-execution-error.md +43 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +10 -5
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +460 -5
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-display-view.md +11 -15
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +47 -10
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-quality-paths.md +24 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +48 -11
- package/docs/api/facade-simulate-display-view.md +10 -12
- package/docs/api/gpu-image-kernel-execution.md +0 -36
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-camera-channel-response.md +55 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -4
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-sdr-rendering.md +6 -1
- package/docs/api/sensor-sensor-characteristics.md +54 -2
- package/llms.txt +3 -0
- package/package.json +6 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subject-motion.js","sourceRoot":"","sources":["../../src/motion/subject-motion.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Subject motion (#551, 2.22.0): the whole scene, or one declared rectangle of it, moving on the scene plane during the\n * exposure, over a still background. Translation in m/s on the plane and, optionally, a constant rotation about an axis\n * parallel to the optical axis (through the plane's origin for the whole scene, through the rectangle's centre for a\n * region). Times are seconds from the first opening reference, the camera rotation's time reference, so each site's own\n * exposure window (shifted by its rolling-shutter offset) sees the subject where it is during that window.\n *\n * The scene at plane point p and time t: a point of the subject at rest point q is at R(θ)(q − c) + c + d at time t\n * (d = v·t, θ = ω·t, c the centre), so p shows the subject's q = R(−θ)(p − c − d) + c when q lies in the subject; else\n * a region's rest rectangle shows a neutral 18 % background (what the subject revealed), and anywhere else the still scene at p. Exact for this\n * model (rigid motion of a planar layer); the blur is the capture's temporal midpoint quadrature.\n * @see docs/QUICK_START.md, \"Subject motion\".\n */\n\nimport type { PlanarSceneRadianceProvider } from \"../facade/scene-radiance.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\n\n/** `motion.subject` of `simulateCapture`: a moving subject on the scene plane (#551). */\nexport interface SimulateCaptureSubjectMotion {\n /** Velocity on the scene plane, m/s: +x right, +y up as seen from the camera. Missing components are 0. */\n velocityMPerSec?: { x?: number; y?: number };\n /** Rotation about the optical axis direction, rad/s, counter-clockwise as seen from the camera (+x toward +y). Default 0. */\n angularVelocityRadPerSec?: number;\n /**\n * The moving part: a rectangle on the scene plane at rest (at the first opening reference), its centre and size in metres\n * (+x right, +y up from the optical axis). Omitted: the whole scene moves.\n */\n region?: { centerXM: number; centerYM: number; widthM: number; heightM: number };\n /** Exact or fast (2.20.0 convention): fast takes fewer temporal midpoints when the subject's streak is short (see the docs). */\n fast?: boolean;\n}\n\n/** Internal: a validated subject motion (simulate-capture.ts parses it). */\nexport interface SubjectMotion {\n vx: number; vy: number; omega: number;\n /** The region's centre and half sizes in metres, or undefined for the whole scene. */\n region: { cx: number; cy: number; hw: number; hh: number } | undefined;\n}\n\n/**\n * The farthest any subject point travels on the plane during `seconds`, in metres (translation plus rotation at the subject's\n * farthest point from its centre; `reachM` is the whole scene's reach from the axis when no region is declared).\n */\nexport function subjectTravelM(motion: SubjectMotion, seconds: number, reachM: number): number {\n const r = motion.region, arm = r ? Math.hypot(r.hw, r.hh) : reachM;\n return (Math.hypot(motion.vx, motion.vy) + Math.abs(motion.omega) * arm) * seconds;\n}\n\n/**\n * The plane point that shows at (x, y) at time t (in the motion's length unit), written to out[0], out[1]; returns false where a region's rest\n * rectangle shows the revealed background. Without motion at t (d = 0, θ = 0) the point is (x, y) unchanged, bit for bit.\n */\nexport function subjectSourcePoint(motion: SubjectMotion, x: number, y: number, t: number, out: Float64Array): boolean {\n const r = motion.region, th = motion.omega * t;\n let qx = x - motion.vx * t, qy = y - motion.vy * t;\n if (th !== 0) {\n const cx = r ? r.cx : 0, cy = r ? r.cy : 0, c = Math.cos(th), s = Math.sin(th), dx = qx - cx, dy = qy - cy;\n qx = c * dx + s * dy + cx; qy = c * dy - s * dx + cy;\n }\n if (r && !(Math.abs(qx - r.cx) <= r.hw && Math.abs(qy - r.cy) <= r.hh)) {\n out[0] = x; out[1] = y;\n return !(Math.abs(x - r.cx) <= r.hw && Math.abs(y - r.cy) <= r.hh);\n }\n out[0] = qx; out[1] = qy;\n return true;\n}\n\n/**\n * The scene of `base` with the subject moving: `evaluateRadiance` reads each query at its request time (the base result, with the\n * radiance read where the subject shows), and `radianceAt` takes the time as a fourth argument (default 0) for the image-space\n * lattices and the display view. It has no chart material access, so renderers read radiance. `revealedRadiance` is the revealed\n * background's spectral radiance at a plane point in frame units (an 18 % neutral reflectance under the irradiance there).\n */\nexport function createSubjectMotionProvider(base: PlanarSceneRadianceProvider, motion: SubjectMotion, distanceM: number, scale: number,\n revealedRadiance: (u: number, v: number, nm: number) => number): PlanarSceneRadianceProvider {\n // The motion in frame units, so a still instant reads the base at the queried point itself.\n const r = motion.region, framed: SubjectMotion = { vx: motion.vx / scale, vy: motion.vy / scale, omega: motion.omega,\n region: r && { cx: r.cx / scale, cy: r.cy / scale, hw: r.hw / scale, hh: r.hh / scale } }, point = new Float64Array(2);\n const radianceAt = (u: number, v: number, nm: number, t = 0): number =>\n subjectSourcePoint(framed, u, v, t, point) ? base.radianceAt(point[0]!, point[1]!, nm) : revealedRadiance(u, v, nm);\n return { sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt, evaluateApertureRadiance: base.evaluateApertureRadiance,\n evaluateRadiance(request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult {\n const result = base.evaluateRadiance(request), o = (request.target as { outgoingDirectionUnitVector: { x: number; y: number; z: number } }).outgoingDirectionUnitVector;\n if (!(o.z < 0)) return result;\n // The base provider's own plane-point arithmetic for this direction.\n const t = distanceM / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: radianceAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers, request.timeSecondsFromExposureStart) };\n } };\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lateral colour of a catalogue lens as a fringe width (#580): the red–blue image displacement in micrometres and in pixels at
|
|
3
|
+
* one field position, for the app's Guide page on color fringing.
|
|
4
|
+
*
|
|
5
|
+
* It reads the lens's own lateral-colour model (`docs/LENS_CATALOGUE.md`, "Models"), the one `simulateCapture` renders and
|
|
6
|
+
* `createGenericLensCorrectionProfile` corrects: a radial image displacement Δr(λ) = L·R·H·D(λ), with L the lens's
|
|
7
|
+
* `lateralColor.fraction`, R the sensor's half-diagonal, H the field height (fraction of R) and D(λ) the normalised Cauchy
|
|
8
|
+
* dispersion (0 at the Fraunhofer d line, F minus C = 1). The red and blue channels are taken at the effective wavelengths of
|
|
9
|
+
* the lens corrections, `GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM` (610 and 465 nm), so the blue-minus-red displacement
|
|
10
|
+
* is L·R·H·(D(465) − D(610)). With `channelDispersion` (2.21.0, #589's `calculateChannelEffectiveDispersion` for the camera's
|
|
11
|
+
* channels and the light) it is L·R·H·(D̄_blue − D̄_red), the displacement `simulateCapture` renders in RAW. The lens's lateral
|
|
12
|
+
* colour does not change with the f-number or the focus distance.
|
|
13
|
+
*
|
|
14
|
+
* Every value is an approximation: the lens is a generic Photivra design, not a measured product, and a real channel spans a
|
|
15
|
+
* band of wavelengths, not one.
|
|
16
|
+
*/
|
|
17
|
+
import { type CalculationResult } from "../core/calculation-result.js";
|
|
18
|
+
import { type ChannelDispersion } from "../output/developed-lens-corrections.js";
|
|
19
|
+
export interface CalculateLateralColorFringeInput {
|
|
20
|
+
/** A catalogue id (`GENERIC_LENS_IDS`). */
|
|
21
|
+
lensId: string;
|
|
22
|
+
/** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */
|
|
23
|
+
activeImagingArea: {
|
|
24
|
+
widthMm: number;
|
|
25
|
+
heightMm: number;
|
|
26
|
+
};
|
|
27
|
+
/** The sensor's pixel pitch, µm. */
|
|
28
|
+
pixelPitchMicrometers: number;
|
|
29
|
+
/**
|
|
30
|
+
* The field position as fractions of the frame's half-width (x, +right) and half-height (y, +up) from the optical axis, each
|
|
31
|
+
* in [−1, 1]. Default `{ x: 1, y: 1 }`: the top-right corner.
|
|
32
|
+
*/
|
|
33
|
+
fieldPosition?: {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 2.21.0: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light;
|
|
39
|
+
* only `red` and `blue` are read). Default: D at the single wavelengths 610 and 465 nm.
|
|
40
|
+
*/
|
|
41
|
+
channelDispersion?: ChannelDispersion;
|
|
42
|
+
}
|
|
43
|
+
/** A signed displacement of the blue image from the red one: radial (positive: blue farther from the axis) and its x and y parts. */
|
|
44
|
+
export interface LateralColorDisplacement {
|
|
45
|
+
radial: number;
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
}
|
|
49
|
+
export interface LateralColorFringe {
|
|
50
|
+
lensId: string;
|
|
51
|
+
/** The image-plane point, mm from the optical axis (+x right, +y up). */
|
|
52
|
+
imagePointMm: {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
};
|
|
56
|
+
/** Field height H: the point's distance from the axis as a fraction of the half-diagonal (1 at the corner). */
|
|
57
|
+
fieldHeight: number;
|
|
58
|
+
/** The channel wavelengths the displacement is taken at, nm; null with a given `channelDispersion`. */
|
|
59
|
+
channelWavelengthsNm: {
|
|
60
|
+
red: number;
|
|
61
|
+
blue: number;
|
|
62
|
+
} | null;
|
|
63
|
+
/** The red and blue dispersions D used (normalized: 0 at the d line, F minus C = 1). */
|
|
64
|
+
channelDispersion: {
|
|
65
|
+
red: number;
|
|
66
|
+
blue: number;
|
|
67
|
+
};
|
|
68
|
+
/** Blue-minus-red image displacement, µm. */
|
|
69
|
+
blueMinusRedMicrometers: LateralColorDisplacement;
|
|
70
|
+
/** The same in pixels of the given pitch. */
|
|
71
|
+
blueMinusRedPixels: LateralColorDisplacement;
|
|
72
|
+
/** The fringe width: |radial| displacement, µm and pixels. */
|
|
73
|
+
fringeWidthMicrometers: number;
|
|
74
|
+
fringeWidthPixels: number;
|
|
75
|
+
/** The lens's own F-to-C (486 to 656 nm) displacement at this point, L·R·H, µm. */
|
|
76
|
+
fToCMicrometers: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The red–blue image displacement of a catalogue lens at a field position on a sensor, in micrometres and pixels, from the
|
|
80
|
+
* lens's own lateral-colour model Δr(λ) = L·R·H·D(λ) at the lens corrections' channel wavelengths (610 and 465 nm), or at the
|
|
81
|
+
* given `channelDispersion`. Exact for the model (no fast mode); an approximation of a generic lens, not a measured product.
|
|
82
|
+
*/
|
|
83
|
+
export declare function calculateLateralColorFringe(input: CalculateLateralColorFringeInput): CalculationResult<LateralColorFringe>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Lateral colour of a catalogue lens as a fringe width (#580): the red–blue image displacement in micrometres and in pixels at
|
|
4
|
+
* one field position, for the app's Guide page on color fringing.
|
|
5
|
+
*
|
|
6
|
+
* It reads the lens's own lateral-colour model (`docs/LENS_CATALOGUE.md`, "Models"), the one `simulateCapture` renders and
|
|
7
|
+
* `createGenericLensCorrectionProfile` corrects: a radial image displacement Δr(λ) = L·R·H·D(λ), with L the lens's
|
|
8
|
+
* `lateralColor.fraction`, R the sensor's half-diagonal, H the field height (fraction of R) and D(λ) the normalised Cauchy
|
|
9
|
+
* dispersion (0 at the Fraunhofer d line, F minus C = 1). The red and blue channels are taken at the effective wavelengths of
|
|
10
|
+
* the lens corrections, `GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM` (610 and 465 nm), so the blue-minus-red displacement
|
|
11
|
+
* is L·R·H·(D(465) − D(610)). With `channelDispersion` (2.21.0, #589's `calculateChannelEffectiveDispersion` for the camera's
|
|
12
|
+
* channels and the light) it is L·R·H·(D̄_blue − D̄_red), the displacement `simulateCapture` renders in RAW. The lens's lateral
|
|
13
|
+
* colour does not change with the f-number or the focus distance.
|
|
14
|
+
*
|
|
15
|
+
* Every value is an approximation: the lens is a generic Photivra design, not a measured product, and a real channel spans a
|
|
16
|
+
* band of wavelengths, not one.
|
|
17
|
+
*/
|
|
18
|
+
import { approximationResult } from "../core/calculation-result.js";
|
|
19
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
20
|
+
import { requireInputKeys, requireInputObject } from "../core/record-validation.js";
|
|
21
|
+
import { genericLensDispersion, resolveGenericLens } from "../equipment/generic-lens-catalogue.js";
|
|
22
|
+
import { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM } from "../output/developed-lens-corrections.js";
|
|
23
|
+
function positiveFinite(value, name) {
|
|
24
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
25
|
+
throw new InvalidScientificInputError(name + " must be a positive finite number.");
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function fieldCoordinate(value, name) {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < -1 || value > 1)
|
|
30
|
+
throw new InvalidScientificInputError(name + " must be a finite number in [-1, 1].");
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The red–blue image displacement of a catalogue lens at a field position on a sensor, in micrometres and pixels, from the
|
|
35
|
+
* lens's own lateral-colour model Δr(λ) = L·R·H·D(λ) at the lens corrections' channel wavelengths (610 and 465 nm), or at the
|
|
36
|
+
* given `channelDispersion`. Exact for the model (no fast mode); an approximation of a generic lens, not a measured product.
|
|
37
|
+
*/
|
|
38
|
+
export function calculateLateralColorFringe(input) {
|
|
39
|
+
requireInputObject(input, "calculateLateralColorFringe input");
|
|
40
|
+
requireInputKeys(input, "calculateLateralColorFringe", ["lensId", "activeImagingArea", "pixelPitchMicrometers", "fieldPosition", "channelDispersion"]);
|
|
41
|
+
const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea });
|
|
42
|
+
const pitch = positiveFinite(input.pixelPitchMicrometers, "pixelPitchMicrometers");
|
|
43
|
+
let u = 1, v = 1;
|
|
44
|
+
if (input.fieldPosition !== undefined) {
|
|
45
|
+
requireInputObject(input.fieldPosition, "fieldPosition");
|
|
46
|
+
requireInputKeys(input.fieldPosition, "fieldPosition", ["x", "y"]);
|
|
47
|
+
u = fieldCoordinate(input.fieldPosition.x, "fieldPosition.x");
|
|
48
|
+
v = fieldCoordinate(input.fieldPosition.y, "fieldPosition.y");
|
|
49
|
+
}
|
|
50
|
+
const x = u * input.activeImagingArea.widthMm / 2, y = v * input.activeImagingArea.heightMm / 2, r = Math.hypot(x, y);
|
|
51
|
+
const fieldHeight = r / lens.halfDiagonalMm, { red, blue } = GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM, given = input.channelDispersion;
|
|
52
|
+
let dispersion = { red: genericLensDispersion(red), blue: genericLensDispersion(blue) };
|
|
53
|
+
if (given !== undefined) {
|
|
54
|
+
requireInputObject(given, "channelDispersion");
|
|
55
|
+
requireInputKeys(given, "channelDispersion", ["red", "green", "blue"]);
|
|
56
|
+
// D spans −1.08 (1100 nm) to 4.30 (300 nm), as createGenericLensCorrectionProfile checks.
|
|
57
|
+
for (const c of ["red", "blue"])
|
|
58
|
+
if (typeof given[c] !== "number" || !(given[c] >= -1.1 && given[c] <= 4.4))
|
|
59
|
+
throw new InvalidScientificInputError("channelDispersion." + c + " must be a number in [−1.1, 4.4], D between 1100 and 300 nm.");
|
|
60
|
+
dispersion = { red: given.red, blue: given.blue };
|
|
61
|
+
}
|
|
62
|
+
const fToCMicrometers = lens.character.lateralColor.fraction * lens.halfDiagonalMm * 1000 * fieldHeight;
|
|
63
|
+
const radial = fToCMicrometers * (dispersion.blue - dispersion.red);
|
|
64
|
+
const cos = r > 0 ? x / r : 0, sin = r > 0 ? y / r : 0;
|
|
65
|
+
// `+ 0` turns a −0 (from a negative zero cosine or sine) into 0.
|
|
66
|
+
const um = { radial, x: radial * cos + 0, y: radial * sin + 0 };
|
|
67
|
+
return approximationResult({ lensId: lens.lensId, imagePointMm: { x, y }, fieldHeight, channelWavelengthsNm: given ? null : { red, blue }, channelDispersion: dispersion,
|
|
68
|
+
blueMinusRedMicrometers: um, blueMinusRedPixels: { radial: um.radial / pitch, x: um.x / pitch, y: um.y / pitch },
|
|
69
|
+
fringeWidthMicrometers: Math.abs(radial), fringeWidthPixels: Math.abs(radial) / pitch, fToCMicrometers }, "generic-lens-lateral-color-fringe", "1.0.0", [
|
|
70
|
+
"The catalogue lens's lateral-colour model: radial displacement L·R·H·D(λ), D the normalised Cauchy dispersion (Fraunhofer d, F and C lines).",
|
|
71
|
+
given ? "Red and blue at the given channel dispersions (a neutral surface's weighting of each channel's band)." :
|
|
72
|
+
"Red and blue at single effective wavelengths of 610 and 465 nm (GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM); a real channel spans a band.",
|
|
73
|
+
"A generic Photivra lens, not a measured product; lateral colour does not change with the f-number or the focus distance in this model.",
|
|
74
|
+
"Geometric displacement only: the rendered fringe also depends on the lens's blur, the demosaic and the colour conversion."
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=generic-lens-lateral-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-lens-lateral-color.js","sourceRoot":"","sources":["../../src/optics/generic-lens-lateral-color.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Lateral colour of a catalogue lens as a fringe width (#580): the red–blue image displacement in micrometres and in pixels at\n * one field position, for the app's Guide page on color fringing.\n *\n * It reads the lens's own lateral-colour model (`docs/LENS_CATALOGUE.md`, \"Models\"), the one `simulateCapture` renders and\n * `createGenericLensCorrectionProfile` corrects: a radial image displacement Δr(λ) = L·R·H·D(λ), with L the lens's\n * `lateralColor.fraction`, R the sensor's half-diagonal, H the field height (fraction of R) and D(λ) the normalised Cauchy\n * dispersion (0 at the Fraunhofer d line, F minus C = 1). The red and blue channels are taken at the effective wavelengths of\n * the lens corrections, `GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM` (610 and 465 nm), so the blue-minus-red displacement\n * is L·R·H·(D(465) − D(610)). With `channelDispersion` (2.21.0, #589's `calculateChannelEffectiveDispersion` for the camera's\n * channels and the light) it is L·R·H·(D̄_blue − D̄_red), the displacement `simulateCapture` renders in RAW. The lens's lateral\n * colour does not change with the f-number or the focus distance.\n *\n * Every value is an approximation: the lens is a generic Photivra design, not a measured product, and a real channel spans a\n * band of wavelengths, not one.\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { genericLensDispersion, resolveGenericLens } from \"../equipment/generic-lens-catalogue.js\";\nimport { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM, type ChannelDispersion } from \"../output/developed-lens-corrections.js\";\n\nexport interface CalculateLateralColorFringeInput {\n /** A catalogue id (`GENERIC_LENS_IDS`). */\n lensId: string;\n /** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */\n activeImagingArea: { widthMm: number; heightMm: number };\n /** The sensor's pixel pitch, µm. */\n pixelPitchMicrometers: number;\n /**\n * The field position as fractions of the frame's half-width (x, +right) and half-height (y, +up) from the optical axis, each\n * in [−1, 1]. Default `{ x: 1, y: 1 }`: the top-right corner.\n */\n fieldPosition?: { x: number; y: number };\n /**\n * 2.21.0: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light;\n * only `red` and `blue` are read). Default: D at the single wavelengths 610 and 465 nm.\n */\n channelDispersion?: ChannelDispersion;\n}\n\n/** A signed displacement of the blue image from the red one: radial (positive: blue farther from the axis) and its x and y parts. */\nexport interface LateralColorDisplacement {\n radial: number;\n x: number;\n y: number;\n}\n\nexport interface LateralColorFringe {\n lensId: string;\n /** The image-plane point, mm from the optical axis (+x right, +y up). */\n imagePointMm: { x: number; y: number };\n /** Field height H: the point's distance from the axis as a fraction of the half-diagonal (1 at the corner). */\n fieldHeight: number;\n /** The channel wavelengths the displacement is taken at, nm; null with a given `channelDispersion`. */\n channelWavelengthsNm: { red: number; blue: number } | null;\n /** The red and blue dispersions D used (normalized: 0 at the d line, F minus C = 1). */\n channelDispersion: { red: number; blue: number };\n /** Blue-minus-red image displacement, µm. */\n blueMinusRedMicrometers: LateralColorDisplacement;\n /** The same in pixels of the given pitch. */\n blueMinusRedPixels: LateralColorDisplacement;\n /** The fringe width: |radial| displacement, µm and pixels. */\n fringeWidthMicrometers: number;\n fringeWidthPixels: number;\n /** The lens's own F-to-C (486 to 656 nm) displacement at this point, L·R·H, µm. */\n fToCMicrometers: number;\n}\n\nfunction positiveFinite(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) throw new InvalidScientificInputError(name + \" must be a positive finite number.\");\n return value;\n}\nfunction fieldCoordinate(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < -1 || value > 1) throw new InvalidScientificInputError(name + \" must be a finite number in [-1, 1].\");\n return value;\n}\n\n/**\n * The red–blue image displacement of a catalogue lens at a field position on a sensor, in micrometres and pixels, from the\n * lens's own lateral-colour model Δr(λ) = L·R·H·D(λ) at the lens corrections' channel wavelengths (610 and 465 nm), or at the\n * given `channelDispersion`. Exact for the model (no fast mode); an approximation of a generic lens, not a measured product.\n */\nexport function calculateLateralColorFringe(input: CalculateLateralColorFringeInput): CalculationResult<LateralColorFringe> {\n requireInputObject(input, \"calculateLateralColorFringe input\");\n requireInputKeys(input, \"calculateLateralColorFringe\", [\"lensId\", \"activeImagingArea\", \"pixelPitchMicrometers\", \"fieldPosition\", \"channelDispersion\"]);\n const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea });\n const pitch = positiveFinite(input.pixelPitchMicrometers, \"pixelPitchMicrometers\");\n let u = 1, v = 1;\n if (input.fieldPosition !== undefined) {\n requireInputObject(input.fieldPosition, \"fieldPosition\");\n requireInputKeys(input.fieldPosition, \"fieldPosition\", [\"x\", \"y\"]);\n u = fieldCoordinate(input.fieldPosition.x, \"fieldPosition.x\"); v = fieldCoordinate(input.fieldPosition.y, \"fieldPosition.y\");\n }\n const x = u * input.activeImagingArea.widthMm / 2, y = v * input.activeImagingArea.heightMm / 2, r = Math.hypot(x, y);\n const fieldHeight = r / lens.halfDiagonalMm, { red, blue } = GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM, given = input.channelDispersion;\n let dispersion = { red: genericLensDispersion(red), blue: genericLensDispersion(blue) };\n if (given !== undefined) {\n requireInputObject(given, \"channelDispersion\");\n requireInputKeys(given, \"channelDispersion\", [\"red\", \"green\", \"blue\"]);\n // D spans −1.08 (1100 nm) to 4.30 (300 nm), as createGenericLensCorrectionProfile checks.\n for (const c of [\"red\", \"blue\"] as const) if (typeof given[c] !== \"number\" || !(given[c] >= -1.1 && given[c] <= 4.4))\n throw new InvalidScientificInputError(\"channelDispersion.\" + c + \" must be a number in [−1.1, 4.4], D between 1100 and 300 nm.\");\n dispersion = { red: given.red, blue: given.blue };\n }\n const fToCMicrometers = lens.character.lateralColor.fraction * lens.halfDiagonalMm * 1000 * fieldHeight;\n const radial = fToCMicrometers * (dispersion.blue - dispersion.red);\n const cos = r > 0 ? x / r : 0, sin = r > 0 ? y / r : 0;\n // `+ 0` turns a −0 (from a negative zero cosine or sine) into 0.\n const um: LateralColorDisplacement = { radial, x: radial * cos + 0, y: radial * sin + 0 };\n return approximationResult({ lensId: lens.lensId, imagePointMm: { x, y }, fieldHeight, channelWavelengthsNm: given ? null : { red, blue }, channelDispersion: dispersion,\n blueMinusRedMicrometers: um, blueMinusRedPixels: { radial: um.radial / pitch, x: um.x / pitch, y: um.y / pitch },\n fringeWidthMicrometers: Math.abs(radial), fringeWidthPixels: Math.abs(radial) / pitch, fToCMicrometers },\n \"generic-lens-lateral-color-fringe\", \"1.0.0\", [\n \"The catalogue lens's lateral-colour model: radial displacement L·R·H·D(λ), D the normalised Cauchy dispersion (Fraunhofer d, F and C lines).\",\n given ? \"Red and blue at the given channel dispersions (a neutral surface's weighting of each channel's band).\" :\n \"Red and blue at single effective wavelengths of 610 and 465 nm (GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM); a real channel spans a band.\",\n \"A generic Photivra lens, not a measured product; lateral colour does not change with the f-number or the focus distance in this model.\",\n \"Geometric displacement only: the rendered fringe also depends on the lens's blur, the demosaic and the colour conversion.\"]);\n}\n"]}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* U(x) = Σ_pupil exp(i·2π·W(u)/λ)·exp(−i·2π·x·u/(λ·z)) is evaluated with a zero-padded radix-2 FFT (Cooley & Tukey 1965) on
|
|
10
10
|
* an image grid of pitch δ = min(λ·N_w/2, p/4) (p the site pitch), and |U|² is integrated over each site's square cell (exact
|
|
11
11
|
* overlap of each δ-square with the cell). Lateral colour displaces the PSF radially by Δr(λ). The kernel window is (2R + 1)²
|
|
12
|
-
* sites; the energy outside it is reported and put back by unit-sum renormalisation, as for `lens.diffraction`.
|
|
12
|
+
* sites; the energy outside it is reported and put back by unit-sum renormalisation, as for `lens.diffraction`. Each kernel's
|
|
13
|
+
* centroid is then moved onto the PSF's own first moment (#589, `alignKernelCentroid`): the lateral colour and coma's mean tilt.
|
|
13
14
|
*
|
|
14
15
|
* Nodes: a 5 × 5 regular grid of field points (0, ±half the frame, ±the frame edge plus the kernel reach) × 5 wavelengths
|
|
15
16
|
* (380, 450, 550, 650, 780 nm), each computed at its own field azimuth (no rotation of a radial node), interpolated by the
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
*/
|
|
22
23
|
import { InvalidScientificInputError } from "../core/validation.js";
|
|
23
24
|
import { genericLensDispersion } from "../equipment/generic-lens-catalogue.js";
|
|
24
|
-
import { markEngineOwnedLensSampledPsfProfile } from "./lens-psf-profile.js";
|
|
25
|
+
import { kernelCentroid, markEngineOwnedLensSampledPsfProfile } from "./lens-psf-profile.js";
|
|
25
26
|
import { convolveSiteKernels } from "./defocus-disc-kernel.js";
|
|
26
27
|
import { createDefocusBokehSiteKernel } from "./defocus-bokeh-kernel.js";
|
|
27
28
|
/** Wavelength nodes of a catalogue PSF profile, nm (they span the facade's 380–780 nm quadrature). */
|
|
@@ -106,12 +107,35 @@ function geometricRadiusMicrometers(lens, H, lambdaUm, workingF) {
|
|
|
106
107
|
const px = -1 + 2 * i / (n - 1), py = -1 + 2 * j / (n - 1);
|
|
107
108
|
if (px * px + py * py > 1)
|
|
108
109
|
continue;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
largest = Math.max(largest, Math.hypot(gx, gy));
|
|
110
|
+
const slope = (dx, dy) => (opdMicrometers(lens, H, 1, 0, lambdaUm, px + dx, py + dy) - opdMicrometers(lens, H, 1, 0, lambdaUm, px - dx, py - dy)) / (2 * h);
|
|
111
|
+
largest = Math.max(largest, Math.hypot(slope(h, 0), slope(0, h)));
|
|
112
112
|
}
|
|
113
113
|
return 2 * workingF * largest;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* #589: moves a unit-sum kernel's intensity centroid to `target` (sites from the centre tap, +x right, +y up = the row index), each
|
|
117
|
+
* axis by a linear-interpolation shift of the whole kernel; energy that would leave the window stays on its edge tap, so the shift
|
|
118
|
+
* repeats (at most 8 passes per axis) while that leaves a residual. Cell integration alone quantises the centroid of a PSF narrower than a
|
|
119
|
+
* site, and the window's truncation pulls a wide one inwards: either moves the rendered lateral colour and coma off the lens's
|
|
120
|
+
* model. The shift keeps the sum and the other axis's moment, and widens the kernel by f·(1 − f) site² of variance for a move of f.
|
|
121
|
+
*/
|
|
122
|
+
function alignKernelCentroid(kernel, radius, target) {
|
|
123
|
+
const w = 2 * radius + 1;
|
|
124
|
+
const shape = { widthSamples: w, heightSamples: w, centerSampleX: radius, centerSampleY: radius, normalizedIntensity: kernel };
|
|
125
|
+
for (let axis = 0; axis < 2; axis++)
|
|
126
|
+
for (let pass = 0; pass < 8; pass++) {
|
|
127
|
+
const c = kernelCentroid(shape), r = axis ? target.y - c.y : target.x - c.x, whole = Math.floor(r), f = r - whole, moved = new Float64Array(kernel.length);
|
|
128
|
+
if (Math.abs(r) < 1e-9)
|
|
129
|
+
break;
|
|
130
|
+
kernel.forEach((v, k) => {
|
|
131
|
+
// Tap k's index t along the axis moves to t + whole and t + whole + 1, held at the window's edges.
|
|
132
|
+
const t = axis ? Math.floor(k / w) : k % w, step = axis ? w : 1;
|
|
133
|
+
moved[k + (Math.min(w - 1, Math.max(0, t + whole)) - t) * step] += v * (1 - f);
|
|
134
|
+
moved[k + (Math.min(w - 1, Math.max(0, t + whole + 1)) - t) * step] += v * f;
|
|
135
|
+
});
|
|
136
|
+
kernel.set(moved);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
115
139
|
function nodeKernel(lens, xMm, yMm, lambdaNm, workingF, pupilDiameterMm, zMm, pitch, radius) {
|
|
116
140
|
const lambdaUm = lambdaNm / 1000, r = Math.hypot(xMm, yMm), H = Math.min(1, r / lens.halfDiagonalMm);
|
|
117
141
|
const cosPhi = r > 0 ? xMm / r : 1, sinPhi = r > 0 ? yMm / r : 0;
|
|
@@ -145,17 +169,14 @@ function nodeKernel(lens, xMm, yMm, lambdaNm, workingF, pupilDiameterMm, zMm, pi
|
|
|
145
169
|
rowUsed[(v + size) % size] = 1;
|
|
146
170
|
open++;
|
|
147
171
|
}
|
|
172
|
+
// The largest step to the next sample in a row and in a column (outside the pupil the OPD is NaN, and so is the step: skipped).
|
|
148
173
|
const width = 2 * reach + 1;
|
|
149
|
-
for (let
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
phaseStep = Math.max(phaseStep, Math.abs(opd[j * width + i + 1] - w) / lambdaUm);
|
|
156
|
-
if (j + 1 < width && !Number.isNaN(opd[(j + 1) * width + i]))
|
|
157
|
-
phaseStep = Math.max(phaseStep, Math.abs(opd[(j + 1) * width + i] - w) / lambdaUm);
|
|
158
|
-
}
|
|
174
|
+
for (let k = 0; k < opd.length; k++) {
|
|
175
|
+
if ((k + 1) % width)
|
|
176
|
+
phaseStep = Math.max(phaseStep, Math.abs(opd[k + 1] - opd[k]) / lambdaUm || 0);
|
|
177
|
+
if (k + width < opd.length)
|
|
178
|
+
phaseStep = Math.max(phaseStep, Math.abs(opd[k + width] - opd[k]) / lambdaUm || 0);
|
|
179
|
+
}
|
|
159
180
|
if (open < 9)
|
|
160
181
|
throw new InvalidScientificInputError("The catalogue lens PSF pupil has fewer than 9 samples.");
|
|
161
182
|
if (phaseStep > MAXIMUM_PHASE_STEP_WAVES) {
|
|
@@ -167,20 +188,13 @@ function nodeKernel(lens, xMm, yMm, lambdaNm, workingF, pupilDiameterMm, zMm, pi
|
|
|
167
188
|
cos[k] = Math.cos(2 * Math.PI * k / size);
|
|
168
189
|
sin[k] = Math.sin(2 * Math.PI * k / size);
|
|
169
190
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
continue;
|
|
174
|
-
rowRe.set(re.subarray(y * size, (y + 1) * size));
|
|
175
|
-
rowIm.set(im.subarray(y * size, (y + 1) * size));
|
|
176
|
-
radix2Fft(rowRe, rowIm, size, cos, sin);
|
|
177
|
-
re.set(rowRe, y * size);
|
|
178
|
-
im.set(rowIm, y * size);
|
|
179
|
-
}
|
|
191
|
+
for (let y = 0; y < size; y++)
|
|
192
|
+
if (rowUsed[y])
|
|
193
|
+
radix2Fft(re.subarray(y * size, (y + 1) * size), im.subarray(y * size, (y + 1) * size), size, cos, sin);
|
|
180
194
|
const colRe = new Float64Array(size), colIm = new Float64Array(size);
|
|
181
195
|
const ox = overlaps(size, delta, shift * cosPhi, pitch, radius), oy = overlaps(size, delta, shift * sinPhi, pitch, radius);
|
|
182
196
|
const w2 = 2 * radius + 1, kernel = new Float64Array(w2 * w2);
|
|
183
|
-
let total = 0
|
|
197
|
+
let total = 0;
|
|
184
198
|
for (let x = 0; x < size; x++) {
|
|
185
199
|
for (let y = 0; y < size; y++) {
|
|
186
200
|
colRe[y] = re[y * size + x];
|
|
@@ -199,33 +213,24 @@ function nodeKernel(lens, xMm, yMm, lambdaNm, workingF, pupilDiameterMm, zMm, pi
|
|
|
199
213
|
const cy2 = oy.cell2[y], wy = oy.weight[y], wy2 = oy.weight2[y];
|
|
200
214
|
// Row index increases with image +Y (the quadrature's orientation).
|
|
201
215
|
kernel[cy * w2 + cx] += intensity * wx * wy;
|
|
202
|
-
|
|
203
|
-
if (cx2 >= 0) {
|
|
216
|
+
if (cx2 >= 0)
|
|
204
217
|
kernel[cy * w2 + cx2] += intensity * wx2 * wy;
|
|
205
|
-
inside += intensity * wx2 * wy;
|
|
206
|
-
}
|
|
207
218
|
if (cy2 >= 0) {
|
|
208
219
|
kernel[cy2 * w2 + cx] += intensity * wx * wy2;
|
|
209
|
-
|
|
210
|
-
if (cx2 >= 0) {
|
|
220
|
+
if (cx2 >= 0)
|
|
211
221
|
kernel[cy2 * w2 + cx2] += intensity * wx2 * wy2;
|
|
212
|
-
inside += intensity * wx2 * wy2;
|
|
213
|
-
}
|
|
214
222
|
}
|
|
215
223
|
}
|
|
216
224
|
}
|
|
217
|
-
if (!(total > 0) || !(
|
|
225
|
+
if (!(total > 0) || !kernel.some((v) => v > 0))
|
|
218
226
|
throw new InvalidScientificInputError("The catalogue lens PSF has no energy in its window.");
|
|
219
|
-
|
|
227
|
+
// #589: the PSF's first moment is the pupil-mean wavefront tilt 2·N_w·⟨∇W⟩ (the Fourier transform's moment theorem; Goodman,
|
|
228
|
+
// Introduction to Fourier Optics, ch. 4). Of the Seidel terms only coma has a mean tilt, ⟨∇(W131·H·ρ²·ρcos(θ − φ))⟩ = W131·H
|
|
229
|
+
// along the field azimuth (⟨ρ²⟩ = 1/2, ⟨x²⟩ = 1/4 on the unit disc); the lateral colour adds its displacement. Sites.
|
|
230
|
+
const moment = (shift + 2 * workingF * DESIGN_WAVELENGTH_UM * lens.character.aberrations.comaWaves * H) / pitch;
|
|
231
|
+
return { kernel, total, transform: size, phaseStep, centroid: { x: moment * cosPhi, y: moment * sinPhi } };
|
|
220
232
|
}
|
|
221
233
|
}
|
|
222
|
-
function mirror(kernel, radius, flipX, flipY) {
|
|
223
|
-
const w = 2 * radius + 1, out = new Float64Array(kernel.length);
|
|
224
|
-
for (let j = 0; j < w; j++)
|
|
225
|
-
for (let i = 0; i < w; i++)
|
|
226
|
-
out[(flipY ? w - 1 - j : j) * w + (flipX ? w - 1 - i : i)] = kernel[j * w + i];
|
|
227
|
-
return out;
|
|
228
|
-
}
|
|
229
234
|
/** Thin-lens image distance z (mm), pupil diameter D = f/N (mm) and working f-number z/D at a finite focus. */
|
|
230
235
|
function pupilGeometry(lens, focusDistanceM) {
|
|
231
236
|
const f = lens.focalLengthMm, d = focusDistanceM * 1000;
|
|
@@ -290,17 +295,20 @@ export function createGenericLensPsfProfile(input) {
|
|
|
290
295
|
while (radius < bound && outside(radius) > GENERIC_LENS_PSF_WINDOW_TRUNCATION)
|
|
291
296
|
radius++;
|
|
292
297
|
const truncated = Math.max(0, outside(radius));
|
|
293
|
-
|
|
298
|
+
/** The node's kernel cropped to the window, unit-sum, mirrored for a field point with x or y < 0. */
|
|
299
|
+
const crop = (node, flipX, flipY) => {
|
|
294
300
|
const w0 = 2 * bound + 1, w1 = 2 * radius + 1, out = new Float64Array(w1 * w1);
|
|
295
301
|
let sum = 0;
|
|
296
302
|
for (let j = 0; j < w1; j++)
|
|
297
303
|
for (let i = 0; i < w1; i++) {
|
|
298
304
|
const v = node.kernel[(j + bound - radius) * w0 + i + bound - radius];
|
|
299
|
-
out[j * w1 + i] = v;
|
|
305
|
+
out[(flipY ? w1 - 1 - j : j) * w1 + (flipX ? w1 - 1 - i : i)] = v;
|
|
300
306
|
sum += v;
|
|
301
307
|
}
|
|
302
308
|
for (let k = 0; k < out.length; k++)
|
|
303
309
|
out[k] = out[k] / sum;
|
|
310
|
+
// #589: the kernel's first moment is the PSF's (lateral colour and coma), not the cells' quantised or truncated one.
|
|
311
|
+
alignKernelCentroid(out, radius, { x: flipX ? -node.centroid.x : node.centroid.x, y: flipY ? -node.centroid.y : node.centroid.y });
|
|
304
312
|
return out;
|
|
305
313
|
};
|
|
306
314
|
const extra = input.extraKernel, kernelRadius = radius + (extra ? extra.radius : 0), w = 2 * kernelRadius + 1;
|
|
@@ -312,7 +320,7 @@ export function createGenericLensPsfProfile(input) {
|
|
|
312
320
|
for (const y of fieldY)
|
|
313
321
|
for (const nm of GENERIC_LENS_PSF_WAVELENGTHS_NM) {
|
|
314
322
|
const base = quadrant.get(Math.abs(x) + ":" + Math.abs(y) + ":" + nm);
|
|
315
|
-
let kernel =
|
|
323
|
+
let kernel = crop(base, x < 0, y < 0);
|
|
316
324
|
if (extra)
|
|
317
325
|
kernel = convolveSiteKernels(kernel, radius, extra.at(x, y), extra.radius);
|
|
318
326
|
nodes.push({ nodeId: "pv-" + x.toFixed(6) + "-" + y.toFixed(6) + "-" + nm,
|