@photivra/engine 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,3 +53,65 @@ export interface SubjectFraming {
53
53
  }
54
54
  /** The subject distance that fits a chart of a given physical size to the frame, with magnification, coverage and the minimum-focus check (2.3). */
55
55
  export declare function calculateSubjectDistanceForFraming(input: CalculateSubjectDistanceForFramingInput): SubjectFraming;
56
+ export interface CalculateChartFramingInput {
57
+ /** Active sensor (or simulated frame) size, mm: for `simulateCapture`, output width × height × pixel pitch. */
58
+ sensor: {
59
+ widthMm: number;
60
+ heightMm: number;
61
+ };
62
+ /** Physical focal length, mm. Give exactly one of this and `equivalentFocalLengthMm`. */
63
+ focalLengthMm?: number;
64
+ /** Full-frame-equivalent focal length (diagonal crop factor); a generic prime's value also reports that lens (as `lens.prime`). */
65
+ equivalentFocalLengthMm?: number;
66
+ /** Chart size as for `simulateCapture` `chartSize`: heightM defaults to widthM × the sensor aspect (height / width). */
67
+ chart: {
68
+ widthM: number;
69
+ heightM?: number;
70
+ };
71
+ /** Distance from the lens to the chart plane, metres; more than the focal length. */
72
+ subjectDistanceM: number;
73
+ /** Focus distance, metres (as `lens.focusDistanceM`); default the subject distance. More than the focal length. */
74
+ focusDistanceM?: number;
75
+ }
76
+ export interface ChartFraming {
77
+ subjectDistanceM: number;
78
+ focusDistanceM: number;
79
+ /** Thin-lens image distance at the focus distance, f·s/(s − f), mm. */
80
+ imageDistanceMm: number;
81
+ /** Image size ÷ object size on the chart plane, v/d: f/(d − f) when focused on the chart. */
82
+ magnification: number;
83
+ focalLengthMm: number;
84
+ equivalentFocalLengthMm: number;
85
+ cropFactor35Mm: number;
86
+ chart: {
87
+ widthM: number;
88
+ heightM: number;
89
+ };
90
+ /** Plane area the frame covers at the subject distance, metres: sensor side ÷ magnification. */
91
+ framedWidthM: number;
92
+ framedHeightM: number;
93
+ /** Fraction of the frame width and height the chart covers; above 1 means the chart is larger than the frame and cropped. */
94
+ coverage: {
95
+ frameWidthFraction: number;
96
+ frameHeightFraction: number;
97
+ };
98
+ /** The chart's image on the sensor, mm. */
99
+ chartImageMm: {
100
+ width: number;
101
+ height: number;
102
+ };
103
+ /** The generic prime of this equivalent focal length, when there is one. */
104
+ prime: GenericPrimeLens | null;
105
+ /** The generic prime rule max(0.10 m, f·(1 + 1/0.15)) at this focal length (the prime's own value when there is one). */
106
+ minimumFocusDistanceM: number;
107
+ /** True when the subject or focus distance is closer than the minimum focus distance (`simulateCapture` with `lens.prime` rejects it). */
108
+ belowMinimumFocus: boolean;
109
+ scientificStatus: "approximation";
110
+ assumptions: readonly SimulateCaptureAssumption[];
111
+ }
112
+ /**
113
+ * How much of the frame a chart of a given physical size covers at any subject distance (2.5): magnification, framed plane size,
114
+ * coverage fractions (above 1 when cropped), the chart's image size and the minimum-focus check. The same thin-lens framing as
115
+ * `simulateCapture` (`scene.framing`, `result.scene.chart`) and `calculateSubjectDistanceForFraming`.
116
+ */
117
+ export declare function calculateChartFraming(input: CalculateChartFramingInput): ChartFraming;
@@ -4,7 +4,10 @@
4
4
  * images at v = f·d/(d − f) with magnification m = v/d = f/(d − f), so the distance for a magnification m is
5
5
  * d = f·(1 + 1/m). This is the same framing the capture facade uses (`scene.framing`, `chartSize`), so a chart
6
6
  * placed at the returned distance with `chartSize` fills the frame as reported.
7
- * @see docs/QUICK_START.md, "Fitting a chart to the frame".
7
+ *
8
+ * 2.5: `calculateChartFraming` is the inverse question: at a given subject distance (and focus distance), how much of the
9
+ * frame does the chart cover? It uses the facade's own framing, m = v/d with v = f·s/(s − f) at the focus distance s.
10
+ * @see docs/QUICK_START.md, "Fitting a chart to the frame" and "Chart coverage at any distance (2.5)".
8
11
  */
9
12
  import { InvalidConfigurationError } from "../core/configuration-error.js";
10
13
  import { requireInputObject } from "../core/record-validation.js";
@@ -15,33 +18,40 @@ function positive(value, name) {
15
18
  throw new InvalidConfigurationError(name + " must be a positive finite number.");
16
19
  return value;
17
20
  }
