jevimage 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 (47) hide show
  1. jevimage-0.1.0/CHANGELOG.md +117 -0
  2. jevimage-0.1.0/LICENSE +21 -0
  3. jevimage-0.1.0/MANIFEST.in +9 -0
  4. jevimage-0.1.0/PKG-INFO +196 -0
  5. jevimage-0.1.0/README.md +162 -0
  6. jevimage-0.1.0/docs/cookbook.md +715 -0
  7. jevimage-0.1.0/docs/encoders.md +764 -0
  8. jevimage-0.1.0/docs/index.md +141 -0
  9. jevimage-0.1.0/docs/install.md +419 -0
  10. jevimage-0.1.0/docs/questions.md +863 -0
  11. jevimage-0.1.0/docs/quickstart.md +398 -0
  12. jevimage-0.1.0/docs/reference-cli.md +865 -0
  13. jevimage-0.1.0/docs/reference-http.md +713 -0
  14. jevimage-0.1.0/docs/reference-python.md +993 -0
  15. jevimage-0.1.0/docs/serving.md +790 -0
  16. jevimage-0.1.0/docs/training.md +739 -0
  17. jevimage-0.1.0/docs/troubleshooting.md +1253 -0
  18. jevimage-0.1.0/docs.json +53 -0
  19. jevimage-0.1.0/examples/make_fixtures.py +57 -0
  20. jevimage-0.1.0/examples/quickstart.py +47 -0
  21. jevimage-0.1.0/examples/train_your_own.py +39 -0
  22. jevimage-0.1.0/jevimage/__init__.py +119 -0
  23. jevimage-0.1.0/jevimage/answers.py +125 -0
  24. jevimage-0.1.0/jevimage/batcher.py +212 -0
  25. jevimage-0.1.0/jevimage/cli.py +548 -0
  26. jevimage-0.1.0/jevimage/client.py +611 -0
  27. jevimage-0.1.0/jevimage/core.py +298 -0
  28. jevimage-0.1.0/jevimage/encoders.py +342 -0
  29. jevimage-0.1.0/jevimage/images.py +213 -0
  30. jevimage-0.1.0/jevimage/local.py +188 -0
  31. jevimage-0.1.0/jevimage/server.py +442 -0
  32. jevimage-0.1.0/jevimage/store.py +228 -0
  33. jevimage-0.1.0/jevimage/training.py +226 -0
  34. jevimage-0.1.0/jevimage.egg-info/PKG-INFO +196 -0
  35. jevimage-0.1.0/jevimage.egg-info/SOURCES.txt +45 -0
  36. jevimage-0.1.0/jevimage.egg-info/dependency_links.txt +1 -0
  37. jevimage-0.1.0/jevimage.egg-info/entry_points.txt +2 -0
  38. jevimage-0.1.0/jevimage.egg-info/requires.txt +18 -0
  39. jevimage-0.1.0/jevimage.egg-info/top_level.txt +1 -0
  40. jevimage-0.1.0/mkdocs.yml +23 -0
  41. jevimage-0.1.0/pyproject.toml +71 -0
  42. jevimage-0.1.0/setup.cfg +4 -0
  43. jevimage-0.1.0/tests/conftest.py +103 -0
  44. jevimage-0.1.0/tests/test_api_only.py +314 -0
  45. jevimage-0.1.0/tests/test_core.py +287 -0
  46. jevimage-0.1.0/tests/test_store.py +154 -0
  47. jevimage-0.1.0/tests/test_training.py +141 -0
