penampakan 0.1.0__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.
Files changed (103) hide show
  1. penampakan-0.1.0/.github/workflows/ci.yml +38 -0
  2. penampakan-0.1.0/.gitignore +14 -0
  3. penampakan-0.1.0/CHANGELOG +12 -0
  4. penampakan-0.1.0/IMPLEMENTATION_SPEC.md +51 -0
  5. penampakan-0.1.0/LICENSE +21 -0
  6. penampakan-0.1.0/PKG-INFO +200 -0
  7. penampakan-0.1.0/README.md +160 -0
  8. penampakan-0.1.0/benchmarks/benchmark_metadata.py +783 -0
  9. penampakan-0.1.0/benchmarks/metadata_latency.png +0 -0
  10. penampakan-0.1.0/pyproject.toml +87 -0
  11. penampakan-0.1.0/src/penampakan/__init__.py +214 -0
  12. penampakan-0.1.0/src/penampakan/_version.py +3 -0
  13. penampakan-0.1.0/src/penampakan/backends/__init__.py +17 -0
  14. penampakan-0.1.0/src/penampakan/backends/callable.py +106 -0
  15. penampakan-0.1.0/src/penampakan/backends/pillow.py +206 -0
  16. penampakan-0.1.0/src/penampakan/backends/tesseract.py +635 -0
  17. penampakan-0.1.0/src/penampakan/backends/transformers.py +593 -0
  18. penampakan-0.1.0/src/penampakan/client.py +398 -0
  19. penampakan-0.1.0/src/penampakan/config.py +154 -0
  20. penampakan-0.1.0/src/penampakan/errors.py +363 -0
  21. penampakan-0.1.0/src/penampakan/evaluation.py +174 -0
  22. penampakan-0.1.0/src/penampakan/image/__init__.py +5 -0
  23. penampakan-0.1.0/src/penampakan/image/assets.py +462 -0
  24. penampakan-0.1.0/src/penampakan/image/canonical.py +39 -0
  25. penampakan-0.1.0/src/penampakan/image/geometry.py +407 -0
  26. penampakan-0.1.0/src/penampakan/image/loader.py +344 -0
  27. penampakan-0.1.0/src/penampakan/image/transforms.py +307 -0
  28. penampakan-0.1.0/src/penampakan/llms/__init__.py +5 -0
  29. penampakan-0.1.0/src/penampakan/llms/callable.py +60 -0
  30. penampakan-0.1.0/src/penampakan/models.py +957 -0
  31. penampakan-0.1.0/src/penampakan/perception/__init__.py +1 -0
  32. penampakan-0.1.0/src/penampakan/perception/cache.py +364 -0
  33. penampakan-0.1.0/src/penampakan/perception/normalize.py +413 -0
  34. penampakan-0.1.0/src/penampakan/perception/registry.py +175 -0
  35. penampakan-0.1.0/src/penampakan/perception/router.py +599 -0
  36. penampakan-0.1.0/src/penampakan/perception/store.py +300 -0
  37. penampakan-0.1.0/src/penampakan/protocols.py +88 -0
  38. penampakan-0.1.0/src/penampakan/py.typed +0 -0
  39. penampakan-0.1.0/src/penampakan/reasoning/__init__.py +1 -0
  40. penampakan-0.1.0/src/penampakan/reasoning/actions.py +279 -0
  41. penampakan-0.1.0/src/penampakan/reasoning/answer.py +125 -0
  42. penampakan-0.1.0/src/penampakan/reasoning/budget.py +279 -0
  43. penampakan-0.1.0/src/penampakan/reasoning/context.py +530 -0
  44. penampakan-0.1.0/src/penampakan/reasoning/policy.py +84 -0
  45. penampakan-0.1.0/src/penampakan/reasoning/prompts.py +372 -0
  46. penampakan-0.1.0/src/penampakan/session.py +1410 -0
  47. penampakan-0.1.0/src/penampakan/sync.py +478 -0
  48. penampakan-0.1.0/src/penampakan/tools/__init__.py +1 -0
  49. penampakan-0.1.0/src/penampakan/tools/builtin.py +254 -0
  50. penampakan-0.1.0/src/penampakan/tools/vision.py +225 -0
  51. penampakan-0.1.0/src/penampakan/tracing.py +591 -0
  52. penampakan-0.1.0/tests/__init__.py +1 -0
  53. penampakan-0.1.0/tests/contract/__init__.py +1 -0
  54. penampakan-0.1.0/tests/contract/test_backend_contract.py +81 -0
  55. penampakan-0.1.0/tests/contract/test_distribution.py +45 -0
  56. penampakan-0.1.0/tests/contract/test_json_schemas.py +313 -0
  57. penampakan-0.1.0/tests/contract/test_protocols.py +279 -0
  58. penampakan-0.1.0/tests/contract/test_trace_redaction.py +126 -0
  59. penampakan-0.1.0/tests/e2e/__init__.py +1 -0
  60. penampakan-0.1.0/tests/e2e/test_workflows.py +266 -0
  61. penampakan-0.1.0/tests/fixtures/__init__.py +1 -0
  62. penampakan-0.1.0/tests/fixtures/images.py +52 -0
  63. penampakan-0.1.0/tests/integration/__init__.py +1 -0
  64. penampakan-0.1.0/tests/unit/__init__.py +1 -0
  65. penampakan-0.1.0/tests/unit/backends/__init__.py +1 -0
  66. penampakan-0.1.0/tests/unit/backends/test_callable.py +114 -0
  67. penampakan-0.1.0/tests/unit/backends/test_pillow.py +94 -0
  68. penampakan-0.1.0/tests/unit/backends/test_tesseract.py +415 -0
  69. penampakan-0.1.0/tests/unit/backends/test_transformers.py +426 -0
  70. penampakan-0.1.0/tests/unit/image/__init__.py +1 -0
  71. penampakan-0.1.0/tests/unit/image/test_assets.py +120 -0
  72. penampakan-0.1.0/tests/unit/image/test_fuzz.py +15 -0
  73. penampakan-0.1.0/tests/unit/image/test_geometry.py +97 -0
  74. penampakan-0.1.0/tests/unit/image/test_geometry_contract.py +133 -0
  75. penampakan-0.1.0/tests/unit/image/test_loader.py +193 -0
  76. penampakan-0.1.0/tests/unit/image/test_transforms.py +121 -0
  77. penampakan-0.1.0/tests/unit/llms/__init__.py +0 -0
  78. penampakan-0.1.0/tests/unit/llms/test_callable.py +96 -0
  79. penampakan-0.1.0/tests/unit/perception/__init__.py +0 -0
  80. penampakan-0.1.0/tests/unit/perception/test_cache.py +317 -0
  81. penampakan-0.1.0/tests/unit/perception/test_normalize.py +348 -0
  82. penampakan-0.1.0/tests/unit/perception/test_registry.py +129 -0
  83. penampakan-0.1.0/tests/unit/perception/test_router.py +440 -0
  84. penampakan-0.1.0/tests/unit/perception/test_store.py +436 -0
  85. penampakan-0.1.0/tests/unit/reasoning/__init__.py +0 -0
  86. penampakan-0.1.0/tests/unit/reasoning/helpers.py +214 -0
  87. penampakan-0.1.0/tests/unit/reasoning/test_actions.py +86 -0
  88. penampakan-0.1.0/tests/unit/reasoning/test_answer.py +208 -0
  89. penampakan-0.1.0/tests/unit/reasoning/test_budget.py +260 -0
  90. penampakan-0.1.0/tests/unit/reasoning/test_context.py +296 -0
  91. penampakan-0.1.0/tests/unit/reasoning/test_policy.py +155 -0
  92. penampakan-0.1.0/tests/unit/reasoning/test_prompts.py +150 -0
  93. penampakan-0.1.0/tests/unit/test_client.py +571 -0
  94. penampakan-0.1.0/tests/unit/test_config.py +62 -0
  95. penampakan-0.1.0/tests/unit/test_errors.py +43 -0
  96. penampakan-0.1.0/tests/unit/test_evaluation.py +118 -0
  97. penampakan-0.1.0/tests/unit/test_imports.py +55 -0
  98. penampakan-0.1.0/tests/unit/test_models.py +146 -0
  99. penampakan-0.1.0/tests/unit/test_session.py +1024 -0
  100. penampakan-0.1.0/tests/unit/test_sync.py +524 -0
  101. penampakan-0.1.0/tests/unit/test_tracing.py +232 -0
  102. penampakan-0.1.0/tests/unit/tools/__init__.py +0 -0
  103. penampakan-0.1.0/tests/unit/tools/test_builtin.py +107 -0
