@photivra/engine 0.1.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.
Files changed (97) hide show
  1. package/CITATION.cff +23 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +9 -0
  4. package/README.md +235 -0
  5. package/dist/camera/field-of-view.d.ts +37 -0
  6. package/dist/camera/field-of-view.d.ts.map +1 -0
  7. package/dist/camera/field-of-view.js +47 -0
  8. package/dist/camera/field-of-view.js.map +1 -0
  9. package/dist/camera/projected-object-size.d.ts +34 -0
  10. package/dist/camera/projected-object-size.d.ts.map +1 -0
  11. package/dist/camera/projected-object-size.js +57 -0
  12. package/dist/camera/projected-object-size.js.map +1 -0
  13. package/dist/core/calculation-result.d.ts +125 -0
  14. package/dist/core/calculation-result.d.ts.map +1 -0
  15. package/dist/core/calculation-result.js +175 -0
  16. package/dist/core/calculation-result.js.map +1 -0
  17. package/dist/core/validation.d.ts +30 -0
  18. package/dist/core/validation.d.ts.map +1 -0
  19. package/dist/core/validation.js +45 -0
  20. package/dist/core/validation.js.map +1 -0
  21. package/dist/exposure/exposure.d.ts +50 -0
  22. package/dist/exposure/exposure.d.ts.map +1 -0
  23. package/dist/exposure/exposure.js +62 -0
  24. package/dist/exposure/exposure.js.map +1 -0
  25. package/dist/index.d.ts +28 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +27 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/motion/projected-motion.d.ts +41 -0
  30. package/dist/motion/projected-motion.d.ts.map +1 -0
  31. package/dist/motion/projected-motion.js +84 -0
  32. package/dist/motion/projected-motion.js.map +1 -0
  33. package/dist/optics/aperture.d.ts +39 -0
  34. package/dist/optics/aperture.d.ts.map +1 -0
  35. package/dist/optics/aperture.js +62 -0
  36. package/dist/optics/aperture.js.map +1 -0
  37. package/dist/optics/circle-of-confusion.d.ts +23 -0
  38. package/dist/optics/circle-of-confusion.d.ts.map +1 -0
  39. package/dist/optics/circle-of-confusion.js +32 -0
  40. package/dist/optics/circle-of-confusion.js.map +1 -0
  41. package/dist/optics/depth-of-field.d.ts +53 -0
  42. package/dist/optics/depth-of-field.d.ts.map +1 -0
  43. package/dist/optics/depth-of-field.js +81 -0
  44. package/dist/optics/depth-of-field.js.map +1 -0
  45. package/dist/optics/diffraction.d.ts +26 -0
  46. package/dist/optics/diffraction.d.ts.map +1 -0
  47. package/dist/optics/diffraction.js +21 -0
  48. package/dist/optics/diffraction.js.map +1 -0
  49. package/dist/optics/thin-lens.d.ts +30 -0
  50. package/dist/optics/thin-lens.d.ts.map +1 -0
  51. package/dist/optics/thin-lens.js +33 -0
  52. package/dist/optics/thin-lens.js.map +1 -0
  53. package/dist/output/crop.d.ts +27 -0
  54. package/dist/output/crop.d.ts.map +1 -0
  55. package/dist/output/crop.js +26 -0
  56. package/dist/output/crop.js.map +1 -0
  57. package/dist/output/subject-framing-crop.d.ts +36 -0
  58. package/dist/output/subject-framing-crop.d.ts.map +1 -0
  59. package/dist/output/subject-framing-crop.js +42 -0
  60. package/dist/output/subject-framing-crop.js.map +1 -0
  61. package/dist/schema/camera.d.ts +34 -0
  62. package/dist/schema/camera.d.ts.map +1 -0
  63. package/dist/schema/camera.js +3 -0
  64. package/dist/schema/camera.js.map +1 -0
  65. package/dist/schema/scene.d.ts +69 -0
  66. package/dist/schema/scene.d.ts.map +1 -0
  67. package/dist/schema/scene.js +3 -0
  68. package/dist/schema/scene.js.map +1 -0
  69. package/dist/schema/validation.d.ts +25 -0
  70. package/dist/schema/validation.d.ts.map +1 -0
  71. package/dist/schema/validation.js +180 -0
  72. package/dist/schema/validation.js.map +1 -0
  73. package/dist/sensor/pixel-pitch.d.ts +21 -0
  74. package/dist/sensor/pixel-pitch.d.ts.map +1 -0
  75. package/dist/sensor/pixel-pitch.js +19 -0
  76. package/dist/sensor/pixel-pitch.js.map +1 -0
  77. package/dist/sensor/signal-noise.d.ts +37 -0
  78. package/dist/sensor/signal-noise.d.ts.map +1 -0
  79. package/dist/sensor/signal-noise.js +53 -0
  80. package/dist/sensor/signal-noise.js.map +1 -0
  81. package/dist/simulation/poc-simulation.d.ts +264 -0
  82. package/dist/simulation/poc-simulation.d.ts.map +1 -0
  83. package/dist/simulation/poc-simulation.js +449 -0
  84. package/dist/simulation/poc-simulation.js.map +1 -0
  85. package/dist/stabilization/camera-shake.d.ts +41 -0
  86. package/dist/stabilization/camera-shake.d.ts.map +1 -0
  87. package/dist/stabilization/camera-shake.js +79 -0
  88. package/dist/stabilization/camera-shake.js.map +1 -0
  89. package/docs/API_STYLE.md +65 -0
  90. package/docs/ARCHITECTURE.md +51 -0
  91. package/docs/MOTION_AND_SIGNAL.md +53 -0
  92. package/docs/PHYSICS_FOUNDATION.md +92 -0
  93. package/docs/POC_API.md +210 -0
  94. package/docs/PROVENANCE.md +87 -0
  95. package/docs/STABILIZATION.md +78 -0
  96. package/docs/USAGE.md +538 -0
  97. package/package.json +91 -0