@@ -0,0 +1,117 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ Local SDK fixes - input that was accepted and then failed somewhere else, and three
6
+ things that were wrong rather than unclear:
7
+
8
+ - **`batch=True` no longer fails a whole batch for one bad image.** One caller's
9
+ truncated upload answered every other caller who rode the same forward pass with an
10
+ error about a file they never sent - on a server, up to 15 healthy requests at a time.
11
+ A failing batch is now re-run one item at a time, so each caller gets its own result
12
+ or its own error.
13
+ - **A head retrained by another process is picked up by `head()` and `ask()`,** not only
14
+ by `list_heads()`. The freshness check ran on a cache MISS, so once a name was cached
15
+ every later read served the pre-retrain weights and class names, with no error.
16
+ Deleting a head propagates the same way.
17
+ - **`HeadStore` is thread-safe for reads again.** Two threads syncing at once both
18
+ removed the same vanished head, and the loser raised `KeyError` out of `name in store`
19
+ - the check that gates every `head` question.
20
+ - Two processes saving the same head name no longer collide: the temp file is per
21
+ writer, so the loser is overwritten instead of failing with a `FileNotFoundError`
22
+ about a `.pt.tmp` it never named. Retraining a broken head clears it from
23
+ `heads.broken`.
24
+ - `load()` refuses to rename an encoder instance that is already cached under another
25
+ registry name, instead of silently renaming it and invalidating every head trained
26
+ under the first name. `register()` also drops cached ensembles built from that name.
27
+ - `Ensemble` normalises each member before concatenating, as `docs/encoders.md` always
28
+ said it did. Without it a member whose output is not unit-norm outvoted the others,
29
+ and the engine's L2 check could not see it.
30
+ - SigLIP entries truncate a caption to the tokeniser's 64 tokens instead of failing with
31
+ a transformers error about `max_position_embeddings`.
32
+ - A `prompt` or `template` is validated by trial substitution, so `'{} and {}'`,
33
+ `'a photo of {x}'` and `'{.__class__}'` are refused by name instead of escaping as a
34
+ raw `IndexError`/`KeyError`, or captioning every option `"<class 'str'>"`. `train()`
35
+ and `compare()` check `template=` before any work, so a bad one no longer raises
36
+ *after* the head is on disk.
37
+ - `ask_embedding()` checks what it was handed: a numpy array, a list, a stray batch
38
+ dimension or the wrong width are named, not left to fail inside the engine.
39
+ - `max_per_class` means one thing in all three `data` shapes: a positive int, or `None`.
40
+ `0` used to mean "no limit", `-1` dropped different examples in each shape, and a
41
+ folder was truncated twice - which could delete a whole class.
42
+ - `to_image()` treats `Path` exactly like `str`, truncates what it quotes back, reports a
43
+ malformed `data:` URL as a `ValueError` naming the URL, and accepts `DATA:`.
44
+ `read_folder` sorts by filename, not by full path, which is what the docs promised
45
+ once sub-directories are involved.
46
+ - `temperature` accepts any real number (numpy scalars included) in `1e-6 .. 1e6`, and
47
+ says so. Outside that range fp32 underflowed and the reported winner could flip. An
48
+ encoder's `logit_scale` goes through the same guard, so a negative one is an error
49
+ rather than a confident answer on the wrong option.
50
+ - The singular `instruction` is read again as a spelling of `instructions`, which is
51
+ what `docs/questions.md` documents. (0.1.0 removed it as "undocumented"; the docs row
52
+ says otherwise, so the docs win.)
53
+ - `jevimage[local]` requires `transformers>=4.56`: `metaclip2-huge` is `model_type
54
+ metaclip_2`, which older versions do not recognise, so the old floor allowed an
55
+ install that could not load a listed encoder.
56
+ - `dfn5b-h-14-384` names the extra it needs when `open_clip` is missing.
57
+
58
+ Client fixes, all in `jevimage.connect()`:
59
+
60
+ - `max_edge=` is now actually used. It was stored and never passed to the upload, so
61
+ every image was downscaled to 384px whatever you asked for, and `None` did nothing.
62
+ - An oversized image is exif-transposed before it is re-encoded for upload. A portrait
63
+ phone photo used to reach the server sideways, with its Orientation tag dropped by the
64
+ re-encode, so remote answers disagreed with local ones.
65
+ - A `POST` (`ask`, `embed`, `train`) is no longer silently replayed when a kept
66
+ connection dies. The failure can surface after the server has done the work, so a
67
+ `train()` could run twice. `GET` and `DELETE` still reconnect once.
68
+ - `api_key` is validated in `connect()`: a trailing newline or a non-ASCII character
69
+ used to blow up inside `http.client`, with the key itself in the traceback.
70
+ - `Remote.ask` gives `Jev.ask`'s wording for a bad temperature, `Remote.train` refuses
71
+ an out-of-range `epochs` (the server's 1..5000) with a sentence instead of a pydantic
72
+ field dump, a 2xx that is not JSON is a `JevError` naming the URL, and a redirect
73
+ leads with where it pointed instead of the proxy's HTML page.
74
+
75
+ Docs:
76
+
77
+ - **`examples/make_fixtures.py` ships the images the docs read.** Every page's
78
+ transcripts were run over `ds/`, `shots/` and `colours/` folders that were never in
79
+ the repo, so no block on any page ran as written, and the stand-in encoders key off
80
+ raw pixels, so no number could be reproduced either. The script writes all three, and
81
+ every printed number on quickstart, questions, training, cookbook, encoders, the three
82
+ references, index and README was re-run against them and re-pasted.
83
+ - Blocks that could not run as printed now run: `import json` in the quickstart's first
84
+ block, `ToyCalibrated` in the questions prelude, `import base64` and `import tempfile`
85
+ where the reference and encoders pages use them, a `uvicorn` line for the mounted-app
86
+ transcript in serving, and `jevimage.load(Toy())` where troubleshooting said
87
+ `jevimage.load("toy")`, which is not a registry name.
88
+ - `jevimage.encoders` (and any other submodule) resolves after a plain `import
89
+ jevimage`, so `troubleshooting.md`'s ensemble-name example and the advice in the
90
+ engine's own "wrap your encoder's output in `jevimage.encoders.unit()`" both work.
91
+ - Claims corrected against measurement: warm `load()` of `siglip2-base-224` is 8-10 s on
92
+ the 16-thread laptop CPU these docs are measured on, not 4.8/5.0 s; 5000 epochs on
93
+ 4000 examples is about 70 s, not "seconds"; 16 concurrent single-image embeds coalesce
94
+ into 2 forward passes on the toy encoder rather than staying 16; several keys may be
95
+ configured so callers can migrate, but withdrawing one is a restart, not zero
96
+ downtime; and N questions are only near-free once their captions are cached.
97
+
98
+ ## 0.1.0
99
+
100
+ First release.
101
+
102
+ - `choice`, `score`, `noul` and `head` questions against one image encode.
103
+ - `jevimage.load()` for in-process use, `jevimage.connect(url)` for a server. Same
104
+ methods, same return shapes.
105
+ - `jev` CLI: `ask`, `train`, `heads`, `rm`, `encoders`, `serve`. The first four take
106
+ `--url`.
107
+ - Linear heads trained on a labelled folder, with a per-class comparison against the
108
+ zero-shot prompts.
109
+ - Seven built-in encoders plus `'+'` ensembles, and `register()` for your own.
110
+
111
+ Two notes for anyone who tracked the pre-release source:
112
+
113
+ - `plan()` no longer accepts the singular `instruction` key as an alias for
114
+ `instructions`. It was undocumented; the singular spelling now raises.
115
+ - `Encoder` has no `preprocess()` / `img_from_px()` pair. Nothing called it, and the
116
+ cross-request batching it was meant to enable was never wired up. `Batcher` batches
117
+ whole images through `img()`.
jevimage-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the jevimage authors
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,9 @@
1
+ # The sdist is the whole project, not only the importable package: a distro packager or
2
+ # `pip install --no-binary :all:` builds from this archive, and setuptools' defaults ship
3
+ # tests/test_*.py without tests/conftest.py, which holds every fixture they import.
4
+ recursive-include tests *.py
5
+ recursive-include docs *.md
6
+ recursive-include examples *.py
7
+ include mkdocs.yml
8
+ include CHANGELOG.md
9
+ include docs.json
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: jevimage
3
+ Version: 0.1.0
4
+ Summary: A probability-first image classifier you can self-host: one encode, many typed questions, calibrated answers.
5
+ Author: the jevimage authors
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://docs.jevimage.org
8
+ Project-URL: Source, https://github.com/Per0x1de-1337/jevimage
9
+ Project-URL: Issues, https://github.com/Per0x1de-1337/jevimage/issues
10
+ Keywords: image-classification,zero-shot,clip,siglip,embeddings,calibration,vision,self-hosted
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: pillow>=9.0
20
+ Provides-Extra: local
21
+ Requires-Dist: torch>=2.0; extra == "local"
22
+ Requires-Dist: transformers>=4.56; extra == "local"
23
+ Provides-Extra: serve
24
+ Requires-Dist: jevimage[local]; extra == "serve"
25
+ Requires-Dist: fastapi>=0.110; extra == "serve"
26
+ Requires-Dist: uvicorn>=0.27; extra == "serve"
27
+ Provides-Extra: openclip
28
+ Requires-Dist: jevimage[local]; extra == "openclip"
29
+ Requires-Dist: open_clip_torch>=2.24; extra == "openclip"
30
+ Provides-Extra: dev
31
+ Requires-Dist: jevimage[serve]; extra == "dev"
32
+ Requires-Dist: pytest>=7; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # jevimage
36
+
37
+ jevimage answers typed questions about an image with probabilities instead of prose. The
38
+ image goes through a frozen dual encoder once and becomes a single unit-norm vector.
39
+ Every question after that (pick one of these options, place it on this rubric, yes or
40
+ no, run the head I trained on my own photos) is a matmul against that same vector.
41
+
42
+ Nothing is generated. An answer is a distribution over names *you* chose, plus a
43
+ confidence rescaled so one threshold works whether the question had two options or fifty.
44
+ So there is no sentence to parse and no retry loop. The same image and question give the
45
+ same numbers, to the six decimals they are printed to, every time, and the tenth question
46
+ about an image costs a matmul rather than another forward pass once its captions are
47
+ cached. A repeated question set is what gets cheap; a caption the process has never
48
+ seen still costs one text-tower pass. When prompts are not good enough you fit a linear
49
+ head on a folder of your own labelled images in seconds — and jevimage tells you, per
50
+ class, whether that head beat the prompts. Sometimes it did not, and it says so.
51
+
52
+ Full documentation is in [docs/](https://docs.jevimage.org/docs/index/).
53
+
54
+ ## Two ways to use it
55
+
56
+ Both are first class, and they are the same API. Pick the row you are in.
57
+
58
+ | | Use someone's server | Run it yourself |
59
+ |---|---|---|
60
+ | install | `pip install jevimage` | `pip install 'jevimage[local]'` |
61
+ | what that pulls in | pillow and the standard library. **No torch, no transformers, no weights.** | torch, transformers, and encoder weights on first use |
62
+ | entry point | `jevimage.connect(url)` | `jevimage.load()` |
63
+ | where the model runs | on that server | in your process |
64
+ | where your images go | up to that server | nowhere |
65
+
66
+ The base install is an HTTP client. Asking, embedding, managing heads **and training**
67
+ all work through it, because the encoder that fits the head lives on the server.
68
+ `jev encoders` works with no torch on the machine, and `jev ask/train/heads/rm --url ...`
69
+ (or `$JEV_URL` with `$JEV_API_KEY`) drive a server from the shell.
70
+
71
+ In that install `jevimage.load()` does not fail obscurely; it names both fixes:
72
+
73
+ ```console
74
+ $ python -c "import jevimage; jevimage.load()"
75
+ ImportError: jevimage.load runs the encoder in this process, which needs torch and
76
+ transformers: pip install 'jevimage[local]'
77
+ To use a server instead - no torch required - call jevimage.connect(url).
78
+ (underlying import error: No module named 'torch')
79
+ ```
80
+
81
+ ## Install
82
+
83
+ Python 3.10+. Four installs, each a superset of the base. The package is **not on PyPI
84
+ yet** — until it is, these mean `pip install .` and `pip install '.[local]'` against a
85
+ clone, as [docs/install.md](https://docs.jevimage.org/docs/install/#from-source) shows:
86
+
87
+ ```bash
88
+ pip install jevimage # API only: pillow + stdlib. connect(), and the CLI's --url mode.
89
+ pip install 'jevimage[local]' # + torch, transformers. load() runs the encoder here.
90
+ pip install 'jevimage[serve]' # + fastapi, uvicorn. `jev serve` hosts it for others.
91
+ pip install 'jevimage[openclip]' # + open_clip, for the dfn5b-h-14-384 entry (Apple's licence — see below).
92
+ ```
93
+
94
+ `[serve]` and `[openclip]` both include `[local]`, because a server runs the encoder and
95
+ open_clip is an encoder. Sizes, offline and air-gapped use, and what the first `load()`
96
+ downloads: [docs/install.md](https://docs.jevimage.org/docs/install/).
97
+
98
+ ## Quickstart
99
+
100
+ Three questions, one encode. This runs on the API-only install:
101
+
102
+ ```python
103
+ import jevimage
104
+
105
+ jev = jevimage.connect("http://127.0.0.1:8123") # or jevimage.load(), same methods
106
+
107
+ a = jev.ask("ds/red/0.png", { # or any image of your own
108
+ "colour": {"type": "choice",
109
+ "criteria": {"red": "a red square", "blue": "a blue square"}},
110
+ "bright": {"type": "score",
111
+ "criteria": ["a dark square", "a mid-tone square", "a bright square"]},
112
+ "plain": {"type": "noul",
113
+ "criteria": {"true": "one flat colour", "false": "a detailed photo"}},
114
+ })
115
+
116
+ print(a["colour"])
117
+ print(a["colour"].choice, a["colour"].probabilities, a["colour"].confidence)
118
+ print(a["bright"], a["plain"])
119
+ ```
120
+
121
+ ```text
122
+ <choice 'red' p=1.000>
123
+ red {'red': 1.0, 'blue': 0.0} 0.999999
124
+ <score 0.02/2> <noul 0.038>
125
+ ```
126
+
127
+ > That transcript is real, against a red square: `ds/red/0.png`, one of the twelve
128
+ > `python examples/make_fixtures.py` writes. The server was hosting a deterministic
129
+ > stand-in encoder, so the numbers are reproducible and the example costs nothing to
130
+ > re-run; the code, the shapes and the reprs are exactly what you get. On a real encoder
131
+ > the numbers move but the saturation does not necessarily: the same `colour` question on
132
+ > `siglip2-base-224` measured `{'red': 0.997553, 'blue': 0.002447}`, and `bright` came
133
+ > out `<score 1.52/2>` where the stand-in said `0.02`.
134
+
135
+ An `Answer` is a `dict` subclass with attribute access: `a["colour"].choice` and
136
+ `a["colour"]["choice"]` are the same thing, printing one gives the short repr above, and
137
+ `json.dumps(a)` gives the full shape with no conversion step. Swapping `connect(url)` for
138
+ `load()` changes nothing else in that file.
139
+
140
+ The four question types are `choice` (one of 2–255 named options), `score` (a position
141
+ on an ordered rubric), `noul` ("no-or-yes level": one yes/no probability) and `head` (a
142
+ classifier you trained). Up to 64 of them ride one encode. See
143
+ [docs/questions.md](https://docs.jevimage.org/docs/questions/).
144
+
145
+ Training, in one line and with the honest number attached:
146
+
147
+ ```console
148
+ $ jev train colour ds/ --url http://127.0.0.1:8123
149
+ 'colour': 12 examples, 2 classes, encoder toy
150
+
151
+ class n zero-shot trained delta
152
+ blue 6 50.0% 50.0% 0.0%
153
+ red 6 100.0% 50.0% -50.0%
154
+ ALL 12 75.0% 50.0% -25.0%
155
+
156
+ trained is k-fold held-out; zero-shot needs no holdout so it is scored on every example.
157
+ Training did not beat the prompts here - try more examples per class, or a bigger encoder.
158
+ ```
159
+
160
+ The zero-shot column is what the prompts alone scored on the same images, because
161
+ training does not always help, and the only useful answer is the one for your images,
162
+ per class.
163
+
164
+ ## Documentation
165
+
166
+ | Page | Read it when |
167
+ |---|---|
168
+ | [Overview](https://docs.jevimage.org/docs/index/) | You want the shape of the whole thing, including what it deliberately does not do. |
169
+ | [Install](https://docs.jevimage.org/docs/install/) | Choosing between the four installs, or working offline. |
170
+ | [Quickstart](https://docs.jevimage.org/docs/quickstart/) | The same walkthrough twice, once against a server and once in-process. |
171
+ | [Question types](https://docs.jevimage.org/docs/questions/) | Writing questions: `choice`, `score`, `noul`, `head`, and the two forms of `noul`. |
172
+ | [Training a head](https://docs.jevimage.org/docs/training/) | Prompts are not good enough and you have labelled images. Also: how to tell whether training helped. |
173
+ | [Choosing an encoder](https://docs.jevimage.org/docs/encoders/) | Picking from the registry, joining two with `+`, or plugging in your own model. |
174
+ | [Serving](https://docs.jevimage.org/docs/serving/) | Running `jev serve` for other people: routes, API keys, limits, deployment. |
175
+ | [CLI reference](https://docs.jevimage.org/docs/reference-cli/) | `jev ask`, `train`, `heads`, `rm`, `encoders`, `serve` — local or `--url`. |
176
+ | [Python reference](https://docs.jevimage.org/docs/reference-python/) | The exact public surface: every function, method and return shape. |
177
+ | [HTTP reference](https://docs.jevimage.org/docs/reference-http/) | The routes, if you are writing your own client. |
178
+ | [Cookbook](https://docs.jevimage.org/docs/cookbook/) | Complete scripts: screening an upload, caching an embedding, routing a directory. |
179
+ | [Troubleshooting](https://docs.jevimage.org/docs/troubleshooting/) | The error's sentence is clear but the reason it fired is not. |
180
+
181
+ The pages are plain Markdown with relative links, so they read on GitHub as they are.
182
+ [`mkdocs.yml`](https://github.com/Per0x1de-1337/jevimage/blob/main/mkdocs.yml) is there if you want a searchable site instead:
183
+ `pip install 'jevimage[docs]' && mkdocs serve`.
184
+
185
+ Runnable examples live in [`examples/`](https://github.com/Per0x1de-1337/jevimage/blob/main/examples/). The test suite is
186
+ `pip install 'jevimage[dev]' && python -m pytest tests/ -q`.
187
+
188
+ ## Licence
189
+
190
+ MIT. See [LICENSE](https://github.com/Per0x1de-1337/jevimage/blob/main/LICENSE).
191
+
192
+ The model weights are not MIT and are not redistributed here — each one downloads from
193
+ its own source under its own terms. In particular, `dfn5b-h-14-384` pulls Apple's DFN5B
194
+ checkpoint, which carries **Apple's own licence**. Read it before you ship or redistribute
195
+ anything built on that encoder. It sits behind the optional `[openclip]` extra for exactly
196
+ that reason: the choice is yours to make, so it is never installed on your behalf.
@@ -0,0 +1,162 @@
1
+ # jevimage
2
+
3
+ jevimage answers typed questions about an image with probabilities instead of prose. The
4
+ image goes through a frozen dual encoder once and becomes a single unit-norm vector.
5
+ Every question after that (pick one of these options, place it on this rubric, yes or
6
+ no, run the head I trained on my own photos) is a matmul against that same vector.
7
+
8
+ Nothing is generated. An answer is a distribution over names *you* chose, plus a
9
+ confidence rescaled so one threshold works whether the question had two options or fifty.
10
+ So there is no sentence to parse and no retry loop. The same image and question give the
11
+ same numbers, to the six decimals they are printed to, every time, and the tenth question
12
+ about an image costs a matmul rather than another forward pass once its captions are
13
+ cached. A repeated question set is what gets cheap; a caption the process has never
14
+ seen still costs one text-tower pass. When prompts are not good enough you fit a linear
15
+ head on a folder of your own labelled images in seconds — and jevimage tells you, per
16
+ class, whether that head beat the prompts. Sometimes it did not, and it says so.
17
+
18
+ Full documentation is in [docs/](https://docs.jevimage.org/docs/index/).
19
+
20
+ ## Two ways to use it
21
+
22
+ Both are first class, and they are the same API. Pick the row you are in.
23
+
24
+ | | Use someone's server | Run it yourself |
25
+ |---|---|---|
26
+ | install | `pip install jevimage` | `pip install 'jevimage[local]'` |
27
+ | what that pulls in | pillow and the standard library. **No torch, no transformers, no weights.** | torch, transformers, and encoder weights on first use |
28
+ | entry point | `jevimage.connect(url)` | `jevimage.load()` |
29
+ | where the model runs | on that server | in your process |
30
+ | where your images go | up to that server | nowhere |
31
+
32
+ The base install is an HTTP client. Asking, embedding, managing heads **and training**
33
+ all work through it, because the encoder that fits the head lives on the server.
34
+ `jev encoders` works with no torch on the machine, and `jev ask/train/heads/rm --url ...`
35
+ (or `$JEV_URL` with `$JEV_API_KEY`) drive a server from the shell.
36
+
37
+ In that install `jevimage.load()` does not fail obscurely; it names both fixes:
38
+
39
+ ```console
40
+ $ python -c "import jevimage; jevimage.load()"
41
+ ImportError: jevimage.load runs the encoder in this process, which needs torch and
42
+ transformers: pip install 'jevimage[local]'
43
+ To use a server instead - no torch required - call jevimage.connect(url).
44
+ (underlying import error: No module named 'torch')
45
+ ```
46
+
47
+ ## Install
48
+
49
+ Python 3.10+. Four installs, each a superset of the base. The package is **not on PyPI
50
+ yet** — until it is, these mean `pip install .` and `pip install '.[local]'` against a
51
+ clone, as [docs/install.md](https://docs.jevimage.org/docs/install/#from-source) shows:
52
+
53
+ ```bash
54
+ pip install jevimage # API only: pillow + stdlib. connect(), and the CLI's --url mode.
55
+ pip install 'jevimage[local]' # + torch, transformers. load() runs the encoder here.
56
+ pip install 'jevimage[serve]' # + fastapi, uvicorn. `jev serve` hosts it for others.
57
+ pip install 'jevimage[openclip]' # + open_clip, for the dfn5b-h-14-384 entry (Apple's licence — see below).
58
+ ```
59
+
60
+ `[serve]` and `[openclip]` both include `[local]`, because a server runs the encoder and
61
+ open_clip is an encoder. Sizes, offline and air-gapped use, and what the first `load()`
62
+ downloads: [docs/install.md](https://docs.jevimage.org/docs/install/).
63
+
64
+ ## Quickstart
65
+
66
+ Three questions, one encode. This runs on the API-only install:
67
+
68
+ ```python
69
+ import jevimage
70
+
71
+ jev = jevimage.connect("http://127.0.0.1:8123") # or jevimage.load(), same methods
72
+
73
+ a = jev.ask("ds/red/0.png", { # or any image of your own
74
+ "colour": {"type": "choice",
75
+ "criteria": {"red": "a red square", "blue": "a blue square"}},
76
+ "bright": {"type": "score",
77
+ "criteria": ["a dark square", "a mid-tone square", "a bright square"]},
78
+ "plain": {"type": "noul",
79
+ "criteria": {"true": "one flat colour", "false": "a detailed photo"}},
80
+ })
81
+
82
+ print(a["colour"])
83
+ print(a["colour"].choice, a["colour"].probabilities, a["colour"].confidence)
84
+ print(a["bright"], a["plain"])
85
+ ```
86
+
87
+ ```text
88
+ <choice 'red' p=1.000>
89
+ red {'red': 1.0, 'blue': 0.0} 0.999999
90
+ <score 0.02/2> <noul 0.038>
91
+ ```
92
+
93
+ > That transcript is real, against a red square: `ds/red/0.png`, one of the twelve
94
+ > `python examples/make_fixtures.py` writes. The server was hosting a deterministic
95
+ > stand-in encoder, so the numbers are reproducible and the example costs nothing to
96
+ > re-run; the code, the shapes and the reprs are exactly what you get. On a real encoder
97
+ > the numbers move but the saturation does not necessarily: the same `colour` question on
98
+ > `siglip2-base-224` measured `{'red': 0.997553, 'blue': 0.002447}`, and `bright` came
99
+ > out `<score 1.52/2>` where the stand-in said `0.02`.
100
+
101
+ An `Answer` is a `dict` subclass with attribute access: `a["colour"].choice` and
102
+ `a["colour"]["choice"]` are the same thing, printing one gives the short repr above, and
103
+ `json.dumps(a)` gives the full shape with no conversion step. Swapping `connect(url)` for
104
+ `load()` changes nothing else in that file.
105
+
106
+ The four question types are `choice` (one of 2–255 named options), `score` (a position
107
+ on an ordered rubric), `noul` ("no-or-yes level": one yes/no probability) and `head` (a
108
+ classifier you trained). Up to 64 of them ride one encode. See
109
+ [docs/questions.md](https://docs.jevimage.org/docs/questions/).
110
+
111
+ Training, in one line and with the honest number attached:
112
+
113
+ ```console
114
+ $ jev train colour ds/ --url http://127.0.0.1:8123
115
+ 'colour': 12 examples, 2 classes, encoder toy
116
+
117
+ class n zero-shot trained delta
118
+ blue 6 50.0% 50.0% 0.0%
119
+ red 6 100.0% 50.0% -50.0%
120
+ ALL 12 75.0% 50.0% -25.0%
121
+
122
+ trained is k-fold held-out; zero-shot needs no holdout so it is scored on every example.
123
+ Training did not beat the prompts here - try more examples per class, or a bigger encoder.
124
+ ```
125
+
126
+ The zero-shot column is what the prompts alone scored on the same images, because
127
+ training does not always help, and the only useful answer is the one for your images,
128
+ per class.
129
+
130
+ ## Documentation
131
+
132
+ | Page | Read it when |
133
+ |---|---|
134
+ | [Overview](https://docs.jevimage.org/docs/index/) | You want the shape of the whole thing, including what it deliberately does not do. |
135
+ | [Install](https://docs.jevimage.org/docs/install/) | Choosing between the four installs, or working offline. |
136
+ | [Quickstart](https://docs.jevimage.org/docs/quickstart/) | The same walkthrough twice, once against a server and once in-process. |
137
+ | [Question types](https://docs.jevimage.org/docs/questions/) | Writing questions: `choice`, `score`, `noul`, `head`, and the two forms of `noul`. |
138
+ | [Training a head](https://docs.jevimage.org/docs/training/) | Prompts are not good enough and you have labelled images. Also: how to tell whether training helped. |
139
+ | [Choosing an encoder](https://docs.jevimage.org/docs/encoders/) | Picking from the registry, joining two with `+`, or plugging in your own model. |
140
+ | [Serving](https://docs.jevimage.org/docs/serving/) | Running `jev serve` for other people: routes, API keys, limits, deployment. |
141
+ | [CLI reference](https://docs.jevimage.org/docs/reference-cli/) | `jev ask`, `train`, `heads`, `rm`, `encoders`, `serve` — local or `--url`. |
142
+ | [Python reference](https://docs.jevimage.org/docs/reference-python/) | The exact public surface: every function, method and return shape. |
143
+ | [HTTP reference](https://docs.jevimage.org/docs/reference-http/) | The routes, if you are writing your own client. |
144
+ | [Cookbook](https://docs.jevimage.org/docs/cookbook/) | Complete scripts: screening an upload, caching an embedding, routing a directory. |
145
+ | [Troubleshooting](https://docs.jevimage.org/docs/troubleshooting/) | The error's sentence is clear but the reason it fired is not. |
146
+
147
+ The pages are plain Markdown with relative links, so they read on GitHub as they are.
148
+ [`mkdocs.yml`](https://github.com/Per0x1de-1337/jevimage/blob/main/mkdocs.yml) is there if you want a searchable site instead:
149
+ `pip install 'jevimage[docs]' && mkdocs serve`.
150
+
151
+ Runnable examples live in [`examples/`](https://github.com/Per0x1de-1337/jevimage/blob/main/examples/). The test suite is
152
+ `pip install 'jevimage[dev]' && python -m pytest tests/ -q`.
153
+
154
+ ## Licence
155
+
156
+ MIT. See [LICENSE](https://github.com/Per0x1de-1337/jevimage/blob/main/LICENSE).
157
+
158
+ The model weights are not MIT and are not redistributed here — each one downloads from
159
+ its own source under its own terms. In particular, `dfn5b-h-14-384` pulls Apple's DFN5B
160
+ checkpoint, which carries **Apple's own licence**. Read it before you ship or redistribute
161
+ anything built on that encoder. It sits behind the optional `[openclip]` extra for exactly
162
+ that reason: the choice is yours to make, so it is never installed on your behalf.