18
- /** The subject distance that fits a chart of a given physical size to the frame, with magnification, coverage and the minimum-focus check (2.3). */
19
- export function calculateSubjectDistanceForFraming(input) {
20
- requireInputObject(input, "calculateSubjectDistanceForFraming input");
21
- for (const key of Object.keys(input))
22
- if (!["sensor", "focalLengthMm", "equivalentFocalLengthMm", "chart", "fit"].includes(key))
23
- throw new InvalidConfigurationError("calculateSubjectDistanceForFraming input has unknown field \"" + key + "\".");
24
- requireInputObject(input.sensor, "sensor");
25
- requireInputObject(input.chart, "chart");
26
- for (const key of Object.keys(input.sensor))
21
+ /** Shared input checks and lens resolution of the two framing functions. */
22
+ function resolveFramingInput(input, name, extraKeys) {
23
+ requireInputObject(input, name + " input");
24
+ const record = input;
25
+ for (const key of Object.keys(record))
26
+ if (!["sensor", "focalLengthMm", "equivalentFocalLengthMm", "chart", ...extraKeys].includes(key))
27
+ throw new InvalidConfigurationError(name + " input has unknown field \"" + key + "\".");
28
+ requireInputObject(record.sensor, "sensor");
29
+ requireInputObject(record.chart, "chart");
30
+ const sensor = record.sensor, chart = record.chart;
31
+ for (const key of Object.keys(sensor))
27
32
  if (key !== "widthMm" && key !== "heightMm")
28
33
  throw new InvalidConfigurationError("Unknown sensor field: " + key + ".");
29
- for (const key of Object.keys(input.chart))
34
+ for (const key of Object.keys(chart))
30
35
  if (key !== "widthM" && key !== "heightM")
31
36
  throw new InvalidConfigurationError("Unknown chart field: " + key + ".");
32
- const sensorW = positive(input.sensor.widthMm, "sensor.widthMm"), sensorH = positive(input.sensor.heightMm, "sensor.heightMm");
33
- if ((input.focalLengthMm === undefined) === (input.equivalentFocalLengthMm === undefined))
37
+ const sensorW = positive(sensor.widthMm, "sensor.widthMm"), sensorH = positive(sensor.heightMm, "sensor.heightMm");
38
+ if ((record.focalLengthMm === undefined) === (record.equivalentFocalLengthMm === undefined))
34
39
  throw new InvalidConfigurationError("Give exactly one of focalLengthMm and equivalentFocalLengthMm.");
35
40
  const crop = calculateEquivalentFocalLength35Mm({ focalLengthMm: 1, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }).value.cropFactor35Mm;
36
41
  const assumptions = [];
37
- const equivalent = input.equivalentFocalLengthMm === undefined ? positive(input.focalLengthMm, "focalLengthMm") * crop : positive(input.equivalentFocalLengthMm, "equivalentFocalLengthMm");
38
- const focalLengthMm = input.focalLengthMm === undefined ? equivalent / crop : input.focalLengthMm;
39
- const prime = GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM.includes(equivalent) && input.equivalentFocalLengthMm !== undefined ?
42
+ const equivalent = record.equivalentFocalLengthMm === undefined ? positive(record.focalLengthMm, "focalLengthMm") * crop : positive(record.equivalentFocalLengthMm, "equivalentFocalLengthMm");
43
+ const focalLengthMm = record.focalLengthMm === undefined ? equivalent / crop : record.focalLengthMm;
44
+ const prime = GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM.includes(equivalent) && record.equivalentFocalLengthMm !== undefined ?
40
45
  resolveGenericPrimeLens({ equivalentFocalLengthMm: equivalent, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }) : null;
41
- const widthM = positive(input.chart.widthM, "chart.widthM");
42
- const heightM = input.chart.heightM === undefined ? widthM * sensorH / sensorW : positive(input.chart.heightM, "chart.heightM");
43
- if (input.chart.heightM === undefined)
46
+ const widthM = positive(chart.widthM, "chart.widthM");
47
+ const heightM = chart.heightM === undefined ? widthM * sensorH / sensorW : positive(chart.heightM, "chart.heightM");
48
+ if (chart.heightM === undefined)
44
49
  assumptions.push({ id: "chart.heightM", kind: "default", value: heightM, explanation: "The chart has the sensor's shape: height = width × sensor height / sensor width." });
50
+ return { record, sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions };
51
+ }
52
+ /** The subject distance that fits a chart of a given physical size to the frame, with magnification, coverage and the minimum-focus check (2.3). */
53
+ export function calculateSubjectDistanceForFraming(input) {
54
+ const { sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions } = resolveFramingInput(input, "calculateSubjectDistanceForFraming", ["fit"]);
45
55
  const fit = input.fit ?? "contain";
46
56
  if (!["contain", "width", "height", "cover"].includes(fit))
47
57
  throw new InvalidConfigurationError("fit must be contain, width, height or cover.");
@@ -63,4 +73,37 @@ export function calculateSubjectDistanceForFraming(input) {
63
73
  coverage: { frameWidthFraction: widthM * 1000 * m / sensorW, frameHeightFraction: heightM * 1000 * m / sensorH }, prime, minimumFocusDistanceM,
64
74
  belowMinimumFocus: subjectDistanceM < minimumFocusDistanceM, scientificStatus: "approximation", assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a))) });
65
75
  }
