splatthis 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {splatthis-0.2.2 → splatthis-0.2.4}/CHANGELOG.md +77 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/MANIFEST.in +1 -0
- {splatthis-0.2.2/src/splatthis.egg-info → splatthis-0.2.4}/PKG-INFO +24 -17
- {splatthis-0.2.2 → splatthis-0.2.4}/README.md +23 -16
- {splatthis-0.2.2 → splatthis-0.2.4}/pyproject.toml +3 -1
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/__init__.py +3 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/_version.py +1 -1
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/artifact_backends.py +21 -21
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/artifact_evaluation.py +12 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/browser_export.py +3 -2
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/cli.py +9 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/color.py +17 -6
- splatthis-0.2.4/src/splatthis/data/compositor-fidelity.json +175 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/distillation.py +12 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/domain.py +2 -1
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_artifacts.py +4 -3
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_configuration.py +27 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_densification.py +12 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_guidance.py +19 -16
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_initialization.py +10 -9
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_optimization.py +37 -13
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_postfit.py +10 -9
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/engine_state.py +32 -29
- splatthis-0.2.4/src/splatthis/expectations.py +102 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/export_common.py +10 -7
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/features.py +23 -16
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/analysis.py +16 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/evaluator.py +7 -6
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/metrics.py +19 -14
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/operators.py +3 -2
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mixed_primitives.py +9 -8
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mlx_optimizer.py +5 -2
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mlx_renderer.py +3 -2
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mlx_stage.py +4 -3
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/pipeline_artifacts.py +2 -2
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/pipeline_phases.py +28 -5
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/pixel_runtime.py +5 -4
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/pptx_export.py +7 -6
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/quality.py +33 -10
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/renderer.py +16 -11
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/splat.py +14 -13
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/storage.py +5 -4
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/svg_export.py +28 -25
- {splatthis-0.2.2 → splatthis-0.2.4/src/splatthis.egg-info}/PKG-INFO +24 -17
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis.egg-info/SOURCES.txt +2 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/CONTRIBUTING.md +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/LICENSE +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/setup.cfg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/adaptive_compute.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/artifact_gates.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/artifact_io.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/browser_capture.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/budgets.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/canvas_parity.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/config.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/conversion_engine.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/converter.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/__init__.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/config.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/report.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/fidelity/stage.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/io.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mlx_losses.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/mlx_runtime.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/optimizer.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/pipeline.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/profiles.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/proxies.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/py.typed +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/reporting.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/roundtrip.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/svg_recipe_gate.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/template_assets.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/__init__.py +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/background_shape.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/blur.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/close_group.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/edge_segment.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/effect_list.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/gradient_fill.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/gradient_stop.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/group.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/shape.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/shape_no_effect.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/slide.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/soft_edge.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/drawingml/solid_fill.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/app_props.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/content_types.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/core_props.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/pres_props.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/presentation.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/presentation_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/raster_slide.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/raster_slide_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/root_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/slide_layout.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/slide_layout_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/slide_master.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/slide_master_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/theme.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/vector_slide_rels.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/pptx/view_props.xml +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/reporting/side_by_side.html +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/background.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/blur_document.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/blur_ellipse.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/blur_filter.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/close.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/edge_group.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/edge_line.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/edge_path.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/ellipse.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/empty_document.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/palette_background.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/palette_document.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/palette_ellipse.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/palette_gradient.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/palette_stop.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/scripted_document.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/scripted_runtime.js +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/standard_document.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/standard_gradient.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis/templates/svg/standard_stop.svg +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis.egg-info/dependency_links.txt +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis.egg-info/entry_points.txt +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis.egg-info/requires.txt +0 -0
- {splatthis-0.2.2 → splatthis-0.2.4}/src/splatthis.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to SplatThis are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.4 - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `splatthis.compositor_fidelity()`, publishing how faithfully a deployed
|
|
10
|
+
compositor reproduces the *same* splat population the internal reference
|
|
11
|
+
renderer draws. A consumer that supplies its own splats -- projecting 3D
|
|
12
|
+
Gaussians through EWA rather than fitting a bitmap -- inherits only this
|
|
13
|
+
term, not the fitting loss that dominates the headline source-fidelity
|
|
14
|
+
numbers, and previously had to re-derive it from the corpus.
|
|
15
|
+
|
|
16
|
+
compositor SSIM_sRGB min 0.5965 median 0.7540 max 0.8991 (n=21)
|
|
17
|
+
r(content gradient) -0.470
|
|
18
|
+
|
|
19
|
+
The correlation is the substantive result. End-to-end fidelity correlates
|
|
20
|
+
with content gradient at -0.84, which invites the conclusion that a splat
|
|
21
|
+
view's quality is predictable from what it depicts. It is not: that
|
|
22
|
+
dependence is almost entirely the fitting stage, and compositor loss is
|
|
23
|
+
broadly uniform. `is_content_predictable()` returns that judgement so it
|
|
24
|
+
cannot be re-inferred wrongly.
|
|
25
|
+
|
|
26
|
+
- `--save-json` on the CLI. `ConversionRequest.save_json` was honoured but
|
|
27
|
+
unreachable from the command line.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Acceptance no longer gates on wall-clock by default. `max_runtime_sec: 60.0`
|
|
32
|
+
failed correct runs against presets that train for minutes -- 8 of 70 stored
|
|
33
|
+
manifests failed on runtime alone, including full-quality runs at SSIM 0.91.
|
|
34
|
+
Runtime remains measured under `acceptance.measured` and honoured when set
|
|
35
|
+
explicitly or by `--time-budget`.
|
|
36
|
+
- Run manifests record `artifact_hash_stable` (false under MLX, with a note)
|
|
37
|
+
and `memory_guard`, so reproducibility and host-memory reductions travel with
|
|
38
|
+
the data instead of living in prose.
|
|
39
|
+
- Corpus result rows carry `schema_version`, and resume re-scores anything
|
|
40
|
+
older, so an evidence level can no longer vanish across a resumed run.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- A failing SSIM silently became an inflated one: `except Exception` around the
|
|
45
|
+
skimage call fell through to a global SSIM reading ~0.10 higher, far above
|
|
46
|
+
the 0.50 acceptance floor. Only an absent dependency or an input too small to
|
|
47
|
+
carry a window may fall back now.
|
|
48
|
+
- Colour transforms no longer raise a negative base to a fractional exponent on
|
|
49
|
+
the discarded branch of `np.where`, which warned during ordinary compositing.
|
|
50
|
+
- The splat-orientation convention is pinned by direct tests, and
|
|
51
|
+
`mixed_primitives.py` no longer open-codes it.
|
|
52
|
+
|
|
53
|
+
## 0.2.3 - 2026-08-01
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- The README showcase renders all four outputs at one size and adds scriptless
|
|
58
|
+
CSS as a fourth column. The previous table sized each image by its own
|
|
59
|
+
intrinsic pixels -- 476x502, 364x384 and 728x768 -- so a three-way comparison
|
|
60
|
+
appeared at three different scales.
|
|
61
|
+
|
|
62
|
+
Sized with explicit `width` attributes rather than by resampling the files:
|
|
63
|
+
PyPI's `readme_renderer` allowlist keeps `width` and `height` on `<img>`, and
|
|
64
|
+
GitHub does too, so every asset stays at native resolution and stays crisp on
|
|
65
|
+
high-DPI displays.
|
|
66
|
+
|
|
67
|
+
CSS appears as a Chromium capture rather than live. Both platforms sanitize
|
|
68
|
+
README markup through an allowlist that omits the `<style>` tag and the
|
|
69
|
+
`style` attribute -- `readme_renderer` runs `nh3.clean()` with no CSS
|
|
70
|
+
sanitizer configured -- so a build made entirely of CSS radial gradients
|
|
71
|
+
arrives as 1,615 unstyled `<div>` elements. Each cell is labelled as live
|
|
72
|
+
vector or as a capture, and captures are noted as lower bounds: they carry
|
|
73
|
+
screen-capture and rescaling losses on top of the compositor's own.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- The relative-link guard now covers `src="..."` as well as markdown `](...)`.
|
|
78
|
+
The showcase moved to raw `<img>` tags for sizing, which had made the
|
|
79
|
+
markdown-only check blind to every image on the page -- the same class of
|
|
80
|
+
defect that put 18 dead links on the 0.2.0 project page.
|
|
81
|
+
|
|
5
82
|
## 0.2.2 - 2026-08-01
|
|
6
83
|
|
|
7
84
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splatthis
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Fit 2D Gaussian splats to an image and deploy them as SVG, CSS, Canvas, a pixel runtime, or editable PowerPoint shapes
|
|
5
5
|
Author: Bram Alkema
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,6 +47,11 @@ Dynamic: license-file
|
|
|
47
47
|
|
|
48
48
|
# SplatThis
|
|
49
49
|
|
|
50
|
+
[](https://github.com/BramAlkema/SplatThis/actions/workflows/ci.yml)
|
|
51
|
+
[](https://pypi.org/project/splatthis/)
|
|
52
|
+
[](https://pypi.org/project/splatthis/)
|
|
53
|
+
[](https://github.com/BramAlkema/SplatThis/blob/main/LICENSE)
|
|
54
|
+
|
|
50
55
|
Convert a bitmap into Gaussian splats and deploy them as browser-rendered SVG,
|
|
51
56
|
scriptless CSS, browser-native Canvas primitives, an accelerated pixel runtime
|
|
52
57
|
with exact CPU fallbacks, or native editable PowerPoint shapes.
|
|
@@ -71,26 +76,28 @@ deployed-fidelity claim.
|
|
|
71
76
|
## See it
|
|
72
77
|
|
|
73
78
|
One 476x502 photograph, fitted to Gaussian splats and emitted to three
|
|
74
|
-
compositors that share no rendering code
|
|
75
|
-
raster of one — your browser is drawing 1,615 gradient ellipses.
|
|
79
|
+
compositors that share no rendering code — vector, stylesheet, and DrawingML.
|
|
76
80
|
|
|
77
|
-
| Source
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
| the input | **0.8665** SSIM
|
|
81
|
+
| Source | SVG | Scriptless CSS | PowerPoint |
|
|
82
|
+
|:---:|:---:|:---:|:---:|
|
|
83
|
+
| <img src="https://bramalkema.github.io/SplatThis/demo/source.png" alt="Source photograph" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon.svg" alt="Browser-rendered SVG" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon-css.png" alt="Chromium capture of the scriptless CSS build" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon-pptx.png" alt="Microsoft PowerPoint slideshow capture" width="210"> |
|
|
84
|
+
| the input | **0.8665** SSIM<br>vector · 1.5 MB | **0.8748** SSIM<br>DOM + CSS · 0.8 MB | **0.7885** SSIM<br>editable shapes · 161 KB |
|
|
85
|
+
| — | live vector | Chromium capture | PowerPoint capture |
|
|
81
86
|
|
|
82
|
-
The
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
and
|
|
87
|
+
The SVG is the real thing: your browser is drawing 1,615 gradient ellipses, not
|
|
88
|
+
displaying a picture of them. The other two are captures because they have to
|
|
89
|
+
be. GitHub and PyPI both sanitize README markup through an allowlist that omits
|
|
90
|
+
the `<style>` tag and the `style` attribute — PyPI's `readme_renderer` runs
|
|
91
|
+
`nh3.clean()` with no CSS sanitizer configured — so a build made entirely of
|
|
92
|
+
CSS radial gradients arrives as 1,615 unstyled `<div>` elements on either site.
|
|
93
|
+
PowerPoint, obviously, does not run in a browser at all.
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
Both captures are therefore lower bounds: they carry the losses of screen
|
|
96
|
+
capture and rescaling on top of whatever the compositor itself costs.
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
compositing it from a stylesheet. View source on it.
|
|
98
|
+
**[Open the scriptless CSS build →](https://bramalkema.github.io/SplatThis/demo/chameleon-css.html)**
|
|
99
|
+
— no script, no canvas, no SVG, no bitmap. View source on it; the capture above
|
|
100
|
+
cannot show that the page is 1,615 DOM elements composited from a stylesheet.
|
|
94
101
|
|
|
95
102
|
Also available: the
|
|
96
103
|
[editable .pptx](https://bramalkema.github.io/SplatThis/demo/chameleon.pptx),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# SplatThis
|
|
2
2
|
|
|
3
|
+
[](https://github.com/BramAlkema/SplatThis/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/splatthis/)
|
|
5
|
+
[](https://pypi.org/project/splatthis/)
|
|
6
|
+
[](https://github.com/BramAlkema/SplatThis/blob/main/LICENSE)
|
|
7
|
+
|
|
3
8
|
Convert a bitmap into Gaussian splats and deploy them as browser-rendered SVG,
|
|
4
9
|
scriptless CSS, browser-native Canvas primitives, an accelerated pixel runtime
|
|
5
10
|
with exact CPU fallbacks, or native editable PowerPoint shapes.
|
|
@@ -24,26 +29,28 @@ deployed-fidelity claim.
|
|
|
24
29
|
## See it
|
|
25
30
|
|
|
26
31
|
One 476x502 photograph, fitted to Gaussian splats and emitted to three
|
|
27
|
-
compositors that share no rendering code
|
|
28
|
-
raster of one — your browser is drawing 1,615 gradient ellipses.
|
|
32
|
+
compositors that share no rendering code — vector, stylesheet, and DrawingML.
|
|
29
33
|
|
|
30
|
-
| Source
|
|
31
|
-
|
|
32
|
-
|
|
|
33
|
-
| the input | **0.8665** SSIM
|
|
34
|
+
| Source | SVG | Scriptless CSS | PowerPoint |
|
|
35
|
+
|:---:|:---:|:---:|:---:|
|
|
36
|
+
| <img src="https://bramalkema.github.io/SplatThis/demo/source.png" alt="Source photograph" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon.svg" alt="Browser-rendered SVG" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon-css.png" alt="Chromium capture of the scriptless CSS build" width="210"> | <img src="https://bramalkema.github.io/SplatThis/demo/chameleon-pptx.png" alt="Microsoft PowerPoint slideshow capture" width="210"> |
|
|
37
|
+
| the input | **0.8665** SSIM<br>vector · 1.5 MB | **0.8748** SSIM<br>DOM + CSS · 0.8 MB | **0.7885** SSIM<br>editable shapes · 161 KB |
|
|
38
|
+
| — | live vector | Chromium capture | PowerPoint capture |
|
|
34
39
|
|
|
35
|
-
The
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
and
|
|
40
|
+
The SVG is the real thing: your browser is drawing 1,615 gradient ellipses, not
|
|
41
|
+
displaying a picture of them. The other two are captures because they have to
|
|
42
|
+
be. GitHub and PyPI both sanitize README markup through an allowlist that omits
|
|
43
|
+
the `<style>` tag and the `style` attribute — PyPI's `readme_renderer` runs
|
|
44
|
+
`nh3.clean()` with no CSS sanitizer configured — so a build made entirely of
|
|
45
|
+
CSS radial gradients arrives as 1,615 unstyled `<div>` elements on either site.
|
|
46
|
+
PowerPoint, obviously, does not run in a browser at all.
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
Both captures are therefore lower bounds: they carry the losses of screen
|
|
49
|
+
capture and rescaling on top of whatever the compositor itself costs.
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
compositing it from a stylesheet. View source on it.
|
|
51
|
+
**[Open the scriptless CSS build →](https://bramalkema.github.io/SplatThis/demo/chameleon-css.html)**
|
|
52
|
+
— no script, no canvas, no SVG, no bitmap. View source on it; the capture above
|
|
53
|
+
cannot show that the page is 1,615 DOM elements composited from a stylesheet.
|
|
47
54
|
|
|
48
55
|
Also available: the
|
|
49
56
|
[editable .pptx](https://bramalkema.github.io/SplatThis/demo/chameleon.pptx),
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splatthis"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Fit 2D Gaussian splats to an image and deploy them as SVG, CSS, Canvas, a pixel runtime, or editable PowerPoint shapes"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -87,6 +87,8 @@ splatthis = [
|
|
|
87
87
|
# PEP 561 marker: without it, type checkers ignore this package's
|
|
88
88
|
# annotations entirely and every consumer sees Any.
|
|
89
89
|
"py.typed",
|
|
90
|
+
# Published fidelity expectations, read by consumers at runtime.
|
|
91
|
+
"data/*.json",
|
|
90
92
|
"templates/**/*.html",
|
|
91
93
|
"templates/**/*.js",
|
|
92
94
|
"templates/**/*.svg",
|
|
@@ -10,6 +10,7 @@ from .browser_export import generate_css_splat_html, generate_native_canvas_html
|
|
|
10
10
|
from .config import ConversionRequest, ConverterConfig
|
|
11
11
|
from .converter import PNG2SVGConverter
|
|
12
12
|
from .domain import EvidenceLevel, SplatScene
|
|
13
|
+
from .expectations import CompositorFidelity, compositor_fidelity
|
|
13
14
|
from .pixel_runtime import (
|
|
14
15
|
generate_parallax_pixel_runtime_html,
|
|
15
16
|
generate_pixel_runtime_html,
|
|
@@ -28,6 +29,8 @@ __all__ = [
|
|
|
28
29
|
"RawSplat",
|
|
29
30
|
"SplatScene",
|
|
30
31
|
"EvidenceLevel",
|
|
32
|
+
"CompositorFidelity",
|
|
33
|
+
"compositor_fidelity",
|
|
31
34
|
"ConversionRequest",
|
|
32
35
|
"ConverterConfig",
|
|
33
36
|
"load_png",
|
|
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Dict, Mapping
|
|
8
8
|
|
|
9
|
-
import numpy as
|
|
9
|
+
import numpy.typing as npt
|
|
10
10
|
|
|
11
11
|
from .artifact_evaluation import (
|
|
12
12
|
evaluate_css_export_quality,
|
|
@@ -56,7 +56,7 @@ class ArtifactBackend(ABC):
|
|
|
56
56
|
converter: "ConversionEngine",
|
|
57
57
|
request: ConversionRequest,
|
|
58
58
|
scene: SplatScene,
|
|
59
|
-
target_linear_rgb:
|
|
59
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
60
60
|
artifacts_path: Path | None,
|
|
61
61
|
) -> ArtifactPayload:
|
|
62
62
|
"""Emit the primary target exactly once."""
|
|
@@ -79,8 +79,8 @@ class ArtifactBackend(ABC):
|
|
|
79
79
|
*,
|
|
80
80
|
converter: "ConversionEngine",
|
|
81
81
|
request: ConversionRequest,
|
|
82
|
-
target_linear_rgb:
|
|
83
|
-
fallback_linear_rgb:
|
|
82
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
83
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
84
84
|
proxy_quality: Mapping[str, Any],
|
|
85
85
|
) -> ArtifactEvaluation:
|
|
86
86
|
quality = dict(proxy_quality)
|
|
@@ -104,8 +104,8 @@ class ArtifactBackend(ABC):
|
|
|
104
104
|
evaluate: Any,
|
|
105
105
|
success_kind: str,
|
|
106
106
|
unavailable_kind: str,
|
|
107
|
-
target_linear_rgb:
|
|
108
|
-
fallback_linear_rgb:
|
|
107
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
108
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
109
109
|
) -> ArtifactEvaluation:
|
|
110
110
|
quality = dict(proxy_quality)
|
|
111
111
|
if request.output_path is not None:
|
|
@@ -159,7 +159,7 @@ class SvgArtifactBackend(ArtifactBackend):
|
|
|
159
159
|
converter: "ConversionEngine",
|
|
160
160
|
request: ConversionRequest,
|
|
161
161
|
scene: SplatScene,
|
|
162
|
-
target_linear_rgb:
|
|
162
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
163
163
|
artifacts_path: Path | None,
|
|
164
164
|
) -> ArtifactPayload:
|
|
165
165
|
metadata: Dict[str, Any] = {}
|
|
@@ -229,8 +229,8 @@ class SvgArtifactBackend(ArtifactBackend):
|
|
|
229
229
|
*,
|
|
230
230
|
converter: "ConversionEngine",
|
|
231
231
|
request: ConversionRequest,
|
|
232
|
-
target_linear_rgb:
|
|
233
|
-
fallback_linear_rgb:
|
|
232
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
233
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
234
234
|
proxy_quality: Mapping[str, Any],
|
|
235
235
|
) -> ArtifactEvaluation:
|
|
236
236
|
return self._browser_evaluation(
|
|
@@ -258,7 +258,7 @@ class DrawingMLArtifactBackend(ArtifactBackend):
|
|
|
258
258
|
converter: "ConversionEngine",
|
|
259
259
|
request: ConversionRequest,
|
|
260
260
|
scene: SplatScene,
|
|
261
|
-
target_linear_rgb:
|
|
261
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
262
262
|
artifacts_path: Path | None,
|
|
263
263
|
) -> ArtifactPayload:
|
|
264
264
|
return ArtifactPayload(
|
|
@@ -304,8 +304,8 @@ class PptxArtifactBackend(DrawingMLArtifactBackend):
|
|
|
304
304
|
*,
|
|
305
305
|
converter: "ConversionEngine",
|
|
306
306
|
request: ConversionRequest,
|
|
307
|
-
target_linear_rgb:
|
|
308
|
-
fallback_linear_rgb:
|
|
307
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
308
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
309
309
|
proxy_quality: Mapping[str, Any],
|
|
310
310
|
) -> ArtifactEvaluation:
|
|
311
311
|
return ArtifactEvaluation(
|
|
@@ -335,7 +335,7 @@ class CanvasArtifactBackend(ArtifactBackend):
|
|
|
335
335
|
converter: "ConversionEngine",
|
|
336
336
|
request: ConversionRequest,
|
|
337
337
|
scene: SplatScene,
|
|
338
|
-
target_linear_rgb:
|
|
338
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
339
339
|
artifacts_path: Path | None,
|
|
340
340
|
) -> ArtifactPayload:
|
|
341
341
|
parallax_strength = float(
|
|
@@ -361,8 +361,8 @@ class CanvasArtifactBackend(ArtifactBackend):
|
|
|
361
361
|
*,
|
|
362
362
|
converter: "ConversionEngine",
|
|
363
363
|
request: ConversionRequest,
|
|
364
|
-
target_linear_rgb:
|
|
365
|
-
fallback_linear_rgb:
|
|
364
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
365
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
366
366
|
proxy_quality: Mapping[str, Any],
|
|
367
367
|
) -> ArtifactEvaluation:
|
|
368
368
|
return self._browser_evaluation(
|
|
@@ -392,7 +392,7 @@ class CssArtifactBackend(ArtifactBackend):
|
|
|
392
392
|
converter: "ConversionEngine",
|
|
393
393
|
request: ConversionRequest,
|
|
394
394
|
scene: SplatScene,
|
|
395
|
-
target_linear_rgb:
|
|
395
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
396
396
|
artifacts_path: Path | None,
|
|
397
397
|
) -> ArtifactPayload:
|
|
398
398
|
parallax_strength = float(
|
|
@@ -425,8 +425,8 @@ class CssArtifactBackend(ArtifactBackend):
|
|
|
425
425
|
*,
|
|
426
426
|
converter: "ConversionEngine",
|
|
427
427
|
request: ConversionRequest,
|
|
428
|
-
target_linear_rgb:
|
|
429
|
-
fallback_linear_rgb:
|
|
428
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
429
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
430
430
|
proxy_quality: Mapping[str, Any],
|
|
431
431
|
) -> ArtifactEvaluation:
|
|
432
432
|
return self._browser_evaluation(
|
|
@@ -468,7 +468,7 @@ class PixelRuntimeArtifactBackend(ArtifactBackend):
|
|
|
468
468
|
converter: "ConversionEngine",
|
|
469
469
|
request: ConversionRequest,
|
|
470
470
|
scene: SplatScene,
|
|
471
|
-
target_linear_rgb:
|
|
471
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
472
472
|
artifacts_path: Path | None,
|
|
473
473
|
) -> ArtifactPayload:
|
|
474
474
|
parallax_strength = self._parallax_strength(converter)
|
|
@@ -502,8 +502,8 @@ class PixelRuntimeArtifactBackend(ArtifactBackend):
|
|
|
502
502
|
*,
|
|
503
503
|
converter: "ConversionEngine",
|
|
504
504
|
request: ConversionRequest,
|
|
505
|
-
target_linear_rgb:
|
|
506
|
-
fallback_linear_rgb:
|
|
505
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
506
|
+
fallback_linear_rgb: npt.NDArray[Any],
|
|
507
507
|
proxy_quality: Mapping[str, Any],
|
|
508
508
|
) -> ArtifactEvaluation:
|
|
509
509
|
if self.requires_governing_render(converter):
|
|
@@ -6,6 +6,7 @@ import logging
|
|
|
6
6
|
from typing import Any, Callable, Dict, Optional, Tuple
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
9
10
|
|
|
10
11
|
from .quality import compute_quality_metrics
|
|
11
12
|
|
|
@@ -13,11 +14,11 @@ logger = logging.getLogger(__name__)
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def _evaluate_browser_export_quality(
|
|
16
|
-
target_linear_rgb:
|
|
17
|
-
fallback_linear_rgb: Optional[
|
|
17
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
18
|
+
fallback_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
18
19
|
*,
|
|
19
20
|
artifact_name: str,
|
|
20
|
-
capture: Callable[[int, int], Tuple[
|
|
21
|
+
capture: Callable[[int, int], Tuple[npt.NDArray[Any], str]],
|
|
21
22
|
) -> Dict[str, Any]:
|
|
22
23
|
"""Grade a browser artifact while keeping proxy evidence diagnostic-only."""
|
|
23
24
|
|
|
@@ -59,9 +60,9 @@ def _evaluate_browser_export_quality(
|
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def evaluate_svg_export_quality(
|
|
62
|
-
target_linear_rgb:
|
|
63
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
63
64
|
svg_path: str,
|
|
64
|
-
fallback_linear_rgb: Optional[
|
|
65
|
+
fallback_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
65
66
|
) -> Dict[str, Any]:
|
|
66
67
|
from .browser_capture import render_svg_in_browser_to_linear_rgb
|
|
67
68
|
|
|
@@ -76,9 +77,9 @@ def evaluate_svg_export_quality(
|
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
def evaluate_css_export_quality(
|
|
79
|
-
target_linear_rgb:
|
|
80
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
80
81
|
html_path: str,
|
|
81
|
-
fallback_linear_rgb: Optional[
|
|
82
|
+
fallback_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
82
83
|
) -> Dict[str, Any]:
|
|
83
84
|
from .browser_capture import render_css_html_in_browser_to_linear_rgb
|
|
84
85
|
|
|
@@ -93,9 +94,9 @@ def evaluate_css_export_quality(
|
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
def evaluate_native_canvas_export_quality(
|
|
96
|
-
target_linear_rgb:
|
|
97
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
97
98
|
html_path: str,
|
|
98
|
-
fallback_linear_rgb: Optional[
|
|
99
|
+
fallback_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
99
100
|
) -> Dict[str, Any]:
|
|
100
101
|
from .browser_capture import render_canvas_html_in_browser_to_linear_rgb
|
|
101
102
|
|
|
@@ -110,9 +111,9 @@ def evaluate_native_canvas_export_quality(
|
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
def evaluate_pixel_runtime_export_quality(
|
|
113
|
-
target_linear_rgb:
|
|
114
|
+
target_linear_rgb: npt.NDArray[Any],
|
|
114
115
|
html_path: str,
|
|
115
|
-
fallback_linear_rgb: Optional[
|
|
116
|
+
fallback_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
116
117
|
) -> Dict[str, Any]:
|
|
117
118
|
from .browser_capture import render_pixel_runtime_html_in_browser_to_linear_rgb
|
|
118
119
|
|
|
@@ -7,6 +7,7 @@ from html import escape as escape_html
|
|
|
7
7
|
from typing import Any, Dict, List, Optional
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
10
11
|
|
|
11
12
|
from .color import linear_to_srgb
|
|
12
13
|
from .export_common import (
|
|
@@ -24,7 +25,7 @@ def generate_css_splat_html(
|
|
|
24
25
|
splats: List[GaussianSplat],
|
|
25
26
|
width: int,
|
|
26
27
|
height: int,
|
|
27
|
-
background_linear_rgb: Optional[
|
|
28
|
+
background_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
28
29
|
title: str = "SplatThis CSS",
|
|
29
30
|
parallax_strength: float = 0.0,
|
|
30
31
|
hover_grid_size: int = 10,
|
|
@@ -210,7 +211,7 @@ def generate_native_canvas_html(
|
|
|
210
211
|
splats: List[GaussianSplat],
|
|
211
212
|
width: int,
|
|
212
213
|
height: int,
|
|
213
|
-
background_linear_rgb: Optional[
|
|
214
|
+
background_linear_rgb: Optional[npt.NDArray[Any]] = None,
|
|
214
215
|
title: str = "SplatThis Canvas",
|
|
215
216
|
parallax_strength: float = 0.0,
|
|
216
217
|
k_sigma: float = 2.5,
|
|
@@ -551,6 +551,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
551
551
|
default=None,
|
|
552
552
|
help="Optional directory for run manifest + iteration dumps",
|
|
553
553
|
)
|
|
554
|
+
parser.add_argument(
|
|
555
|
+
"--save-json",
|
|
556
|
+
action="store_true",
|
|
557
|
+
help=(
|
|
558
|
+
"Also write the splat population as canonical raw JSON beside the "
|
|
559
|
+
"output, for re-scoring or re-emitting without retraining"
|
|
560
|
+
),
|
|
561
|
+
)
|
|
554
562
|
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose logging")
|
|
555
563
|
return parser
|
|
556
564
|
|
|
@@ -696,6 +704,7 @@ def _run_conversion(args: argparse.Namespace) -> int:
|
|
|
696
704
|
output_format=args.fmt,
|
|
697
705
|
seed=args.seed,
|
|
698
706
|
artifacts_dir=args.artifacts_dir,
|
|
707
|
+
save_json=args.save_json,
|
|
699
708
|
verbose=args.verbose,
|
|
700
709
|
)
|
|
701
710
|
print(f"Wrote {output}")
|
|
@@ -2,33 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
5
7
|
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
# `np.where` evaluates both branches, so the power term sees every input --
|
|
11
|
+
# including the negatives the linear branch exists to handle. Raising a
|
|
12
|
+
# negative base to a fractional exponent yields NaN and a RuntimeWarning, which
|
|
13
|
+
# `where` then discards. The result is correct but the warning is real, and it
|
|
14
|
+
# fires during ordinary compositing wherever a linear value dips below zero.
|
|
15
|
+
# The float32 variants below already clamped for this reason; these did not.
|
|
16
|
+
_POWER_FLOOR = 1e-12
|
|
6
17
|
|
|
7
18
|
|
|
8
|
-
def srgb_to_linear(srgb:
|
|
19
|
+
def srgb_to_linear(srgb: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
9
20
|
"""Convert normalized sRGB values to linear RGB."""
|
|
10
21
|
|
|
11
22
|
values = np.asarray(srgb)
|
|
12
23
|
return np.where(
|
|
13
24
|
values <= 0.04045,
|
|
14
25
|
values / 12.92,
|
|
15
|
-
np.power((values + 0.055) / 1.055, 2.4),
|
|
26
|
+
np.power(np.maximum((values + 0.055) / 1.055, _POWER_FLOOR), 2.4),
|
|
16
27
|
)
|
|
17
28
|
|
|
18
29
|
|
|
19
|
-
def linear_to_srgb(linear:
|
|
30
|
+
def linear_to_srgb(linear: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
20
31
|
"""Convert normalized linear RGB values to clipped sRGB."""
|
|
21
32
|
|
|
22
33
|
values = np.asarray(linear)
|
|
23
34
|
srgb = np.where(
|
|
24
35
|
values <= 0.0031308,
|
|
25
36
|
12.92 * values,
|
|
26
|
-
1.055 * np.power(values, 1.0 / 2.4) - 0.055,
|
|
37
|
+
1.055 * np.power(np.maximum(values, _POWER_FLOOR), 1.0 / 2.4) - 0.055,
|
|
27
38
|
)
|
|
28
39
|
return np.clip(srgb, 0.0, 1.0)
|
|
29
40
|
|
|
30
41
|
|
|
31
|
-
def linear_to_srgb_float32(linear:
|
|
42
|
+
def linear_to_srgb_float32(linear: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
32
43
|
"""Convert to sRGB with the clipped float32 semantics used by scorers."""
|
|
33
44
|
|
|
34
45
|
values = np.clip(np.asarray(linear), 0.0, 1.0)
|
|
@@ -39,7 +50,7 @@ def linear_to_srgb_float32(linear: np.ndarray) -> np.ndarray:
|
|
|
39
50
|
).astype(np.float32)
|
|
40
51
|
|
|
41
52
|
|
|
42
|
-
def srgb_to_linear_float32(srgb:
|
|
53
|
+
def srgb_to_linear_float32(srgb: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
43
54
|
"""Convert to linear RGB with the clipped float32 scorer semantics."""
|
|
44
55
|
|
|
45
56
|
values = np.clip(np.asarray(srgb), 0.0, 1.0)
|