Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ All notable changes to this project will be documented in this file.
- Propose dark and flat frames from their metadata and let DataLab's generic launcher run the recipes, replacing the Desktop dark/flat checklist.
- Pair the photon transfer ladder with both the photon transfer and relative-DN methods; example parameter values are now keyed by method.
- Document how to set the exposure time and the frame role on your own frames with DataLab's Add metadata dialog (`doc/preparing-frames.md`).
- Add a scientific camera simulator tool, in DataLab Desktop and DataLab-Web: a live view next to the sensor, illumination and acquisition settings, and acquisitions of dark and flat frames tagged for the Camera methods, in single-exposure or exposure-sequence mode (`doc/camera-simulator.md`).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ truth = result.truth
The same parameters and seed produce identical frames and truth maps. See
[`doc/simulation.md`](doc/simulation.md) for units, equations, and limitations.

In DataLab Desktop and DataLab-Web, the **Scientific camera simulator** tool shows a live view of this model next to its settings, and acquires dark and flat frames tagged for the Camera methods. See [`doc/camera-simulator.md`](doc/camera-simulator.md).

## Relative Characterization in DN

The headless core validates dark and uniform-illumination frame stacks before
Expand Down
39 changes: 39 additions & 0 deletions doc/camera-simulator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Scientific Camera Simulator

The Camera application includes a simulated scientific camera. It is a DataLab tool: open it from the Camera page of the **Applications** catalog (**Tools** section), or from the **Plugins > Camera & Detector Characterization** menu of the image panel. It works the same way in DataLab Desktop and DataLab-Web.

The window shows a live view on the left and the camera settings on the right. Every change of a setting refreshes the view. **Live** refreshes it continuously, with new noise at every frame, like the viewer of a real camera. **Acquire** adds the frames to the workspace, in a new group per acquisition; the window stays open for the next acquisition.

## Settings

The settings are grouped in four tabs.

- **Acquisition.** Single exposure, or a sequence of exposure times (in ms, separated by commas). Number of frames per exposure. Shutter open (flat frames) or closed (dark frames). With the shutter open, dark frames can be added at the longest exposure time.
- **Illumination.** Photoelectron rate per pixel, vignetting and dust shadows.
- **Sensor.** Frame size, ADC resolution, conversion gain, read noise, offset, PRNU, DSNU, row and column patterns, defective pixels, and the serial number.
- **Dark current.** Dark current, its non-uniformity, hot pixels and amplifier glow.

The ADC saturates at its largest code, 2^bits - 1 DN. The live view uses this full range, so the image gets brighter as the exposure grows, and saturated pixels stand out. Below the view, a summary gives the mean and standard deviation of the frame, and the share of saturated pixels.

The serial number seeds the sensor: PRNU, DSNU, defects and hot pixels stay the same from one acquisition to the next, as on a real camera. Change it to simulate another camera of the same model. Each frame draws new temporal noise.

One acquisition is limited to 20 megapixels (all frames together), which keeps DataLab-Web within the browser memory.

## Acquired Frames

Frames are named like the generated examples, for instance `Dark 136 ms 01` or `Flat 10 ms 02`. Each frame carries the metadata the Camera methods expect:

- `plugin.org.datalab.camera-characterization.frame_role`: `dark` or `flat`;
- `plugin.org.datalab.camera-characterization.exposure_time_s`: the exposure time, in seconds;
- `plugin.org.datalab.camera-characterization.synthetic` and `.simulation_seed`, which record the simulated origin.

So an acquisition can be analyzed right away:

| Acquisition | Method |
| --- | --- |
| Exposure sequence, shutter open, with dark frames (the default sequence) | Photon transfer curve, relative camera characterization |
| Exposure sequence with the shutter closed, for instance `500, 1000, 2000, 4000, 8000` ms (raise the dark current to a few tens of e⁻/s to see it clearly) | Dark current and hot pixels |

## Limits

The simulator uses the model described in [`simulation.md`](simulation.md), with the same limitations: no wavelength or quantum efficiency, no charge transfer, temporal drift, nonlinear response or blooming, and no calibrated radiometry. It helps learning the methods and checking a workflow; it does not replace measurements of a real camera.
2 changes: 2 additions & 0 deletions doc/simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ static/noiseless ground truth used to generate them. It is intended for testing
future relative characterization algorithms, not for claiming a calibrated or
EMVA 1288-compliant camera model.

In DataLab, the scientific camera simulator tool drives this model interactively, with a live view and acquisitions tagged for the Camera methods. See [`camera-simulator.md`](camera-simulator.md).