76
+ /**
77
+ * How much of the frame a chart of a given physical size covers at any subject distance (2.5): magnification, framed plane size,
78
+ * coverage fractions (above 1 when cropped), the chart's image size and the minimum-focus check. The same thin-lens framing as
79
+ * `simulateCapture` (`scene.framing`, `result.scene.chart`) and `calculateSubjectDistanceForFraming`.
80
+ */
81
+ export function calculateChartFraming(input) {
82
+ const { record, sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions } = resolveFramingInput(input, "calculateChartFraming", ["subjectDistanceM", "focusDistanceM"]);
83
+ const subjectDistanceM = positive(record.subjectDistanceM, "subjectDistanceM");
84
+ const focusDistanceM = record.focusDistanceM === undefined ? subjectDistanceM : positive(record.focusDistanceM, "focusDistanceM");
85
+ if (record.focusDistanceM === undefined)
86
+ assumptions.push({ id: "focusDistanceM", kind: "default", value: subjectDistanceM, explanation: "The lens is focused on the chart plane." });
87
+ const f = focalLengthMm / 1000;
88
+ if (!(subjectDistanceM > f) || !(focusDistanceM > f))
89
+ throw new InvalidConfigurationError("subjectDistanceM and focusDistanceM must be more than the focal length (" + f + " m).");
90
+ // Written exactly as simulateCapture forms it, so the two agree to the last bit.
91
+ const imageDistanceMm = focalLengthMm * focusDistanceM * 1000 / (focusDistanceM * 1000 - focalLengthMm);
92
+ const magnification = imageDistanceMm / (subjectDistanceM * 1000);
93
+ const framedWidthM = sensorW * subjectDistanceM / imageDistanceMm, framedHeightM = sensorH * subjectDistanceM / imageDistanceMm;
94
+ const minimumFocusDistanceM = prime ? prime.minimumFocusDistanceM : calculateGenericPrimeMinimumFocusDistanceM(focalLengthMm);
95
+ assumptions.push({ id: "model.thin-lens-framing", kind: "model", value: { magnification, imageDistanceMm },
96
+ explanation: "Thin lens: image distance v = f·s/(s − f) at the focus distance s, magnification m = v/d on the chart plane at d, and the frame spans sensor side / m, " +
97
+ "the same framing simulateCapture uses. Distances are from the lens; pupil positions, focus breathing and distortion are not modelled." });
98
+ assumptions.push({ id: "minimumFocus", kind: "model", value: { minimumFocusDistanceM, rule: "max(0.10 m, f·(1 + 1/0.15))" },
99
+ explanation: "The generic prime family's minimum focus rule (a thin-lens approximation, not a real lens)" + (prime ? " for " + prime.lensId : " applied to this focal length") + "." });
100
+ if (record.equivalentFocalLengthMm !== undefined)
101
+ assumptions.push({ id: "focalLengthMm", kind: "derived", value: focalLengthMm,
102
+ explanation: "Equivalent focal length ÷ diagonal crop factor " + crop + " (calculateEquivalentFocalLength35Mm, 36 × 24 mm reference)." });
103
+ return Object.freeze({ subjectDistanceM, focusDistanceM, imageDistanceMm, magnification, focalLengthMm, equivalentFocalLengthMm: equivalent, cropFactor35Mm: crop,
104
+ chart: { widthM, heightM }, framedWidthM, framedHeightM, coverage: { frameWidthFraction: widthM / framedWidthM, frameHeightFraction: heightM / framedHeightM },
105
+ chartImageMm: { width: widthM * 1000 * magnification, height: heightM * 1000 * magnification }, prime, minimumFocusDistanceM,
106
+ belowMinimumFocus: subjectDistanceM < minimumFocusDistanceM || focusDistanceM < minimumFocusDistanceM, scientificStatus: "approximation",
107
+ assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a))) });
108
+ }
66
109
  //# sourceMappingURL=subject-framing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"subject-framing.js","sourceRoot":"","sources":["../../src/facade/subject-framing.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Subject distance for framing a chart (2.3). Thin lens: an object at distance d from a lens of focal length f\n * images at v = f·d/(d − f) with magnification m = v/d = f/(d − f), so the distance for a magnification m is\n * d = f·(1 + 1/m). This is the same framing the capture facade uses (`scene.framing`, `chartSize`), so a chart\n * placed at the returned distance with `chartSize` fills the frame as reported.\n * @see docs/QUICK_START.md, \"Fitting a chart to the frame\".\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateEquivalentFocalLength35Mm } from \"../camera/equivalent-focal-length.js\";\nimport { calculateGenericPrimeMinimumFocusDistanceM, resolveGenericPrimeLens, GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM, type GenericPrimeLens } from \"../equipment/generic-prime-lenses.js\";\nimport type { SimulateCaptureAssumption } from \"./simulate-capture.js\";\n\n/** How the chart fits the frame: `contain` (default; the whole chart is visible and its limiting side touches the frame), `width`, `height` or `cover` (the chart fills the frame and is cropped on its other side). */\nexport type SubjectFramingFit = \"contain\" | \"width\" | \"height\" | \"cover\";\n\nexport interface CalculateSubjectDistanceForFramingInput {\n /** Active sensor (or simulated frame) size, mm. */\n sensor: { widthMm: number; heightMm: number };\n /** Physical focal length, mm. Give exactly one of this and `equivalentFocalLengthMm`. */\n focalLengthMm?: number;\n /** Full-frame-equivalent focal length (diagonal crop factor); a generic prime's value also reports that lens. */\n equivalentFocalLengthMm?: number;\n /** Chart size as for `simulateCapture` `chartSize`: heightM defaults to widthM × the sensor aspect (height / width). */\n chart: { widthM: number; heightM?: number };\n fit?: SubjectFramingFit;\n}\n\nexport interface SubjectFraming {\n /** Distance from the lens to the chart plane, metres: f·(1 + 1/m). */\n subjectDistanceM: number;\n /** Image size ÷ object size, f/(d − f). */\n magnification: number;\n /** Thin-lens image distance f·(1 + m), mm. */\n imageDistanceMm: number;\n focalLengthMm: number;\n equivalentFocalLengthMm: number;\n cropFactor35Mm: number;\n chart: { widthM: number; heightM: number };\n fit: SubjectFramingFit;\n /** Plane area the frame covers at that distance, metres. */\n framedWidthM: number;\n framedHeightM: number;\n /** Fraction of the frame width and height the chart covers (1 on the fitted side; above 1 means cropped). */\n coverage: { frameWidthFraction: number; frameHeightFraction: number };\n /** The generic prime of this equivalent focal length, when there is one. */\n prime: GenericPrimeLens | null;\n /** The generic prime rule max(0.10 m, f·(1 + 1/0.15)) at this focal length (the prime's own value when there is one). */\n minimumFocusDistanceM: number;\n /** True when the fit distance is closer than the minimum focus distance: the chart cannot be framed this way. */\n belowMinimumFocus: boolean;\n scientificStatus: \"approximation\";\n assumptions: readonly SimulateCaptureAssumption[];\n}\n\nfunction positive(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1e7) throw new InvalidConfigurationError(name + \" must be a positive finite number.\");\n return value;\n}\n\n/** The subject distance that fits a chart of a given physical size to the frame, with magnification, coverage and the minimum-focus check (2.3). */\nexport function calculateSubjectDistanceForFraming(input: CalculateSubjectDistanceForFramingInput): SubjectFraming {\n requireInputObject(input, \"calculateSubjectDistanceForFraming input\");\n for (const key of Object.keys(input)) if (![\"sensor\", \"focalLengthMm\", \"equivalentFocalLengthMm\", \"chart\", \"fit\"].includes(key))\n throw new InvalidConfigurationError(\"calculateSubjectDistanceForFraming input has unknown field \\\"\" + key + \"\\\".\");\n requireInputObject(input.sensor, \"sensor\");\n requireInputObject(input.chart, \"chart\");\n for (const key of Object.keys(input.sensor)) if (key !== \"widthMm\" && key !== \"heightMm\") throw new InvalidConfigurationError(\"Unknown sensor field: \" + key + \".\");\n for (const key of Object.keys(input.chart)) if (key !== \"widthM\" && key !== \"heightM\") throw new InvalidConfigurationError(\"Unknown chart field: \" + key + \".\");\n const sensorW = positive(input.sensor.widthMm, \"sensor.widthMm\"), sensorH = positive(input.sensor.heightMm, \"sensor.heightMm\");\n if ((input.focalLengthMm === undefined) === (input.equivalentFocalLengthMm === undefined)) throw new InvalidConfigurationError(\"Give exactly one of focalLengthMm and equivalentFocalLengthMm.\");\n const crop = calculateEquivalentFocalLength35Mm({ focalLengthMm: 1, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }).value.cropFactor35Mm;\n const assumptions: SimulateCaptureAssumption[] = [];\n const equivalent = input.equivalentFocalLengthMm === undefined ? positive(input.focalLengthMm, \"focalLengthMm\") * crop : positive(input.equivalentFocalLengthMm, \"equivalentFocalLengthMm\");\n const focalLengthMm = input.focalLengthMm === undefined ? equivalent / crop : input.focalLengthMm;\n const prime = (GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM as readonly number[]).includes(equivalent) && input.equivalentFocalLengthMm !== undefined ?\n resolveGenericPrimeLens({ equivalentFocalLengthMm: equivalent, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }) : null;\n const widthM = positive(input.chart.widthM, \"chart.widthM\");\n const heightM = input.chart.heightM === undefined ? widthM * sensorH / sensorW : positive(input.chart.heightM, \"chart.heightM\");\n if (input.chart.heightM === undefined) assumptions.push({ id: \"chart.heightM\", kind: \"default\", value: heightM, explanation: \"The chart has the sensor's shape: height = width × sensor height / sensor width.\" });\n const fit = input.fit ?? \"contain\";\n if (![\"contain\", \"width\", \"height\", \"cover\"].includes(fit)) throw new InvalidConfigurationError(\"fit must be contain, width, height or cover.\");\n if (input.fit === undefined) assumptions.push({ id: \"fit\", kind: \"default\", value: \"contain\", explanation: \"The whole chart is visible: the side whose chart-to-sensor ratio is larger (the limiting side) fills the frame.\" });\n // Magnification that makes each chart side exactly fill the matching sensor side.\n const mWidth = sensorW / (widthM * 1000), mHeight = sensorH / (heightM * 1000);\n const m = fit === \"width\" ? mWidth : fit === \"height\" ? mHeight : fit === \"contain\" ? Math.min(mWidth, mHeight) : Math.max(mWidth, mHeight);\n const f = focalLengthMm / 1000, subjectDistanceM = f * (1 + 1 / m), minimumFocusDistanceM = prime ? prime.minimumFocusDistanceM : calculateGenericPrimeMinimumFocusDistanceM(focalLengthMm);\n assumptions.push({ id: \"model.thin-lens-framing\", kind: \"model\", value: { magnification: m, subjectDistanceM },\n explanation: \"Thin lens focused on the chart plane: d = f·(1 + 1/m), image distance v = f·(1 + m), the same framing simulateCapture uses. Distances are from the lens; pupil positions, focus breathing and distortion are not modelled.\" });\n assumptions.push({ id: \"minimumFocus\", kind: \"model\", value: { minimumFocusDistanceM, rule: \"max(0.10 m, f·(1 + 1/0.15))\" },\n explanation: \"The generic prime family's minimum focus rule (a thin-lens approximation, not a real lens)\" + (prime ? \" for \" + prime.lensId : \" applied to this focal length\") + \".\" });\n if (input.equivalentFocalLengthMm !== undefined) assumptions.push({ id: \"focalLengthMm\", kind: \"derived\", value: focalLengthMm,\n explanation: \"Equivalent focal length ÷ diagonal crop factor \" + crop + \" (calculateEquivalentFocalLength35Mm, 36 × 24 mm reference).\" });\n return Object.freeze({ subjectDistanceM, magnification: m, imageDistanceMm: focalLengthMm * (1 + m), focalLengthMm, equivalentFocalLengthMm: equivalent, cropFactor35Mm: crop,\n chart: { widthM, heightM }, fit, framedWidthM: sensorW / 1000 / m, framedHeightM: sensorH / 1000 / m,\n coverage: { frameWidthFraction: widthM * 1000 * m / sensorW, frameHeightFraction: heightM * 1000 * m / sensorH }, prime, minimumFocusDistanceM,\n belowMinimumFocus: subjectDistanceM < minimumFocusDistanceM, scientificStatus: \"approximation\" as const, assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a))) });\n}\n"]}
