@photivra/engine 2.13.0 → 2.14.1
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 +75 -1
- package/CITATION.cff +1 -1
- package/NOTICE +5 -4
- package/README.md +10 -8
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +16 -10
- package/dist/camera/lens-projection.js +2 -1
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/capture/dng-import.js +1 -1
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/host-yield-cadence.js +1 -1
- package/dist/capture/host-yield-cadence.js.map +1 -1
- package/dist/capture/native-environment-optical-stage.js +1 -1
- package/dist/capture/native-environment-optical-stage.js.map +1 -1
- package/dist/capture/native-raw.js +1 -1
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.js +3 -3
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.js +1 -1
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/capture-color.js +3 -3
- package/dist/color/capture-color.js.map +1 -1
- package/dist/color/chromatic-adaptation.js +2 -2
- package/dist/color/chromatic-adaptation.js.map +1 -1
- package/dist/color/color-matching-functions.js +1 -1
- package/dist/color/color-matching-functions.js.map +1 -1
- package/dist/color/colorimetry-math.js +2 -2
- package/dist/color/colorimetry-math.js.map +1 -1
- package/dist/color/illuminants.js +3 -3
- package/dist/color/illuminants.js.map +1 -1
- package/dist/color/rgb-color-spaces.js +1 -1
- package/dist/color/rgb-color-spaces.js.map +1 -1
- package/dist/color/sensor-color-development.js +1 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/core/record-validation.js +3 -3
- package/dist/core/record-validation.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-prime-lenses.js +3 -3
- package/dist/equipment/generic-prime-lenses.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +23 -1
- package/dist/equipment/generic-tier-assets.js +58 -30
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.d.ts +6 -3
- package/dist/equipment/generic-tier-presets.js +26 -13
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/exposure/auto-iso-slowest-shutter.js +2 -1
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
- package/dist/exposure/flash-ttl.js +1 -1
- package/dist/exposure/flash-ttl.js.map +1 -1
- package/dist/exposure/flash.js +1 -1
- package/dist/exposure/flash.js.map +1 -1
- package/dist/facade/chart-material-layers.js +2 -2
- package/dist/facade/chart-material-layers.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +1 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +45 -8
- package/dist/facade/chart-scenes.js +101 -44
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/color-patches.d.ts +129 -0
- package/dist/facade/color-patches.js +209 -0
- package/dist/facade/color-patches.js.map +1 -0
- package/dist/facade/display-view-gpu.js +1 -1
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/scene-radiance.js +2 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +47 -3
- package/dist/facade/simulate-capture.js +71 -22
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -2
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.js +2 -2
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.js +1 -1
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/image-kernel-execution.js +2 -2
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/temporal-sum-gpu.js +23 -23
- package/dist/gpu/webgpu-runtime.js +1 -1
- package/dist/gpu/webgpu-runtime.js.map +1 -1
- package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
- package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
- package/dist/gpu/wgsl-chart-kernel.js +117 -117
- package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.js +505 -505
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-kernels.js +203 -203
- package/dist/gpu/wgsl-kernels.js.map +1 -1
- package/dist/gpu/wgsl-sensor-kernel.js +302 -302
- package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.js +1 -1
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.js +4 -4
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/front-of-lens-filter.js +1 -1
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/irradiance-convolution.js +1 -1
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.js +1 -1
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.js +2 -2
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-contrast-reference.js +2 -2
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-jpeg.js +1 -1
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/sensor/fixed-pattern-noise.js +1 -1
- package/dist/sensor/fixed-pattern-noise.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.js +1 -1
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/docs/API_REFERENCE.md +29 -1
- package/docs/QUICK_START.md +40 -5
- package/docs/USAGE.md +35 -1
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-tier-assets.md +41 -1
- package/docs/api/equipment-generic-tier-presets.md +11 -3
- package/docs/api/exports.json +107 -2
- package/docs/api/facade-chart-scenes.md +36 -5
- package/docs/api/facade-color-patches.md +150 -0
- package/docs/api/facade-simulate-capture.md +52 -2
- package/llms.txt +1 -0
- package/package.json +2 -2
|
@@ -30,7 +30,7 @@ import { InvalidScientificInputError } from "../core/validation.js";
|
|
|
30
30
|
import { createDeterministicUniformStream, deriveSensorFixedPatternSeedUint32, sampleDeterministicStandardNormal, SENSOR_FIXED_PATTERN_COLUMN_STREAM_ID, SENSOR_FIXED_PATTERN_DSNU_STREAM_ID, SENSOR_FIXED_PATTERN_HOT_PIXEL_STREAM_ID, SENSOR_FIXED_PATTERN_PRNU_STREAM_ID, SENSOR_FIXED_PATTERN_ROW_STREAM_ID } from "./stochastic-seed.js";
|
|
31
31
|
export const SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION = "0.1.0";
|
|
32
32
|
/** Versioned identity of the pattern-generation equations and seed schedule. */
|
|
33
|
-
export const SENSOR_FIXED_PATTERN_NOISE_MODEL = Object.freeze({ id: "photivra-sensor-fixed-pattern", version: "1.0.0" });
|
|
33
|
+
export const SENSOR_FIXED_PATTERN_NOISE_MODEL = /* @__PURE__ */ Object.freeze({ id: "photivra-sensor-fixed-pattern", version: "1.0.0" });
|
|
34
34
|
const FACT_KEYS = ["value", "evidence"];
|
|
35
35
|
function fact(value, path, maximum, minimum = 0) {
|
|
36
36
|
const record = requireAllowlistedRecord(value, FACT_KEYS, path + " must be an evidence-backed fact.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixed-pattern-noise.js","sourceRoot":"","sources":["../../src/sensor/fixed-pattern-noise.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fixed-pattern noise (FPN): the part of sensor noise that stays the same from shot to shot for one\n * physical sensor. It covers photo-response non-uniformity (PRNU), dark-signal non-uniformity (DSNU),\n * hot pixels and row and column readout offsets, as defined in EMVA Standard 1288 Release 4.0 (spatial\n * non-uniformity of the photo response and of the dark signal).\n *\n * The pattern is deterministic. It is seeded from an explicit sensor-instance seed and absolute\n * colour-sampling site coordinates, never from the per-capture noise seed, so the same sensor shows\n * the same pattern in every capture.\n *\n * Application points in the readout chain (see raw-readout.ts):\n * - PRNU multiplies the expected photo charge before photo shot-noise sampling;\n * - DSNU and hot pixels multiply the population-mean dark-current expectation before dark shot-noise\n * sampling, so their excess charge scales with exposure duration and with the declared\n * temperature model of the dark-current profile;\n * - row and column offsets are input-referred electron offsets added with read noise, before the\n * pre-ADC threshold.\n *\n * The dark-current model itself stays a population mean; this module is a separate spatial model that\n * must be attached explicitly. Values are declared by the caller (or are synthetic preset values).\n * Nothing here is a measured calibration.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { parseEvidenceList, type EvidenceBackedFact, type EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n createDeterministicUniformStream,\n deriveSensorFixedPatternSeedUint32,\n sampleDeterministicStandardNormal,\n SENSOR_FIXED_PATTERN_COLUMN_STREAM_ID,\n SENSOR_FIXED_PATTERN_DSNU_STREAM_ID,\n SENSOR_FIXED_PATTERN_HOT_PIXEL_STREAM_ID,\n SENSOR_FIXED_PATTERN_PRNU_STREAM_ID,\n SENSOR_FIXED_PATTERN_ROW_STREAM_ID\n} from \"./stochastic-seed.js\";\n\nexport const SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION = \"0.1.0\" as const;\n/** Versioned identity of the pattern-generation equations and seed schedule. */\nexport const SENSOR_FIXED_PATTERN_NOISE_MODEL = Object.freeze({ id: \"photivra-sensor-fixed-pattern\", version: \"1.0.0\" } as const);\n\n/**\n * Explicit fixed-pattern parameters for one sensor instance. Every component is required; a zero\n * standard deviation, fraction or RMS disables that component.\n */\nexport interface SensorFixedPatternNoiseProfile {\n schemaVersion: typeof SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n /** The colour-sampling grid whose absolute site coordinates index the pattern. */\n colorSamplingProfileId: string;\n scientificStatus: \"approximation\";\n /** Identifies the physical sensor. Independent of every per-capture noise seed. */\n sensorInstanceSeedUint32: number;\n /** EMVA 1288 PRNU: spatial standard deviation of the photo response divided by its mean. */\n photoResponseNonUniformity: { relativeStandardDeviation: EvidenceBackedFact<number> };\n /** Spatial standard deviation of the dark-current rate divided by the population-mean rate. */\n darkSignalNonUniformity: { relativeStandardDeviation: EvidenceBackedFact<number> };\n /** Fraction of sites whose dark-current rate is the population mean times the multiplier. */\n hotPixels: { siteFraction: EvidenceBackedFact<number>; darkCurrentMultiplier: EvidenceBackedFact<number> };\n /** Fixed readout offset per row, in input-referred electrons, normally distributed with zero mean. */\n rowOffset: { rmsElectrons: EvidenceBackedFact<number> };\n /** Fixed readout offset per column, in input-referred electrons, normally distributed with zero mean. */\n columnOffset: { rmsElectrons: EvidenceBackedFact<number> };\n patternDistribution: \"normal-factors-truncated-at-zero\";\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\n/** The deterministic pattern values of one colour-sampling site. */\nexport interface SensorFixedPatternSiteValues {\n profileId: string;\n modelId: typeof SENSOR_FIXED_PATTERN_NOISE_MODEL.id;\n modelVersion: typeof SENSOR_FIXED_PATTERN_NOISE_MODEL.version;\n site: { x: number; y: number };\n /** Multiplies the expected photo charge (1 = no PRNU); never negative. */\n photoResponseGainFactor: number;\n /** Multiplies the population-mean dark expectation (1 = no DSNU); never negative. */\n darkCurrentFactor: number;\n hotPixel: boolean;\n rowOffsetElectrons: number;\n columnOffsetElectrons: number;\n /** rowOffsetElectrons + columnOffsetElectrons, added at readout. */\n readoutOffsetElectrons: number;\n}\n\nconst FACT_KEYS = [\"value\", \"evidence\"];\n\nfunction fact(value: unknown, path: string, maximum: number, minimum = 0): EvidenceBackedFact<number> {\n const record = requireAllowlistedRecord(value, FACT_KEYS, path + \" must be an evidence-backed fact.\");\n const v = record.value;\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < minimum || v > maximum) {\n throw new InvalidConfigurationError(path + \".value must be finite from \" + minimum + \" through \" + maximum + \".\");\n }\n return { value: v, evidence: parseEvidenceList(record.evidence, path + \".evidence\") };\n}\n\nfunction limitations(value: unknown): readonly string[] {\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\" || entry.trim().length === 0)) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.limitations must be an array of non-empty strings.\");\n }\n if (new Set(value).size !== value.length) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.limitations must not contain duplicates.\");\n }\n return value.map((entry: string) => entry.trim());\n}\n\n/**\n * Validates an untrusted fixed-pattern noise profile. Unknown keys, out-of-range parameters and\n * missing evidence are rejected. The PRNU and DSNU relative standard deviations are limited to 0.5\n * and 5, and the hot-pixel multiplier to 1–1e6.\n * @param value - Untrusted data. TypeScript types alone are not validation.\n * @returns The validated profile.\n * @see docs/MOTION_AND_SIGNAL.md for the model and its sources.\n */\nexport function parseSensorFixedPatternNoiseProfile(value: unknown): SensorFixedPatternNoiseProfile {\n const r = requireAllowlistedRecord(value, [\"schemaVersion\", \"profileId\", \"colorSamplingProfileId\", \"scientificStatus\",\n \"sensorInstanceSeedUint32\", \"photoResponseNonUniformity\", \"darkSignalNonUniformity\", \"hotPixels\", \"rowOffset\", \"columnOffset\",\n \"patternDistribution\", \"evidence\", \"limitations\"], \"Invalid sensor fixed-pattern noise profile fields.\");\n if (r.schemaVersion !== SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION) {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.schemaVersion must be \"' + SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION + '\".');\n }\n if (r.scientificStatus !== \"approximation\") {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.scientificStatus must be \"approximation\".');\n }\n if (r.patternDistribution !== \"normal-factors-truncated-at-zero\") {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.patternDistribution must be \"normal-factors-truncated-at-zero\".');\n }\n const seed = r.sensorInstanceSeedUint32;\n if (typeof seed !== \"number\" || !Number.isSafeInteger(seed) || seed < 0 || seed > 0xffff_ffff) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.sensorInstanceSeedUint32 must be an unsigned 32-bit integer.\");\n }\n const prnu = requireAllowlistedRecord(r.photoResponseNonUniformity, [\"relativeStandardDeviation\"], \"Invalid PRNU fields.\");\n const dsnu = requireAllowlistedRecord(r.darkSignalNonUniformity, [\"relativeStandardDeviation\"], \"Invalid DSNU fields.\");\n const hot = requireAllowlistedRecord(r.hotPixels, [\"siteFraction\", \"darkCurrentMultiplier\"], \"Invalid hot-pixel fields.\");\n const row = requireAllowlistedRecord(r.rowOffset, [\"rmsElectrons\"], \"Invalid row-offset fields.\");\n const column = requireAllowlistedRecord(r.columnOffset, [\"rmsElectrons\"], \"Invalid column-offset fields.\");\n return {\n schemaVersion: SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION,\n profileId: requirePublicOpaqueId(r.profileId, \"sensorFixedPatternNoise.profileId must be a public opaque identifier.\"),\n colorSamplingProfileId: requirePublicOpaqueId(r.colorSamplingProfileId, \"sensorFixedPatternNoise.colorSamplingProfileId must be a public opaque identifier.\"),\n scientificStatus: \"approximation\",\n sensorInstanceSeedUint32: seed,\n photoResponseNonUniformity: { relativeStandardDeviation: fact(prnu.relativeStandardDeviation, \"photoResponseNonUniformity.relativeStandardDeviation\", 0.5) },\n darkSignalNonUniformity: { relativeStandardDeviation: fact(dsnu.relativeStandardDeviation, \"darkSignalNonUniformity.relativeStandardDeviation\", 5) },\n hotPixels: { siteFraction: fact(hot.siteFraction, \"hotPixels.siteFraction\", 1),\n darkCurrentMultiplier: fact(hot.darkCurrentMultiplier, \"hotPixels.darkCurrentMultiplier\", 1e6, 1) },\n rowOffset: { rmsElectrons: fact(row.rmsElectrons, \"rowOffset.rmsElectrons\", 1e6) },\n columnOffset: { rmsElectrons: fact(column.rmsElectrons, \"columnOffset.rmsElectrons\", 1e6) },\n patternDistribution: \"normal-factors-truncated-at-zero\",\n evidence: parseEvidenceList(r.evidence, \"sensorFixedPatternNoise.evidence\"),\n limitations: limitations(r.limitations)\n };\n}\n\nfunction normal(seed: number, x: number, y: number, stream: number): number {\n return sampleDeterministicStandardNormal(createDeterministicUniformStream(deriveSensorFixedPatternSeedUint32(seed, x, y, stream)));\n}\n\n/**\n * Computes the fixed-pattern values of one absolute colour-sampling site.\n *\n * - PRNU gain g = max(0, 1 + σ_PRNU·z₁);\n * - hot pixel when u < f_hot, then dark factor d = m_hot; otherwise d = max(0, 1 + σ_DSNU·z₂);\n * - row offset σ_row·z(row y), column offset σ_col·z(column x), in input-referred electrons.\n *\n * Each z is a standard normal value and u a uniform value, each from its own stream hashed with\n * MurmurHash3 from (sensorInstanceSeed, x, y, stream). Row values use x = 0 and column values y = 0,\n * so a whole row or column shares one value. Cutting negative factors off at zero shifts the mean\n * factor by less than 1e-6 for σ ≤ 0.2.\n * @param input - A parsed or untrusted profile plus a nonnegative integer site.\n * @returns The site's fixed-pattern values.\n */\nexport function calculateSensorFixedPatternSite(input: {\n profile: SensorFixedPatternNoiseProfile; site: { x: number; y: number };\n}): SensorFixedPatternSiteValues {\n requireInputKeys(input, \"calculateSensorFixedPatternSite\", [\"profile\", \"site\"]);\n const profile = parseSensorFixedPatternNoiseProfile(input.profile);\n return evaluateParsedFixedPatternSite(profile, input.site);\n}\n\n/** Internal: the PRNU gain, dark-current factor and hot-pixel state of one site (no readout offsets). */\nexport function evaluateFixedPatternSiteFactors(profile: SensorFixedPatternNoiseProfile, x: number, y: number):\n { photoResponseGainFactor: number; darkCurrentFactor: number; hotPixel: boolean } {\n const seed = profile.sensorInstanceSeedUint32;\n const prnuSd = profile.photoResponseNonUniformity.relativeStandardDeviation.value;\n const dsnuSd = profile.darkSignalNonUniformity.relativeStandardDeviation.value;\n const hotFraction = profile.hotPixels.siteFraction.value;\n const photoResponseGainFactor = prnuSd === 0 ? 1 : Math.max(0, 1 + prnuSd * normal(seed, x, y, SENSOR_FIXED_PATTERN_PRNU_STREAM_ID));\n const hotPixel = hotFraction > 0 &&\n createDeterministicUniformStream(deriveSensorFixedPatternSeedUint32(seed, x, y, SENSOR_FIXED_PATTERN_HOT_PIXEL_STREAM_ID)).next() < hotFraction;\n const darkCurrentFactor = hotPixel ? profile.hotPixels.darkCurrentMultiplier.value :\n dsnuSd === 0 ? 1 : Math.max(0, 1 + dsnuSd * normal(seed, x, y, SENSOR_FIXED_PATTERN_DSNU_STREAM_ID));\n return { photoResponseGainFactor, darkCurrentFactor, hotPixel };\n}\n\n/** Internal: the fixed readout offset of row `y`, in input-referred electrons. */\nexport function evaluateFixedPatternRowOffset(profile: SensorFixedPatternNoiseProfile, y: number): number {\n const rms = profile.rowOffset.rmsElectrons.value;\n return rms === 0 ? 0 : rms * normal(profile.sensorInstanceSeedUint32, 0, y, SENSOR_FIXED_PATTERN_ROW_STREAM_ID);\n}\n\n/** Internal: the fixed readout offset of column `x`, in input-referred electrons. */\nexport function evaluateFixedPatternColumnOffset(profile: SensorFixedPatternNoiseProfile, x: number): number {\n const rms = profile.columnOffset.rmsElectrons.value;\n return rms === 0 ? 0 : rms * normal(profile.sensorInstanceSeedUint32, x, 0, SENSOR_FIXED_PATTERN_COLUMN_STREAM_ID);\n}\n\n/** Internal: the same calculation for an already parsed profile. */\nexport function evaluateParsedFixedPatternSite(profile: SensorFixedPatternNoiseProfile,\n site: { x: number; y: number }): SensorFixedPatternSiteValues {\n const { x, y } = site ?? {};\n if (!Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 || x > 0xffff_ffff || y > 0xffff_ffff) {\n throw new InvalidScientificInputError(\"Fixed-pattern site coordinates must be nonnegative uint32 integers.\");\n }\n const { photoResponseGainFactor, darkCurrentFactor, hotPixel } = evaluateFixedPatternSiteFactors(profile, x, y);\n const rowOffsetElectrons = evaluateFixedPatternRowOffset(profile, y);\n const columnOffsetElectrons = evaluateFixedPatternColumnOffset(profile, x);\n return {\n profileId: profile.profileId,\n modelId: SENSOR_FIXED_PATTERN_NOISE_MODEL.id,\n modelVersion: SENSOR_FIXED_PATTERN_NOISE_MODEL.version,\n site: { x, y },\n photoResponseGainFactor,\n darkCurrentFactor,\n hotPixel,\n rowOffsetElectrons,\n columnOffsetElectrons,\n readoutOffsetElectrons: rowOffsetElectrons + columnOffsetElectrons\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fixed-pattern-noise.js","sourceRoot":"","sources":["../../src/sensor/fixed-pattern-noise.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fixed-pattern noise (FPN): the part of sensor noise that stays the same from shot to shot for one\n * physical sensor. It covers photo-response non-uniformity (PRNU), dark-signal non-uniformity (DSNU),\n * hot pixels and row and column readout offsets, as defined in EMVA Standard 1288 Release 4.0 (spatial\n * non-uniformity of the photo response and of the dark signal).\n *\n * The pattern is deterministic. It is seeded from an explicit sensor-instance seed and absolute\n * colour-sampling site coordinates, never from the per-capture noise seed, so the same sensor shows\n * the same pattern in every capture.\n *\n * Application points in the readout chain (see raw-readout.ts):\n * - PRNU multiplies the expected photo charge before photo shot-noise sampling;\n * - DSNU and hot pixels multiply the population-mean dark-current expectation before dark shot-noise\n * sampling, so their excess charge scales with exposure duration and with the declared\n * temperature model of the dark-current profile;\n * - row and column offsets are input-referred electron offsets added with read noise, before the\n * pre-ADC threshold.\n *\n * The dark-current model itself stays a population mean; this module is a separate spatial model that\n * must be attached explicitly. Values are declared by the caller (or are synthetic preset values).\n * Nothing here is a measured calibration.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { parseEvidenceList, type EvidenceBackedFact, type EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n createDeterministicUniformStream,\n deriveSensorFixedPatternSeedUint32,\n sampleDeterministicStandardNormal,\n SENSOR_FIXED_PATTERN_COLUMN_STREAM_ID,\n SENSOR_FIXED_PATTERN_DSNU_STREAM_ID,\n SENSOR_FIXED_PATTERN_HOT_PIXEL_STREAM_ID,\n SENSOR_FIXED_PATTERN_PRNU_STREAM_ID,\n SENSOR_FIXED_PATTERN_ROW_STREAM_ID\n} from \"./stochastic-seed.js\";\n\nexport const SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION = \"0.1.0\" as const;\n/** Versioned identity of the pattern-generation equations and seed schedule. */\nexport const SENSOR_FIXED_PATTERN_NOISE_MODEL = /* @__PURE__ */ Object.freeze({ id: \"photivra-sensor-fixed-pattern\", version: \"1.0.0\" } as const);\n\n/**\n * Explicit fixed-pattern parameters for one sensor instance. Every component is required; a zero\n * standard deviation, fraction or RMS disables that component.\n */\nexport interface SensorFixedPatternNoiseProfile {\n schemaVersion: typeof SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n /** The colour-sampling grid whose absolute site coordinates index the pattern. */\n colorSamplingProfileId: string;\n scientificStatus: \"approximation\";\n /** Identifies the physical sensor. Independent of every per-capture noise seed. */\n sensorInstanceSeedUint32: number;\n /** EMVA 1288 PRNU: spatial standard deviation of the photo response divided by its mean. */\n photoResponseNonUniformity: { relativeStandardDeviation: EvidenceBackedFact<number> };\n /** Spatial standard deviation of the dark-current rate divided by the population-mean rate. */\n darkSignalNonUniformity: { relativeStandardDeviation: EvidenceBackedFact<number> };\n /** Fraction of sites whose dark-current rate is the population mean times the multiplier. */\n hotPixels: { siteFraction: EvidenceBackedFact<number>; darkCurrentMultiplier: EvidenceBackedFact<number> };\n /** Fixed readout offset per row, in input-referred electrons, normally distributed with zero mean. */\n rowOffset: { rmsElectrons: EvidenceBackedFact<number> };\n /** Fixed readout offset per column, in input-referred electrons, normally distributed with zero mean. */\n columnOffset: { rmsElectrons: EvidenceBackedFact<number> };\n patternDistribution: \"normal-factors-truncated-at-zero\";\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\n/** The deterministic pattern values of one colour-sampling site. */\nexport interface SensorFixedPatternSiteValues {\n profileId: string;\n modelId: typeof SENSOR_FIXED_PATTERN_NOISE_MODEL.id;\n modelVersion: typeof SENSOR_FIXED_PATTERN_NOISE_MODEL.version;\n site: { x: number; y: number };\n /** Multiplies the expected photo charge (1 = no PRNU); never negative. */\n photoResponseGainFactor: number;\n /** Multiplies the population-mean dark expectation (1 = no DSNU); never negative. */\n darkCurrentFactor: number;\n hotPixel: boolean;\n rowOffsetElectrons: number;\n columnOffsetElectrons: number;\n /** rowOffsetElectrons + columnOffsetElectrons, added at readout. */\n readoutOffsetElectrons: number;\n}\n\nconst FACT_KEYS = [\"value\", \"evidence\"];\n\nfunction fact(value: unknown, path: string, maximum: number, minimum = 0): EvidenceBackedFact<number> {\n const record = requireAllowlistedRecord(value, FACT_KEYS, path + \" must be an evidence-backed fact.\");\n const v = record.value;\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < minimum || v > maximum) {\n throw new InvalidConfigurationError(path + \".value must be finite from \" + minimum + \" through \" + maximum + \".\");\n }\n return { value: v, evidence: parseEvidenceList(record.evidence, path + \".evidence\") };\n}\n\nfunction limitations(value: unknown): readonly string[] {\n if (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\" || entry.trim().length === 0)) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.limitations must be an array of non-empty strings.\");\n }\n if (new Set(value).size !== value.length) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.limitations must not contain duplicates.\");\n }\n return value.map((entry: string) => entry.trim());\n}\n\n/**\n * Validates an untrusted fixed-pattern noise profile. Unknown keys, out-of-range parameters and\n * missing evidence are rejected. The PRNU and DSNU relative standard deviations are limited to 0.5\n * and 5, and the hot-pixel multiplier to 1–1e6.\n * @param value - Untrusted data. TypeScript types alone are not validation.\n * @returns The validated profile.\n * @see docs/MOTION_AND_SIGNAL.md for the model and its sources.\n */\nexport function parseSensorFixedPatternNoiseProfile(value: unknown): SensorFixedPatternNoiseProfile {\n const r = requireAllowlistedRecord(value, [\"schemaVersion\", \"profileId\", \"colorSamplingProfileId\", \"scientificStatus\",\n \"sensorInstanceSeedUint32\", \"photoResponseNonUniformity\", \"darkSignalNonUniformity\", \"hotPixels\", \"rowOffset\", \"columnOffset\",\n \"patternDistribution\", \"evidence\", \"limitations\"], \"Invalid sensor fixed-pattern noise profile fields.\");\n if (r.schemaVersion !== SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION) {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.schemaVersion must be \"' + SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION + '\".');\n }\n if (r.scientificStatus !== \"approximation\") {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.scientificStatus must be \"approximation\".');\n }\n if (r.patternDistribution !== \"normal-factors-truncated-at-zero\") {\n throw new InvalidConfigurationError('sensorFixedPatternNoise.patternDistribution must be \"normal-factors-truncated-at-zero\".');\n }\n const seed = r.sensorInstanceSeedUint32;\n if (typeof seed !== \"number\" || !Number.isSafeInteger(seed) || seed < 0 || seed > 0xffff_ffff) {\n throw new InvalidConfigurationError(\"sensorFixedPatternNoise.sensorInstanceSeedUint32 must be an unsigned 32-bit integer.\");\n }\n const prnu = requireAllowlistedRecord(r.photoResponseNonUniformity, [\"relativeStandardDeviation\"], \"Invalid PRNU fields.\");\n const dsnu = requireAllowlistedRecord(r.darkSignalNonUniformity, [\"relativeStandardDeviation\"], \"Invalid DSNU fields.\");\n const hot = requireAllowlistedRecord(r.hotPixels, [\"siteFraction\", \"darkCurrentMultiplier\"], \"Invalid hot-pixel fields.\");\n const row = requireAllowlistedRecord(r.rowOffset, [\"rmsElectrons\"], \"Invalid row-offset fields.\");\n const column = requireAllowlistedRecord(r.columnOffset, [\"rmsElectrons\"], \"Invalid column-offset fields.\");\n return {\n schemaVersion: SENSOR_FIXED_PATTERN_NOISE_PROFILE_SCHEMA_VERSION,\n profileId: requirePublicOpaqueId(r.profileId, \"sensorFixedPatternNoise.profileId must be a public opaque identifier.\"),\n colorSamplingProfileId: requirePublicOpaqueId(r.colorSamplingProfileId, \"sensorFixedPatternNoise.colorSamplingProfileId must be a public opaque identifier.\"),\n scientificStatus: \"approximation\",\n sensorInstanceSeedUint32: seed,\n photoResponseNonUniformity: { relativeStandardDeviation: fact(prnu.relativeStandardDeviation, \"photoResponseNonUniformity.relativeStandardDeviation\", 0.5) },\n darkSignalNonUniformity: { relativeStandardDeviation: fact(dsnu.relativeStandardDeviation, \"darkSignalNonUniformity.relativeStandardDeviation\", 5) },\n hotPixels: { siteFraction: fact(hot.siteFraction, \"hotPixels.siteFraction\", 1),\n darkCurrentMultiplier: fact(hot.darkCurrentMultiplier, \"hotPixels.darkCurrentMultiplier\", 1e6, 1) },\n rowOffset: { rmsElectrons: fact(row.rmsElectrons, \"rowOffset.rmsElectrons\", 1e6) },\n columnOffset: { rmsElectrons: fact(column.rmsElectrons, \"columnOffset.rmsElectrons\", 1e6) },\n patternDistribution: \"normal-factors-truncated-at-zero\",\n evidence: parseEvidenceList(r.evidence, \"sensorFixedPatternNoise.evidence\"),\n limitations: limitations(r.limitations)\n };\n}\n\nfunction normal(seed: number, x: number, y: number, stream: number): number {\n return sampleDeterministicStandardNormal(createDeterministicUniformStream(deriveSensorFixedPatternSeedUint32(seed, x, y, stream)));\n}\n\n/**\n * Computes the fixed-pattern values of one absolute colour-sampling site.\n *\n * - PRNU gain g = max(0, 1 + σ_PRNU·z₁);\n * - hot pixel when u < f_hot, then dark factor d = m_hot; otherwise d = max(0, 1 + σ_DSNU·z₂);\n * - row offset σ_row·z(row y), column offset σ_col·z(column x), in input-referred electrons.\n *\n * Each z is a standard normal value and u a uniform value, each from its own stream hashed with\n * MurmurHash3 from (sensorInstanceSeed, x, y, stream). Row values use x = 0 and column values y = 0,\n * so a whole row or column shares one value. Cutting negative factors off at zero shifts the mean\n * factor by less than 1e-6 for σ ≤ 0.2.\n * @param input - A parsed or untrusted profile plus a nonnegative integer site.\n * @returns The site's fixed-pattern values.\n */\nexport function calculateSensorFixedPatternSite(input: {\n profile: SensorFixedPatternNoiseProfile; site: { x: number; y: number };\n}): SensorFixedPatternSiteValues {\n requireInputKeys(input, \"calculateSensorFixedPatternSite\", [\"profile\", \"site\"]);\n const profile = parseSensorFixedPatternNoiseProfile(input.profile);\n return evaluateParsedFixedPatternSite(profile, input.site);\n}\n\n/** Internal: the PRNU gain, dark-current factor and hot-pixel state of one site (no readout offsets). */\nexport function evaluateFixedPatternSiteFactors(profile: SensorFixedPatternNoiseProfile, x: number, y: number):\n { photoResponseGainFactor: number; darkCurrentFactor: number; hotPixel: boolean } {\n const seed = profile.sensorInstanceSeedUint32;\n const prnuSd = profile.photoResponseNonUniformity.relativeStandardDeviation.value;\n const dsnuSd = profile.darkSignalNonUniformity.relativeStandardDeviation.value;\n const hotFraction = profile.hotPixels.siteFraction.value;\n const photoResponseGainFactor = prnuSd === 0 ? 1 : Math.max(0, 1 + prnuSd * normal(seed, x, y, SENSOR_FIXED_PATTERN_PRNU_STREAM_ID));\n const hotPixel = hotFraction > 0 &&\n createDeterministicUniformStream(deriveSensorFixedPatternSeedUint32(seed, x, y, SENSOR_FIXED_PATTERN_HOT_PIXEL_STREAM_ID)).next() < hotFraction;\n const darkCurrentFactor = hotPixel ? profile.hotPixels.darkCurrentMultiplier.value :\n dsnuSd === 0 ? 1 : Math.max(0, 1 + dsnuSd * normal(seed, x, y, SENSOR_FIXED_PATTERN_DSNU_STREAM_ID));\n return { photoResponseGainFactor, darkCurrentFactor, hotPixel };\n}\n\n/** Internal: the fixed readout offset of row `y`, in input-referred electrons. */\nexport function evaluateFixedPatternRowOffset(profile: SensorFixedPatternNoiseProfile, y: number): number {\n const rms = profile.rowOffset.rmsElectrons.value;\n return rms === 0 ? 0 : rms * normal(profile.sensorInstanceSeedUint32, 0, y, SENSOR_FIXED_PATTERN_ROW_STREAM_ID);\n}\n\n/** Internal: the fixed readout offset of column `x`, in input-referred electrons. */\nexport function evaluateFixedPatternColumnOffset(profile: SensorFixedPatternNoiseProfile, x: number): number {\n const rms = profile.columnOffset.rmsElectrons.value;\n return rms === 0 ? 0 : rms * normal(profile.sensorInstanceSeedUint32, x, 0, SENSOR_FIXED_PATTERN_COLUMN_STREAM_ID);\n}\n\n/** Internal: the same calculation for an already parsed profile. */\nexport function evaluateParsedFixedPatternSite(profile: SensorFixedPatternNoiseProfile,\n site: { x: number; y: number }): SensorFixedPatternSiteValues {\n const { x, y } = site ?? {};\n if (!Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 || x > 0xffff_ffff || y > 0xffff_ffff) {\n throw new InvalidScientificInputError(\"Fixed-pattern site coordinates must be nonnegative uint32 integers.\");\n }\n const { photoResponseGainFactor, darkCurrentFactor, hotPixel } = evaluateFixedPatternSiteFactors(profile, x, y);\n const rowOffsetElectrons = evaluateFixedPatternRowOffset(profile, y);\n const columnOffsetElectrons = evaluateFixedPatternColumnOffset(profile, x);\n return {\n profileId: profile.profileId,\n modelId: SENSOR_FIXED_PATTERN_NOISE_MODEL.id,\n modelVersion: SENSOR_FIXED_PATTERN_NOISE_MODEL.version,\n site: { x, y },\n photoResponseGainFactor,\n darkCurrentFactor,\n hotPixel,\n rowOffsetElectrons,\n columnOffsetElectrons,\n readoutOffsetElectrons: rowOffsetElectrons + columnOffsetElectrons\n };\n}\n"]}
|
|
@@ -223,7 +223,7 @@ export function assessReadoutExposureTimingLinkage(input) {
|
|
|
223
223
|
]);
|
|
224
224
|
}
|
|
225
225
|
/** Model identity of the electronic rolling-shutter (line reset → line read) exposure schedule. */
|
|
226
|
-
export const ELECTRONIC_ROLLING_SHUTTER_MODEL = Object.freeze({
|
|
226
|
+
export const ELECTRONIC_ROLLING_SHUTTER_MODEL = /* @__PURE__ */ Object.freeze({
|
|
227
227
|
id: "electronic-rolling-shutter-line-reset-read",
|
|
228
228
|
version: "1.0.0"
|
|
229
229
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readout-exposure-linkage.js","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Links rolling sensor readout to electronic shutter scans, and derives the exposure schedule of a\n * fully electronic rolling shutter.\n *\n * A declared link says that the readout scan and one or more exposure-boundary scans move across the\n * sensor in the same order (or in reverse). That is deliberately weaker than time synchronization: it\n * does not say when readout happens relative to the start or end of exposure. The total data-readout\n * time is kept for diagnostics but never used to check the link. The readout skew and the boundary\n * sweep times may differ; their ratio is reported without implying a shared clock.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n parseBoundarySchedule,\n parseSecondsFact,\n type CalculateCaptureExposureWindowsInput,\n type ExposureBoundarySchedule,\n type UniformLinearExposureBoundarySchedule,\n type SourcedCaptureTimingSeconds\n} from \"./exposure-window.js\";\nimport {\n calculateSensorReadoutTiming,\n type CaptureShutterMechanism,\n type NativeSensorReadoutScanDirection,\n type RollingSensorReadoutTimingDeclaration,\n type SensorReadoutTimingDeclaration,\n type SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport { requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type ReadoutExposureBoundaryId = \"opening\" | \"closing\";\n\nexport type ReadoutExposureSpatialPhaseOrientation =\n | \"same\"\n | \"reversed\";\n\nexport interface ReadoutExposureBoundarySpatialLink {\n boundary: ReadoutExposureBoundaryId;\n /**\n * How the normalized position of the rolling readout scan relates to that\n * of the selected electronic exposure-boundary scan.\n *\n * \"same\": boundary normalized phase = readout normalized phase.\n * \"reversed\": boundary normalized phase = 1 - readout normalized phase.\n *\n * This does not establish absolute temporal synchronization.\n */\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n /**\n * Evidence for the link itself.\n *\n * The readout direction and timing, and the exposure-boundary direction and\n * timing, keep their own separate evidence.\n */\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type ReadoutExposureTimingLinkageDeclaration =\n | {\n /**\n * No link between readout and exposure is stated for this capture.\n * This is not evidence that the physical processes are independent.\n */\n kind: \"unlinked\";\n links?: never;\n }\n | {\n /**\n * One or more exposure boundaries are explicitly linked to the rolling\n * readout by their normalized position across the native sensor.\n */\n kind: \"spatial-phase-linked\";\n links: readonly ReadoutExposureBoundarySpatialLink[];\n };\n\nexport interface AssessReadoutExposureTimingLinkageInput {\n nativeRaster: NativeImageRaster;\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n linkage: ReadoutExposureTimingLinkageDeclaration;\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ReadoutExposureBoundarySpatialLinkAssessment {\n boundary: ReadoutExposureBoundaryId;\n boundaryActuator: \"electronic\";\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n normalizedPhaseRelationship:\n | \"boundary-phase-equals-readout-phase\"\n | \"boundary-phase-equals-one-minus-readout-phase\";\n relationshipEvidence: readonly EvidenceProvenance[];\n readoutDirectionNative: NativeSensorReadoutScanDirection;\n boundaryDirectionNative: NativeSensorReadoutScanDirection;\n /**\n * Rolling readout skew: time from the first to the last scan position.\n */\n readoutSpatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n /**\n * Time for the exposure boundary to sweep from first to last position.\n */\n boundaryTraversalDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * A descriptive ratio only. A value of 1 does not prove time\n * synchronization or a common clock origin.\n */\n boundaryTraversalToReadoutSpatialSkewRatio: number;\n absoluteTemporalAlignment: \"not-established\";\n}\n\nexport interface ReadoutExposureTimingLinkageAssessment {\n linkageKind: ReadoutExposureTimingLinkageDeclaration[\"kind\"];\n relationshipMeaning:\n | \"no-readout-exposure-relationship-asserted\"\n | \"normalized-spatial-phase-relationship-asserted\";\n readoutMode: \"rolling\" | \"global\";\n shutterMechanism: CaptureShutterMechanism;\n activeCaptureRect: RasterRect;\n absoluteTemporalAlignment: \"not-established\";\n /**\n * Data-readout time for the capture. Kept for diagnostics, but never used to\n * check an exposure-boundary link.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n links: readonly ReadoutExposureBoundarySpatialLinkAssessment[];\n componentProvenance: {\n sensorReadout: CalculationProvenance;\n exposureWindows: CalculationProvenance;\n };\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nconst BOUNDARIES = new Set<ReadoutExposureBoundaryId>([\n \"opening\",\n \"closing\"\n]);\n\nconst PHASE_ORIENTATIONS =\n new Set<ReadoutExposureSpatialPhaseOrientation>([\n \"same\",\n \"reversed\"\n ]);\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction parseSpatialLink(\n value: unknown,\n path: string\n): ReadoutExposureBoundarySpatialLink {\n const record = requireRecord(value, path, [\"boundary\", \"phaseOrientation\", \"evidence\"]);\n\n if (\n typeof record.boundary !== \"string\" ||\n !BOUNDARIES.has(record.boundary as ReadoutExposureBoundaryId)\n ) {\n throw new InvalidConfigurationError(path + \".boundary is invalid.\");\n }\n\n if (\n typeof record.phaseOrientation !== \"string\" ||\n !PHASE_ORIENTATIONS.has(\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation\n )\n ) {\n throw new InvalidConfigurationError(\n path + \".phaseOrientation is invalid.\"\n );\n }\n\n return {\n boundary: record.boundary as ReadoutExposureBoundaryId,\n phaseOrientation:\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseLinkageDeclaration(\n value: unknown\n): ReadoutExposureTimingLinkageDeclaration {\n const record = requireRecord(value, \"linkage\", [\"kind\", \"links\"]);\n\n if (record.kind === \"unlinked\") {\n if (record.links !== undefined) {\n throw new InvalidScientificInputError(\n \"linkage.links must be omitted when linkage.kind is unlinked.\"\n );\n }\n return { kind: \"unlinked\" };\n }\n\n if (record.kind !== \"spatial-phase-linked\") {\n throw new InvalidConfigurationError(\"linkage.kind is invalid.\");\n }\n\n if (!Array.isArray(record.links) || record.links.length === 0) {\n throw new InvalidConfigurationError(\n \"linkage.links must be a non-empty array for spatial-phase-linked linkage.\"\n );\n }\n\n const links = record.links.map((entry, index) =>\n parseSpatialLink(entry, \"linkage.links[\" + index + \"]\")\n );\n\n const seen = new Set<ReadoutExposureBoundaryId>();\n for (const link of links) {\n if (seen.has(link.boundary)) {\n throw new InvalidScientificInputError(\n \"linkage.links must not contain duplicate boundary links.\"\n );\n }\n seen.add(link.boundary);\n }\n\n return {\n kind: \"spatial-phase-linked\",\n links\n };\n}\n\nfunction oppositeDirection(\n direction: NativeSensorReadoutScanDirection\n): NativeSensorReadoutScanDirection {\n switch (direction) {\n case \"top-to-bottom\":\n return \"bottom-to-top\";\n case \"bottom-to-top\":\n return \"top-to-bottom\";\n case \"left-to-right\":\n return \"right-to-left\";\n case \"right-to-left\":\n return \"left-to-right\";\n }\n}\n\nfunction expectedBoundaryDirection(\n readoutDirection: NativeSensorReadoutScanDirection,\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation\n): NativeSensorReadoutScanDirection {\n return phaseOrientation === \"same\"\n ? readoutDirection\n : oppositeDirection(readoutDirection);\n}\n\n/**\n * Checks an explicitly declared link between the rolling readout scan and one\n * or more electronic exposure-boundary scans.\n *\n * This is deliberately weaker than time synchronization. It can show that two\n * schedules sweep the native sensor in the same order (or in reverse), but not\n * when readout happens relative to the start or end of exposure.\n *\n * The total data-readout time is kept for diagnostics and never used to check\n * the link. The readout skew and the boundary sweep time may differ; their\n * ratio is reported without implying a shared clock.\n */\nexport function assessReadoutExposureTimingLinkage(\n input: AssessReadoutExposureTimingLinkageInput\n): CalculationResult<ReadoutExposureTimingLinkageAssessment> {\n requireInputKeys(input, \"assessReadoutExposureTimingLinkage\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"samplePointsNative\"]);\n const linkage = parseLinkageDeclaration(input.linkage);\n\n const sensorReadout = calculateSensorReadoutTiming({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n readout: input.readout,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n const exposureWindows = calculateCaptureExposureWindows({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n nominalExposureDurationSeconds:\n input.nominalExposureDurationSeconds,\n opening: input.opening,\n closing: input.closing,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n if (linkage.kind === \"unlinked\") {\n return approximationResult(\n {\n linkageKind: \"unlinked\",\n relationshipMeaning:\n \"no-readout-exposure-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: [],\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"Unlinked means Photivra asserts no readout-to-exposure relationship for this capture; it does not prove the physical processes are independent.\",\n \"Sensor data-readout timing and exposure-window timing retain independent provenance and semantics.\",\n \"Absolute temporal alignment between data readout and exposure boundaries is not established.\"\n ]\n );\n }\n\n const readoutScan = sensorReadout.value.scan;\n if (readoutScan === null) {\n throw new InvalidScientificInputError(\n \"spatial-phase-linked linkage requires rolling sensor readout with a spatial scan.\"\n );\n }\n\n const assessments =\n linkage.links.map<ReadoutExposureBoundarySpatialLinkAssessment>(\n (link) => {\n const boundary =\n link.boundary === \"opening\"\n ? exposureWindows.value.opening\n : exposureWindows.value.closing;\n\n if (boundary.actuator !== \"electronic\") {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link may target only an electronic exposure boundary.\"\n );\n }\n\n if (\n boundary.schedule.kind !==\n \"uniform-linear-native-scan\"\n ) {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link requires the selected exposure boundary to use a uniform-linear native scan.\"\n );\n }\n\n const readoutDirection =\n readoutScan.directionNative.value;\n const boundaryDirection =\n boundary.schedule.directionNative.value;\n const expectedDirection = expectedBoundaryDirection(\n readoutDirection,\n link.phaseOrientation\n );\n\n if (boundaryDirection !== expectedDirection) {\n throw new InvalidScientificInputError(\n \"Declared readout/exposure spatial phase orientation contradicts the readout and exposure-boundary scan directions.\"\n );\n }\n\n const readoutSpatialSamplingSkewSeconds =\n readoutScan.spatialSamplingSkewSeconds;\n const boundaryTraversalDurationSeconds =\n boundary.schedule.traversalDurationSeconds;\n\n return {\n boundary: link.boundary,\n boundaryActuator: \"electronic\",\n phaseOrientation: link.phaseOrientation,\n normalizedPhaseRelationship:\n link.phaseOrientation === \"same\"\n ? \"boundary-phase-equals-readout-phase\"\n : \"boundary-phase-equals-one-minus-readout-phase\",\n relationshipEvidence: link.evidence,\n readoutDirectionNative: readoutDirection,\n boundaryDirectionNative: boundaryDirection,\n readoutSpatialSamplingSkewSeconds,\n boundaryTraversalDurationSeconds,\n boundaryTraversalToReadoutSpatialSkewRatio:\n boundaryTraversalDurationSeconds.value /\n readoutSpatialSamplingSkewSeconds.value,\n absoluteTemporalAlignment: \"not-established\"\n };\n }\n );\n\n return approximationResult(\n {\n linkageKind: \"spatial-phase-linked\",\n relationshipMeaning:\n \"normalized-spatial-phase-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: assessments,\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"The linkage establishes normalized native-sensor spatial phase/order only, not absolute temporal synchronization.\",\n \"Every asserted boundary link carries evidence separate from the evidence for the underlying readout and exposure timing facts.\",\n \"Only electronic exposure boundaries may be linked to rolling sensor readout spatial phase.\",\n \"Same phase orientation requires matching native scan direction; reversed phase orientation requires the exact opposite direction on the same native axis.\",\n \"Rolling readout spatial skew and exposure-boundary traversal duration may differ; their ratio is descriptive and does not establish a common clock origin.\",\n \"Capture data-readout duration is not used to validate exposure-boundary timing linkage.\",\n \"Active capture, native coordinate semantics and shutter mechanism are shared because both component schedules are resolved from the same input.\",\n \"Sensor readout and exposure-window component provenance remain visible; linkage does not upgrade either component to calibrated status.\",\n \"No camera-specific timing relationship is inferred from shutter mechanism, sensor architecture metadata, output resolution or adjacent specifications.\"\n ]\n );\n}\n\n/** Model identity of the electronic rolling-shutter (line reset → line read) exposure schedule. */\nexport const ELECTRONIC_ROLLING_SHUTTER_MODEL = Object.freeze({\n id: \"electronic-rolling-shutter-line-reset-read\",\n version: \"1.0.0\"\n} as const);\n\nexport interface ResolveElectronicRollingShutterExposureScheduleInput {\n /** Rolling readout declaration whose scan direction and spatial skew drive both exposure boundaries. */\n readout: RollingSensorReadoutTimingDeclaration;\n /** Per-line integration time between line reset and line read. */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * Evidence that this capture uses a fully electronic, line-by-line (rolling) shutter whose reset\n * scan runs at the readout line rate. This explicit link is required before sensor readout timing\n * may set the exposure-window timing.\n */\n linkageEvidence: readonly EvidenceProvenance[];\n}\n\n/**\n * Exposure-window schedule of an electronic rolling shutter. Its `shutterMechanism`,\n * `nominalExposureDurationSeconds`, `opening` and `closing` fields form a complete exposure schedule, so\n * they can be passed as a native RAW / environment `exposureWindow` or spread into a production\n * `temporalCapture.exposureWindowInput` (see {@link createRollingShutterCaptureExposureSchedule}).\n */\nexport interface ElectronicRollingShutterExposureSchedule {\n modelId: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.id;\n modelVersion: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.version;\n shutterMechanism: \"electronic\";\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /** Line-reset scan: a native point at normalized scan position s starts integrating at s·τ. */\n opening: UniformLinearExposureBoundarySchedule;\n /** Line-read scan: the same point stops integrating at T + s·τ. */\n closing: UniformLinearExposureBoundarySchedule;\n /** Explicit linkage declaration accepted by {@link assessReadoutExposureTimingLinkage}. */\n linkage: Extract<ReadoutExposureTimingLinkageDeclaration, { kind: \"spatial-phase-linked\" }>;\n /** τ: offset between the first and last exposure starts (equal to the readout skew). */\n lineScanSkewSeconds: number;\n /** Every point integrates for exactly the nominal exposure duration T. */\n localExposureDurationSeconds: number;\n limitations: readonly string[];\n}\n\n/** The exposure-schedule fields of a derived rolling-shutter schedule, without model metadata. */\nexport function createRollingShutterCaptureExposureSchedule(\n schedule: ElectronicRollingShutterExposureSchedule\n): Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"nominalExposureDurationSeconds\" | \"opening\" | \"closing\"> {\n // 2.0.0: the whole schedule is checked, so an unknown field at any level is rejected.\n const record = requireRecord(schedule, \"rollingShutterSchedule\", [\"modelId\", \"modelVersion\", \"shutterMechanism\",\n \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"lineScanSkewSeconds\", \"localExposureDurationSeconds\",\n \"limitations\"]);\n parseSecondsFact(record.nominalExposureDurationSeconds, \"rollingShutterSchedule.nominalExposureDurationSeconds\");\n parseBoundarySchedule(record.opening, \"rollingShutterSchedule.opening\");\n parseBoundarySchedule(record.closing, \"rollingShutterSchedule.closing\");\n if (record.linkage !== undefined) parseLinkageDeclaration(record.linkage);\n return structuredClone({ shutterMechanism: schedule.shutterMechanism,\n nominalExposureDurationSeconds: schedule.nominalExposureDurationSeconds,\n opening: schedule.opening, closing: schedule.closing });\n}\n\n/**\n * Derives the exposure-window schedule of a fully electronic rolling shutter from its rolling readout\n * declaration.\n *\n * In a CMOS rolling shutter, each line is reset, collects light for the exposure time T and is then\n * read. The reset pointer runs T ahead of the read pointer, and both move at the line rate (El Gamal &\n * Eltoukhy 2005; Liang, Chang & Chen 2008). With the uniform linear scan used throughout this engine, a\n * native point at normalized scan position s ∈ [0, 1] (along the declared scan direction, over the\n * active capture) collects light over [s·τ, s·τ + T], where τ is the declared first-to-last readout\n * skew. So both boundaries become uniform linear scans in the readout direction that take τ to sweep.\n *\n * When this schedule feeds the per-site exposure windows, everything that samples motion, moving\n * sources or time-varying light at local exposure times (environment RAW, rotation quadrature,\n * temporal illumination integration) produces rolling-shutter skew, wobble, partial exposure and\n * flicker banding.\n *\n * Approximation: constant line rate, no line-reset jitter, no hybrid of global reset with a mechanical\n * closing shutter, and the active-capture skew is declared by the caller (never scaled from a\n * full-frame value).\n * @see docs/MOTION_AND_SIGNAL.md#electronic-rolling-shutter-image-formation\n */\nexport function resolveElectronicRollingShutterExposureSchedule(\n input: ResolveElectronicRollingShutterExposureScheduleInput\n): ElectronicRollingShutterExposureSchedule {\n requireInputKeys(input, \"resolveElectronicRollingShutterExposureSchedule\", [\"readout\", \"nominalExposureDurationSeconds\", \"linkageEvidence\"]);\n // The input keys are checked above; calculateSensorReadoutTiming checks the readout's own fields.\n const record = requireRecord(input, \"rollingShutter\", null);\n const readout = requireRecord(record.readout, \"rollingShutter.readout\", null);\n if (readout.readoutMode !== \"rolling\") {\n throw new InvalidScientificInputError(\n \"An electronic rolling-shutter exposure schedule requires a rolling sensor readout declaration.\"\n );\n }\n const linkageEvidence = parseEvidenceList(input.linkageEvidence, \"rollingShutter.linkageEvidence\");\n const rolling = input.readout;\n // Check the declaration and the exposure fact with the existing readout and exposure-window calculators.\n const probeRaster = { pixelWidth: 2, pixelHeight: 2 };\n calculateSensorReadoutTiming({ nativeRaster: probeRaster, shutterMechanism: \"electronic\", readout: rolling });\n const opening: UniformLinearExposureBoundarySchedule = {\n kind: \"uniform-linear-native-scan\",\n directionNative: { value: rolling.scanDirectionNative.value, evidence: [...rolling.scanDirectionNative.evidence] },\n traversalDurationSeconds: { value: rolling.spatialSamplingSkewSeconds.value, unit: \"s\",\n evidence: [...rolling.spatialSamplingSkewSeconds.evidence] }\n };\n const closing: UniformLinearExposureBoundarySchedule = structuredClone(opening);\n const nominal = calculateCaptureExposureWindows({ nativeRaster: probeRaster, shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: input.nominalExposureDurationSeconds, opening, closing }).value.nominalExposureDurationSeconds;\n return {\n modelId: ELECTRONIC_ROLLING_SHUTTER_MODEL.id,\n modelVersion: ELECTRONIC_ROLLING_SHUTTER_MODEL.version,\n shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: { value: nominal.value, unit: \"s\", evidence: [...nominal.evidence] },\n opening,\n closing,\n linkage: { kind: \"spatial-phase-linked\", links: [\n { boundary: \"opening\", phaseOrientation: \"same\", evidence: linkageEvidence },\n { boundary: \"closing\", phaseOrientation: \"same\", evidence: linkageEvidence }] },\n lineScanSkewSeconds: rolling.spatialSamplingSkewSeconds.value,\n localExposureDurationSeconds: nominal.value,\n limitations: [\n \"Uniform-linear line timing: each native point integrates over [s·τ, s·τ + T] with s its normalized scan position.\",\n \"Line-reset jitter, non-uniform line timing, global-reset release and mechanical-close hybrids are not modeled.\",\n \"The skew τ is the caller-declared active-capture spatial sampling skew; it is never scaled from a full-frame value.\"\n ]\n };\n}\n\n/**\n * True when an exposure schedule is exactly the electronic rolling-shutter schedule derived from this\n * readout declaration: an electronic mechanism, with both boundaries scanning in the readout direction\n * and taking the readout skew to sweep.\n */\nexport function isElectronicRollingShutterExposureSchedule(\n schedule: Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"opening\" | \"closing\">,\n readout: SensorReadoutTimingDeclaration\n): boolean {\n if (readout.readoutMode !== \"rolling\" || schedule.shutterMechanism !== \"electronic\") return false;\n return [schedule.opening, schedule.closing].every((boundary) => boundary.kind === \"uniform-linear-native-scan\" &&\n boundary.directionNative.value === readout.scanDirectionNative.value &&\n boundary.traversalDurationSeconds.value === readout.spatialSamplingSkewSeconds.value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"readout-exposure-linkage.js","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Links rolling sensor readout to electronic shutter scans, and derives the exposure schedule of a\n * fully electronic rolling shutter.\n *\n * A declared link says that the readout scan and one or more exposure-boundary scans move across the\n * sensor in the same order (or in reverse). That is deliberately weaker than time synchronization: it\n * does not say when readout happens relative to the start or end of exposure. The total data-readout\n * time is kept for diagnostics but never used to check the link. The readout skew and the boundary\n * sweep times may differ; their ratio is reported without implying a shared clock.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n parseBoundarySchedule,\n parseSecondsFact,\n type CalculateCaptureExposureWindowsInput,\n type ExposureBoundarySchedule,\n type UniformLinearExposureBoundarySchedule,\n type SourcedCaptureTimingSeconds\n} from \"./exposure-window.js\";\nimport {\n calculateSensorReadoutTiming,\n type CaptureShutterMechanism,\n type NativeSensorReadoutScanDirection,\n type RollingSensorReadoutTimingDeclaration,\n type SensorReadoutTimingDeclaration,\n type SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport { requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type ReadoutExposureBoundaryId = \"opening\" | \"closing\";\n\nexport type ReadoutExposureSpatialPhaseOrientation =\n | \"same\"\n | \"reversed\";\n\nexport interface ReadoutExposureBoundarySpatialLink {\n boundary: ReadoutExposureBoundaryId;\n /**\n * How the normalized position of the rolling readout scan relates to that\n * of the selected electronic exposure-boundary scan.\n *\n * \"same\": boundary normalized phase = readout normalized phase.\n * \"reversed\": boundary normalized phase = 1 - readout normalized phase.\n *\n * This does not establish absolute temporal synchronization.\n */\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n /**\n * Evidence for the link itself.\n *\n * The readout direction and timing, and the exposure-boundary direction and\n * timing, keep their own separate evidence.\n */\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type ReadoutExposureTimingLinkageDeclaration =\n | {\n /**\n * No link between readout and exposure is stated for this capture.\n * This is not evidence that the physical processes are independent.\n */\n kind: \"unlinked\";\n links?: never;\n }\n | {\n /**\n * One or more exposure boundaries are explicitly linked to the rolling\n * readout by their normalized position across the native sensor.\n */\n kind: \"spatial-phase-linked\";\n links: readonly ReadoutExposureBoundarySpatialLink[];\n };\n\nexport interface AssessReadoutExposureTimingLinkageInput {\n nativeRaster: NativeImageRaster;\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n linkage: ReadoutExposureTimingLinkageDeclaration;\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ReadoutExposureBoundarySpatialLinkAssessment {\n boundary: ReadoutExposureBoundaryId;\n boundaryActuator: \"electronic\";\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n normalizedPhaseRelationship:\n | \"boundary-phase-equals-readout-phase\"\n | \"boundary-phase-equals-one-minus-readout-phase\";\n relationshipEvidence: readonly EvidenceProvenance[];\n readoutDirectionNative: NativeSensorReadoutScanDirection;\n boundaryDirectionNative: NativeSensorReadoutScanDirection;\n /**\n * Rolling readout skew: time from the first to the last scan position.\n */\n readoutSpatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n /**\n * Time for the exposure boundary to sweep from first to last position.\n */\n boundaryTraversalDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * A descriptive ratio only. A value of 1 does not prove time\n * synchronization or a common clock origin.\n */\n boundaryTraversalToReadoutSpatialSkewRatio: number;\n absoluteTemporalAlignment: \"not-established\";\n}\n\nexport interface ReadoutExposureTimingLinkageAssessment {\n linkageKind: ReadoutExposureTimingLinkageDeclaration[\"kind\"];\n relationshipMeaning:\n | \"no-readout-exposure-relationship-asserted\"\n | \"normalized-spatial-phase-relationship-asserted\";\n readoutMode: \"rolling\" | \"global\";\n shutterMechanism: CaptureShutterMechanism;\n activeCaptureRect: RasterRect;\n absoluteTemporalAlignment: \"not-established\";\n /**\n * Data-readout time for the capture. Kept for diagnostics, but never used to\n * check an exposure-boundary link.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n links: readonly ReadoutExposureBoundarySpatialLinkAssessment[];\n componentProvenance: {\n sensorReadout: CalculationProvenance;\n exposureWindows: CalculationProvenance;\n };\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nconst BOUNDARIES = new Set<ReadoutExposureBoundaryId>([\n \"opening\",\n \"closing\"\n]);\n\nconst PHASE_ORIENTATIONS =\n new Set<ReadoutExposureSpatialPhaseOrientation>([\n \"same\",\n \"reversed\"\n ]);\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction parseSpatialLink(\n value: unknown,\n path: string\n): ReadoutExposureBoundarySpatialLink {\n const record = requireRecord(value, path, [\"boundary\", \"phaseOrientation\", \"evidence\"]);\n\n if (\n typeof record.boundary !== \"string\" ||\n !BOUNDARIES.has(record.boundary as ReadoutExposureBoundaryId)\n ) {\n throw new InvalidConfigurationError(path + \".boundary is invalid.\");\n }\n\n if (\n typeof record.phaseOrientation !== \"string\" ||\n !PHASE_ORIENTATIONS.has(\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation\n )\n ) {\n throw new InvalidConfigurationError(\n path + \".phaseOrientation is invalid.\"\n );\n }\n\n return {\n boundary: record.boundary as ReadoutExposureBoundaryId,\n phaseOrientation:\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseLinkageDeclaration(\n value: unknown\n): ReadoutExposureTimingLinkageDeclaration {\n const record = requireRecord(value, \"linkage\", [\"kind\", \"links\"]);\n\n if (record.kind === \"unlinked\") {\n if (record.links !== undefined) {\n throw new InvalidScientificInputError(\n \"linkage.links must be omitted when linkage.kind is unlinked.\"\n );\n }\n return { kind: \"unlinked\" };\n }\n\n if (record.kind !== \"spatial-phase-linked\") {\n throw new InvalidConfigurationError(\"linkage.kind is invalid.\");\n }\n\n if (!Array.isArray(record.links) || record.links.length === 0) {\n throw new InvalidConfigurationError(\n \"linkage.links must be a non-empty array for spatial-phase-linked linkage.\"\n );\n }\n\n const links = record.links.map((entry, index) =>\n parseSpatialLink(entry, \"linkage.links[\" + index + \"]\")\n );\n\n const seen = new Set<ReadoutExposureBoundaryId>();\n for (const link of links) {\n if (seen.has(link.boundary)) {\n throw new InvalidScientificInputError(\n \"linkage.links must not contain duplicate boundary links.\"\n );\n }\n seen.add(link.boundary);\n }\n\n return {\n kind: \"spatial-phase-linked\",\n links\n };\n}\n\nfunction oppositeDirection(\n direction: NativeSensorReadoutScanDirection\n): NativeSensorReadoutScanDirection {\n switch (direction) {\n case \"top-to-bottom\":\n return \"bottom-to-top\";\n case \"bottom-to-top\":\n return \"top-to-bottom\";\n case \"left-to-right\":\n return \"right-to-left\";\n case \"right-to-left\":\n return \"left-to-right\";\n }\n}\n\nfunction expectedBoundaryDirection(\n readoutDirection: NativeSensorReadoutScanDirection,\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation\n): NativeSensorReadoutScanDirection {\n return phaseOrientation === \"same\"\n ? readoutDirection\n : oppositeDirection(readoutDirection);\n}\n\n/**\n * Checks an explicitly declared link between the rolling readout scan and one\n * or more electronic exposure-boundary scans.\n *\n * This is deliberately weaker than time synchronization. It can show that two\n * schedules sweep the native sensor in the same order (or in reverse), but not\n * when readout happens relative to the start or end of exposure.\n *\n * The total data-readout time is kept for diagnostics and never used to check\n * the link. The readout skew and the boundary sweep time may differ; their\n * ratio is reported without implying a shared clock.\n */\nexport function assessReadoutExposureTimingLinkage(\n input: AssessReadoutExposureTimingLinkageInput\n): CalculationResult<ReadoutExposureTimingLinkageAssessment> {\n requireInputKeys(input, \"assessReadoutExposureTimingLinkage\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"samplePointsNative\"]);\n const linkage = parseLinkageDeclaration(input.linkage);\n\n const sensorReadout = calculateSensorReadoutTiming({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n readout: input.readout,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n const exposureWindows = calculateCaptureExposureWindows({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n nominalExposureDurationSeconds:\n input.nominalExposureDurationSeconds,\n opening: input.opening,\n closing: input.closing,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n if (linkage.kind === \"unlinked\") {\n return approximationResult(\n {\n linkageKind: \"unlinked\",\n relationshipMeaning:\n \"no-readout-exposure-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: [],\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"Unlinked means Photivra asserts no readout-to-exposure relationship for this capture; it does not prove the physical processes are independent.\",\n \"Sensor data-readout timing and exposure-window timing retain independent provenance and semantics.\",\n \"Absolute temporal alignment between data readout and exposure boundaries is not established.\"\n ]\n );\n }\n\n const readoutScan = sensorReadout.value.scan;\n if (readoutScan === null) {\n throw new InvalidScientificInputError(\n \"spatial-phase-linked linkage requires rolling sensor readout with a spatial scan.\"\n );\n }\n\n const assessments =\n linkage.links.map<ReadoutExposureBoundarySpatialLinkAssessment>(\n (link) => {\n const boundary =\n link.boundary === \"opening\"\n ? exposureWindows.value.opening\n : exposureWindows.value.closing;\n\n if (boundary.actuator !== \"electronic\") {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link may target only an electronic exposure boundary.\"\n );\n }\n\n if (\n boundary.schedule.kind !==\n \"uniform-linear-native-scan\"\n ) {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link requires the selected exposure boundary to use a uniform-linear native scan.\"\n );\n }\n\n const readoutDirection =\n readoutScan.directionNative.value;\n const boundaryDirection =\n boundary.schedule.directionNative.value;\n const expectedDirection = expectedBoundaryDirection(\n readoutDirection,\n link.phaseOrientation\n );\n\n if (boundaryDirection !== expectedDirection) {\n throw new InvalidScientificInputError(\n \"Declared readout/exposure spatial phase orientation contradicts the readout and exposure-boundary scan directions.\"\n );\n }\n\n const readoutSpatialSamplingSkewSeconds =\n readoutScan.spatialSamplingSkewSeconds;\n const boundaryTraversalDurationSeconds =\n boundary.schedule.traversalDurationSeconds;\n\n return {\n boundary: link.boundary,\n boundaryActuator: \"electronic\",\n phaseOrientation: link.phaseOrientation,\n normalizedPhaseRelationship:\n link.phaseOrientation === \"same\"\n ? \"boundary-phase-equals-readout-phase\"\n : \"boundary-phase-equals-one-minus-readout-phase\",\n relationshipEvidence: link.evidence,\n readoutDirectionNative: readoutDirection,\n boundaryDirectionNative: boundaryDirection,\n readoutSpatialSamplingSkewSeconds,\n boundaryTraversalDurationSeconds,\n boundaryTraversalToReadoutSpatialSkewRatio:\n boundaryTraversalDurationSeconds.value /\n readoutSpatialSamplingSkewSeconds.value,\n absoluteTemporalAlignment: \"not-established\"\n };\n }\n );\n\n return approximationResult(\n {\n linkageKind: \"spatial-phase-linked\",\n relationshipMeaning:\n \"normalized-spatial-phase-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: assessments,\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"The linkage establishes normalized native-sensor spatial phase/order only, not absolute temporal synchronization.\",\n \"Every asserted boundary link carries evidence separate from the evidence for the underlying readout and exposure timing facts.\",\n \"Only electronic exposure boundaries may be linked to rolling sensor readout spatial phase.\",\n \"Same phase orientation requires matching native scan direction; reversed phase orientation requires the exact opposite direction on the same native axis.\",\n \"Rolling readout spatial skew and exposure-boundary traversal duration may differ; their ratio is descriptive and does not establish a common clock origin.\",\n \"Capture data-readout duration is not used to validate exposure-boundary timing linkage.\",\n \"Active capture, native coordinate semantics and shutter mechanism are shared because both component schedules are resolved from the same input.\",\n \"Sensor readout and exposure-window component provenance remain visible; linkage does not upgrade either component to calibrated status.\",\n \"No camera-specific timing relationship is inferred from shutter mechanism, sensor architecture metadata, output resolution or adjacent specifications.\"\n ]\n );\n}\n\n/** Model identity of the electronic rolling-shutter (line reset → line read) exposure schedule. */\nexport const ELECTRONIC_ROLLING_SHUTTER_MODEL = /* @__PURE__ */ Object.freeze({\n id: \"electronic-rolling-shutter-line-reset-read\",\n version: \"1.0.0\"\n} as const);\n\nexport interface ResolveElectronicRollingShutterExposureScheduleInput {\n /** Rolling readout declaration whose scan direction and spatial skew drive both exposure boundaries. */\n readout: RollingSensorReadoutTimingDeclaration;\n /** Per-line integration time between line reset and line read. */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * Evidence that this capture uses a fully electronic, line-by-line (rolling) shutter whose reset\n * scan runs at the readout line rate. This explicit link is required before sensor readout timing\n * may set the exposure-window timing.\n */\n linkageEvidence: readonly EvidenceProvenance[];\n}\n\n/**\n * Exposure-window schedule of an electronic rolling shutter. Its `shutterMechanism`,\n * `nominalExposureDurationSeconds`, `opening` and `closing` fields form a complete exposure schedule, so\n * they can be passed as a native RAW / environment `exposureWindow` or spread into a production\n * `temporalCapture.exposureWindowInput` (see {@link createRollingShutterCaptureExposureSchedule}).\n */\nexport interface ElectronicRollingShutterExposureSchedule {\n modelId: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.id;\n modelVersion: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.version;\n shutterMechanism: \"electronic\";\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /** Line-reset scan: a native point at normalized scan position s starts integrating at s·τ. */\n opening: UniformLinearExposureBoundarySchedule;\n /** Line-read scan: the same point stops integrating at T + s·τ. */\n closing: UniformLinearExposureBoundarySchedule;\n /** Explicit linkage declaration accepted by {@link assessReadoutExposureTimingLinkage}. */\n linkage: Extract<ReadoutExposureTimingLinkageDeclaration, { kind: \"spatial-phase-linked\" }>;\n /** τ: offset between the first and last exposure starts (equal to the readout skew). */\n lineScanSkewSeconds: number;\n /** Every point integrates for exactly the nominal exposure duration T. */\n localExposureDurationSeconds: number;\n limitations: readonly string[];\n}\n\n/** The exposure-schedule fields of a derived rolling-shutter schedule, without model metadata. */\nexport function createRollingShutterCaptureExposureSchedule(\n schedule: ElectronicRollingShutterExposureSchedule\n): Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"nominalExposureDurationSeconds\" | \"opening\" | \"closing\"> {\n // 2.0.0: the whole schedule is checked, so an unknown field at any level is rejected.\n const record = requireRecord(schedule, \"rollingShutterSchedule\", [\"modelId\", \"modelVersion\", \"shutterMechanism\",\n \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"lineScanSkewSeconds\", \"localExposureDurationSeconds\",\n \"limitations\"]);\n parseSecondsFact(record.nominalExposureDurationSeconds, \"rollingShutterSchedule.nominalExposureDurationSeconds\");\n parseBoundarySchedule(record.opening, \"rollingShutterSchedule.opening\");\n parseBoundarySchedule(record.closing, \"rollingShutterSchedule.closing\");\n if (record.linkage !== undefined) parseLinkageDeclaration(record.linkage);\n return structuredClone({ shutterMechanism: schedule.shutterMechanism,\n nominalExposureDurationSeconds: schedule.nominalExposureDurationSeconds,\n opening: schedule.opening, closing: schedule.closing });\n}\n\n/**\n * Derives the exposure-window schedule of a fully electronic rolling shutter from its rolling readout\n * declaration.\n *\n * In a CMOS rolling shutter, each line is reset, collects light for the exposure time T and is then\n * read. The reset pointer runs T ahead of the read pointer, and both move at the line rate (El Gamal &\n * Eltoukhy 2005; Liang, Chang & Chen 2008). With the uniform linear scan used throughout this engine, a\n * native point at normalized scan position s ∈ [0, 1] (along the declared scan direction, over the\n * active capture) collects light over [s·τ, s·τ + T], where τ is the declared first-to-last readout\n * skew. So both boundaries become uniform linear scans in the readout direction that take τ to sweep.\n *\n * When this schedule feeds the per-site exposure windows, everything that samples motion, moving\n * sources or time-varying light at local exposure times (environment RAW, rotation quadrature,\n * temporal illumination integration) produces rolling-shutter skew, wobble, partial exposure and\n * flicker banding.\n *\n * Approximation: constant line rate, no line-reset jitter, no hybrid of global reset with a mechanical\n * closing shutter, and the active-capture skew is declared by the caller (never scaled from a\n * full-frame value).\n * @see docs/MOTION_AND_SIGNAL.md#electronic-rolling-shutter-image-formation\n */\nexport function resolveElectronicRollingShutterExposureSchedule(\n input: ResolveElectronicRollingShutterExposureScheduleInput\n): ElectronicRollingShutterExposureSchedule {\n requireInputKeys(input, \"resolveElectronicRollingShutterExposureSchedule\", [\"readout\", \"nominalExposureDurationSeconds\", \"linkageEvidence\"]);\n // The input keys are checked above; calculateSensorReadoutTiming checks the readout's own fields.\n const record = requireRecord(input, \"rollingShutter\", null);\n const readout = requireRecord(record.readout, \"rollingShutter.readout\", null);\n if (readout.readoutMode !== \"rolling\") {\n throw new InvalidScientificInputError(\n \"An electronic rolling-shutter exposure schedule requires a rolling sensor readout declaration.\"\n );\n }\n const linkageEvidence = parseEvidenceList(input.linkageEvidence, \"rollingShutter.linkageEvidence\");\n const rolling = input.readout;\n // Check the declaration and the exposure fact with the existing readout and exposure-window calculators.\n const probeRaster = { pixelWidth: 2, pixelHeight: 2 };\n calculateSensorReadoutTiming({ nativeRaster: probeRaster, shutterMechanism: \"electronic\", readout: rolling });\n const opening: UniformLinearExposureBoundarySchedule = {\n kind: \"uniform-linear-native-scan\",\n directionNative: { value: rolling.scanDirectionNative.value, evidence: [...rolling.scanDirectionNative.evidence] },\n traversalDurationSeconds: { value: rolling.spatialSamplingSkewSeconds.value, unit: \"s\",\n evidence: [...rolling.spatialSamplingSkewSeconds.evidence] }\n };\n const closing: UniformLinearExposureBoundarySchedule = structuredClone(opening);\n const nominal = calculateCaptureExposureWindows({ nativeRaster: probeRaster, shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: input.nominalExposureDurationSeconds, opening, closing }).value.nominalExposureDurationSeconds;\n return {\n modelId: ELECTRONIC_ROLLING_SHUTTER_MODEL.id,\n modelVersion: ELECTRONIC_ROLLING_SHUTTER_MODEL.version,\n shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: { value: nominal.value, unit: \"s\", evidence: [...nominal.evidence] },\n opening,\n closing,\n linkage: { kind: \"spatial-phase-linked\", links: [\n { boundary: \"opening\", phaseOrientation: \"same\", evidence: linkageEvidence },\n { boundary: \"closing\", phaseOrientation: \"same\", evidence: linkageEvidence }] },\n lineScanSkewSeconds: rolling.spatialSamplingSkewSeconds.value,\n localExposureDurationSeconds: nominal.value,\n limitations: [\n \"Uniform-linear line timing: each native point integrates over [s·τ, s·τ + T] with s its normalized scan position.\",\n \"Line-reset jitter, non-uniform line timing, global-reset release and mechanical-close hybrids are not modeled.\",\n \"The skew τ is the caller-declared active-capture spatial sampling skew; it is never scaled from a full-frame value.\"\n ]\n };\n}\n\n/**\n * True when an exposure schedule is exactly the electronic rolling-shutter schedule derived from this\n * readout declaration: an electronic mechanism, with both boundaries scanning in the readout direction\n * and taking the readout skew to sweep.\n */\nexport function isElectronicRollingShutterExposureSchedule(\n schedule: Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"opening\" | \"closing\">,\n readout: SensorReadoutTimingDeclaration\n): boolean {\n if (readout.readoutMode !== \"rolling\" || schedule.shutterMechanism !== \"electronic\") return false;\n return [schedule.opening, schedule.closing].every((boundary) => boundary.kind === \"uniform-linear-native-scan\" &&\n boundary.directionNative.value === readout.scanDirectionNative.value &&\n boundary.traversalDurationSeconds.value === readout.spatialSamplingSkewSeconds.value);\n}\n"]}
|
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.14.1 / root contract 2.14.1** 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
|
|
|
@@ -846,10 +846,14 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
846
846
|
|
|
847
847
|
| Export | Kind |
|
|
848
848
|
| --- | --- |
|
|
849
|
+
| [`GENERIC_THIRD_STOP_ISO_SETTINGS`](api/equipment-generic-tier-assets.md#generic_third_stop_iso_settings) | Runtime |
|
|
850
|
+
| [`GENERIC_THIRD_STOP_SHUTTER_SECONDS`](api/equipment-generic-tier-assets.md#generic_third_stop_shutter_seconds) | Runtime |
|
|
851
|
+
| [`GENERIC_WHOLE_STOP_ISO_SETTINGS`](api/equipment-generic-tier-assets.md#generic_whole_stop_iso_settings) | Runtime |
|
|
849
852
|
| [`GenericEquipmentTier`](api/equipment-generic-tier-assets.md#genericequipmenttier) | Type |
|
|
850
853
|
| [`GenericEquipmentTierPreset`](api/equipment-generic-tier-assets.md#genericequipmenttierpreset) | Type |
|
|
851
854
|
| [`GenericTierBodyAssets`](api/equipment-generic-tier-assets.md#generictierbodyassets) | Type |
|
|
852
855
|
| [`GenericTierLensAssets`](api/equipment-generic-tier-assets.md#generictierlensassets) | Type |
|
|
856
|
+
| [`GenericTierPresetVersion`](api/equipment-generic-tier-assets.md#generictierpresetversion) | Type |
|
|
853
857
|
| [`GenericVersionedAsset`](api/equipment-generic-tier-assets.md#genericversionedasset) | Type |
|
|
854
858
|
|
|
855
859
|
## equipment/generic-tier-presets.ts
|
|
@@ -860,6 +864,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
860
864
|
| --- | --- |
|
|
861
865
|
| [`createGenericEquipmentTierSelection`](api/equipment-generic-tier-presets.md#creategenericequipmenttierselection) | Runtime |
|
|
862
866
|
| [`GENERIC_EQUIPMENT_TIER_PRESET_VERSION`](api/equipment-generic-tier-presets.md#generic_equipment_tier_preset_version) | Runtime |
|
|
867
|
+
| [`GENERIC_EQUIPMENT_TIER_PRESET_VERSIONS`](api/equipment-generic-tier-presets.md#generic_equipment_tier_preset_versions) | Runtime |
|
|
863
868
|
| [`GENERIC_EQUIPMENT_TIERS`](api/equipment-generic-tier-presets.md#generic_equipment_tiers) | Runtime |
|
|
864
869
|
| [`GenericEquipmentTierProfileReference`](api/equipment-generic-tier-presets.md#genericequipmenttierprofilereference) | Type |
|
|
865
870
|
| [`GenericEquipmentTierSelection`](api/equipment-generic-tier-presets.md#genericequipmenttierselection) | Type |
|
|
@@ -1173,6 +1178,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1173
1178
|
| [`ChartScenePresetId`](api/facade-chart-scenes.md#chartscenepresetid) | Type |
|
|
1174
1179
|
| [`ChartSlantedSquare`](api/facade-chart-scenes.md#chartslantedsquare) | Type |
|
|
1175
1180
|
| [`CheckerboardChartScene`](api/facade-chart-scenes.md#checkerboardchartscene) | Type |
|
|
1181
|
+
| [`ColorPatchesChartScene`](api/facade-chart-scenes.md#colorpatcheschartscene) | Type |
|
|
1182
|
+
| [`ColorPatchReflectance`](api/facade-chart-scenes.md#colorpatchreflectance) | Type |
|
|
1176
1183
|
| [`CompositeChartElement`](api/facade-chart-scenes.md#compositechartelement) | Type |
|
|
1177
1184
|
| [`CompositeChartScene`](api/facade-chart-scenes.md#compositechartscene) | Type |
|
|
1178
1185
|
| [`DeadLeavesChartScene`](api/facade-chart-scenes.md#deadleaveschartscene) | Type |
|
|
@@ -1191,6 +1198,25 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1191
1198
|
| [`UniformChartScene`](api/facade-chart-scenes.md#uniformchartscene) | Type |
|
|
1192
1199
|
| [`ZonePlateChartScene`](api/facade-chart-scenes.md#zoneplatechartscene) | Type |
|
|
1193
1200
|
|
|
1201
|
+
## facade/color-patches.ts
|
|
1202
|
+
|
|
1203
|
+
[Detailed contracts](api/facade-color-patches.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/color-patches.ts)
|
|
1204
|
+
|
|
1205
|
+
| Export | Kind |
|
|
1206
|
+
| --- | --- |
|
|
1207
|
+
| [`COLOR_PATCH_IDS`](api/facade-color-patches.md#color_patch_ids) | Runtime |
|
|
1208
|
+
| [`COLOR_PATCH_SET_ID`](api/facade-color-patches.md#color_patch_set_id) | Runtime |
|
|
1209
|
+
| [`COLOR_PATCH_SET_VERSION`](api/facade-color-patches.md#color_patch_set_version) | Runtime |
|
|
1210
|
+
| [`COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS`](api/facade-color-patches.md#color_patch_wavelength_range_nanometers) | Runtime |
|
|
1211
|
+
| [`ColorPatch`](api/facade-color-patches.md#colorpatch) | Type |
|
|
1212
|
+
| [`ColorPatchGroup`](api/facade-color-patches.md#colorpatchgroup) | Type |
|
|
1213
|
+
| [`ColorPatchId`](api/facade-color-patches.md#colorpatchid) | Type |
|
|
1214
|
+
| [`ColorPatchSet`](api/facade-color-patches.md#colorpatchset) | Type |
|
|
1215
|
+
| [`ColorPatchSpectralTerm`](api/facade-color-patches.md#colorpatchspectralterm) | Type |
|
|
1216
|
+
| [`evaluateColorPatchReflectance`](api/facade-color-patches.md#evaluatecolorpatchreflectance) | Runtime |
|
|
1217
|
+
| [`EvaluateColorPatchReflectanceInput`](api/facade-color-patches.md#evaluatecolorpatchreflectanceinput) | Type |
|
|
1218
|
+
| [`getColorPatchSet`](api/facade-color-patches.md#getcolorpatchset) | Runtime |
|
|
1219
|
+
|
|
1194
1220
|
## facade/display-view-gpu.ts
|
|
1195
1221
|
|
|
1196
1222
|
[Detailed contracts](api/facade-display-view-gpu.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/display-view-gpu.ts)
|
|
@@ -1250,6 +1276,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1250
1276
|
| [`SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX`](api/facade-simulate-capture.md#simulate_capture_default_illuminance_lux) | Runtime |
|
|
1251
1277
|
| [`SIMULATE_CAPTURE_FACADE_VERSION`](api/facade-simulate-capture.md#simulate_capture_facade_version) | Runtime |
|
|
1252
1278
|
| [`SIMULATE_CAPTURE_FAST_OPTIONS`](api/facade-simulate-capture.md#simulate_capture_fast_options) | Runtime |
|
|
1279
|
+
| [`SIMULATE_CAPTURE_METERING_MODES`](api/facade-simulate-capture.md#simulate_capture_metering_modes) | Runtime |
|
|
1253
1280
|
| [`SIMULATE_DISPLAY_VIEW_DEFAULT_SAMPLES_PER_AXIS`](api/facade-simulate-capture.md#simulate_display_view_default_samples_per_axis) | Runtime |
|
|
1254
1281
|
| [`simulateCapture`](api/facade-simulate-capture.md#simulatecapture) | Runtime |
|
|
1255
1282
|
| [`SimulateCaptureAssumption`](api/facade-simulate-capture.md#simulatecaptureassumption) | Type |
|
|
@@ -1267,6 +1294,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1267
1294
|
| [`SimulateCaptureFastOption`](api/facade-simulate-capture.md#simulatecapturefastoption) | Type |
|
|
1268
1295
|
| [`SimulateCaptureFastReport`](api/facade-simulate-capture.md#simulatecapturefastreport) | Type |
|
|
1269
1296
|
| [`SimulateCaptureLensDistortion`](api/facade-simulate-capture.md#simulatecapturelensdistortion) | Type |
|
|
1297
|
+
| [`SimulateCaptureMetering`](api/facade-simulate-capture.md#simulatecapturemetering) | Type |
|
|
1270
1298
|
| [`SimulateCaptureMotion`](api/facade-simulate-capture.md#simulatecapturemotion) | Type |
|
|
1271
1299
|
| [`SimulateCaptureOptions`](api/facade-simulate-capture.md#simulatecaptureoptions) | Type |
|
|
1272
1300
|
| [`SimulateCaptureOutputFormat`](api/facade-simulate-capture.md#simulatecaptureoutputformat) | Type |
|
package/docs/QUICK_START.md
CHANGED
|
@@ -48,7 +48,7 @@ the example above is [simulate-capture.mjs](examples/simulate-capture.mjs).
|
|
|
48
48
|
| `raw` | The packed native RAW: codes, black levels, saturation flags and per-phase white levels |
|
|
49
49
|
| `plan` | The native RAW plan the engine executed |
|
|
50
50
|
| `request` | The complete underlying request: `environmentRaw` (the native environment RAW input), `siteInput(index)` (rebuilds the physical input of any sensor site), `development` (the RAW development input) and `whiteBalanceState` |
|
|
51
|
-
| `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`), the illuminance at which the settings give the standard exposure (`standardExposureIlluminanceLux`) and the exposure-mode resolver's full output |
|
|
51
|
+
| `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`), the illuminance at which the settings give the standard exposure (`standardExposureIlluminanceLux`), from 2.14 the deviation from the standard exposure of an 18 % grey card under the scene's illuminance (`greyCardDeviationStops`, incident metering) and the exposure-mode resolver's full output |
|
|
52
52
|
| `scene` | Chart parameters, illuminant, distance, framed size and the scene's SHA-256 digest (it covers caller illuminant and reflectance spectra; see `sourceSha256`) |
|
|
53
53
|
| `diagnostics` | Radiance query count, highlight and rendering counters, per-stage timings (`timingsMs`) and, from 2.1, the opt-ins that ran (`options`); see [Cost of each option](#cost-of-each-option) |
|
|
54
54
|
| `provenance` | Status (`approximation`), camera preset identity, evidence, the composed contracts in order, and limitations |
|
|
@@ -75,10 +75,11 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
75
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. |
|
|
76
76
|
| `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
77
|
| `exposure.mode` | `"manual"` | Also `"aperture-priority"`, `"shutter-priority"`, `"program"`. |
|
|
78
|
-
| `exposure.iso` | 100 | Selects the body's ISO readout regime (gain and read noise). `"auto"` lets Auto ISO choose it from the meter in any mode (see [Auto ISO](#auto-iso-516)). |
|
|
78
|
+
| `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"`. |
|
|
79
79
|
| `exposure.autoIso` | none | With `iso: "auto"` only. `aperture-priority` requires `{ slowestPreferredShutterSeconds, afterMaximumIso? }`, and `program` takes them optionally; the slowest shutter may be `"auto"`, set by `automaticSlowestShutter`. Every mode takes `limits: { maximum, minimum? }`, the photographer's Auto ISO range (see [Auto ISO](#auto-iso-516)). |
|
|
80
80
|
| `exposure.shutterSeconds` | 1/125 | Used in `manual` and `shutter-priority`. |
|
|
81
81
|
| `exposure.compensationStops` | 0 | Applied to the meter target. |
|
|
82
|
+
| `exposure.metering` | `"scene-average"` | 2.14: or `"grey-card"`, incident metering: the meter reads an 18 % grey card under the scene's illuminance instead of the scene, so the automatic modes and Auto ISO land on `greyCardDeviationStops` = 0 (see [Exposure](#exposure)). Reported as `exposure.metering`. |
|
|
82
83
|
| `scene` | required | A chart preset or a caller function (below). |
|
|
83
84
|
| `subjectDistanceM` | 5 | Distance of the scene plane. |
|
|
84
85
|
| `chartSize` | fill the frame | 2.3: `{ widthM, heightM?, surroundReflectance? }` places the chart at its physical size (see [Fixed-size chart](#fixed-size-chart-23)). |
|
|
@@ -173,6 +174,10 @@ real products, and carry no brand:
|
|
|
173
174
|
to 135 mm, f/32 beyond. Any f-number inside the range is accepted; `fNumbers` lists the marked
|
|
174
175
|
third-stop values within it (`GENERIC_PRIME_THIRD_STOP_F_NUMBERS`: 1, 1.1, 1.2, 1.4, …, 22, 25, 29, 32,
|
|
175
176
|
the conventional rounded values of 2^(k/6)). An f-number outside the range is rejected.
|
|
177
|
+
- **Third-stop dials (2.14):** `GENERIC_THIRD_STOP_ISO_SETTINGS` lists the bodies' ISO settings (the rounded values of
|
|
178
|
+
100·2^(k/3)), and `GENERIC_THIRD_STOP_SHUTTER_SECONDS` the standard third-stop shutter times from 1/8000 s to 30 s
|
|
179
|
+
(the rounded values of 2^(k/3) s: 1/8000, 1/6400, 1/5000, … 1/13, 1/10, 1/8, 1/6, 1/5, 1/4, 0.3, 0.4 … 25, 30). The
|
|
180
|
+
bodies' shutters are continuous over 1/8000–30 s and take each value as the exact time.
|
|
176
181
|
- **Minimum focus** = max(0.10 m, f·(1 + 1/0.15)): the thin-lens object distance at 0.15× magnification,
|
|
177
182
|
never closer than 10 cm (`GENERIC_PRIME_MINIMUM_FOCUS`). A subject or focus distance below it is
|
|
178
183
|
rejected. This is a generic thin-lens approximation, measured from the lens, not a real lens's
|
|
@@ -375,6 +380,32 @@ The facade meters the scene like a reflected-light meter:
|
|
|
375
380
|
illuminances). With the defaults it is the default illuminance; the marked f/11 gives 2^0.919 times it, about
|
|
376
381
|
40 612 lux. It is `null` for a black scene and for a `radiance-function` scene, whose radiance does not depend on the
|
|
377
382
|
illuminance. It can exceed the 1e7 lux `illuminanceLux` accepts. The display view reports the same value.
|
|
383
|
+
- `exposure.greyCardDeviationStops` (2.14) is the same deviation for incident metering: the light falling on the scene,
|
|
384
|
+
as an 18 % grey card or a hand-held incident meter reads it, instead of the scene's own luminance. It uses the grey
|
|
385
|
+
card's luminance L = 0.18·E/π (a Lambertian reflector; E the request's `illuminanceLux`) in place of the metered L:
|
|
386
|
+
Δ_grey = log₂(L_ref/(0.18·E/π)), L_ref = 10·N²/(0.65·t·S). Same sign: positive is darker (under). It depends only on
|
|
387
|
+
the settings used and the illuminance, never on the scene's reflectances, so a chart's white paper cannot fool it.
|
|
388
|
+
With the default settings and illuminance it is exactly 0 (the default illuminance is defined from this grey card,
|
|
389
|
+
below); each stop of shutter, aperture or ISO, and each halving of the illuminance, moves it by one stop. It is `null`
|
|
390
|
+
for a `radiance-function` scene, whose radiance ignores the illuminance. The display view reports the same value.
|
|
391
|
+
- **By default the automatic modes meter the scene** (`exposure.metering: "scene-average"`: the body's
|
|
392
|
+
16 × 16-zone meter, `meteredSceneLuminanceCdPerSquareMeter`), exactly as before 2.14, and `greyCardDeviationStops`
|
|
393
|
+
reports where their exposure sits against the grey card. On a scene brighter than 18 % grey they expose darker
|
|
394
|
+
than the grey card: a uniform 90 % white gives log₂(0.9/0.18) ≈ +2.32, and the app's round 4 All-in-one chart on
|
|
395
|
+
90 % white paper about +1.78 (its metered luminance is 3.4 times the grey card's).
|
|
396
|
+
- **`exposure.metering: "grey-card"` (2.14) meters by the grey card**: every meter zone reads L = 0.18·E/π, so
|
|
397
|
+
aperture priority, shutter priority and program, with a fixed ISO or Auto ISO, choose settings with
|
|
398
|
+
`greyCardDeviationStops` = 0 whatever the scene's reflectances, as a manual exposure at the default settings and
|
|
399
|
+
illuminance has. What is left is the mode's own steps and limits: the resolvers set a continuous shutter or
|
|
400
|
+
aperture, so only rounding remains unless the lens or shutter range stops them (shutter priority at 1/125 s and
|
|
401
|
+
5000 lx would need f/3.9 and stops at f/4, 0.10 stop under); Auto ISO chooses whole-stop ISOs, so up to half a
|
|
402
|
+
stop remains, and that remainder is the Auto ISO report's `residualStops`, with `limitingConstraint` naming
|
|
403
|
+
`iso-grid-quantization` or the ISO limit. Compensation still applies (+⅔ gives −⅔). Manual exposure ignores the
|
|
404
|
+
meter, so the option changes nothing there except Auto ISO. `meteredSceneLuminanceCdPerSquareMeter`,
|
|
405
|
+
`standardExposureDeviationStops` and `standardExposureIlluminanceLux` still describe the scene's own luminance.
|
|
406
|
+
A `radiance-function` scene is refused, since its radiance ignores the illuminance. The capture reports
|
|
407
|
+
`exposure.metering`, and the assumption `exposure.meteringSource` gives the card's luminance. When the option is
|
|
408
|
+
omitted, nothing is added to the assumptions, so 2.13 requests give the same assumptions and bytes.
|
|
378
409
|
|
|
379
410
|
DC-004 ties the reference level to an 18 % grey (Explanation 1 and 2.2). The default illuminance
|
|
380
411
|
puts an 18 % Lambertian grey card, whose luminance is L = 0.18·E/π, exactly at that luminance:
|
|
@@ -482,18 +513,22 @@ console.log(handheld.exposure.autoIso?.automaticSlowestShutter, handheld.exposur
|
|
|
482
513
|
|
|
483
514
|
All charts are analytic patterns written by the engine; no third-party chart data is bundled. Each
|
|
484
515
|
is a flat Lambertian reflector (it looks equally bright from every direction). Neutral reflectances
|
|
485
|
-
are spectrally flat.
|
|
516
|
+
are spectrally flat. Where a preset takes a reflectance spectrum (`uniform`, `patches`), it accepts a
|
|
517
|
+
neutral number, a caller spectrum (`{ kind: "tabulated" }` or `{ kind: "function" }`, 380–780 nm at
|
|
518
|
+
least) or, from 2.14, a built-in color patch `{ kind: "color-patch", patchId }` (see
|
|
519
|
+
[Color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md)).
|
|
486
520
|
|
|
487
521
|
| Preset | Parameters (defaults) | Definition |
|
|
488
522
|
| --- | --- | --- |
|
|
489
|
-
| `uniform` | `reflectance` (0.18) | One reflectance
|
|
523
|
+
| `uniform` | `reflectance` (0.18) | One reflectance, caller spectrum or built-in colour patch everywhere. |
|
|
490
524
|
| `slanted-edge` | `angleDegrees` (5), `darkReflectance` (0.15), `lightReflectance` (0.6) | An edge through the centre, tilted clockwise from vertical: light where u·cos θ − v·sin θ ≥ 0. The geometry follows the slanted-edge method described by Burns (2000), also used by ISO 12233 (a few degrees of slant, 4:1 contrast by default). |
|
|
491
525
|
| `siemens-star` | `cycles` (36), `radiusFraction` (0.9), `darkReflectance` (0.05), `lightReflectance` (0.8), `backgroundReflectance` (0.18) | Alternating sectors of angular width π/cycles inside radius `radiusFraction` × frame half-height. |
|
|
492
526
|
| `step-wedge` | `steps` (11), `minimumReflectance` (0.03), `maximumReflectance` (0.9), `spacing` (`equal-density`) | Equal-width vertical steps from left to right. Equal density spaces D = −log₁₀ R evenly; `linear` spaces R evenly. |
|
|
493
527
|
| `grey-ramp` | `minimumReflectance` (0.03), `maximumReflectance` (0.9) | Reflectance linear in u, represented by 1024 equal levels. |
|
|
494
528
|
| `checkerboard` | `squaresAcross` (8), `darkReflectance` (0.05), `lightReflectance` (0.8) | Square cells, light at the top-left. |
|
|
495
529
|
| `point-grid` | `columns` (5), `rows` (3), `radius` (0.01), `pointReflectance` (0.9), `backgroundReflectance` (0.02) | Small disks centred in equal cells, for PSF and defocus inspection. |
|
|
496
|
-
| `patches` | `patches` (required), `columns`, `gapFraction` (0.1), `backgroundReflectance` (0.18) | Caller reflectance per patch, row-major. Use it for a
|
|
530
|
+
| `patches` | `patches` (required), `columns`, `gapFraction` (0.1), `backgroundReflectance` (0.18) | Caller reflectance per patch, row-major. Use it for a chart with reflectance data you are entitled to use; the engine does not ship third-party chart data because its licence is unclear. |
|
|
531
|
+
| `color-patches` | `patchIds` (all 30), `columns` (min(6, count)), `gapFraction` (0.1), `backgroundReflectance` (0.18) | 2.14: Photivra's own 30 color patches (neutral ramp, strong and soft primaries and secondaries, skin tones, memory colors, two near the sRGB gamut edge) drawn like `patches`. Each is an analytic reflectance spectrum; see [Color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md) for the formula, the parameters and the code, name, CIELAB (D65) and sRGB of every patch. |
|
|
497
532
|
|
|
498
533
|
### Chart elements and composite charts (2.3)
|
|
499
534
|
|
package/docs/USAGE.md
CHANGED
|
@@ -7696,6 +7696,26 @@ console.log(star.assumptions.filter((a) => a.kind === "default").map((a) => a.id
|
|
|
7696
7696
|
console.log(star.request.siteInput(0).environment.optics.nominalFNumber);
|
|
7697
7697
|
```
|
|
7698
7698
|
|
|
7699
|
+
### Color charts (2.14)
|
|
7700
|
+
|
|
7701
|
+
The engine's own [color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md) are reflectance spectra lit by the capture's illuminant. Draw the
|
|
7702
|
+
whole set with the `color-patches` preset, or use one patch wherever a chart takes a reflectance spectrum, as
|
|
7703
|
+
`{ kind: "color-patch", patchId }`. `getColorPatchSet()` gives each patch's code, name, reason for being on the chart and reference CIELAB and sRGB
|
|
7704
|
+
under D65.
|
|
7705
|
+
|
|
7706
|
+
```ts
|
|
7707
|
+
import { getColorPatchSet, simulateCapture } from "@photivra/engine";
|
|
7708
|
+
|
|
7709
|
+
const tungsten = await simulateCapture({
|
|
7710
|
+
scene: { preset: "color-patches", patchIds: ["neutral-4", "red", "skin-medium", "sky-blue"], columns: 4 },
|
|
7711
|
+
illuminant: "cie-a",
|
|
7712
|
+
sensor: { spectralStepNanometers: 5 },
|
|
7713
|
+
output: { width: 64, height: 16 }
|
|
7714
|
+
});
|
|
7715
|
+
const sky = getColorPatchSet().patches.find((p) => p.patchId === "sky-blue");
|
|
7716
|
+
console.log(tungsten.scene.parameters.patchSet, sky?.code, sky?.name, sky?.reference.cielabD65); // "PV-M1", "Midday Sky"
|
|
7717
|
+
```
|
|
7718
|
+
|
|
7699
7719
|
In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relations S = 10/H and H = 0.65·L·t/N²) through
|
|
7700
7720
|
`resolveAperturePriorityExposureMode()`.
|
|
7701
7721
|
`exposure.standardExposureDeviationStops` is the exposure used minus the standard exposure for the metered luminance L, in stops,
|
|
@@ -7703,7 +7723,21 @@ log₂(N²/t) − log₂(0.65·L·S/10) from the same relations: positive is dar
|
|
|
7703
7723
|
mode it is minus the compensation plus any limit the resolver hit; it is `null` only for a scene with no metered luminance.
|
|
7704
7724
|
`exposure.standardExposureIlluminanceLux` is the illuminance at which those settings give the standard exposure, E·2^Δ for the
|
|
7705
7725
|
request's illuminance E (a reflective scene's metered luminance is proportional to E); `null` for a black scene or a
|
|
7706
|
-
`radiance-function` scene.
|
|
7726
|
+
`radiance-function` scene. `exposure.greyCardDeviationStops` (2.14) is the deviation from the standard exposure of an 18 % grey
|
|
7727
|
+
card under that illuminance, log₂(L_ref/(0.18·E/π)): incident metering, independent of the scene's reflectances, exactly 0 at
|
|
7728
|
+
the defaults. The automatic modes meter the scene by default; `exposure.metering: "grey-card"` makes them (and Auto ISO) meter
|
|
7729
|
+
that grey card instead, so they land on a grey-card deviation of 0. The display view's `exposure` carries the same values.
|
|
7730
|
+
|
|
7731
|
+
```ts
|
|
7732
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7733
|
+
|
|
7734
|
+
const chart = await simulateCapture({
|
|
7735
|
+
scene: { preset: "uniform", reflectance: 0.9 },
|
|
7736
|
+
exposure: { mode: "aperture-priority", metering: "grey-card" },
|
|
7737
|
+
output: { width: 8, height: 8 }
|
|
7738
|
+
});
|
|
7739
|
+
console.log(chart.exposure.metering, chart.exposure.settings.shutterSeconds, chart.exposure.greyCardDeviationStops);
|
|
7740
|
+
```
|
|
7707
7741
|
|
|
7708
7742
|
With `exposure.iso: "auto"` (#516) the ISO is chosen too, in every mode, by the resolvers' Auto ISO branches
|
|
7709
7743
|
([Priority modes with Auto ISO](#priority-modes-with-auto-iso)). Aperture priority then needs the slowest shutter
|
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.14.1" as const
|
|
15
15
|
```
|