## Units and Model

The conversion gain is expressed in electrons per digital number (`e-/DN`).
Expand Down
6 changes: 6 additions & 0 deletions src/datalab_camera_characterization/adapters/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
__version__,
)
from ..demo import DARK_RAMP_DEMO, PHOTON_TRANSFER_DEMO, materialize_generated_example
from ..simulator import CAMERA_SIMULATOR_TOOL, CameraSimulator
from ..workflow import (
CAMERA_RECIPES,
DARK_CURRENT_RECIPE,
Expand Down Expand Up @@ -64,6 +65,7 @@ class CameraDetectorCharacterizationPlugin(PluginBase):
)
RECIPES = CAMERA_RECIPES
EXAMPLES = (CAMERA_QUICKSTART, PHOTON_TRANSFER_DEMO, DARK_RAMP_DEMO)
TOOLS = (CAMERA_SIMULATOR_TOOL,)
WELCOME_TILES = (
WelcomeTile(
id="application",
Expand All @@ -86,6 +88,10 @@ def materialize_example(cls, example_id: str) -> PluginExampleData | None:
cls.get_example(example_id)
return materialize_generated_example(example_id)

def camera_simulator(self) -> CameraSimulator:
"""Return a new scientific camera simulator."""
return CameraSimulator()

def run_relative_dn(self) -> RecipeOutcome | None:
"""Run the relative-DN characterization on the selected frames."""
return self.start_recipe(RELATIVE_DN_RECIPE.recipe_id)
Expand Down
6 changes: 6 additions & 0 deletions src/datalab_camera_characterization/adapters/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from .. import PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME, __version__
from ..demo import DARK_RAMP_DEMO, PHOTON_TRANSFER_DEMO, materialize_generated_example
from ..simulator import CAMERA_SIMULATOR_TOOL, CameraSimulator
from ..workflow import (
CAMERA_RECIPES,
RELATIVE_DN_RECIPE,
Expand Down Expand Up @@ -56,10 +57,15 @@ class CameraDetectorCharacterizationWebPlugin(PluginBase):
)
RECIPES = CAMERA_RECIPES
EXAMPLES = (CAMERA_QUICKSTART, PHOTON_TRANSFER_DEMO, DARK_RAMP_DEMO)
TOOLS = (CAMERA_SIMULATOR_TOOL,)

def create_actions(self) -> None:
"""Application actions are provided by DataLab-Web's generic host."""

def camera_simulator(self) -> CameraSimulator:
"""Return a new scientific camera simulator."""
return CameraSimulator()

@classmethod
def materialize_example(cls, example_id: str) -> PluginExampleData | None:
"""Generate in-memory examples; the quickstart stays a packaged file."""
Expand Down
9 changes: 5 additions & 4 deletions src/datalab_camera_characterization/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _sensor_parameters(seed: int, **overrides: object) -> CameraSimulationParame
return CameraSimulationParameters(**values)


def _series_images(
def series_images(
title_prefix: str,
parameters: CameraSimulationParameters,
role: str,
Expand All @@ -116,7 +116,7 @@ def build_photon_transfer_campaign() -> PluginExampleData:
"""Build the deterministic photon transfer demonstration campaign."""
stream = 1
longest = max(PHOTON_TRANSFER_EXPOSURES_S)
images = _series_images(
images = series_images(
"Dark",
_sensor_parameters(
PHOTON_TRANSFER_SEED,
Expand All @@ -130,7 +130,7 @@ def build_photon_transfer_campaign() -> PluginExampleData:
for exposure_time_s in PHOTON_TRANSFER_EXPOSURES_S:
stream += 1
images.extend(
_series_images(
series_images(
f"Flat {exposure_time_s * 1_000:g} ms",
_sensor_parameters(
PHOTON_TRANSFER_SEED,
Expand All @@ -150,7 +150,7 @@ def build_dark_ramp_campaign() -> PluginExampleData:
images: list[ImageObj] = []
for stream, exposure_time_s in enumerate(DARK_RAMP_EXPOSURES_S, start=1):
images.extend(
_series_images(
series_images(
f"Dark {exposure_time_s:g} s",
_sensor_parameters(
DARK_RAMP_SEED,
Expand Down Expand Up @@ -198,4 +198,5 @@ def materialize_generated_example(example_id: str) -> PluginExampleData | None:
"build_dark_ramp_campaign",
"build_photon_transfer_campaign",
"materialize_generated_example",
"series_images",
]
Loading