package/docs/USAGE.md ADDED
@@ -0,0 +1,538 @@
1
+ # Usage Guide
2
+
3
+ This guide shows how to call the capabilities exported by the root `@photivra/engine` package.
4
+
5
+ The package is ESM-only. Primitive scientific calculations generally return a `CalculationResult<T>`:
6
+
7
+ ```ts
8
+ const result = calculateSomething(input);
9
+
10
+ console.log(result.value);
11
+ console.log(result.provenance);
12
+ console.log(result.quality);
13
+ ```
14
+
15
+ `quality` is optional and is omitted when the engine does not have defensible quantified uncertainty/accuracy metadata to report.
16
+
17
+ ## Field of view
18
+
19
+ Use `calculateFieldOfView()` for one sensor dimension at a time.
20
+
21
+ ```ts
22
+ import { calculateFieldOfView } from "@photivra/engine";
23
+
24
+ const horizontal = calculateFieldOfView({
25
+ focalLengthMm: 50,
26
+ sensorDimensionMm: 36
27
+ });
28
+
29
+ console.log(horizontal.value.degrees);
30
+ ```
31
+
32
+ To use the ideal thin-lens sensor plane at a selected focus distance, provide `focusDistanceM`:
33
+
34
+ ```ts
35
+ const focused = calculateFieldOfView({
36
+ focalLengthMm: 200,
37
+ sensorDimensionMm: 36,
38
+ focusDistanceM: 22
39
+ });
40
+
41
+ console.log(focused.value.projectionDistanceMm);
42
+ console.log(focused.value.degrees);
43
+ ```
44
+
45
+ Without `focusDistanceM`, nominal focal length is used as the infinity-focus/pinhole projection distance. Neither mode models real-lens distortion or focus breathing.
46
+
47
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#rectilinear-field-of-view).
48
+
49
+ ## Thin-lens image distance and magnification
50
+
51
+ Use `calculateThinLensImageDistance()` to calculate ideal Gaussian thin-lens image distance for an object/focus plane.
52
+
53
+ ```ts
54
+ import { calculateThinLensImageDistance } from "@photivra/engine";
55
+
56
+ const projection = calculateThinLensImageDistance({
57
+ focalLengthMm: 200,
58
+ objectDistanceM: 22
59
+ });
60
+
61
+ console.log(projection.value.imageDistanceMm);
62
+ console.log(projection.value.magnification);
63
+ console.log(projection.value.infinityProjectionScale);
64
+ ```
65
+
66
+ This is a paraxial ideal-lens model. It does not model real-lens focus breathing, pupil magnification, principal-plane movement, or aberrations.
67
+
68
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
69
+
70
+ ## Projected object size and sensor sampling
71
+
72
+ Use `calculateProjectedObjectSize()` for a fronto-parallel object with known physical dimensions and distance.
73
+
74
+ ```ts
75
+ import { calculateProjectedObjectSize } from "@photivra/engine";
76
+
77
+ const subject = calculateProjectedObjectSize({
78
+ focalLengthMm: 200,
79
+ objectWidthM: 0.7,
80
+ objectHeightM: 1.8,
81
+ distanceM: 22,
82
+ focusDistanceM: 22,
83
+ pixelPitchMicrometers: 6
84
+ });
85
+
86
+ console.log(subject.value.widthMm);
87
+ console.log(subject.value.heightMm);
88
+ console.log(subject.value.widthPixels);
89
+ console.log(subject.value.heightPixels);
90
+ ```
91
+
92
+ Omit `pixelPitchMicrometers` when only physical image-plane dimensions are needed.
93
+
94
+ The object plane is assumed to be fronto-parallel to the sensor.
95
+
96
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
97
+
98
+ ## Depth of field and defocus
99
+
100
+ Use `calculateDepthOfField()` for conventional geometric near/far limits:
101
+
102
+ ```ts
103
+ import { calculateDepthOfField } from "@photivra/engine";
104
+
105
+ const dof = calculateDepthOfField({
106
+ focalLengthMm: 85,
107
+ aperture: 2.8,
108
+ focusDistanceM: 10,
109
+ circleOfConfusionMm: 0.03
110
+ });
111
+
112
+ console.log(dof.value.nearLimitM);
113
+ console.log(dof.value.farLimitM); // null means infinity
114
+ console.log(dof.value.totalDepthOfFieldM);
115
+ ```
116
+
117
+ Use `calculateDefocusCircle()` when you need the geometric blur-circle diameter for a specific subject plane:
118
+
119
+ ```ts
120
+ import { calculateDefocusCircle } from "@photivra/engine";
121
+
122
+ const blur = calculateDefocusCircle({
123
+ focalLengthMm: 85,
124
+ aperture: 2.8,
125
+ focusDistanceM: 10,
126
+ subjectDistanceM: 20
127
+ });
128
+
129
+ console.log(blur.value.diameterMm);
130
+ ```
131
+
132
+ The circle-of-confusion criterion used for depth-of-field is caller supplied. These are ideal geometric thin-lens models and do not incorporate diffraction or real-lens aberrations into the DOF criterion.
133
+
134
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#thin-lens-focus-and-depth-of-field).
135
+
136
+ ## Circular-aperture diffraction
137
+
138
+ Use `calculateAiryDisk()` for the ideal monochromatic circular-pupil first-zero Airy diameter:
139
+
140
+ ```ts
141
+ import { calculateAiryDisk } from "@photivra/engine";
142
+
143
+ const diffraction = calculateAiryDisk({
144
+ aperture: 8,
145
+ wavelengthNm: 550
146
+ });
147
+
148
+ console.log(diffraction.value.firstZeroDiameterMicrometers);
149
+ ```
150
+
151
+ This calculation uses `d = 2.44 λ N`. It is not a polygon-aperture diffraction PSF.
152
+
153
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#diffraction).
154
+
155
+ ## Aperture geometry and sunstar directions
156
+
157
+ Use `calculateIdealApertureGeometry()` for a regular, straight-edged diaphragm:
158
+
159
+ ```ts
160
+ import { calculateIdealApertureGeometry } from "@photivra/engine";
161
+
162
+ const aperture = calculateIdealApertureGeometry({
163
+ bladeCount: 7,
164
+ firstBladeEdgeAngleDegrees: 15
165
+ });
166
+
167
+ console.log(aperture.value.normalizedVertices);
168
+ console.log(aperture.value.sunstarRayCount);
169
+ console.log(aperture.value.sunstarRayAnglesDegrees);
170
+ ```
171
+
172
+ The output describes normalized aperture geometry and idealized ray-direction symmetry only. It does not calculate diffraction intensity, star length, flare, coatings, or a full PSF.
173
+
174
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#ideal-diaphragm-geometry-and-sunstar-symmetry).
175
+
176
+ ## Pixel pitch
177
+
178
+ Use `calculatePixelPitch()` with physical sensor width and horizontal active pixel count:
179
+
180
+ ```ts
181
+ import { calculatePixelPitch } from "@photivra/engine";
182
+
183
+ const pitch = calculatePixelPitch({
184
+ sensorWidthMm: 36,
185
+ pixelWidth: 6000
186
+ });
187
+
188
+ console.log(pitch.value.micrometers);
189
+ console.log(pitch.value.millimeters);
190
+ ```
191
+
192
+ The current primitive calculates horizontal pixel pitch. It assumes the supplied sensor width and pixel count describe the same active dimension.
193
+
194
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#pixel-pitch-and-crop).
195
+
196
+ ## Centered crop and subject framing crop
197
+
198
+ Use `calculateCenteredCrop()` for a same-aspect centered digital crop:
199
+
200
+ ```ts
201
+ import { calculateCenteredCrop } from "@photivra/engine";
202
+
203
+ const crop = calculateCenteredCrop({
204
+ pixelWidth: 6000,
205
+ pixelHeight: 4000,
206
+ cropFactor: 1.5
207
+ });
208
+
209
+ console.log(crop.value.pixelWidth);
210
+ console.log(crop.value.pixelHeight);
211
+ console.log(crop.value.megapixels);
212
+ console.log(crop.value.retainedAreaFraction);
213
+ ```
214
+
215
+ Use `calculateSubjectFramingCrop()` to calculate the additional crop needed for a projected subject to occupy a target fraction of frame height:
216
+
217
+ ```ts
218
+ import { calculateSubjectFramingCrop } from "@photivra/engine";
219
+
220
+ const framing = calculateSubjectFramingCrop({
221
+ pixelWidth: 6000,
222
+ pixelHeight: 4000,
223
+ subjectHeightPixels: 1200,
224
+ targetSubjectHeightFraction: 0.5
225
+ });
226
+
227
+ console.log(framing.value.cropFactor);
228
+ console.log(framing.value.subjectHeightFraction);
229
+ console.log(framing.value.subjectClipped);
230
+ ```
231
+
232
+ Subject framing assumes the crop can be positioned around the subject; it does not check the subject's actual position against image edges.
233
+
234
+ See [Physics Foundation](PHYSICS_FOUNDATION.md#pixel-pitch-and-crop).
235
+
236
+ ## Projected subject motion
237
+
238
+ Use `calculateProjectedMotionBlur()` for representative-point image-plane displacement during an exposure:
239
+
240
+ ```ts
241
+ import { calculateProjectedMotionBlur } from "@photivra/engine";
242
+
243
+ const motion = calculateProjectedMotionBlur({
244
+ focalLengthMm: 200,
245
+ shutterSeconds: 1 / 500,
246
+ positionM: { x: 0.5, y: 1.2, z: 22 },
247
+ velocityMps: { x: 8, y: 0, z: 0 },
248
+ focusDistanceM: 22,
249
+ pixelPitchMicrometers: 6
250
+ });
251
+
252
+ console.log(motion.value.deltaXMm);
253
+ console.log(motion.value.deltaYMm);
254
+ console.log(motion.value.distanceMm);
255
+ console.log(motion.value.distancePixels);
256
+ ```
257
+
258
+ The model assumes constant world-space linear velocity. It tracks a representative point rather than extended-object scale blur, rotation, deformation, or acceleration.
259
+
260
+ See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#projected-subject-motion).
261
+
262
+ ## Exposure and equivalent ISO
263
+
264
+ Use `calculateExposureValue100()` for EV100:
265
+
266
+ ```ts
267
+ import { calculateExposureValue100 } from "@photivra/engine";
268
+
269
+ const ev = calculateExposureValue100({
270
+ aperture: 8,
271
+ shutterSeconds: 1 / 125
272
+ });
273
+
274
+ console.log(ev.value);
275
+ ```
276
+
277
+ Use `calculateRelativeOpticalExposure()` to compare aperture/shutter combinations:
278
+
279
+ ```ts
280
+ import { calculateRelativeOpticalExposure } from "@photivra/engine";
281
+
282
+ const exposure = calculateRelativeOpticalExposure({
283
+ aperture: 4,
284
+ shutterSeconds: 1 / 250,
285
+ referenceAperture: 5.6,
286
+ referenceShutterSeconds: 1 / 125
287
+ });
288
+
289
+ console.log(exposure.value.factor);
290
+ console.log(exposure.value.stops);
291
+ ```
292
+
293
+ Use `calculateEquivalentIso()` to calculate the nominal ISO/gain compensation needed to preserve rendered exposure:
294
+
295
+ ```ts
296
+ import { calculateEquivalentIso } from "@photivra/engine";
297
+
298
+ const iso = calculateEquivalentIso({
299
+ baseIso: 400,
300
+ baseAperture: 4,
301
+ baseShutterSeconds: 1 / 500,
302
+ aperture: 5.6,
303
+ shutterSeconds: 1 / 500
304
+ });
305
+
306
+ console.log(iso.value);
307
+ ```
308
+
309
+ These relations do not model scene radiance, lens T-stop/transmission, vignetting, or sensor-specific gain/noise behavior.
310
+
311
+ See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#exposure-relations).
312
+
313
+ ## Camera shake and stabilization-equivalent approximation
314
+
315
+ Use `estimateCameraShakeBlur()` with a controlled yaw/pitch angular-velocity profile:
316
+
317
+ ```ts
318
+ import { estimateCameraShakeBlur } from "@photivra/engine";
319
+
320
+ const shake = estimateCameraShakeBlur({
321
+ focalLengthMm: 200,
322
+ shutterSeconds: 1 / 60,
323
+ angularVelocityRadPerSec: {
324
+ yaw: 0.002,
325
+ pitch: 0.001
326
+ },
327
+ stabilizationStopsEquivalent: 3,
328
+ focusDistanceM: 22,
329
+ pixelPitchMicrometers: 6
330
+ });
331
+
332
+ console.log(shake.value.unstabilized.distancePixels);
333
+ console.log(shake.value.stabilized.distancePixels);
334
+ console.log(shake.value.residualMotionFactor);
335
+ console.log(shake.provenance.kind); // "approximation"
336
+ ```
337
+
338
+ The `2^-stops` attenuation is an educational approximation, not a real IBIS/OIS or CIPA rating. The current output is one global image-plane vector rather than spatially varying rotational optical flow.
339
+
340
+ See [Camera Shake and Stabilization](STABILIZATION.md).
341
+
342
+ ## Photoelectron and SNR primitives
343
+
344
+ Use `calculatePhotoelectrons()` only when you already have a defensible mean incident photon count and quantum efficiency:
345
+
346
+ ```ts
347
+ import { calculatePhotoelectrons } from "@photivra/engine";
348
+
349
+ const electrons = calculatePhotoelectrons({
350
+ incidentPhotons: 10_000,
351
+ quantumEfficiency: 0.7
352
+ });
353
+
354
+ console.log(electrons.value);
355
+ ```
356
+
357
+ Use `calculateSignalToNoise()` for the basic Poisson-shot-noise plus independent RMS read-noise model:
358
+
359
+ ```ts
360
+ import { calculateSignalToNoise } from "@photivra/engine";
361
+
362
+ const snr = calculateSignalToNoise({
363
+ signalElectrons: 7000,
364
+ readNoiseElectrons: 3
365
+ });
366
+
367
+ console.log(snr.value.shotNoiseStdElectrons);
368
+ console.log(snr.value.combinedNoiseStdElectrons);
369
+ console.log(snr.value.snrLinear);
370
+ console.log(snr.value.snrDb);
371
+ ```
372
+
373
+ These primitives do not derive photons from scene imagery and do not model a complete commercial sensor pipeline.
374
+
375
+ See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#signalnoise-primitives).
376
+
377
+ ## Camera and scene schema validation
378
+
379
+ Use the runtime parsers when camera or scene data crosses an untrusted JSON boundary.
380
+
381
+ ```ts
382
+ import {
383
+ parseCameraConfiguration,
384
+ parseSceneDefinition
385
+ } from "@photivra/engine";
386
+
387
+ const camera = parseCameraConfiguration(JSON.parse(cameraJson));
388
+ const scene = parseSceneDefinition(JSON.parse(sceneJson));
389
+ ```
390
+
391
+ The parsers throw `InvalidConfigurationError` when the supplied structure or supported values are invalid.
392
+
393
+ Example camera shape:
394
+
395
+ ```ts
396
+ const camera = parseCameraConfiguration({
397
+ sensor: {
398
+ widthMm: 36,
399
+ heightMm: 24,
400
+ pixelWidth: 6000,
401
+ pixelHeight: 4000
402
+ },
403
+ lens: {
404
+ focalLengthMm: 50,
405
+ aperture: 2.8
406
+ },
407
+ exposure: {
408
+ shutterSeconds: 1 / 250,
409
+ iso: 400
410
+ },
411
+ focus: {
412
+ focusDistanceM: 10
413
+ },
414
+ stabilization: {
415
+ bodyEnabled: true,
416
+ lensEnabled: false,
417
+ support: "handheld"
418
+ }
419
+ });
420
+ ```
421
+
422
+ See [Validation boundaries in the README](../README.md#validation-boundaries) and [Architecture](ARCHITECTURE.md).
423
+
424
+ ## Provenance, uncertainty, and quality metadata
425
+
426
+ Primitive scientific calculations return model provenance alongside the value:
427
+
428
+ ```ts
429
+ import { calculateFieldOfView } from "@photivra/engine";
430
+
431
+ const result = calculateFieldOfView({
432
+ focalLengthMm: 50,
433
+ sensorDimensionMm: 36
434
+ });
435
+
436
+ console.log(result.provenance.kind);
437
+ console.log(result.provenance.model);
438
+ console.log(result.provenance.modelVersion);
439
+ console.log(result.provenance.assumptions);
440
+ ```
441
+
442
+ For code that defines a new scientific result, the package exports helpers such as `calculatedResult()`, `approximationResult()`, `estimatedResult()`, and `calibratedResult()`.
443
+
444
+ ```ts
445
+ import { calculatedResult } from "@photivra/engine";
446
+
447
+ const result = calculatedResult(
448
+ { distanceM: 10 },
449
+ "example-model",
450
+ "1.0.0",
451
+ ["Example assumption"],
452
+ {
453
+ uncertainty: [
454
+ {
455
+ kind: "absolute",
456
+ quantityPath: "value.distanceM",
457
+ source: "measurement",
458
+ plusMinus: 0.1,
459
+ unit: "m"
460
+ }
461
+ ]
462
+ }
463
+ );
464
+ ```
465
+
466
+ Do not invent uncertainty values. Quality metadata should only be supplied when the uncertainty, calibration, valid range, or quality note has a defensible basis.
467
+
468
+ See [Scientific and Source Provenance](PROVENANCE.md) and [Public API Style](API_STYLE.md#result-metadata).
469
+
470
+ ## Composed POC simulation
471
+
472
+ Use `simulatePocCamera()` when you want one composed response containing the current POC calculations.
473
+
474
+ ```ts
475
+ import { simulatePocCamera } from "@photivra/engine";
476
+
477
+ const simulation = simulatePocCamera({
478
+ sensor: {
479
+ widthMm: 36,
480
+ heightMm: 24,
481
+ pixelWidth: 6000,
482
+ pixelHeight: 4000
483
+ },
484
+ lens: {
485
+ focalLengthMm: 200,
486
+ aperture: 5.6
487
+ },
488
+ exposure: {
489
+ shutterSeconds: 1 / 1000,
490
+ iso: 800
491
+ },
492
+ focus: {
493
+ focusDistanceM: 22,
494
+ circleOfConfusionMm: 0.03
495
+ },
496
+ crop: {
497
+ factor: 1
498
+ },
499
+ diffraction: {
500
+ wavelengthNm: 550
501
+ },
502
+ motion: {
503
+ positionM: { x: 0.2, y: 1.2, z: 21.8 },
504
+ velocityMps: { x: 35, y: 0, z: 0 }
505
+ },
506
+ subject: {
507
+ widthM: 0.7,
508
+ heightM: 1.8,
509
+ distanceM: 22
510
+ },
511
+ subjectCrop: {
512
+ targetSubjectHeightFraction: 0.5
513
+ },
514
+ cameraShake: {
515
+ angularVelocityRadPerSec: {
516
+ yaw: 0.002,
517
+ pitch: 0.001
518
+ },
519
+ stabilizationStopsEquivalent: 2
520
+ },
521
+ apertureShape: {
522
+ bladeCount: 7,
523
+ firstBladeEdgeAngleDegrees: 0
524
+ }
525
+ });
526
+
527
+ console.log(simulation.apiVersion);
528
+ console.log(simulation.fieldOfView);
529
+ console.log(simulation.depthOfField);
530
+ console.log(simulation.motion);
531
+ console.log(simulation.provenance);
532
+ ```
533
+
534
+ The focus request must supply exactly one circle-of-confusion criterion: either `circleOfConfusionMm` or `equivalentViewingCircleOfConfusion`.
535
+
536
+ Additional named defocus, sampling, and motion samples can be supplied when a renderer or analysis client needs per-object outputs.
537
+
538
+ See [Local POC Simulation API](POC_API.md#simulate) for the complete composed request/response semantics. That HTTP transport is repository-only contributor tooling and is not shipped as a package subpath; `simulatePocCamera()` itself is part of the browser-safe root package.
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@photivra/engine",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
6
+ "type": "module",
7
+ "license": "Apache-2.0",
8
+ "sideEffects": false,
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist/index.js",
19
+ "dist/index.js.map",
20
+ "dist/index.d.ts",
21
+ "dist/index.d.ts.map",
22
+ "dist/camera",
23
+ "dist/core",
24
+ "dist/exposure",
25
+ "dist/motion",
26
+ "dist/optics",
27
+ "dist/output",
28
+ "dist/schema",
29
+ "dist/sensor",
30
+ "dist/simulation",
31
+ "dist/stabilization",
32
+ "README.md",
33
+ "CITATION.cff",
34
+ "LICENSE",
35
+ "NOTICE",
36
+ "docs"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Photivra/photivra.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/photivra/photivra/issues"
44
+ },
45
+ "homepage": "https://github.com/photivra/photivra#readme",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "engines": {
50
+ "node": ">=22.13.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -p tsconfig.build.json",
54
+ "prepare": "npm run build",
55
+ "check": "eslint . && tsc --noEmit && vitest run && npm run check:licenses && npm run check:license-headers && npm run check:identity && npm run check:browser-surface",
56
+ "lint": "eslint .",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "typecheck": "tsc --noEmit",
60
+ "pack:check": "node scripts/check-package-surface.mjs",
61
+ "coverage": "vitest run --coverage",
62
+ "check:browser-surface": "node scripts/check-browser-surface.mjs",
63
+ "check:licenses": "node scripts/check-license-policy.mjs",
64
+ "check:license-headers": "node scripts/check-license-headers.mjs",
65
+ "check:identity": "node scripts/check-project-identity.mjs"
66
+ },
67
+ "devDependencies": {
68
+ "@eslint/js": "10.0.1",
69
+ "eslint": "10.10.0",
70
+ "typescript": "6.0.3",
71
+ "typescript-eslint": "8.70.0",
72
+ "vitest": "5.0.1",
73
+ "@types/node": "24.13.6",
74
+ "@vitest/coverage-v8": "5.0.1"
75
+ },
76
+ "keywords": [
77
+ "photography",
78
+ "camera",
79
+ "optics",
80
+ "digital-imaging",
81
+ "imaging-science",
82
+ "depth-of-field",
83
+ "exposure",
84
+ "motion-blur",
85
+ "diffraction",
86
+ "image-sensor",
87
+ "signal-to-noise",
88
+ "scientific-computing",
89
+ "typescript"
90
+ ]
91
+ }