1
+ {"version":3,"file":"subject-framing.js","sourceRoot":"","sources":["../../src/facade/subject-framing.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Subject distance for framing a chart (2.3). Thin lens: an object at distance d from a lens of focal length f\n * images at v = f·d/(d − f) with magnification m = v/d = f/(d − f), so the distance for a magnification m is\n * d = f·(1 + 1/m). This is the same framing the capture facade uses (`scene.framing`, `chartSize`), so a chart\n * placed at the returned distance with `chartSize` fills the frame as reported.\n *\n * 2.5: `calculateChartFraming` is the inverse question: at a given subject distance (and focus distance), how much of the\n * frame does the chart cover? It uses the facade's own framing, m = v/d with v = f·s/(s − f) at the focus distance s.\n * @see docs/QUICK_START.md, \"Fitting a chart to the frame\" and \"Chart coverage at any distance (2.5)\".\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateEquivalentFocalLength35Mm } from \"../camera/equivalent-focal-length.js\";\nimport { calculateGenericPrimeMinimumFocusDistanceM, resolveGenericPrimeLens, GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM, type GenericPrimeLens } from \"../equipment/generic-prime-lenses.js\";\nimport type { SimulateCaptureAssumption } from \"./simulate-capture.js\";\n\n/** How the chart fits the frame: `contain` (default; the whole chart is visible and its limiting side touches the frame), `width`, `height` or `cover` (the chart fills the frame and is cropped on its other side). */\nexport type SubjectFramingFit = \"contain\" | \"width\" | \"height\" | \"cover\";\n\nexport interface CalculateSubjectDistanceForFramingInput {\n /** Active sensor (or simulated frame) size, mm. */\n sensor: { widthMm: number; heightMm: number };\n /** Physical focal length, mm. Give exactly one of this and `equivalentFocalLengthMm`. */\n focalLengthMm?: number;\n /** Full-frame-equivalent focal length (diagonal crop factor); a generic prime's value also reports that lens. */\n equivalentFocalLengthMm?: number;\n /** Chart size as for `simulateCapture` `chartSize`: heightM defaults to widthM × the sensor aspect (height / width). */\n chart: { widthM: number; heightM?: number };\n fit?: SubjectFramingFit;\n}\n\nexport interface SubjectFraming {\n /** Distance from the lens to the chart plane, metres: f·(1 + 1/m). */\n subjectDistanceM: number;\n /** Image size ÷ object size, f/(d − f). */\n magnification: number;\n /** Thin-lens image distance f·(1 + m), mm. */\n imageDistanceMm: number;\n focalLengthMm: number;\n equivalentFocalLengthMm: number;\n cropFactor35Mm: number;\n chart: { widthM: number; heightM: number };\n fit: SubjectFramingFit;\n /** Plane area the frame covers at that distance, metres. */\n framedWidthM: number;\n framedHeightM: number;\n /** Fraction of the frame width and height the chart covers (1 on the fitted side; above 1 means cropped). */\n coverage: { frameWidthFraction: number; frameHeightFraction: number };\n /** The generic prime of this equivalent focal length, when there is one. */\n prime: GenericPrimeLens | null;\n /** The generic prime rule max(0.10 m, f·(1 + 1/0.15)) at this focal length (the prime's own value when there is one). */\n minimumFocusDistanceM: number;\n /** True when the fit distance is closer than the minimum focus distance: the chart cannot be framed this way. */\n belowMinimumFocus: boolean;\n scientificStatus: \"approximation\";\n assumptions: readonly SimulateCaptureAssumption[];\n}\n\nfunction positive(value: unknown, name: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1e7) throw new InvalidConfigurationError(name + \" must be a positive finite number.\");\n return value;\n}\n\n/** Shared input checks and lens resolution of the two framing functions. */\nfunction resolveFramingInput(input: unknown, name: string, extraKeys: readonly string[]): { record: Record<string, unknown>; sensorW: number; sensorH: number; crop: number;\n equivalent: number; focalLengthMm: number; prime: GenericPrimeLens | null; widthM: number; heightM: number; assumptions: SimulateCaptureAssumption[] } {\n requireInputObject(input, name + \" input\");\n const record = input as Record<string, unknown>;\n for (const key of Object.keys(record)) if (![\"sensor\", \"focalLengthMm\", \"equivalentFocalLengthMm\", \"chart\", ...extraKeys].includes(key))\n throw new InvalidConfigurationError(name + \" input has unknown field \\\"\" + key + \"\\\".\");\n requireInputObject(record.sensor, \"sensor\");\n requireInputObject(record.chart, \"chart\");\n const sensor = record.sensor as Record<string, unknown>, chart = record.chart as Record<string, unknown>;\n for (const key of Object.keys(sensor)) if (key !== \"widthMm\" && key !== \"heightMm\") throw new InvalidConfigurationError(\"Unknown sensor field: \" + key + \".\");\n for (const key of Object.keys(chart)) if (key !== \"widthM\" && key !== \"heightM\") throw new InvalidConfigurationError(\"Unknown chart field: \" + key + \".\");\n const sensorW = positive(sensor.widthMm, \"sensor.widthMm\"), sensorH = positive(sensor.heightMm, \"sensor.heightMm\");\n if ((record.focalLengthMm === undefined) === (record.equivalentFocalLengthMm === undefined)) throw new InvalidConfigurationError(\"Give exactly one of focalLengthMm and equivalentFocalLengthMm.\");\n const crop = calculateEquivalentFocalLength35Mm({ focalLengthMm: 1, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }).value.cropFactor35Mm;\n const assumptions: SimulateCaptureAssumption[] = [];\n const equivalent = record.equivalentFocalLengthMm === undefined ? positive(record.focalLengthMm, \"focalLengthMm\") * crop : positive(record.equivalentFocalLengthMm, \"equivalentFocalLengthMm\");\n const focalLengthMm = record.focalLengthMm === undefined ? equivalent / crop : record.focalLengthMm as number;\n const prime = (GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM as readonly number[]).includes(equivalent) && record.equivalentFocalLengthMm !== undefined ?\n resolveGenericPrimeLens({ equivalentFocalLengthMm: equivalent, activeImagingArea: { widthMm: sensorW, heightMm: sensorH } }) : null;\n const widthM = positive(chart.widthM, \"chart.widthM\");\n const heightM = chart.heightM === undefined ? widthM * sensorH / sensorW : positive(chart.heightM, \"chart.heightM\");\n if (chart.heightM === undefined) assumptions.push({ id: \"chart.heightM\", kind: \"default\", value: heightM, explanation: \"The chart has the sensor's shape: height = width × sensor height / sensor width.\" });\n return { record, sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions };\n}\n\n/** The subject distance that fits a chart of a given physical size to the frame, with magnification, coverage and the minimum-focus check (2.3). */\nexport function calculateSubjectDistanceForFraming(input: CalculateSubjectDistanceForFramingInput): SubjectFraming {\n const { sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions } = resolveFramingInput(input, \"calculateSubjectDistanceForFraming\", [\"fit\"]);\n const fit = input.fit ?? \"contain\";\n if (![\"contain\", \"width\", \"height\", \"cover\"].includes(fit)) throw new InvalidConfigurationError(\"fit must be contain, width, height or cover.\");\n if (input.fit === undefined) assumptions.push({ id: \"fit\", kind: \"default\", value: \"contain\", explanation: \"The whole chart is visible: the side whose chart-to-sensor ratio is larger (the limiting side) fills the frame.\" });\n // Magnification that makes each chart side exactly fill the matching sensor side.\n const mWidth = sensorW / (widthM * 1000), mHeight = sensorH / (heightM * 1000);\n const m = fit === \"width\" ? mWidth : fit === \"height\" ? mHeight : fit === \"contain\" ? Math.min(mWidth, mHeight) : Math.max(mWidth, mHeight);\n const f = focalLengthMm / 1000, subjectDistanceM = f * (1 + 1 / m), minimumFocusDistanceM = prime ? prime.minimumFocusDistanceM : calculateGenericPrimeMinimumFocusDistanceM(focalLengthMm);\n assumptions.push({ id: \"model.thin-lens-framing\", kind: \"model\", value: { magnification: m, subjectDistanceM },\n explanation: \"Thin lens focused on the chart plane: d = f·(1 + 1/m), image distance v = f·(1 + m), the same framing simulateCapture uses. Distances are from the lens; pupil positions, focus breathing and distortion are not modelled.\" });\n assumptions.push({ id: \"minimumFocus\", kind: \"model\", value: { minimumFocusDistanceM, rule: \"max(0.10 m, f·(1 + 1/0.15))\" },\n explanation: \"The generic prime family's minimum focus rule (a thin-lens approximation, not a real lens)\" + (prime ? \" for \" + prime.lensId : \" applied to this focal length\") + \".\" });\n if (input.equivalentFocalLengthMm !== undefined) assumptions.push({ id: \"focalLengthMm\", kind: \"derived\", value: focalLengthMm,\n explanation: \"Equivalent focal length ÷ diagonal crop factor \" + crop + \" (calculateEquivalentFocalLength35Mm, 36 × 24 mm reference).\" });\n return Object.freeze({ subjectDistanceM, magnification: m, imageDistanceMm: focalLengthMm * (1 + m), focalLengthMm, equivalentFocalLengthMm: equivalent, cropFactor35Mm: crop,\n chart: { widthM, heightM }, fit, framedWidthM: sensorW / 1000 / m, framedHeightM: sensorH / 1000 / m,\n coverage: { frameWidthFraction: widthM * 1000 * m / sensorW, frameHeightFraction: heightM * 1000 * m / sensorH }, prime, minimumFocusDistanceM,\n belowMinimumFocus: subjectDistanceM < minimumFocusDistanceM, scientificStatus: \"approximation\" as const, assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a))) });\n}\n\nexport interface CalculateChartFramingInput {\n /** Active sensor (or simulated frame) size, mm: for `simulateCapture`, output width × height × pixel pitch. */\n sensor: { widthMm: number; heightMm: number };\n /** Physical focal length, mm. Give exactly one of this and `equivalentFocalLengthMm`. */\n focalLengthMm?: number;\n /** Full-frame-equivalent focal length (diagonal crop factor); a generic prime's value also reports that lens (as `lens.prime`). */\n equivalentFocalLengthMm?: number;\n /** Chart size as for `simulateCapture` `chartSize`: heightM defaults to widthM × the sensor aspect (height / width). */\n chart: { widthM: number; heightM?: number };\n /** Distance from the lens to the chart plane, metres; more than the focal length. */\n subjectDistanceM: number;\n /** Focus distance, metres (as `lens.focusDistanceM`); default the subject distance. More than the focal length. */\n focusDistanceM?: number;\n}\n\nexport interface ChartFraming {\n subjectDistanceM: number;\n focusDistanceM: number;\n /** Thin-lens image distance at the focus distance, f·s/(s − f), mm. */\n imageDistanceMm: number;\n /** Image size ÷ object size on the chart plane, v/d: f/(d − f) when focused on the chart. */\n magnification: number;\n focalLengthMm: number;\n equivalentFocalLengthMm: number;\n cropFactor35Mm: number;\n chart: { widthM: number; heightM: number };\n /** Plane area the frame covers at the subject distance, metres: sensor side ÷ magnification. */\n framedWidthM: number;\n framedHeightM: number;\n /** Fraction of the frame width and height the chart covers; above 1 means the chart is larger than the frame and cropped. */\n coverage: { frameWidthFraction: number; frameHeightFraction: number };\n /** The chart's image on the sensor, mm. */\n chartImageMm: { width: number; height: number };\n /** The generic prime of this equivalent focal length, when there is one. */\n prime: GenericPrimeLens | null;\n /** The generic prime rule max(0.10 m, f·(1 + 1/0.15)) at this focal length (the prime's own value when there is one). */\n minimumFocusDistanceM: number;\n /** True when the subject or focus distance is closer than the minimum focus distance (`simulateCapture` with `lens.prime` rejects it). */\n belowMinimumFocus: boolean;\n scientificStatus: \"approximation\";\n assumptions: readonly SimulateCaptureAssumption[];\n}\n\n/**\n * How much of the frame a chart of a given physical size covers at any subject distance (2.5): magnification, framed plane size,\n * coverage fractions (above 1 when cropped), the chart's image size and the minimum-focus check. The same thin-lens framing as\n * `simulateCapture` (`scene.framing`, `result.scene.chart`) and `calculateSubjectDistanceForFraming`.\n */\nexport function calculateChartFraming(input: CalculateChartFramingInput): ChartFraming {\n const { record, sensorW, sensorH, crop, equivalent, focalLengthMm, prime, widthM, heightM, assumptions } = resolveFramingInput(input, \"calculateChartFraming\",\n [\"subjectDistanceM\", \"focusDistanceM\"]);\n const subjectDistanceM = positive(record.subjectDistanceM, \"subjectDistanceM\");\n const focusDistanceM = record.focusDistanceM === undefined ? subjectDistanceM : positive(record.focusDistanceM, \"focusDistanceM\");\n if (record.focusDistanceM === undefined) assumptions.push({ id: \"focusDistanceM\", kind: \"default\", value: subjectDistanceM, explanation: \"The lens is focused on the chart plane.\" });\n const f = focalLengthMm / 1000;\n if (!(subjectDistanceM > f) || !(focusDistanceM > f)) throw new InvalidConfigurationError(\"subjectDistanceM and focusDistanceM must be more than the focal length (\" + f + \" m).\");\n // Written exactly as simulateCapture forms it, so the two agree to the last bit.\n const imageDistanceMm = focalLengthMm * focusDistanceM * 1000 / (focusDistanceM * 1000 - focalLengthMm);\n const magnification = imageDistanceMm / (subjectDistanceM * 1000);\n const framedWidthM = sensorW * subjectDistanceM / imageDistanceMm, framedHeightM = sensorH * subjectDistanceM / imageDistanceMm;\n const minimumFocusDistanceM = prime ? prime.minimumFocusDistanceM : calculateGenericPrimeMinimumFocusDistanceM(focalLengthMm);\n assumptions.push({ id: \"model.thin-lens-framing\", kind: \"model\", value: { magnification, imageDistanceMm },\n explanation: \"Thin lens: image distance v = f·s/(s − f) at the focus distance s, magnification m = v/d on the chart plane at d, and the frame spans sensor side / m, \" +\n \"the same framing simulateCapture uses. Distances are from the lens; pupil positions, focus breathing and distortion are not modelled.\" });\n assumptions.push({ id: \"minimumFocus\", kind: \"model\", value: { minimumFocusDistanceM, rule: \"max(0.10 m, f·(1 + 1/0.15))\" },\n explanation: \"The generic prime family's minimum focus rule (a thin-lens approximation, not a real lens)\" + (prime ? \" for \" + prime.lensId : \" applied to this focal length\") + \".\" });\n if (record.equivalentFocalLengthMm !== undefined) assumptions.push({ id: \"focalLengthMm\", kind: \"derived\", value: focalLengthMm,\n explanation: \"Equivalent focal length ÷ diagonal crop factor \" + crop + \" (calculateEquivalentFocalLength35Mm, 36 × 24 mm reference).\" });\n return Object.freeze({ subjectDistanceM, focusDistanceM, imageDistanceMm, magnification, focalLengthMm, equivalentFocalLengthMm: equivalent, cropFactor35Mm: crop,\n chart: { widthM, heightM }, framedWidthM, framedHeightM, coverage: { frameWidthFraction: widthM / framedWidthM, frameHeightFraction: heightM / framedHeightM },\n chartImageMm: { width: widthM * 1000 * magnification, height: heightM * 1000 * magnification }, prime, minimumFocusDistanceM,\n belowMinimumFocus: subjectDistanceM < minimumFocusDistanceM || focusDistanceM < minimumFocusDistanceM, scientificStatus: \"approximation\" as const,\n assumptions: Object.freeze(assumptions.map((a) => Object.freeze(a))) });\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -200,7 +200,7 @@ export { calculateSensorApertureRays, type CalculateSensorApertureRaysInput, typ
200
200
  export { SIMULATE_CAPTURE_FACADE_VERSION, SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX, SIMULATE_CAPTURE_COST_OPTIONS, simulateCapture, type SimulateCaptureOptions, type SimulateCaptureResult, type SimulateCaptureAssumption, type SimulateCaptureProgress, type SimulateCaptureExposureMode, type SimulateCaptureOutputFormat, type SimulateCaptureWhiteBalance, type SimulateCaptureColorEncoding, type SimulateCaptureDemosaic, type SimulateCaptureBorderHandling, type SimulateCaptureLensDistortion, type SimulateCaptureMotion, type SimulateCaptureSensorAntiAliasing, type SimulateCaptureCostOption, type SimulateCapturePixelAperture, SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES, simulateIdealReference, type IdealReferenceResult } from "./facade/simulate-capture.js";
201
201
  export { type OutputTextMetadata } from "./output/text-metadata.js";
202
202
  export { GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM, GENERIC_PRIME_THIRD_STOP_F_NUMBERS, GENERIC_PRIME_MINIMUM_FOCUS, resolveGenericPrimeLens, calculateGenericPrimeMinimumFocusDistanceM, type GenericPrimeLens, type GenericPrimeEquivalentFocalLengthMm } from "./equipment/generic-prime-lenses.js";
203
- export { calculateSubjectDistanceForFraming, type CalculateSubjectDistanceForFramingInput, type SubjectFraming, type SubjectFramingFit } from "./facade/subject-framing.js";
203
+ export { calculateChartFraming, calculateSubjectDistanceForFraming, type CalculateChartFramingInput, type CalculateSubjectDistanceForFramingInput, type ChartFraming, type SubjectFraming, type SubjectFramingFit } from "./facade/subject-framing.js";
204
204
  export { CHART_SCENE_PRESETS, CHART_SCENE_DEFAULTS, CHART_SPECTRAL_RANGE_NANOMETERS, resolveChartScene, calculateStepWedgeReflectances, calculatePointGridCenters, type ChartScene, type ChartScenePresetId, type ChartReflectance, type ResolvedChartScene, type UniformChartScene, type SlantedEdgeChartScene, type SiemensStarChartScene, type StepWedgeChartScene, type GreyRampChartScene, type CheckerboardChartScene, type PointGridChartScene, type PatchChartScene, CHART_CONTINUOUS_LEVELS, calculateBarGroupTargets, calculateSlantedSquareEdges, type DeadLeavesChartScene, type BarGroupsChartScene, type HyperbolicWedgeChartScene, type ZonePlateChartScene, type SlantedSquaresChartScene, type LineGridChartScene, type FiducialChartScene, type CompositeChartScene, type CompositeChartElement, type ChartElementScene, type ChartRectangle, type ChartBarGroupTarget, type ChartSlantedSquare } from "./facade/chart-scenes.js";
205
205
  export { PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W, resolveCaptureIlluminant, createPlanarSceneRadianceProvider, CHART_SIZE_DEFAULT_SURROUND_REFLECTANCE, type ChartSizeOption, type CaptureIlluminant, type ResolvedCaptureIlluminant, type CallerReflectanceScene, type CallerRadianceScene, type ScenePlanePoint, type PlanarSceneRadianceProvider } from "./facade/scene-radiance.js";
206
206
  export { CHART_RADIANCE_BATCH_SCHEMA_VERSION, CHART_RADIANCE_BATCH_LIMITS, calculateChartRadianceBatchPlan, calculateChartRadianceBatch, mapEnvironmentDirectionsToChartPoints, type ChartRadianceBatchInput, type ChartRadianceBatchPlan } from "./facade/chart-radiance-batch.js";
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ export { calculateSensorApertureRays } from "./optics/sensor-aperture-rays.js";
179
179
  export { SIMULATE_CAPTURE_FACADE_VERSION, SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX, SIMULATE_CAPTURE_COST_OPTIONS, simulateCapture, SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES, simulateIdealReference } from "./facade/simulate-capture.js";
180
180
  export {} from "./output/text-metadata.js";
181
181
  export { GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM, GENERIC_PRIME_THIRD_STOP_F_NUMBERS, GENERIC_PRIME_MINIMUM_FOCUS, resolveGenericPrimeLens, calculateGenericPrimeMinimumFocusDistanceM } from "./equipment/generic-prime-lenses.js";
182
- export { calculateSubjectDistanceForFraming } from "./facade/subject-framing.js";
182
+ export { calculateChartFraming, calculateSubjectDistanceForFraming } from "./facade/subject-framing.js";
183
183
  export { CHART_SCENE_PRESETS, CHART_SCENE_DEFAULTS, CHART_SPECTRAL_RANGE_NANOMETERS, resolveChartScene, calculateStepWedgeReflectances, calculatePointGridCenters, CHART_CONTINUOUS_LEVELS, calculateBarGroupTargets, calculateSlantedSquareEdges } from "./facade/chart-scenes.js";
184
184
  export { PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W, resolveCaptureIlluminant, createPlanarSceneRadianceProvider, CHART_SIZE_DEFAULT_SURROUND_REFLECTANCE } from "./facade/scene-radiance.js";
185
185
  export { CHART_RADIANCE_BATCH_SCHEMA_VERSION, CHART_RADIANCE_BATCH_LIMITS, calculateChartRadianceBatchPlan, calculateChartRadianceBatch, mapEnvironmentDirectionsToChartPoints } from "./facade/chart-radiance-batch.js";