@@ -0,0 +1,38 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ cache: pip
20
+ - run: python -m pip install -e '.[dev]'
21
+ - run: ruff check .
22
+ - run: ruff format --check .
23
+ - run: mypy --strict src/penampakan
24
+ - run: pytest -m 'not models and not ocr' --cov=penampakan --cov-fail-under=90
25
+ - run: python -m build
26
+ - run: python -m twine check dist/*
27
+
28
+ windows-smoke:
29
+ runs-on: windows-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.13"
35
+ cache: pip
36
+ - run: python -m pip install -e '.[dev]'
37
+ - run: python -c "import penampakan"
38
+ - run: pytest -m "not models and not ocr"
@@ -0,0 +1,14 @@
1
+ .coverage
2
+ .env
3
+ .hypothesis/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .venv/
8
+ __pycache__/
9
+ build/
10
+ dist/
11
+ htmlcov/
12
+ *.egg-info/
13
+ *.py[cod]
14
+ !README.md
@@ -0,0 +1,12 @@
1
+ Penampakan 0.1.0
2
+ Released 2026-08-10
3
+
4
+ Initial release of bounded visual tool orchestration for text-only language
5
+ models. It includes strict public contracts, safe image normalization,
6
+ deterministic perception routing, evidence-grounded question answering,
7
+ reusable asynchronous and synchronous sessions, optional local model adapters,
8
+ redacted tracing, and experimental evaluation metrics.
9
+
10
+ Metadata inspection now reuses the loader's canonical image, uses balanced PNG
11
+ compression, and reports reusable-session latency plus executed normalization
12
+ and safety checks in the benchmark.
@@ -0,0 +1,51 @@
1
+ # Penampakan implementation specification
2
+
3
+ Status: proposed v0.1 specification
4
+
5
+ Last updated: 2026-08-10
6
+
7
+ Penampakan is a Python library that lets a text-only language model answer
8
+ questions about static images. It does this by translating image evidence into
9
+ typed, attributable text observations and letting the language model request
10
+ additional bounded vision operations when the initial evidence is insufficient.
11
+
12
+ This document set is normative for the first implementation. Read it in order:
13
+
14
+ 1. [Research and product scope](docs/specs/00-research-and-product.md)
15
+ 2. [Architecture](docs/specs/01-architecture.md)
16
+ 3. [Public API and data contracts](docs/specs/02-public-api-and-contracts.md)
17
+ 4. [Runtime, tools, and backends](docs/specs/03-runtime-tools-and-backends.md)
18
+ 5. [Delivery plan and quality gates](docs/specs/04-delivery-and-quality.md)
19
+
20
+ The words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are requirements in the
21
+ sense used by RFC 2119. A coding agent must not silently replace a MUST with a
22
+ different design. If a requirement proves impractical, record the deviation in
23
+ an architecture decision record before implementing it.
24
+
25
+ ## One-paragraph implementation target
26
+
27
+ Build an async-first, provider-neutral package named `penampakan`. It must load
28
+ and normalize safe raster image inputs, retain immutable original and derived
29
+ image assets, invoke registered vision backends through typed capabilities,
30
+ store every result as a provenance-bearing observation, and run a bounded JSON
31
+ action loop in which a text-only LLM either calls a declared vision tool or
32
+ answers using observation citations. Ship a synchronous facade, deterministic
33
+ fake backends, core Pillow tools, at least one usable caption/detection adapter
34
+ and one OCR adapter, complete contract tests, and examples that work without a
35
+ multimodal LLM.
36
+
37
+ ## Definition of success for v0.1
38
+
39
+ The release is complete only when all of the following are true:
40
+
41
+ - A user can call `Penampakan.ask(image, question)` with a text-only LLM and
42
+ receive a `VisionAnswer` containing inspectable evidence references.
43
+ - A user can call `inspect` without configuring any LLM.
44
+ - Vision capabilities are replaceable independently; the orchestrator never
45
+ imports a concrete model package.
46
+ - Unsupported or inconclusive perception produces an explicit
47
+ `insufficient_evidence` result instead of a fabricated answer.
48
+ - Image bytes, OCR text, and prompts are not logged or persisted by default.
49
+ - No LLM-generated Python or shell code is executed in v0.1.
50
+ - Unit, backend-contract, orchestration, security, and end-to-end tests described
51
+ in the quality spec pass on all supported Python versions.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Penampakan contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.4
2
+ Name: penampakan
3
+ Version: 0.1.0
4
+ Summary: Safe visual tool orchestration for text-only language models
5
+ Author: Penampakan contributors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: <3.14,>=3.10
17
+ Requires-Dist: pillow>=10
18
+ Requires-Dist: pydantic<3,>=2.7
19
+ Requires-Dist: typing-extensions>=4.10; python_version < '3.12'
20
+ Provides-Extra: benchmark
21
+ Requires-Dist: imageio>=2.31; extra == 'benchmark'
22
+ Requires-Dist: matplotlib>=3.8; extra == 'benchmark'
23
+ Requires-Dist: numpy>=1.23; extra == 'benchmark'
24
+ Requires-Dist: opencv-python-headless>=4.8; extra == 'benchmark'
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == 'dev'
27
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5; extra == 'dev'
33
+ Requires-Dist: twine>=5; extra == 'dev'
34
+ Provides-Extra: ocr
35
+ Requires-Dist: pytesseract>=0.3.10; extra == 'ocr'
36
+ Provides-Extra: transformers
37
+ Requires-Dist: torch>=2.1; extra == 'transformers'
38
+ Requires-Dist: transformers<5,>=4.40; extra == 'transformers'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # Penampakan
42
+
43
+ Penampakan is an async-first Python library that lets a text-only language
44
+ model reason about static images. It turns results from replaceable vision
45
+ backends into typed, attributable observations, then runs a bounded tool loop
46
+ that requires the model to cite those observations in its answer.
47
+
48
+ The package can also be used without a language model for deterministic image
49
+ inspection. Its built-in Pillow backend provides normalized metadata and
50
+ dominant colors; optional adapters add Tesseract OCR and local Transformers
51
+ captioning or detection.
52
+
53
+ Penampakan is currently alpha software.
54
+
55
+ ## Why Penampakan?
56
+
57
+ - Provider-neutral protocols for text LLMs and vision backends.
58
+ - Async clients and sessions, plus a blocking facade for synchronous programs.
59
+ - Immutable Pydantic contracts for assets, observations, evidence, and traces.
60
+ - Bounded tool, backend, LLM, image, and timeout budgets.
61
+ - Safe raster normalization with provenance for derived images.
62
+ - Evidence-grounded answers and redacted tracing by default.
63
+ - No generated Python or shell execution.
64
+
65
+ ## Installation
66
+
67
+ Penampakan requires Python 3.10 through 3.13.
68
+
69
+ ```bash
70
+ python -m pip install penampakan
71
+ ```
72
+
73
+ Optional integrations are installed separately:
74
+
75
+ ```bash
76
+ python -m pip install 'penampakan[ocr]'
77
+ python -m pip install 'penampakan[transformers]'
78
+ ```
79
+
80
+ The OCR extra installs the Python adapter; the Tesseract executable must also
81
+ be available on the system.
82
+
83
+ ## Quick start
84
+
85
+ Inspect an image without configuring an LLM:
86
+
87
+ ```python
88
+ from penampakan import (
89
+ ColorsRequest,
90
+ InspectionOperation,
91
+ InspectionPlan,
92
+ MetadataRequest,
93
+ Penampakan,
94
+ )
95
+
96
+ plan = InspectionPlan(
97
+ operations=(
98
+ InspectionOperation(request=MetadataRequest()),
99
+ InspectionOperation(request=ColorsRequest(count=5)),
100
+ ),
101
+ include_available_overview=False,
102
+ )
103
+
104
+ with Penampakan() as vision:
105
+ result = vision.inspect("photo.png", plan)
106
+
107
+ for observation in result.observations:
108
+ print(observation.payload)
109
+ ```
110
+
111
+ For evidence-grounded question answering, construct `AsyncPenampakan` or
112
+ `Penampakan` with a `TextLLM` implementation and the vision backends needed for
113
+ the task, then call `ask(image, question)`. `CallableTextLLM` and
114
+ `CallableVisionBackend` are available as adapters for application functions.
115
+
116
+ Input images may be PNG, JPEG, or WebP paths, encoded bytes, binary streams, or
117
+ Pillow images. Remote URLs are rejected by default. Images are orientation
118
+ corrected, bounded by configurable limits, and normalized to canonical RGB or
119
+ RGBA assets before a backend sees them.
120
+
121
+ ## Benchmark
122
+
123
+ The benchmark is at
124
+ [`benchmarks/benchmark_metadata.py`](benchmarks/benchmark_metadata.py). It
125
+ compares the latency of an end-to-end Penampakan metadata inspection with
126
+ direct metadata decoding through Pillow, OpenCV, and ImageIO. The fixture is
127
+ generated locally, competitors are run in rotating order over multiple rounds,
128
+ and unavailable optional libraries are clearly reported as skipped. It also
129
+ measures Penampakan's reusable-session workflow and executes representative
130
+ normalization, safety, and attribution checks so the additional work in the
131
+ end-to-end path remains visible.
132
+
133
+ Install the benchmark dependencies and run it from the repository root:
134
+
135
+ ```bash
136
+ python -m pip install -e '.[benchmark]'
137
+ python benchmarks/benchmark_metadata.py
138
+ ```
139
+
140
+ Use `--help` to change the image size, warmups, iterations, rounds, or output
141
+ format. For example:
142
+
143
+ ```bash
144
+ python benchmarks/benchmark_metadata.py --iterations 50 --rounds 7
145
+ python benchmarks/benchmark_metadata.py --reuse-count 50
146
+ python benchmarks/benchmark_metadata.py --format json
147
+ python benchmarks/benchmark_metadata.py --plot benchmarks/metadata_latency.png
148
+ ```
149
+
150
+ Reference results captured on 10 August 2026 with Python 3.13.15 on x86-64
151
+ WSL2 (Linux 6.18.33.2, glibc 2.43) are shown below. The run used the default
152
+ 640x480 RGBA PNG fixture (45,019 encoded bytes), 3 warmups, 20 iterations, and
153
+ 5 rounds. The primary table is the one-shot end-to-end comparison.
154
+
155
+ ![Metadata inspection latency benchmark](benchmarks/metadata_latency.png)
156
+
157
+ | Library | Version | Median (ms) | Min (ms) | Max (ms) | Calls/s | vs fastest |
158
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
159
+ | Penampakan | 0.1.0 | 18.846 | 18.781 | 19.030 | 53.1 | 6.85x |
160
+ | Pillow (direct) | 12.3.0 | 2.753 | 2.737 | 2.806 | 363.2 | 1.00x |
161
+ | OpenCV | 5.0.0 | 3.237 | 3.129 | 3.461 | 309.0 | 1.18x |
162
+ | ImageIO | 2.37.4 | 4.431 | 4.347 | 4.470 | 225.7 | 1.61x |
163
+
164
+ The reusable workflow opens and normalizes one image, performs 20 typed
165
+ metadata inspections, and then closes the session. Its median open latency was
166
+ 18.114 ms, each warm inspection was 0.494 ms, and close latency was 0.096 ms.
167
+ Including the open and close costs, that is **1.432 ms per inspection
168
+ amortized**, 13.16x faster than Penampakan's one-shot path. This is a
169
+ Penampakan lifecycle measurement, not a ranking against the direct-library
170
+ cases above.
171
+
172
+ The same run passed six executed contract checks:
173
+
174
+ - equivalent normalized metadata across PNG, JPEG, and WebP;
175
+ - EXIF orientation before reported dimensions;
176
+ - removal of opaque alpha while preserving real transparency;
177
+ - documented rejection of malformed and animated inputs;
178
+ - remote-source policy and input-byte bounds; and
179
+ - authoritative backend provenance with a completed redacted trace.
180
+
181
+ This is an overhead benchmark, not a capability or accuracy ranking. The
182
+ Penampakan path performs bounded input handling, orientation and mode
183
+ normalization, canonical encoding and hashing, typed result validation,
184
+ routing, tracing, and session cleanup. The direct alternatives only decode the
185
+ fixture and return equivalent dimensions and alpha metadata. Contract checks
186
+ are reported separately from latency rankings. Compare results on the same
187
+ machine and treat very short runs as smoke tests rather than stable
188
+ measurements.
189
+
190
+ ## Development
191
+
192
+ ```bash
193
+ python -m pip install -e '.[dev]'
194
+ ruff check .
195
+ ruff format --check .
196
+ mypy --strict src/penampakan
197
+ pytest -m 'not models and not ocr'
198
+ ```
199
+
200
+ Penampakan is licensed under the MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,160 @@
1
+ # Penampakan
2
+
3
+ Penampakan is an async-first Python library that lets a text-only language
4
+ model reason about static images. It turns results from replaceable vision
5
+ backends into typed, attributable observations, then runs a bounded tool loop
6
+ that requires the model to cite those observations in its answer.
7
+
8
+ The package can also be used without a language model for deterministic image
9
+ inspection. Its built-in Pillow backend provides normalized metadata and
10
+ dominant colors; optional adapters add Tesseract OCR and local Transformers
11
+ captioning or detection.
12
+
13
+ Penampakan is currently alpha software.
14
+
15
+ ## Why Penampakan?
16
+
17
+ - Provider-neutral protocols for text LLMs and vision backends.
18
+ - Async clients and sessions, plus a blocking facade for synchronous programs.
19
+ - Immutable Pydantic contracts for assets, observations, evidence, and traces.
20
+ - Bounded tool, backend, LLM, image, and timeout budgets.
21
+ - Safe raster normalization with provenance for derived images.
22
+ - Evidence-grounded answers and redacted tracing by default.
23
+ - No generated Python or shell execution.
24
+
25
+ ## Installation
26
+
27
+ Penampakan requires Python 3.10 through 3.13.
28
+
29
+ ```bash
30
+ python -m pip install penampakan
31
+ ```
32
+
33
+ Optional integrations are installed separately:
34
+
35
+ ```bash
36
+ python -m pip install 'penampakan[ocr]'
37
+ python -m pip install 'penampakan[transformers]'
38
+ ```
39
+
40
+ The OCR extra installs the Python adapter; the Tesseract executable must also
41
+ be available on the system.
42
+
43
+ ## Quick start
44
+
45
+ Inspect an image without configuring an LLM:
46
+
47
+ ```python
48
+ from penampakan import (
49
+ ColorsRequest,
50
+ InspectionOperation,
51
+ InspectionPlan,
52
+ MetadataRequest,
53
+ Penampakan,
54
+ )
55
+
56
+ plan = InspectionPlan(
57
+ operations=(
58
+ InspectionOperation(request=MetadataRequest()),
59
+ InspectionOperation(request=ColorsRequest(count=5)),
60
+ ),
61
+ include_available_overview=False,
62
+ )
63
+
64
+ with Penampakan() as vision:
65
+ result = vision.inspect("photo.png", plan)
66
+
67
+ for observation in result.observations:
68
+ print(observation.payload)
69
+ ```
70
+
71
+ For evidence-grounded question answering, construct `AsyncPenampakan` or
72
+ `Penampakan` with a `TextLLM` implementation and the vision backends needed for
73
+ the task, then call `ask(image, question)`. `CallableTextLLM` and
74
+ `CallableVisionBackend` are available as adapters for application functions.
75
+
76
+ Input images may be PNG, JPEG, or WebP paths, encoded bytes, binary streams, or
77
+ Pillow images. Remote URLs are rejected by default. Images are orientation
78
+ corrected, bounded by configurable limits, and normalized to canonical RGB or
79
+ RGBA assets before a backend sees them.
80
+
81
+ ## Benchmark
82
+
83
+ The benchmark is at
84
+ [`benchmarks/benchmark_metadata.py`](benchmarks/benchmark_metadata.py). It
85
+ compares the latency of an end-to-end Penampakan metadata inspection with
86
+ direct metadata decoding through Pillow, OpenCV, and ImageIO. The fixture is
87
+ generated locally, competitors are run in rotating order over multiple rounds,
88
+ and unavailable optional libraries are clearly reported as skipped. It also
89
+ measures Penampakan's reusable-session workflow and executes representative
90
+ normalization, safety, and attribution checks so the additional work in the
91
+ end-to-end path remains visible.
92
+
93
+ Install the benchmark dependencies and run it from the repository root:
94
+
95
+ ```bash
96
+ python -m pip install -e '.[benchmark]'
97
+ python benchmarks/benchmark_metadata.py
98
+ ```
99
+
100
+ Use `--help` to change the image size, warmups, iterations, rounds, or output
101
+ format. For example:
102
+
103
+ ```bash
104
+ python benchmarks/benchmark_metadata.py --iterations 50 --rounds 7
105
+ python benchmarks/benchmark_metadata.py --reuse-count 50
106
+ python benchmarks/benchmark_metadata.py --format json
107
+ python benchmarks/benchmark_metadata.py --plot benchmarks/metadata_latency.png
108
+ ```
109
+
110
+ Reference results captured on 10 August 2026 with Python 3.13.15 on x86-64
111
+ WSL2 (Linux 6.18.33.2, glibc 2.43) are shown below. The run used the default
112
+ 640x480 RGBA PNG fixture (45,019 encoded bytes), 3 warmups, 20 iterations, and
113
+ 5 rounds. The primary table is the one-shot end-to-end comparison.
114
+
115
+ ![Metadata inspection latency benchmark](benchmarks/metadata_latency.png)
116
+
117
+ | Library | Version | Median (ms) | Min (ms) | Max (ms) | Calls/s | vs fastest |
118
+ | --- | --- | ---: | ---: | ---: | ---: | ---: |
119
+ | Penampakan | 0.1.0 | 18.846 | 18.781 | 19.030 | 53.1 | 6.85x |
120
+ | Pillow (direct) | 12.3.0 | 2.753 | 2.737 | 2.806 | 363.2 | 1.00x |
121
+ | OpenCV | 5.0.0 | 3.237 | 3.129 | 3.461 | 309.0 | 1.18x |
122
+ | ImageIO | 2.37.4 | 4.431 | 4.347 | 4.470 | 225.7 | 1.61x |
123
+
124
+ The reusable workflow opens and normalizes one image, performs 20 typed
125
+ metadata inspections, and then closes the session. Its median open latency was
126
+ 18.114 ms, each warm inspection was 0.494 ms, and close latency was 0.096 ms.
127
+ Including the open and close costs, that is **1.432 ms per inspection
128
+ amortized**, 13.16x faster than Penampakan's one-shot path. This is a
129
+ Penampakan lifecycle measurement, not a ranking against the direct-library
130
+ cases above.
131
+
132
+ The same run passed six executed contract checks:
133
+
134
+ - equivalent normalized metadata across PNG, JPEG, and WebP;
135
+ - EXIF orientation before reported dimensions;
136
+ - removal of opaque alpha while preserving real transparency;
137
+ - documented rejection of malformed and animated inputs;
138
+ - remote-source policy and input-byte bounds; and
139
+ - authoritative backend provenance with a completed redacted trace.
140
+
141
+ This is an overhead benchmark, not a capability or accuracy ranking. The
142
+ Penampakan path performs bounded input handling, orientation and mode
143
+ normalization, canonical encoding and hashing, typed result validation,
144
+ routing, tracing, and session cleanup. The direct alternatives only decode the
145
+ fixture and return equivalent dimensions and alpha metadata. Contract checks
146
+ are reported separately from latency rankings. Compare results on the same
147
+ machine and treat very short runs as smoke tests rather than stable
148
+ measurements.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ python -m pip install -e '.[dev]'
154
+ ruff check .
155
+ ruff format --check .
156
+ mypy --strict src/penampakan
157
+ pytest -m 'not models and not ocr'
158
+ ```
159
+
160
+ Penampakan is licensed under the MIT License. See [LICENSE](LICENSE).