@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":"generic-lens-corrections.js","sourceRoot":"","sources":["../../src/output/generic-lens-corrections.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,wBAAwB,EAA4B,MAAM,+BAA+B,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC1H,OAAO,EAAE,iCAAiC,EAAiE,MAAM,uBAAuB,CAAC;AAGzI;;;GAGG;AACH,MAAM,CAAC,MAAM,8CAA8C,GAAoD,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AAWlL;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAAC,KAA8C;IAC/F,kBAAkB,CAAC,KAAK,EAAE,0CAA0C,CAAC,CAAC;IACtE,gBAAgB,CAAC,KAAK,EAAE,oCAAoC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,EAAE,OAAO,CAAC,CAAC,CAAC;IACxG,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,kBAAkB,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/H,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,2BAA2B,CAAC,wCAAwC,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACtI,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,aAAa;QAChF,MAAM,IAAI,2BAA2B,CAAC,kCAAkC,GAAG,IAAI,CAAC,IAAI,GAAG,kCAAkC,GAAG,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC;IAC3J,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,sBAA+B,CAAC;IACnE,MAAM,QAAQ,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAiB,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,EAAc,EAAE,4BAA4B,EAAE,EAAc,EAAE,CAAC;IACjL,MAAM,KAAK,GAAG,CAAC,OAA8B,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,QAAQ,GAAG,qBAAqB,CAAC,8CAA8C,CAAC,OAAO,CAAC,CAAC,CAAC;IACvK,MAAM,MAAM,GAAG,CAAC,OAA8B,EAA4B,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,GAAG,cAAc,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAqB,EAAE,IAAI,EAAE,QAAiB;QAC3M,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAqC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClH,OAAO,iCAAiC,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC,MAAM,GAAG,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK;QACxI,QAAQ,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC,IAAI,GAAG,4EAA4E;YACjK,YAAY,EAAE,gGAAgG,EAAE,OAAO,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE;QACvJ,UAAU,EAAE;YACV,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,iGAAiG;gBAChK,SAAS,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,GAAG,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ;oBAC3G,OAAO,EAAE,EAAE,qBAAqB,EAAE,IAAI,CAAC,cAAc,EAAE,uBAAuB,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE;YACvJ,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,gHAAgH;gBACjL,UAAU,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE;YACpF,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,yBAAyB,EAAE,YAAY,EAAE,6EAA6E;gBAC3J,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;SACrF,EAAE,CAAC,CAAC;AACT,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The generic correction profile of a catalogue lens (#549), for the post-capture lens corrections\n * (`resolveLensCorrectionPlan`, `calculateLensCorrectedCapture`): a distortion map, a lateral-colour map per channel and a\n * peripheral-illumination gain, each from the lens's own character (`resolveGenericLens`). Consumer lenses rely on it more:\n * their uncorrected distortion and vignetting are larger, so the same profile corrects more. Like every catalogue number it is\n * a labelled approximation, bound to one exact camera state (`OpticalProfileState`).\n */\n\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { parseOpticalProfileState, type OpticalProfileState } from \"../optics/profile-contract.js\";\nimport { GENERIC_LENS_EVIDENCE, genericLensDispersion, resolveGenericLens } from \"../equipment/generic-lens-catalogue.js\";\nimport { parseGenericLensCorrectionProfile, type GenericLensCorrectionProfile, type LensCorrectionChannel } from \"./lens-corrections.js\";\nimport type { AffineGeometricTransform } from \"./geometric-transforms.js\";\n\n/**\n * Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where\n * a camera's channel responses peak, not a measured camera.\n */\nexport const GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = /* @__PURE__ */ Object.freeze({ red: 610, green: 540, blue: 465 });\n\nexport interface CreateGenericLensCorrectionProfileInput {\n /** A catalogue id (`GENERIC_LENS_IDS`); `state.lensId` must be the same id. */\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 exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */\n state: OpticalProfileState;\n}\n\n/**\n * Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own\n * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the\n * relative-illumination gain at the state's aperture (strength 1). The result passes `parseGenericLensCorrectionProfile`.\n */\nexport function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile {\n requireInputObject(input, \"createGenericLensCorrectionProfile input\");\n requireInputKeys(input, \"createGenericLensCorrectionProfile\", [\"lensId\", \"activeImagingArea\", \"state\"]);\n const state = parseOpticalProfileState(input.state);\n const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: state.aperture });\n if (state.lensId !== lens.lensId) throw new InvalidScientificInputError(\"state.lensId must be the catalogue id \" + lens.lensId + \".\");\n if (Math.abs(state.focalLengthMm - lens.focalLengthMm) > 1e-9 * lens.focalLengthMm)\n throw new InvalidScientificInputError(\"state.focalLengthMm must be the \" + lens.name + \"'s focal length on this sensor, \" + lens.focalLengthMm + \" mm.\");\n const c = lens.character, domain = \"reconstructed-linear\" as const;\n const identity = { version: \"1.0.0\", domain, availability: \"toggle\" as const, defaultEnabled: true, dependencies: [] as string[], requiredByStabilizationModes: [] as string[] };\n const scale = (channel: LensCorrectionChannel): number => 1 + c.lateralColor.fraction * genericLensDispersion(GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM[channel]);\n const affine = (channel: LensCorrectionChannel): AffineGeometricTransform => ({ id: lens.lensId + \":lateral-ca:\" + channel, version: \"1.0.0\", domain, purpose: \"lateral-ca\" as const, kind: \"affine\" as const,\n matrix: [scale(channel), 0, 0, scale(channel)] as [number, number, number, number], offsetMm: { x: 0, y: 0 } });\n return parseGenericLensCorrectionProfile({ schemaVersion: \"0.1.0\", id: \"photivra-\" + lens.lensId + \":corrections\", version: \"1.0.0\", state,\n evidence: { kind: \"generic-parametric\", basis: \"Generic catalogue lens \" + lens.name + \" (#549): the inverse of its own distortion, lateral colour and vignetting.\",\n residualNote: \"Generic approximation, not a measured lens; axial colour, coma and the other aberrations stay.\", sources: [...GENERIC_LENS_EVIDENCE] },\n components: [\n { ...identity, id: \"distortion\", kind: \"geometry\", residualNote: \"Corrects the lens's radial distortion r_d = r(1 + k1ρ²); the stretched corners lose resolution.\",\n transform: { id: lens.lensId + \":distortion\", version: \"1.0.0\", domain, purpose: \"distortion\", kind: \"radial\",\n profile: { normalizationRadiusMm: lens.halfDiagonalMm, maximumNormalizedRadius: 1.05, coefficients: { k1: c.distortion.corner, k2: 0, k3: 0 } } } },\n { ...identity, id: \"lateral-ca\", kind: \"lateral-ca\", residualNote: \"Registers the channels at one effective wavelength each; colour inside a channel's band and axial colour stay.\",\n transforms: { red: affine(\"red\"), green: affine(\"green\"), blue: affine(\"blue\") } },\n { ...identity, id: \"vignetting\", kind: \"peripheral-illumination\", residualNote: \"Gain 1/E at the profile's aperture; it raises corner noise with the signal.\",\n profile: { ...c.vignetting.profile, maximumNormalizedRadius: 1.05 }, strength: 1 }\n ] });\n}\n"]}
|
package/docs/AGENT_GUIDE.md
CHANGED
|
@@ -27,6 +27,7 @@ one-call capture, see the [Quick Start](QUICK_START.md). For every export, see t
|
|
|
27
27
|
| Depth of field and hyperfocal distance | [`calculateDepthOfField`](api/optics-depth-of-field.md#calculatedepthoffield) | Thin-lens model. `farLimitM` is `null` when it is infinite. |
|
|
28
28
|
| Blur of an out-of-focus subject | [`calculateDefocusCircle`](api/optics-depth-of-field.md#calculatedefocuscircle) | Geometric blur-circle diameter at the sensor. |
|
|
29
29
|
| Check a defocus blur before rendering it | [`calculateDefocusDiscKernelRadius`](api/optics-defocus-disc-kernel.md#calculatedefocusdisckernelradius) | Whether `lens.defocus` renders the disc or refuses it (kernel radius at most `DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES`, 64 sites), and by how much it is over. |
|
|
30
|
+
| A generic catalogue lens and its character | [`resolveGenericLens`](api/equipment-generic-lens-catalogue.md#resolvegenericlens) | 30 Photivra primes ("PV 50 1.2 Pro", "PV 50 1.8"), every value a labelled approximation; `simulateCapture` renders one with `lens.catalogue` ([catalogue](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md)). |
|
|
30
31
|
| Field (angle) of view | [`calculateFieldOfView`](api/camera-field-of-view.md#calculatefieldofview) | Pass the sensor width, height or diagonal for that angle. |
|
|
31
32
|
| Crop factor and 35 mm-equivalent focal length | [`calculateEquivalentFocalLength35Mm`](api/camera-equivalent-focal-length.md#calculateequivalentfocallength35mm) | Diagonal-based, against a 36 × 24 mm frame. |
|
|
32
33
|
| Pixels left after a centred crop | [`calculateCenteredCrop`](api/output-crop.md#calculatecenteredcrop) | Keeps the aspect ratio to whole pixels. |
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Public API reference
|
|
2
2
|
|
|
3
|
-
Generated from the exact root exports for **@photivra/engine 2.
|
|
3
|
+
Generated from the exact root exports for **@photivra/engine 2.17.0 / root contract 2.17.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
|
|
4
4
|
|
|
5
5
|
Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
|
|
6
6
|
|
|
@@ -827,6 +827,28 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
827
827
|
| [`ResolveGenericEquipmentExposureCapabilitiesInput`](api/equipment-exposure-capabilities.md#resolvegenericequipmentexposurecapabilitiesinput) | Type |
|
|
828
828
|
| [`WidestAvailableFNumberCapability`](api/equipment-exposure-capabilities.md#widestavailablefnumbercapability) | Type |
|
|
829
829
|
|
|
830
|
+
## equipment/generic-lens-catalogue.ts
|
|
831
|
+
|
|
832
|
+
[Detailed contracts](api/equipment-generic-lens-catalogue.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/equipment/generic-lens-catalogue.ts)
|
|
833
|
+
|
|
834
|
+
| Export | Kind |
|
|
835
|
+
| --- | --- |
|
|
836
|
+
| [`GENERIC_LENS_CATALOGUE`](api/equipment-generic-lens-catalogue.md#generic_lens_catalogue) | Runtime |
|
|
837
|
+
| [`GENERIC_LENS_IDS`](api/equipment-generic-lens-catalogue.md#generic_lens_ids) | Runtime |
|
|
838
|
+
| [`GENERIC_LENS_SET_BASELINES`](api/equipment-generic-lens-catalogue.md#generic_lens_set_baselines) | Runtime |
|
|
839
|
+
| [`GenericLensAdjustments`](api/equipment-generic-lens-catalogue.md#genericlensadjustments) | Type |
|
|
840
|
+
| [`GenericLensCatalogueEntry`](api/equipment-generic-lens-catalogue.md#genericlenscatalogueentry) | Type |
|
|
841
|
+
| [`GenericLensCharacter`](api/equipment-generic-lens-catalogue.md#genericlenscharacter) | Type |
|
|
842
|
+
| [`GenericLensCharacterValues`](api/equipment-generic-lens-catalogue.md#genericlenscharactervalues) | Type |
|
|
843
|
+
| [`GenericLensFocusError`](api/equipment-generic-lens-catalogue.md#genericlensfocuserror) | Type |
|
|
844
|
+
| [`GenericLensKind`](api/equipment-generic-lens-catalogue.md#genericlenskind) | Type |
|
|
845
|
+
| [`GenericLensSet`](api/equipment-generic-lens-catalogue.md#genericlensset) | Type |
|
|
846
|
+
| [`ResolvedGenericLens`](api/equipment-generic-lens-catalogue.md#resolvedgenericlens) | Type |
|
|
847
|
+
| [`resolveGenericLens`](api/equipment-generic-lens-catalogue.md#resolvegenericlens) | Runtime |
|
|
848
|
+
| [`ResolveGenericLensInput`](api/equipment-generic-lens-catalogue.md#resolvegenericlensinput) | Type |
|
|
849
|
+
| [`simulateGenericLensFocusError`](api/equipment-generic-lens-catalogue.md#simulategenericlensfocuserror) | Runtime |
|
|
850
|
+
| [`SimulateGenericLensFocusErrorInput`](api/equipment-generic-lens-catalogue.md#simulategenericlensfocuserrorinput) | Type |
|
|
851
|
+
|
|
830
852
|
## equipment/generic-prime-lenses.ts
|
|
831
853
|
|
|
832
854
|
[Detailed contracts](api/equipment-generic-prime-lenses.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/equipment/generic-prime-lenses.ts)
|
|
@@ -1575,6 +1597,22 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1575
1597
|
| [`LensComplexPupilPsfSampling`](api/optics-complex-pupil-psf.md#lenscomplexpupilpsfsampling) | Type |
|
|
1576
1598
|
| [`parseLensComplexPupilProfile`](api/optics-complex-pupil-psf.md#parselenscomplexpupilprofile) | Runtime |
|
|
1577
1599
|
|
|
1600
|
+
## optics/defocus-bokeh-kernel.ts
|
|
1601
|
+
|
|
1602
|
+
[Detailed contracts](api/optics-defocus-bokeh-kernel.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/defocus-bokeh-kernel.ts)
|
|
1603
|
+
|
|
1604
|
+
| Export | Kind |
|
|
1605
|
+
| --- | --- |
|
|
1606
|
+
| [`calculateDefocusBokehKernel`](api/optics-defocus-bokeh-kernel.md#calculatedefocusbokehkernel) | Runtime |
|
|
1607
|
+
| [`CalculateDefocusBokehKernelInput`](api/optics-defocus-bokeh-kernel.md#calculatedefocusbokehkernelinput) | Type |
|
|
1608
|
+
| [`DEFOCUS_BOKEH_MAXIMUM_BLADES`](api/optics-defocus-bokeh-kernel.md#defocus_bokeh_maximum_blades) | Runtime |
|
|
1609
|
+
| [`DefocusBokehBlades`](api/optics-defocus-bokeh-kernel.md#defocusbokehblades) | Type |
|
|
1610
|
+
| [`DefocusBokehCatsEye`](api/optics-defocus-bokeh-kernel.md#defocusbokehcatseye) | Type |
|
|
1611
|
+
| [`DefocusBokehClippingAperture`](api/optics-defocus-bokeh-kernel.md#defocusbokehclippingaperture) | Type |
|
|
1612
|
+
| [`DefocusBokehKernel`](api/optics-defocus-bokeh-kernel.md#defocusbokehkernel) | Type |
|
|
1613
|
+
| [`DefocusBokehOptions`](api/optics-defocus-bokeh-kernel.md#defocusbokehoptions) | Type |
|
|
1614
|
+
| [`DefocusBokehSphericalAberration`](api/optics-defocus-bokeh-kernel.md#defocusbokehsphericalaberration) | Type |
|
|
1615
|
+
|
|
1578
1616
|
## optics/defocus-disc-kernel.ts
|
|
1579
1617
|
|
|
1580
1618
|
[Detailed contracts](api/optics-defocus-disc-kernel.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/defocus-disc-kernel.ts)
|
|
@@ -2029,6 +2067,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
2029
2067
|
| [`CalculateCenteredCropInput`](api/output-crop.md#calculatecenteredcropinput) | Type |
|
|
2030
2068
|
| [`CenteredCrop`](api/output-crop.md#centeredcrop) | Type |
|
|
2031
2069
|
|
|
2070
|
+
## output/generic-lens-corrections.ts
|
|
2071
|
+
|
|
2072
|
+
[Detailed contracts](api/output-generic-lens-corrections.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/generic-lens-corrections.ts)
|
|
2073
|
+
|
|
2074
|
+
| Export | Kind |
|
|
2075
|
+
| --- | --- |
|
|
2076
|
+
| [`createGenericLensCorrectionProfile`](api/output-generic-lens-corrections.md#creategenericlenscorrectionprofile) | Runtime |
|
|
2077
|
+
| [`CreateGenericLensCorrectionProfileInput`](api/output-generic-lens-corrections.md#creategenericlenscorrectionprofileinput) | Type |
|
|
2078
|
+
| [`GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM`](api/output-generic-lens-corrections.md#generic_lens_correction_channel_wavelengths_nm) | Runtime |
|
|
2079
|
+
|
|
2032
2080
|
## output/geometric-transforms.ts
|
|
2033
2081
|
|
|
2034
2082
|
[Detailed contracts](api/output-geometric-transforms.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/geometric-transforms.ts)
|
package/docs/QUICK_START.md
CHANGED
|
@@ -65,6 +65,8 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
65
65
|
| `camera` | `"professional"` | A generic tier id (`"consumer"`, `"prosumer"`, `"professional"`) or a full `GenericEquipmentTierPreset` record. A record must have exactly the preset shape (unknown fields at the top level, in `body`, `lens` or `lens.matchedReference` are rejected) and each profile is re-checked by its own parser. Tier values are synthetic educational designs, not real cameras. |
|
|
66
66
|
| `lens.focalLengthMm` | 50 | Must lie in the lens capability; the generic primes are 50 mm. |
|
|
67
67
|
| `lens.prime` | off | 2.3: a generic prime by full-frame-equivalent focal length (see [Generic prime lenses](#generic-prime-lenses-23)). |
|
|
68
|
+
| `lens.catalogue` | off | #549: a generic catalogue lens by id, with its rendered character (see [Generic lens catalogue](#generic-lens-catalogue-549)). |
|
|
69
|
+
| `lens.focusScatter` | off | #549, with `lens.catalogue`: a seeded autofocus error `{ seed, shotIndex? }`. |
|
|
68
70
|
| `lens.fNumber` | 8 | Used in `manual` and `aperture-priority`; resolved automatically otherwise. Must lie in f/4–f/16. |
|
|
69
71
|
| `lens.focusDistanceM` | the subject distance | |
|
|
70
72
|
| `lens.pupilSampling` | off | `{ radialSamples, angularSamples }` traces an ideal circular pupil, which renders defocus. Off means one chief ray per sample. Angular samples 4–64, at most 256 rays. |
|
|
@@ -72,7 +74,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
72
74
|
| `lens.distortion` | none | `{ kind: "radial", profile }` or `{ kind: "brown-conrady", profile }`. |
|
|
73
75
|
| `lens.vignetting` | none | An `IlluminationVignettingProfile` (radial relative illumination). |
|
|
74
76
|
| `lens.psf` | none | A `{ kind: "sampled-local", configuration }` sampled lens PSF, passed to the environment contract unchanged. |
|
|
75
|
-
| `lens.defocus` | off | `{ model: "geometric-disc" }`: defocus of the scene plane as the geometric blur disc (see [Optics](#optics)). `simulateCapture` and `simulateDisplayView` render it. |
|
|
77
|
+
| `lens.defocus` | off | `{ model: "geometric-disc" }`: defocus of the scene plane as the geometric blur disc (see [Optics](#optics)). `simulateCapture` and `simulateDisplayView` render it. Optional bokeh character (#553), each part off by default: `blades`, `sphericalAberration`, `catsEye` (see [Bokeh character](#bokeh-character-553)). |
|
|
76
78
|
| `lens.diffraction` | off | 2.1: `"circular-aperture"` or `{ kind: "circular-aperture", kernelRadiusSamples, fast }`: diffraction blur of an ideal lens at the resolved f-number (see [Optics](#optics)). `aperture` is accepted as a deprecated alias of `kind` until 3.0.0. |
|
|
77
79
|
| `exposure.mode` | `"manual"` | Also `"aperture-priority"`, `"shutter-priority"`, `"program"`. |
|
|
78
80
|
| `exposure.iso` | 100 | Selects the body's ISO readout regime (gain and read noise). From 2.14 the tier bodies (preset 2.1.0) take the third-stop settings `GENERIC_THIRD_STOP_ISO_SETTINGS` (100, 125, 160, 200 … up to the body's maximum: 6400, 12 800 or 25 600). `"auto"` lets Auto ISO choose it from the meter in any mode (see [Auto ISO](#auto-iso-516)); it steps in whole stops unless `exposure.autoIso.isoSteps: "third-stop"`. |
|
|
@@ -191,6 +193,46 @@ kept, and the assumption `lens.prime` records the resolved lens. `resolveGeneric
|
|
|
191
193
|
same lens for any sensor size. `lens.focalLengthMm` keeps working as before and cannot be combined with
|
|
192
194
|
`lens.prime`.
|
|
193
195
|
|
|
196
|
+
### Generic lens catalogue (#549)
|
|
197
|
+
|
|
198
|
+
`lens.catalogue` picks one of the engine's 30 generic lenses by id: two sets of primes, Professional ("PV 50 1.2 Pro",
|
|
199
|
+
`pv-50-1.2-pro`) and Consumer ("PV 50 1.8", `pv-50-1.8`), from the 8 mm circular fisheye to 1200 mm, listed in
|
|
200
|
+
`GENERIC_LENS_CATALOGUE` and [docs/LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md). They are Photivra's own designs, not real products,
|
|
201
|
+
and every number is a labelled approximation.
|
|
202
|
+
|
|
203
|
+
- **Like `lens.prime`:** the focal length is the full-frame equivalent ÷ the simulated frame's diagonal crop factor, and the lens
|
|
204
|
+
replaces the tier lens's focal length and f-number range (its transmission is kept). Close focus is the generic 0.15× rule (1:1
|
|
205
|
+
for the macro); an f-number or distance outside the lens's range is refused.
|
|
206
|
+
- **Its character is rendered:** a field- and wavelength-dependent PSF (diffraction with the lens's Seidel aberrations, axial and
|
|
207
|
+
lateral colour) through the environment PSF quadrature, its radial distortion, its vignetting and, for the fisheyes, the
|
|
208
|
+
equisolid projection (beyond 90° there is no scene, so the 8 mm's circle is black outside). Flare, focus breathing and the bokeh
|
|
209
|
+
shape are reported in the assumption `lens.catalogue.character`, not rendered.
|
|
210
|
+
- **Professional lenses are better** where the owner's table says so: sharper centres and corners, less coma, lateral colour,
|
|
211
|
+
distortion and vignetting, better close focus and focus consistency. The measurements are in the catalogue page.
|
|
212
|
+
- **Combinations:** `lens.defocus` convolves the disc into the lens's PSF (a combined kernel up to 16 sites in radius);
|
|
213
|
+
`lens.focusScatter: { seed, shotIndex? }` moves the focus by a seeded autofocus error (`simulateGenericLensFocusError`), off
|
|
214
|
+
by default. `prime`, `focalLengthMm`, `projection`, `distortion`, `vignetting`, `psf`, `diffraction` and `pupilSampling` are
|
|
215
|
+
set by the lens and refused with it; the display view refuses `lens.catalogue`.
|
|
216
|
+
- **Cost:** one scene query per kernel tap through the lens field mapping, like `lens.distortion`: a 48 × 48 region of a full frame
|
|
217
|
+
at 6 µm takes about 1 s with *PV 50 1.2 Pro* and 6 s with *PV 50 1.8* (Apple M5 Pro), against 0.01 s with `lens.prime`. Render
|
|
218
|
+
regions, or use a coarser `sensor.spectralStepNanometers`.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { resolveGenericLens, simulateCapture } from "@photivra/engine";
|
|
222
|
+
|
|
223
|
+
const lens = resolveGenericLens({ lensId: "pv-50-1.8", activeImagingArea: { widthMm: 36, heightMm: 24 }, fNumber: 2.8 });
|
|
224
|
+
console.log(lens.name, lens.focalLengthMm, lens.character.distortion.corner); // PV 50 1.8, 50, about -0.009 (barrel)
|
|
225
|
+
const capture = await simulateCapture({
|
|
226
|
+
scene: { preset: "point-grid" },
|
|
227
|
+
// A whole 36 × 24 mm frame on 48 × 32 pixels.
|
|
228
|
+
output: { width: 48, height: 32 },
|
|
229
|
+
sensor: { pixelPitchMicrometers: 750, spectralStepNanometers: 50 },
|
|
230
|
+
lens: { catalogue: "pv-50-1.8", fNumber: 2.8 },
|
|
231
|
+
exposure: { mode: "manual", shutterSeconds: 1 / 1000, iso: 100 }
|
|
232
|
+
});
|
|
233
|
+
console.log(capture.assumptions.find((a) => a.id === "lens.catalogue.character")?.value);
|
|
234
|
+
```
|
|
235
|
+
|
|
194
236
|
### Fitting a chart to the frame (2.3)
|
|
195
237
|
|
|
196
238
|
`calculateSubjectDistanceForFraming({ sensor, focalLengthMm | equivalentFocalLengthMm, chart, fit? })`
|
|
@@ -1005,15 +1047,20 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1005
1047
|
- A test blurs a sampled straight edge with the kernel and compares it with the disc's closed-form
|
|
1006
1048
|
edge-spread function, the circular-segment area. They agree to rounding (2e-13) when the edge lies on cell
|
|
1007
1049
|
boundaries. Elsewhere they agree within the site sampling of the edge.
|
|
1008
|
-
- The kernel radius is capped at
|
|
1009
|
-
|
|
1050
|
+
- The kernel radius is capped at 512 sites, a disc of at most 1025 sites across (`DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES`;
|
|
1051
|
+
64 sites before 2.17.0, #564). At 6 µm that is a blur circle of 6.15 mm. A larger blur is refused with a short error,
|
|
1052
|
+
for example `lens.defocus: The blur circle is 7.81 mm (1,303 sites) across; the engine renders at most 1,025 sites
|
|
1053
|
+
(DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES).` The disc is never clipped.
|
|
1054
|
+
- Up to a radius of 64 sites the disc is convolved directly, byte for byte as in 2.16.0. Above it, the
|
|
1055
|
+
[large-disc method](#large-defocus-discs-564) below applies.
|
|
1010
1056
|
- **Check before rendering (#532):** `calculateDefocusDiscKernelRadius({ focalLengthMm, aperture, focusDistanceM,
|
|
1011
1057
|
subjectDistanceM, pixelPitchMicrometers })` returns the disc's diameter in µm and in sites, the kernel radius it
|
|
1012
|
-
needs, `withinLimit` and `
|
|
1013
|
-
test, so it agrees with the refusal of
|
|
1014
|
-
boundary (`test/defocus-disc-limit.test.ts`,
|
|
1015
|
-
|
|
1016
|
-
|
|
1058
|
+
needs, `withinLimit`, `excessKernelRadiusSites` and `convolution` (`"direct"`, `"runs"` or null past the limit),
|
|
1059
|
+
without rendering. It runs the capture's own resolution and limit test, so it agrees with the refusal of
|
|
1060
|
+
`simulateCapture`, `simulateDisplayView` and `prepareDisplayView` at the boundary (`test/defocus-disc-limit.test.ts`,
|
|
1061
|
+
at two adjacent focus distances, for the limit and for the 64-site switch to runs). Pass the f-number the capture
|
|
1062
|
+
uses (in an automatic mode, `exposure.settings.fNumber`). The limit is on the disc alone: with `lens.diffraction`
|
|
1063
|
+
the diffraction kernel is applied with it and has no further limit. The other refusals below are not checked.
|
|
1017
1064
|
- **The capture renders it** (#492, step 2a). The scene radiance on the site lattice is convolved with the disc,
|
|
1018
1065
|
the same kernel at every spectral node, and each site query is answered with the result. This is the object-space
|
|
1019
1066
|
convolution that `lens.diffraction` uses.
|
|
@@ -1022,7 +1069,8 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1022
1069
|
- `diagnostics.options` lists `lens.defocus`, and `diagnostics.timingsMs.defocus` times the provider's convolution.
|
|
1023
1070
|
- The site inputs declare no PSF, because the defocus is applied upstream.
|
|
1024
1071
|
- **With `lens.diffraction`**, each spectral node's diffraction kernel is convolved with the disc and applied as
|
|
1025
|
-
one kernel. The combined radius is the diffraction radius plus the defocus radius, at most 32 + 64 = 96 sites.
|
|
1072
|
+
one kernel. The combined radius is the diffraction radius plus the defocus radius, at most 32 + 64 = 96 sites. Above a
|
|
1073
|
+
disc radius of 64 sites the diffraction kernel is applied after the disc instead (#564, below).
|
|
1026
1074
|
- This is a further approximation, and the assumption says so. The defocused wave PSF of a circular pupil is not
|
|
1027
1075
|
exactly the in-focus Airy pattern convolved with the geometric disc. Where the two differ is measured in a
|
|
1028
1076
|
later step of #492.
|
|
@@ -1075,9 +1123,8 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1075
1123
|
- **Refused:** camera rotation with `motion.shutter: "rolling"` (each row has its own instants, so each row would
|
|
1076
1124
|
need its own image of the 2R + 1 rows the disc reaches), and a vignetting or distortion profile that does not
|
|
1077
1125
|
reach the frame's corners plus the kernel radius (the message names the reach). `lens.pupilSampling` stays refused
|
|
1078
|
-
with projection and distortion. `simulateDisplayView` and `prepareDisplayView`
|
|
1079
|
-
|
|
1080
|
-
factor), so they have nothing to add here.
|
|
1126
|
+
with projection and distortion. `simulateDisplayView` and `prepareDisplayView` render these combinations too (#563),
|
|
1127
|
+
with the same image-space disc and the same refusals ([Display view](#display-view)).
|
|
1081
1128
|
- **Sampling** is the lattice sampling below, unchanged. Measured against a fine-grid oracle: the environment PSF
|
|
1082
1129
|
quadrature with the disc sampled 5 times finer than the site pitch (3 times with the box aperture), on a 16 × 12
|
|
1083
1130
|
slanted edge with the 5.95-site disc (50 mm f/4, subject 2 m, focus 1.8 m, 6 µm, 80 nm spectral step), as the relative
|
|
@@ -1143,7 +1190,7 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1143
1190
|
- The tabulated and environment paths give the same RAW.
|
|
1144
1191
|
- Regions and DNG parts equal the whole frame: chart, Quality options with diffraction, a `material-layers`
|
|
1145
1192
|
scene and a caller function.
|
|
1146
|
-
- **Cost
|
|
1193
|
+
- **Cost of the direct convolution (up to 64 sites).** Direct convolution costs (2R + 1)² multiply-adds per site and node wherever the disc
|
|
1147
1194
|
sees more than one material. Uniform areas cost almost nothing on the tabulated path. Measured on the tabulated
|
|
1148
1195
|
capture path (Node 24, one thread, rounds interleaved over the radii, median [range] of 3 rounds; 2 at 2048 × 1366;
|
|
1149
1196
|
a shared 4-vCPU host):
|
|
@@ -1156,10 +1203,9 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1156
1203
|
| Siemens star, 1024 × 683, Quality (diffraction, four-spot, 2 × 2 box, AHD) | 0.81 s | 0.87 s | 1.7 s | 4.1 s | 12.5 s | 20.0 s | 52.9 s [52.0–53.2] |
|
|
1157
1204
|
| Siemens star, 2048 × 1366 | 1.39 s | | 1.8 s | | 10.4 s | | 38.3 s [37.5–38.3] |
|
|
1158
1205
|
|
|
1159
|
-
-
|
|
1160
|
-
|
|
1161
|
-
- The time grows with R² per edge site, and the caller chooses it through the focus distance.
|
|
1162
|
-
request fails and names the radius it needed.
|
|
1206
|
+
- 2.16.0 capped the radius at 64 sites from these numbers. Since 2.17.0 (#564) the direct convolution stops there and
|
|
1207
|
+
larger discs take the run convolution below.
|
|
1208
|
+
- The time grows with R² per edge site, and the caller chooses it through the focus distance.
|
|
1163
1209
|
- A 24 MP frame costs proportionally more. Render it in parts across workers, as for diffraction.
|
|
1164
1210
|
- Off the tabulated path (caller reflectance or radiance functions), the provider gathers only each 32-row band's
|
|
1165
1211
|
own values, not the 2R extra rows a full-grid convolution would return (#419). On a 256 × 171
|
|
@@ -1167,6 +1213,183 @@ The optics options feed only what the environment site contract accepts:
|
|
|
1167
1213
|
- It cannot be combined with `lens.pupilSampling` (the rays already render defocus) or `lens.psf` (a caller PSF
|
|
1168
1214
|
declares its own defocus). It can be combined with `lens.diffraction`.
|
|
1169
1215
|
|
|
1216
|
+
#### Large defocus discs (#564)
|
|
1217
|
+
|
|
1218
|
+
Wide apertures blur far more than 129 sites. 50 mm f/1.4 on a 6 µm full-frame sensor, focused at infinity with the
|
|
1219
|
+
subject at 0.83 m, gives a 2,144 µm blur circle, 357 sites across (kernel radius 179). Since 2.17.0 `lens.defocus` renders
|
|
1220
|
+
discs up to a kernel radius of 512 sites (1025 sites across, 6.15 mm at 6 µm). It does this in `simulateCapture` (the
|
|
1221
|
+
object-space, image-space #548 and tabulated paths) and in `simulateDisplayView` and `prepareDisplayView` on the CPU.
|
|
1222
|
+
|
|
1223
|
+
- **Method: runs of equal weights.** Each kernel row is split into runs of consecutive equal weights. A run's contribution,
|
|
1224
|
+
w·Σ L over the run, is the difference of two prefix sums of that source row, so it costs two reads however long the run
|
|
1225
|
+
is. Runs that share a weight are summed first and multiplied once. This is the summed-area table of Crow (1984)
|
|
1226
|
+
applied row by row; Heckbert (1986) convolves any piecewise-constant kernel this way. Both are free to read.
|
|
1227
|
+
- Every cell wholly inside the disc has the same weight (its area is exactly 1). So the disc of radius R costs one run
|
|
1228
|
+
per row plus the cells the circle crosses: 2,149 reads per output at R = 179, against 101,689 taps for the direct
|
|
1229
|
+
gather; 6,145 at R = 512 (`src/optics/site-kernel-runs.ts`).
|
|
1230
|
+
- **With bokeh character (#553).** The kernel convolved is the shaped kernel when `lens.defocus` has bokeh options, and
|
|
1231
|
+
the disc otherwise. Blade polygons have the disc's uniform interior, so they take this path at little cost: a 7-blade
|
|
1232
|
+
kernel of radius 85 needs 3,002 reads per output, against 29,241 taps for the direct gather. Spherical aberration's binned
|
|
1233
|
+
kernel has no equal neighbours, so it costs as much as the direct gather. The engine refuses any kernel that needs more
|
|
1234
|
+
reads per site than the direct gather at radius 64 (16,641): `lens.defocus: this blur needs 157,644 reads per site;
|
|
1235
|
+
above a kernel radius of 64 sites the limit is 16,641. Reduce the spherical aberration or the defocus.` A cat's eye
|
|
1236
|
+
above 64 sites is refused too, because this path takes one kernel per request and the cat's eye needs one per field
|
|
1237
|
+
zone (`lens.defocus.catsEye: the blur needs a kernel radius of 179 sites; with a cat's eye the limit is 64 sites
|
|
1238
|
+
(129 across). Reduce the defocus or omit catsEye.`).
|
|
1239
|
+
- **With the display view's lens character (#563).** With `lens.projection`, `lens.distortion`, `lens.vignetting` or
|
|
1240
|
+
camera rotation, the view applies the disc to each channel's in-focus signal through the lens, which already carries
|
|
1241
|
+
each source point's field factor. This is the same reordering as without them. Its site values match the capture's
|
|
1242
|
+
image-space provider within 1e-9 relative at R = 179 with distortion and vignetting
|
|
1243
|
+
(`test/defocus-integration-2-17.test.ts`). The fixed tiles never map lattice points beyond the kernel's reach, so a
|
|
1244
|
+
lens profile only has to cover the reach the error message names.
|
|
1245
|
+
- **Not an approximation.** Each output is the same sum of the same products as the direct gather, regrouped. Only
|
|
1246
|
+
the rounding changes. Measured against a compensated (Neumaier) sum of the same products, on the Siemens star,
|
|
1247
|
+
slanted edge and dead leaves at radii 65, 179 and 512, the runs were within 2.5e-14 relative. The direct gather was
|
|
1248
|
+
up to 9.9e-12 off, because it adds up to 826,537 products in one running sum. The runs and the direct gather
|
|
1249
|
+
differed by at most 9.9e-12 relative (RMS 9.9e-12 at R = 512 on dead leaves; 1.6e-12 at R = 179)
|
|
1250
|
+
(`node scripts/benchmark-large-defocus.mjs accuracy`).
|
|
1251
|
+
- Prefix sums of non-negative samples never decrease, so every output is non-negative.
|
|
1252
|
+
- **Where it runs.** Up to 64 sites nothing changes: the direct convolution, byte for byte as in 2.16.0, and every hash
|
|
1253
|
+
lock holds. Above it:
|
|
1254
|
+
- **The providers** (object space, and image space with the field factor) convolve each spectral node's samples with the
|
|
1255
|
+
disc by runs in fixed 64-site tiles. With `lens.diffraction` each node's diffraction kernel is gathered directly
|
|
1256
|
+
afterwards (associativity: K ⊛ (D ⊛ L) = (K ⊛ D) ⊛ L, the combined kernel's sum regrouped).
|
|
1257
|
+
- **The tabulated expected signal** (the capture's fast path and the display view) uses the fact that defocus of a plane
|
|
1258
|
+
is space-invariant and the same at every wavelength. It commutes with the spectral, query-offset and diffraction sums,
|
|
1259
|
+
so μ_c(x) = Σ_s D(s)·E_c(x − s). Here E_c is a channel's in-focus expected electrons at each site position, on the
|
|
1260
|
+
lattice formula, with diffraction when it is on. The disc is then applied once per channel on the native site lattice
|
|
1261
|
+
instead of once per spectral node and lattice group: E in fixed 64-site tiles, D ⊛ E by runs in fixed 128-site tiles,
|
|
1262
|
+
each site taking its own channel.
|
|
1263
|
+
- **Fixed tiles at absolute positions** make every value a function of its site alone. Regions, strips, DNG parts and
|
|
1264
|
+
display-view parts are bit-identical to the whole frame (tested), and the tabulated and environment paths give the
|
|
1265
|
+
same RAW (tested at R = 179, with and without diffraction and the Quality filters).
|
|
1266
|
+
- The `lens.defocus` and `view.defocus` assumptions record `convolution: "runs"`; below 64 sites the field is absent.
|
|
1267
|
+
- **The GPU display view does not take these discs.** `gpu: "auto"` renders them on the CPU, labelled
|
|
1268
|
+
`unsupported-on-backend` with the reason.
|
|
1269
|
+
- **Validation** (`test/large-defocus.test.ts`, `test/site-kernel-runs.test.ts`, `test/display-view-defocus.test.mjs`):
|
|
1270
|
+
- Each path is compared with the direct gather of the same disc: object space (alone and with a diffraction kernel),
|
|
1271
|
+
image space with vignetting, and the tabulated expected signal with and without diffraction. All agree within 1e-12
|
|
1272
|
+
relative.
|
|
1273
|
+
- A vertical edge between lattice columns at R = 179 gives the exact circular-segment fraction to 1e-12.
|
|
1274
|
+
- The display view at R = 194 (Speed and Quality) matches the in-focus view of the scene blurred beforehand by the disc
|
|
1275
|
+
within ΔE2000 1e-6.
|
|
1276
|
+
- **Cost** (Apple M5 Pro, Node 24.19.0, built `dist`, one process per case, two rounds; the machine was shared, load average
|
|
1277
|
+
5–7, so the times are upper bounds):
|
|
1278
|
+
|
|
1279
|
+
| Case | In focus | R 64 (direct) | R 65 (runs) | R 179 (runs) | R 512 (runs) |
|
|
1280
|
+
| --- | --- | --- | --- | --- | --- |
|
|
1281
|
+
| Per output, one 64-site tile (kernel only) | | 11.4 µs | 0.60 µs | 1.52 µs (direct: 65 µs) | 4.6 µs |
|
|
1282
|
+
| Capture, Siemens star 1024 × 683, 6 µm (tabulated) | 0.24–0.26 s | 5.7–6.8 s | 0.72–0.77 s | 1.59–1.66 s | 5.4–5.7 s |
|
|
1283
|
+
| Peak resident memory of that capture | 134–135 MiB | 157–158 MiB | 216–230 MiB | 247–253 MiB | 440–457 MiB |
|
|
1284
|
+
| Display view, the app's request (24 MP at 6 µm, 2028 × 1352 in 96 × 96 parts, Speed, 85 mm-equivalent f/1.4, subject 3 m), part median [slowest of 8], one round | 44 ms [105 ms] | 274 ms [5.6 s] | | 327 ms [540 ms] | 1.10 s [1.20 s] |
|
|
1285
|
+
| The same view on one thread, extrapolated from the 8 parts (mean × 294) | 14 s | 431 s | | 98 s | 296 s |
|
|
1286
|
+
| Peak resident memory of that view process | 260 MiB | 217 MiB | | 394 MiB | 390 MiB |
|
|
1287
|
+
|
|
1288
|
+
The disc of the owner's case (R = 179) costs the capture 1.6 s here, against 6–7 s for the direct convolution at R = 64;
|
|
1289
|
+
the display view's whole-view estimate is 98 s, against 431 s at R = 64. The view's parts are rendered on the CPU (the GPU
|
|
1290
|
+
does not take these discs) and can be spread across workers as today.
|
|
1291
|
+
|
|
1292
|
+
- Memory: the disc's own weights are (2R + 1)² doubles (1.0 MB at R = 179, 8.4 MB at 512), and each provider tile's
|
|
1293
|
+
source and prefix sums (64 + 2R)² doubles each (1.4 MB each at R = 179, 9.5 MB each at 512). Kept tiles are capped at 96 MiB per
|
|
1294
|
+
capture for the providers, and 32 MiB each for the expected signal's in-focus and output tiles per prepared input (the
|
|
1295
|
+
in-focus cache grows to one output tile's span, about 37 MiB at R = 512).
|
|
1296
|
+
- The cost grows with R, not R²: about 12·R reads per output plus a fixed part per tile. Measured with
|
|
1297
|
+
`RADII=0,64,65,179,512 REPEATS=2 node scripts/benchmark-large-defocus.mjs capture` (and `kernel`, `view`).
|
|
1298
|
+
- **Why 512.** It bounds the reads per site (6,145, below the 16,641 of the direct gather at 64), the kernel's memory and
|
|
1299
|
+
each tile's source, at a measured cost (above). It covers the owner's case (R = 179), and at 6 µm a 50 mm or 85 mm f/1.2
|
|
1300
|
+
focused at infinity with the subject at 1 m or farther (R ≤ 502). A blur past it fails with a short message (above); the disc is never clipped.
|
|
1301
|
+
|
|
1302
|
+
#### Bokeh character (#553)
|
|
1303
|
+
|
|
1304
|
+
`lens.defocus` takes three optional parts that shape the defocus kernel by the lens. Each is off by default; without
|
|
1305
|
+
them the disc above is unchanged and every request is byte-identical. The same options, validated the same way, go to
|
|
1306
|
+
`calculateDefocusBokehKernel({ focalLengthMm, aperture, focusDistanceM, subjectDistanceM, pixelPitchMicrometers, bokeh,
|
|
1307
|
+
fieldTangent? })`, which returns the kernel the capture applies (for previews, tests and lens catalogues; the
|
|
1308
|
+
`DefocusBokehOptions` type is what a catalogue lens can carry, #549).
|
|
1309
|
+
|
|
1310
|
+
```js
|
|
1311
|
+
lens: { focalLengthMm: 50, fNumber: 4, focusDistanceM: 1.4, defocus: { model: "geometric-disc",
|
|
1312
|
+
blades: { count: 9, firstBladeEdgeAngleDegrees: 0, curvature: 0.3, circularAtOrBelowFNumber: 1.8 },
|
|
1313
|
+
sphericalAberration: { w040Micrometers: 0.5, atFNumber: 4 },
|
|
1314
|
+
catsEye: { apertures: [{ radiusMm: 7, shiftPerTanFieldMm: 20 }], fieldZonesAcrossLongSide: 8 } } }
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
- **The model.** A geometric defocused image of a point is the open pupil projected onto the sensor. The ray through
|
|
1318
|
+
pupil point P = ρ·p̂ (in units of the radius of the f/N pupil) lands at (σ·c/2 + b·ρ²)·P sites from the chief ray: c is
|
|
1319
|
+
the thin-lens blur diameter (`calculateDefocusCircle`), σ = +1 for a scene plane behind the focus plane and −1 in front
|
|
1320
|
+
of it, and b = 8·N·W040/pitch is the transverse ray aberration of primary spherical aberration W = W040·ρ⁴,
|
|
1321
|
+
ε = −(R/r_p)·∂W/∂ρ with R/r_p = 2N (Wyant & Creath 1992, §II.A, free). It is an approximation: geometric optics, an ideal thin
|
|
1322
|
+
lens, the aberration referred to the paraxial focus, the exit pupil the same shape and orientation as the entrance pupil;
|
|
1323
|
+
no diffraction of the defocused wave, onion rings, higher-order or field-dependent aberrations, and the same kernel at
|
|
1324
|
+
every wavelength. The `lens.defocus` assumption gains `bokeh` (the options, the side, the shaped kernel's radius,
|
|
1325
|
+
the method, b in sites, and the cat's-eye zones) and says all this.
|
|
1326
|
+
- **`blades`**: a regular polygon of `count` (3–32) straight blades, with `calculateIdealApertureGeometry`'s
|
|
1327
|
+
`firstBladeEdgeAngleDegrees`; `curvature` κ in [0, 1] makes each edge an arc of radius 1/κ circumradii through its two
|
|
1328
|
+
vertices (κ = 1 is the circle); at or below `circularAtOrBelowFNumber` the blades are open and the opening is circular.
|
|
1329
|
+
The opening is scaled to the area of the f/N circle, so the f-number keeps its meaning for light and the blur has the
|
|
1330
|
+
disc's area.
|
|
1331
|
+
- **Front against back.** Behind the focus plane the rays have crossed before the sensor, in front they have not, so the
|
|
1332
|
+
two blurs are the same pupil turned through 180° (visible with an odd blade count or a cat's eye; shapes are declared as
|
|
1333
|
+
the blur behind the focus plane appears in the output image, +X right, +Y up). With spherical aberration the term b·ρ²
|
|
1334
|
+
adds to the defocus on one side and subtracts on the other: the image intensity at r = |σc/2·ρ + bρ³| of a circular
|
|
1335
|
+
pupil is proportional to 1/(|σc/2 + bρ²|·|σc/2 + 3bρ²|), so undercorrected aberration (W040 > 0) gives a soft-edged blur
|
|
1336
|
+
behind the focus plane and a bright-rimmed one in front, and overcorrected the reverse (the photographic description is
|
|
1337
|
+
H. H. Nasse, "Depth of Field and Bokeh", Carl Zeiss, 2010, free). `sphericalAberration` gives W040 in µm at `atFNumber`,
|
|
1338
|
+
scaled to the working f-number as W040·(N₀/N)⁴. The rim can widen the blur beyond the disc. Past a kernel radius of
|
|
1339
|
+
512 sites the request is refused with the disc's short message. Above 64 sites it is refused when its binned kernel
|
|
1340
|
+
needs more than 16,641 reads per site, the run path's limit ("Large defocus discs", above).
|
|
1341
|
+
- **`catsEye`**: mechanical vignetting, the model of `calculateMechanicalVignetting` (Greivenkamp, OPTI-502 notes, sec. 10,
|
|
1342
|
+
free): each clipping aperture is a circle of `radiusMm` in the entrance-pupil plane, shifted by
|
|
1343
|
+
`shiftPerTanFieldMm`·tan θ toward the field point, and the open pupil is the opening inside every circle. The kernel
|
|
1344
|
+
varies over the field, so the capture cuts the native raster into square zones (`fieldZonesAcrossLongSide`, 1–32,
|
|
1345
|
+
default 8) and gives each site the kernel of its zone's centre: a piecewise-constant (sectioned) approximation of the
|
|
1346
|
+
space-variant blur (also see L. Denis, É. Thiébaut, F. Soulez, J.-M. Becker, R. Mourya, "Fast approximations of
|
|
1347
|
+
shift-variant blur", *International Journal of Computer Vision* 115, 2015, pp. 253–278). The assumption reports the
|
|
1348
|
+
zones, the smallest open-area ratio and its largest step between neighbouring zones (the size of the seams; raise the
|
|
1349
|
+
zone count to shrink it). The kernel has unit sum: the light the clip removes is not applied, so declare the falloff with
|
|
1350
|
+
`lens.vignetting`, as for any field throughput (`lightLossApplied: false`). It runs on the object-space and the
|
|
1351
|
+
image-space (#548) paths; the cat's eye skips the tabulated fast path (one kernel per node there), and it cannot be
|
|
1352
|
+
combined with `lens.diffraction` (every zone would need its own combined kernel per node). Above a kernel radius of 64
|
|
1353
|
+
sites it is refused: #564's run convolution takes one kernel per request, and extending it to one kernel per zone is left
|
|
1354
|
+
for later. `simulateDisplayView` and `prepareDisplayView` refuse it; they render `blades` and `sphericalAberration`.
|
|
1355
|
+
- **With `lens.catalogue` (#549)** the three options are refused by name. The catalogue lens folds a plain disc into its
|
|
1356
|
+
own PSF, so they would otherwise be dropped. Feeding a catalogue lens's spherical aberration and character into the
|
|
1357
|
+
bokeh kernel is a follow-up ([#567](https://github.com/Photivra/photivra/issues/567)), not a 2.17.0 feature.
|
|
1358
|
+
- **Sampling.** Without spherical aberration each site's weight is the exact area of the projected open pupil (a convex
|
|
1359
|
+
polygon; circles and blade arcs are 1024-gons scaled to their true area, within 5e-6 of the radius of the true curve)
|
|
1360
|
+
inside the site's cell, divided by its area (`"exact-polygon-cell-area"`). With it the pupil is sampled on a square grid
|
|
1361
|
+
fine enough that neighbouring rays land at most 1/8 site apart, each grid cell weighted by its exact overlap with the
|
|
1362
|
+
pupil's chord, and every ray binned into the site cell it lands in (`"pupil-ray-binning"`; it handles the caustic).
|
|
1363
|
+
Only correctly rounded operations and the portable trigonometry are used, so the weights are the same bits in every
|
|
1364
|
+
JavaScript engine.
|
|
1365
|
+
- **Validation** (`test/defocus-bokeh-kernel.test.ts`, `test/simulate-capture-defocus-bokeh.test.ts`):
|
|
1366
|
+
- Polygon area and shape: an interior cell holds 1/(π·(c/2)²) of the light (to 1e-6 relative) for 5, 6 and 9 blades, and
|
|
1367
|
+
every cell matches a 16 × 16 supersampling of `calculateIdealApertureGeometry`'s polygon to its resolution (1/16 cell).
|
|
1368
|
+
Without blades, at κ = 1 and with the blades open the kernel equals the exact disc kernel to 1e-4 of an interior weight.
|
|
1369
|
+
- Cat's eye against field position: the open-area ratio equals `calculateMechanicalVignetting`'s exact two-circle area to
|
|
1370
|
+
1e-4 at 0–25° and three azimuths; the radial spread falls against the tangential one as the field grows, the light kept
|
|
1371
|
+
is on the field point's side behind the focus plane and on the other in front; on axis the disc is whole.
|
|
1372
|
+
- Spherical-aberration edge profile: the kernel's encircled energy follows the ray map's analytic ρ(r)² within 0.012 at
|
|
1373
|
+
c = 60 sites, b = 4.5 sites on both sides; the rim-to-centre intensity ratio matches the analytic one to 0.05 and is
|
|
1374
|
+
below 0.8 behind and above 1.2 in front for W040 > 0, reversed for W040 < 0; W040 scales as (N₀/N)⁴.
|
|
1375
|
+
- Front against back: a 5-blade kernel in front is the one behind turned through 180° to 1e-15; a circular pupil without
|
|
1376
|
+
aberration gives the same blur on both sides.
|
|
1377
|
+
- On the point grid out of focus (240 × 144, 6 µm, 50 mm f/4, subject 2 m, focus 1.4 m, a 23-site blur): the centre
|
|
1378
|
+
point's blob has a max/min radial extent of 1.07 with the disc and 1.23 with 5 blades (1/cos 36° = 1.236), five-fold;
|
|
1379
|
+
with a cat's eye the left point's blob has a radial-to-tangential spread of 0.57 (0.99–1.01 for the centre point and
|
|
1380
|
+
without the option) and its centroid moves 3.5 px outward; with W040 0.5 µm the rim-to-centre ratio is 0.90 behind the
|
|
1381
|
+
focus plane (focus 1.4 m) and 1.15 in front (focus 3.5 m).
|
|
1382
|
+
- Requests without the options, and in focus with them, give the same RAW codes; regions equal the whole frame with a
|
|
1383
|
+
cat's eye; the GPU display view's float32 emulation gathers a shaped asymmetric kernel within its a-priori bounds.
|
|
1384
|
+
- **Cost** (Apple M5 Pro, Node.js 24, built `dist`, one thread, median of 3 or 5; a shared machine). Building a kernel at
|
|
1385
|
+
1.4 µm, 50 mm f/4, subject 2 m, focus 1.6 m (c = 57.6 sites): the circle or 9 blades 0.2–0.8 ms (R 29–30), a cat's-eye
|
|
1386
|
+
kernel at tan θ = 0.3 3.5 ms, spherical aberration (b = 6.9 sites, R 36, about 0.5 million rays) 3–4 ms. A Siemens star
|
|
1387
|
+
capture at 480 × 320, 6 µm, focus 1.6 m (c = 13.4 sites, R 7–9): the disc, 9 blades and spherical aberration all
|
|
1388
|
+
0.06–0.08 s on the tabulated path; a cat's eye 2.2 s with 8 zones and 3.2 s with 16 (the zoned convolution itself 0.51 s
|
|
1389
|
+
and 1.02 s), because it leaves the tabulated path for the per-site environment path. The display view (CPU, 240 × 160 of
|
|
1390
|
+
that frame) took 0.12–0.16 s with the disc, 9 blades or spherical aberration. The GPU display view gathers the shaped
|
|
1391
|
+
kernel with the disc's own pass and cost (no `src/gpu` change); it does not take the cat's eye.
|
|
1392
|
+
|
|
1170
1393
|
### Pixel aperture (2.3)
|
|
1171
1394
|
|
|
1172
1395
|
By default each site samples the irradiance at its centre (`sensor.pixelAperture: "point"`). Scene
|
|
@@ -1736,6 +1959,14 @@ request took 73 s instead of 103 s). In 2.6.0 the results were byte-identical to
|
|
|
1736
1959
|
merged, which took 222 s, 450 s and 837 s on the phone; later releases changed some views' values (for example the
|
|
1737
1960
|
exact area mean, 2.8.0; AHD's choice, 2.13.0).
|
|
1738
1961
|
|
|
1962
|
+
**Lens character (#563).** `lens.projection`, `lens.distortion`, `lens.vignetting` and camera rotation (global shutter)
|
|
1963
|
+
render in the view, in focus and with `lens.defocus` out of focus: each real site's expected electrons are the capture's,
|
|
1964
|
+
through the same mapping, field factor and rotation (within 1e-9 relative per site). They run on the CPU: with
|
|
1965
|
+
`gpu: "auto"` the view falls back labelled `unsupported-on-backend`. They cost more than the rectilinear view, most for
|
|
1966
|
+
distortion (the inverse distortion is solved per point) and rotation (one scene point per temporal midpoint)
|
|
1967
|
+
([DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#lens-projection-distortion-vignetting-and-rotation)).
|
|
1968
|
+
Rotation with a rolling shutter stays refused.
|
|
1969
|
+
|
|
1739
1970
|
**Options.** `view.width` and `view.height` (1 to the capture's output size), `view.samplesPerAxis` (1–32),
|
|
1740
1971
|
`view.format` (`"srgb-8"` default or `"srgb-16"`) and `view.region` (a rectangle in display pixels, byte-identical to
|
|
1741
1972
|
the same pixels of the whole view, for tiling across workers). The capture's file-format options (`format`,
|
|
@@ -1821,6 +2052,15 @@ provided, for rights reasons.
|
|
|
1821
2052
|
independent test). The original analysis is G. B. Airy, "On the Diffraction of an Object-glass with
|
|
1822
2053
|
Circular Aperture", *Transactions of the Cambridge Philosophical Society* 5, 1835, pp. 283–291. The
|
|
1823
2054
|
engine's implementation and its tests are its own (see [PSF foundation](https://github.com/Photivra/photivra/blob/main/docs/PSF_FOUNDATION.md#diffraction-limited-sampled-profile-21)).
|
|
2055
|
+
- Bokeh character (`lens.defocus` blades, spherical aberration and cat's eye, `calculateDefocusBokehKernel`, #553): the
|
|
2056
|
+
transverse ray aberration ε = −(R/r_p)·∂W/∂ρ of primary spherical aberration from Wyant & Creath 1992 (above), §II.A; the
|
|
2057
|
+
mechanical-vignetting model from J. E. Greivenkamp, OPTI-502 Optical Design and Instrumentation I course notes, University
|
|
2058
|
+
of Arizona, sec. 10 (free; as `calculateMechanicalVignetting`); the photographic account of front and back bokeh under
|
|
2059
|
+
spherical aberration in H. H. Nasse, "Depth of Field and Bokeh", Carl Zeiss Camera Lens Division, March 2010, free at
|
|
2060
|
+
<https://lenspire.zeiss.com/photo/app/uploads/2022/02/technical-article-depth-of-field-and-bokeh.pdf>. The intensity
|
|
2061
|
+
profiles follow from the ray map; the polygon clipping (Sutherland–Hodgman) and the shoelace area are elementary geometry.
|
|
2062
|
+
The zoned (sectioned) treatment of the field-varying cat's eye is surveyed in L. Denis et al., "Fast approximations of
|
|
2063
|
+
shift-variant blur", *International Journal of Computer Vision* 115 (2015) 253–278.
|
|
1824
2064
|
- Print planning (`calculatePrintPlan`, `calculatePrintSizeLimit`): Z. Alomar, K. D. Bojikian, "Visual Acuity
|
|
1825
2065
|
Testing in Adults", EyeWiki, American Academy of Ophthalmology, last edited June 16, 2026, free to read at
|
|
1826
2066
|
<https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults> (the minimum angle of resolution of an ideal eye under optimal
|
package/docs/USAGE.md
CHANGED
|
@@ -1455,7 +1455,7 @@ console.log(blur.value.diameterMm);
|
|
|
1455
1455
|
|
|
1456
1456
|
You supply the circle-of-confusion criterion used for depth of field (DOF). These are ideal geometric thin-lens models. They do not include diffraction or real-lens aberrations in the DOF criterion.
|
|
1457
1457
|
|
|
1458
|
-
`simulateCapture` and the display view render the defocus blur disc with `lens.defocus: { model: "geometric-disc" }`, up to a kernel radius of `DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES` (
|
|
1458
|
+
`simulateCapture` and the display view render the defocus blur disc with `lens.defocus: { model: "geometric-disc" }`, up to a kernel radius of `DEFOCUS_DISC_KERNEL_MAXIMUM_RADIUS_SITES` (512 sites, a disc 1025 sites across, 6.15 mm at 6 µm; 64 sites before 2.17.0) and refuse a larger one. Up to 64 sites the disc is convolved directly, as before; above it, by runs of equal kernel weights (#564), the same sums at a cost that grows with the radius instead of its square ([Quick start, Large defocus discs](QUICK_START.md#large-defocus-discs-564)). Use `calculateDefocusDiscKernelRadius()` to check a request before rendering it. It runs the capture's own resolution and limit test, so it agrees with the capture's refusal, and `convolution` says which method renders it. Pass the f-number the capture uses (in an automatic exposure mode, the resolved one) and the sensor's pixel pitch:
|
|
1459
1459
|
|
|
1460
1460
|
```ts
|
|
1461
1461
|
import { calculateDefocusDiscKernelRadius } from "@photivra/engine";
|
|
@@ -1463,13 +1463,14 @@ import { calculateDefocusDiscKernelRadius } from "@photivra/engine";
|
|
|
1463
1463
|
const check = calculateDefocusDiscKernelRadius({
|
|
1464
1464
|
focalLengthMm: 50,
|
|
1465
1465
|
aperture: 1.4,
|
|
1466
|
-
focusDistanceM:
|
|
1466
|
+
focusDistanceM: 0.25,
|
|
1467
1467
|
subjectDistanceM: 2,
|
|
1468
1468
|
pixelPitchMicrometers: 6
|
|
1469
1469
|
});
|
|
1470
1470
|
|
|
1471
|
-
console.log(check.value.withinLimit); // false: lens.defocus refuses this request
|
|
1472
|
-
console.log(check.value.kernelRadiusSites, check.value.excessKernelRadiusSites); //
|
|
1471
|
+
console.log(check.value.withinLimit); // false: lens.defocus refuses this request (a 7.81 mm blur, 1,303 sites across)
|
|
1472
|
+
console.log(check.value.kernelRadiusSites, check.value.excessKernelRadiusSites); // 651, 139
|
|
1473
|
+
// Focused at 1 m instead: 157 sites across, radius 78, withinLimit true, convolution "runs".
|
|
1473
1474
|
```
|
|
1474
1475
|
|
|
1475
1476
|
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#thin-lens-focus-and-depth-of-field).
|
|
@@ -7470,6 +7471,28 @@ console.log(calculateGeometricFocusBreathing({
|
|
|
7470
7471
|
}).value.deltaDegrees);
|
|
7471
7472
|
```
|
|
7472
7473
|
|
|
7474
|
+
## Generic lens catalogue (#549)
|
|
7475
|
+
|
|
7476
|
+
`GENERIC_LENS_CATALOGUE` lists the engine's 30 generic primes (17 professional, 13 consumer, "PV 50 1.2 Pro", "PV 50 1.8"; Photivra's
|
|
7477
|
+
own designs, not real products). `resolveGenericLens` resolves one for a sensor, an f-number and a focus distance: its focal length
|
|
7478
|
+
(scaled by the crop factor), aperture range, close focus, projection and its character, Seidel aberrations, axial and lateral
|
|
7479
|
+
colour, distortion and vignetting profiles, flare, breathing and focus scatter, each a labelled approximation.
|
|
7480
|
+
`simulateGenericLensFocusError` draws a seeded autofocus error; `createGenericLensCorrectionProfile` builds the lens's generic
|
|
7481
|
+
correction profile for `resolveLensCorrectionPlan`. `simulateCapture` renders a catalogue lens with `lens.catalogue`
|
|
7482
|
+
([quick start](QUICK_START.md#generic-lens-catalogue-549)). Equations, tables and the chart measurements:
|
|
7483
|
+
[LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md).
|
|
7484
|
+
|
|
7485
|
+
```ts
|
|
7486
|
+
import { GENERIC_LENS_CATALOGUE, resolveGenericLens, simulateGenericLensFocusError } from "@photivra/engine";
|
|
7487
|
+
|
|
7488
|
+
const frame = { widthMm: 36, heightMm: 24 };
|
|
7489
|
+
const pro = resolveGenericLens({ lensId: "pv-50-1.2-pro", activeImagingArea: frame, fNumber: 2 });
|
|
7490
|
+
const consumer = resolveGenericLens({ lensId: "pv-50-1.8", activeImagingArea: frame, fNumber: 2 });
|
|
7491
|
+
console.log(GENERIC_LENS_CATALOGUE.length, pro.character.aberrations.comaWaves < consumer.character.aberrations.comaWaves); // 30 true
|
|
7492
|
+
const shot = simulateGenericLensFocusError({ lensId: "pv-50-1.8", activeImagingArea: frame, fNumber: 1.8, focusDistanceM: 2, seed: 1, shotIndex: 0 });
|
|
7493
|
+
console.log(shot.focusDistanceM, shot.imageSideErrorMicrometers);
|
|
7494
|
+
```
|
|
7495
|
+
|
|
7473
7496
|
## Parallel native environment RAW (row-band partitions, 2.0)
|
|
7474
7497
|
|
|
7475
7498
|
`createNativeEnvironmentRawTask` runs on one thread. For several cores, split the capture into row bands with
|
|
@@ -7814,6 +7837,20 @@ region that have a clipped contributing site. `simulateCapture` counts the same
|
|
|
7814
7837
|
are equal where every site is far from white. Counts of regions that tile a view add up to the whole view's count. See
|
|
7815
7838
|
[Highlight count](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#highlight-count-533).
|
|
7816
7839
|
|
|
7840
|
+
From #563 the view renders `lens.projection`, `lens.distortion`, `lens.vignetting` and camera rotation (global
|
|
7841
|
+
shutter), in focus and with `lens.defocus` out of focus, with each real site's expected electrons as the capture's. These
|
|
7842
|
+
requests run on the CPU (`gpu: "auto"` falls back labelled `unsupported-on-backend`); rotation with a rolling shutter is
|
|
7843
|
+
refused. See [Lens projection, distortion, vignetting and rotation](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#lens-projection-distortion-vignetting-and-rotation).
|
|
7844
|
+
|
|
7845
|
+
```ts
|
|
7846
|
+
import { simulateDisplayView } from "@photivra/engine";
|
|
7847
|
+
|
|
7848
|
+
const fisheye = await simulateDisplayView({ scene: { preset: "siemens-star" }, sensor: { pixelPitchMicrometers: 3 }, output: { width: 120, height: 80 },
|
|
7849
|
+
lens: { projection: "fisheye-equisolid-angle", vignetting: { normalizationRadiusMm: 0.25, maximumNormalizedRadius: 1, coefficients: { r2: -0.4, r4: 0, r6: 0 } } } },
|
|
7850
|
+
{ width: 30, height: 20 });
|
|
7851
|
+
console.log(fisheye.kind, fisheye.assumptions.find((a) => a.id === "lens.vignetting")?.kind); // "display-view" "model"
|
|
7852
|
+
```
|
|
7853
|
+
|
|
7817
7854
|
#### Rendering a prepared view on the GPU (2.11, #481)
|
|
7818
7855
|
|
|
7819
7856
|
`prepareDisplayView(request, { gpu })` chooses the renderer: `"off"` (the default) is the CPU reference, `"auto"` the GPU
|
|
@@ -7923,7 +7960,7 @@ console.log(barrel.request.siteInput(0).environment.lensFieldMapping?.distortion
|
|
|
7923
7960
|
lattice, and the capture convolves the scene radiance with it, which is a geometric approximation. With
|
|
7924
7961
|
`lens.diffraction`, each spectral node's diffraction kernel is convolved with the disc, which is a further
|
|
7925
7962
|
approximation. The `lens.defocus` assumption records the diameter in µm and in sites, the kernel radius and the
|
|
7926
|
-
execution. `simulateDisplayView`
|
|
7963
|
+
execution. `simulateDisplayView` renders it too, also with projection, distortion, vignetting and rotation (#563). See [Optics](QUICK_START.md#optics).
|
|
7927
7964
|
|
|
7928
7965
|
```ts
|
|
7929
7966
|
import { simulateCapture } from "@photivra/engine";
|
|
@@ -7937,6 +7974,21 @@ const blurred = await simulateCapture({
|
|
|
7937
7974
|
console.log(blurred.assumptions.find((a) => a.id === "lens.defocus")?.value); // diameterSites ≈ 5.95, kernelRadiusSites 3
|
|
7938
7975
|
```
|
|
7939
7976
|
|
|
7977
|
+
Bokeh character (#553): `lens.defocus` also takes `blades` (a polygonal or rounded-blade opening), `sphericalAberration`
|
|
7978
|
+
(primary W040: a soft-edged blur on one side of the focus plane and a bright-rimmed one on the other) and `catsEye`
|
|
7979
|
+
(mechanical vignetting by field position; `simulateCapture` only). Each is off by default. `calculateDefocusBokehKernel`
|
|
7980
|
+
returns the kernel without rendering. See [Bokeh character](QUICK_START.md#bokeh-character-553).
|
|
7981
|
+
|
|
7982
|
+
```ts
|
|
7983
|
+
import { calculateDefocusBokehKernel } from "@photivra/engine";
|
|
7984
|
+
|
|
7985
|
+
const kernel = calculateDefocusBokehKernel({
|
|
7986
|
+
focalLengthMm: 50, aperture: 4, focusDistanceM: 1.4, subjectDistanceM: 2, pixelPitchMicrometers: 6,
|
|
7987
|
+
bokeh: { blades: { count: 5 }, sphericalAberration: { w040Micrometers: 0.5, atFNumber: 4 } }
|
|
7988
|
+
});
|
|
7989
|
+
console.log(kernel.value.side, kernel.value.kernelRadiusSites, kernel.value.method); // "behind-focus", 17, "pupil-ray-binning"
|
|
7990
|
+
```
|
|
7991
|
+
|
|
7940
7992
|
`motion.angularVelocityRadPerSec` rotates the camera during the exposure (8 time midpoints by
|
|
7941
7993
|
default). `motion.shutter: "rolling"` derives each site's exposure window from the body's rolling
|
|
7942
7994
|
readout, with `resolveElectronicRollingShutterExposureSchedule`. See [Motion](QUICK_START.md#motion).
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.17.0" as const
|
|
15
15
|
```
|