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.
- jevimage-0.1.0/CHANGELOG.md +117 -0
- jevimage-0.1.0/LICENSE +21 -0
- jevimage-0.1.0/MANIFEST.in +9 -0
- jevimage-0.1.0/PKG-INFO +196 -0
- jevimage-0.1.0/README.md +162 -0
- jevimage-0.1.0/docs/cookbook.md +715 -0
- jevimage-0.1.0/docs/encoders.md +764 -0
- jevimage-0.1.0/docs/index.md +141 -0
- jevimage-0.1.0/docs/install.md +419 -0
- jevimage-0.1.0/docs/questions.md +863 -0
- jevimage-0.1.0/docs/quickstart.md +398 -0
- jevimage-0.1.0/docs/reference-cli.md +865 -0
- jevimage-0.1.0/docs/reference-http.md +713 -0
- jevimage-0.1.0/docs/reference-python.md +993 -0
- jevimage-0.1.0/docs/serving.md +790 -0
- jevimage-0.1.0/docs/training.md +739 -0
- jevimage-0.1.0/docs/troubleshooting.md +1253 -0
- jevimage-0.1.0/docs.json +53 -0
- jevimage-0.1.0/examples/make_fixtures.py +57 -0
- jevimage-0.1.0/examples/quickstart.py +47 -0
- jevimage-0.1.0/examples/train_your_own.py +39 -0
- jevimage-0.1.0/jevimage/__init__.py +119 -0
- jevimage-0.1.0/jevimage/answers.py +125 -0
- jevimage-0.1.0/jevimage/batcher.py +212 -0
- jevimage-0.1.0/jevimage/cli.py +548 -0
- jevimage-0.1.0/jevimage/client.py +611 -0
- jevimage-0.1.0/jevimage/core.py +298 -0
- jevimage-0.1.0/jevimage/encoders.py +342 -0
- jevimage-0.1.0/jevimage/images.py +213 -0
- jevimage-0.1.0/jevimage/local.py +188 -0
- jevimage-0.1.0/jevimage/server.py +442 -0
- jevimage-0.1.0/jevimage/store.py +228 -0
- jevimage-0.1.0/jevimage/training.py +226 -0
- jevimage-0.1.0/jevimage.egg-info/PKG-INFO +196 -0
- jevimage-0.1.0/jevimage.egg-info/SOURCES.txt +45 -0
- jevimage-0.1.0/jevimage.egg-info/dependency_links.txt +1 -0
- jevimage-0.1.0/jevimage.egg-info/entry_points.txt +2 -0
- jevimage-0.1.0/jevimage.egg-info/requires.txt +18 -0
- jevimage-0.1.0/jevimage.egg-info/top_level.txt +1 -0
- jevimage-0.1.0/mkdocs.yml +23 -0
- jevimage-0.1.0/pyproject.toml +71 -0
- jevimage-0.1.0/setup.cfg +4 -0
- jevimage-0.1.0/tests/conftest.py +103 -0
- jevimage-0.1.0/tests/test_api_only.py +314 -0
- jevimage-0.1.0/tests/test_core.py +287 -0
- jevimage-0.1.0/tests/test_store.py +154 -0
- 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
|
jevimage-0.1.0/PKG-INFO
ADDED
|
@@ -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.
|
jevimage-0.1.0/README.md
ADDED
|
@@ -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.
|