walsh 0.1.1__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.
- walsh-0.1.1/CHANGELOG.md +70 -0
- walsh-0.1.1/LICENSE +21 -0
- walsh-0.1.1/MANIFEST.in +14 -0
- walsh-0.1.1/PKG-INFO +176 -0
- walsh-0.1.1/README.md +144 -0
- walsh-0.1.1/examples/roundtrip.py +57 -0
- walsh-0.1.1/pyproject.toml +78 -0
- walsh-0.1.1/requirements-demo.txt +7 -0
- walsh-0.1.1/requirements-dev.txt +7 -0
- walsh-0.1.1/requirements.txt +8 -0
- walsh-0.1.1/setup.cfg +4 -0
- walsh-0.1.1/src/walsh/__init__.py +43 -0
- walsh-0.1.1/src/walsh/cli.py +104 -0
- walsh-0.1.1/src/walsh/colors.py +63 -0
- walsh-0.1.1/src/walsh/decorators.py +57 -0
- walsh-0.1.1/src/walsh/image.py +312 -0
- walsh-0.1.1/src/walsh/py.typed +0 -0
- walsh-0.1.1/src/walsh/task.py +226 -0
- walsh-0.1.1/src/walsh/transforms.py +96 -0
- walsh-0.1.1/src/walsh.egg-info/PKG-INFO +176 -0
- walsh-0.1.1/src/walsh.egg-info/SOURCES.txt +29 -0
- walsh-0.1.1/src/walsh.egg-info/dependency_links.txt +1 -0
- walsh-0.1.1/src/walsh.egg-info/entry_points.txt +2 -0
- walsh-0.1.1/src/walsh.egg-info/requires.txt +5 -0
- walsh-0.1.1/src/walsh.egg-info/top_level.txt +1 -0
- walsh-0.1.1/tests/conftest.py +61 -0
- walsh-0.1.1/tests/test_cli.py +44 -0
- walsh-0.1.1/tests/test_colors.py +35 -0
- walsh-0.1.1/tests/test_image.py +126 -0
- walsh-0.1.1/tests/test_task.py +107 -0
- walsh-0.1.1/tests/test_transforms.py +72 -0
walsh-0.1.1/CHANGELOG.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
The `## [x.y.z]` headings are load-bearing: the release workflow extracts the
|
|
9
|
+
section matching the version in `pyproject.toml` and uses it as the GitHub
|
|
10
|
+
Release notes.
|
|
11
|
+
|
|
12
|
+
## [0.1.1]
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `uv` support: a `uv.lock` committed to the repository, and a `dev`
|
|
17
|
+
[PEP 735](https://peps.python.org/pep-0735/) dependency group, so
|
|
18
|
+
`uv sync --group dev` reproduces the full development environment.
|
|
19
|
+
- Release automation. A version bump landing on `master` tags `v<version>`,
|
|
20
|
+
creates a GitHub Release with these notes attached, and publishes the sdist
|
|
21
|
+
and wheel to [PyPI](https://pypi.org/project/walsh/) via Trusted Publishing.
|
|
22
|
+
- CI on every pull request: ruff, `ruff format --check`, strict mypy, the test
|
|
23
|
+
suite on Python 3.10 through 3.14, and a packaging job that builds both
|
|
24
|
+
distributions and smoke-tests the wheel in a clean environment.
|
|
25
|
+
- Python 3.14 to the supported classifiers.
|
|
26
|
+
- `Issues` and `Changelog` project URLs.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- The `dev` extra became the `dev` dependency group. `pip install .[dev]` no
|
|
31
|
+
longer works; use `uv sync --group dev`, `pip install --group dev .`
|
|
32
|
+
(pip 25.1+), or `pip install -r requirements-dev.txt`.
|
|
33
|
+
|
|
34
|
+
## [0.1.0]
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `walsh` console script with `compress` and `extract` subcommands.
|
|
39
|
+
- Test suite covering the transform, colour models, both container formats, the
|
|
40
|
+
task pipelines and the CLI.
|
|
41
|
+
- Type annotations throughout, with a `py.typed` marker; clean under
|
|
42
|
+
`mypy --strict`.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Ported from Python 2.7 to Python 3.10+ and restructured as an installable
|
|
47
|
+
package under `src/walsh`. The old `fal` package and root `transform.py` were
|
|
48
|
+
removed; the demo moved to `examples/roundtrip.py`.
|
|
49
|
+
- `numpy` is now the only runtime dependency. `matplotlib` and `Pillow` were
|
|
50
|
+
only ever used by the demo script and became the `demo` extra.
|
|
51
|
+
- Spectral coefficients are rounded with `np.rint` on write. Python 2 relied on
|
|
52
|
+
`struct.pack` implicitly truncating floats, so output differs from the
|
|
53
|
+
pre-port result by at most 2 per channel (mean 0.33).
|
|
54
|
+
- `np.matrix` replaced with `ndarray` and the `@` operator.
|
|
55
|
+
- Requirements files carry explicit version bounds.
|
|
56
|
+
|
|
57
|
+
### Known issues
|
|
58
|
+
|
|
59
|
+
Both predate the port and are preserved deliberately, because changing either
|
|
60
|
+
alters every compressed output:
|
|
61
|
+
|
|
62
|
+
- `BMPImage` reads pixel triples in BMP's native B, G, R order while `Task`
|
|
63
|
+
treats them as R, G, B. Round trips are exact, but the YCbCr weights land on
|
|
64
|
+
swapped channels.
|
|
65
|
+
- The `coeff_removal` comparison is one-sided rather than on the magnitude, so
|
|
66
|
+
any non-`None` coefficient above `-1/sqrt(2)**n` zeroes essentially the whole
|
|
67
|
+
spectrum.
|
|
68
|
+
|
|
69
|
+
[0.1.1]: https://github.com/oskar-j/walsh-hadamard-transform/releases/tag/v0.1.1
|
|
70
|
+
[0.1.0]: https://github.com/oskar-j/walsh-hadamard-transform/releases/tag/v0.1.0
|
walsh-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Oskar Jarczyk
|
|
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.
|
walsh-0.1.1/MANIFEST.in
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Controls what lands in the sdist. The wheel is driven by
|
|
2
|
+
# [tool.setuptools.packages.find] in pyproject.toml instead.
|
|
3
|
+
#
|
|
4
|
+
# setuptools picks up tests/test_*.py on its own but not conftest.py, which
|
|
5
|
+
# would ship a test suite whose fixtures are all undefined.
|
|
6
|
+
|
|
7
|
+
include CHANGELOG.md
|
|
8
|
+
include requirements.txt requirements-demo.txt requirements-dev.txt
|
|
9
|
+
recursive-include tests *.py
|
|
10
|
+
recursive-include examples *.py
|
|
11
|
+
|
|
12
|
+
# data/image.bmp is 480 KB and only feeds one test, which skips when the file
|
|
13
|
+
# is absent. Keep it out of the distribution.
|
|
14
|
+
exclude data/*.bmp
|
walsh-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: walsh
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Image compression with the Walsh-Hadamard transform
|
|
5
|
+
Author-email: Oskar Jarczyk <oskar.jarczyk@gmail.com>
|
|
6
|
+
Maintainer-email: Oskar Jarczyk <oskar.jarczyk@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/oskar-j/walsh-hadamard-transform
|
|
9
|
+
Project-URL: Repository, https://github.com/oskar-j/walsh-hadamard-transform
|
|
10
|
+
Project-URL: Issues, https://github.com/oskar-j/walsh-hadamard-transform/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/oskar-j/walsh-hadamard-transform/blob/master/CHANGELOG.md
|
|
12
|
+
Keywords: walsh,hadamard,transform,image,compression,dsp
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy<3,>=1.24
|
|
28
|
+
Provides-Extra: demo
|
|
29
|
+
Requires-Dist: matplotlib<4,>=3.7; extra == "demo"
|
|
30
|
+
Requires-Dist: Pillow<13,>=10.0; extra == "demo"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Walsh-hadamard transform
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/walsh/)
|
|
36
|
+
[](https://pypi.org/project/walsh/)
|
|
37
|
+
[](https://github.com/oskar-j/walsh-hadamard-transform/actions/workflows/ci.yml)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Compressing images with a Hadamard transform
|
|
41
|
+
|
|
42
|
+
## Description
|
|
43
|
+
|
|
44
|
+
**From Wikipedia:** The Hadamard transform (also known as the *Walsh–Hadamard transform*,
|
|
45
|
+
*Hadamard–Rademacher–Walsh transform*, *Walsh transform*, or *Walsh–Fourier transform*) is an example
|
|
46
|
+
of a generalized class of Fourier transforms. It performs an orthogonal, symmetric,
|
|
47
|
+
involutive, linear operation on 2m real numbers (or complex numbers, although the
|
|
48
|
+
Hadamard matrices themselves are purely real).
|
|
49
|
+
|
|
50
|
+
The Hadamard transform can be regarded as being built out of *size-2
|
|
51
|
+
discrete Fourier transforms* (DFTs), and is in fact equivalent to a
|
|
52
|
+
multidimensional DFT of size `2 × 2 × ⋯ × 2 × 2`. It decomposes an
|
|
53
|
+
arbitrary input vector into a superposition of *Walsh functions*.
|
|
54
|
+
|
|
55
|
+
The transform is named for the French mathematician Jacques Hadamard,
|
|
56
|
+
the German-American mathematician Hans Rademacher, and the
|
|
57
|
+
American mathematician Joseph L. Walsh.
|
|
58
|
+
|
|
59
|
+
The Hadamard transform is also used in data encryption, as well as many signal processing
|
|
60
|
+
and data compression algorithms, such as `JPEG XR` and `MPEG-4 AVC`. In video compression
|
|
61
|
+
applications, it is usually used in the form of the sum of absolute transformed differences.
|
|
62
|
+
It is also a crucial part of *Grover's algorithm* and *Shor's algorithm* in quantum computing.
|
|
63
|
+
|
|
64
|
+
## Acknowledgement
|
|
65
|
+
|
|
66
|
+
This code is partially based on the solution from [ktisha/python2012](https://github.com/ktisha/python2012/tree/dee4beda8e22f3a66a3e31384d4b72ab66102e88/avereshchagin)
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
Requires Python 3.10 or newer.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
pip install walsh
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
or, with [uv](https://docs.astral.sh/uv/):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
uv add walsh # into a project
|
|
80
|
+
uv tool install walsh # just the command line tool
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The example script additionally needs matplotlib and Pillow, which are the
|
|
84
|
+
`demo` extra: `pip install "walsh[demo]"`.
|
|
85
|
+
|
|
86
|
+
### Development
|
|
87
|
+
|
|
88
|
+
`uv.lock` is committed, so a checkout reproduces exactly the environment CI
|
|
89
|
+
uses:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
uv sync --group dev --all-extras
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`--group dev` brings in pytest, ruff and mypy; `--all-extras` adds the `demo`
|
|
96
|
+
extra so `examples/roundtrip.py` runs too. Without uv:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
pip install -e ".[demo]" -r requirements-dev.txt
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## How to run
|
|
103
|
+
|
|
104
|
+
### Command line
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
walsh compress data/image.bmp data/transformed.cim
|
|
108
|
+
walsh extract data/transformed.cim data/recreated.bmp
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`compress` accepts `--packed-block-size` (how many low-frequency coefficients
|
|
112
|
+
per axis to keep -- lower is smaller and lossier), `--y-block-size`,
|
|
113
|
+
`--chroma-block-size` and `--coeff-removal`. Add `-v`/`-vv` for progress
|
|
114
|
+
logging, and see `walsh --help` for the full list.
|
|
115
|
+
|
|
116
|
+
### As a library
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from walsh import Task
|
|
120
|
+
|
|
121
|
+
Task().with_action("compress").with_input("data/image.bmp").with_output("out.cim").run()
|
|
122
|
+
Task().with_action("extract").with_input("out.cim").with_output("back.bmp").run()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Example
|
|
126
|
+
|
|
127
|
+
`examples/roundtrip.py` compresses the sample image, restores it, and plots
|
|
128
|
+
both images with their histograms side by side (needs the `demo` extra):
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
python examples/roundtrip.py
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
The package itself needs only `numpy` -- BMP parsing is done by hand with
|
|
137
|
+
`struct`. `matplotlib` and `Pillow` are needed only by the example script, and
|
|
138
|
+
are declared as the `demo` extra. Versions are pinned in `pyproject.toml`;
|
|
139
|
+
`requirements.txt`, `requirements-demo.txt` and `requirements-dev.txt` mirror
|
|
140
|
+
them for plain `pip install -r` workflows.
|
|
141
|
+
|
|
142
|
+
## Development commands
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
uv run pytest # test suite
|
|
146
|
+
uv run ruff check . # lint
|
|
147
|
+
uv run ruff format . # format
|
|
148
|
+
uv run mypy # strict type check
|
|
149
|
+
uv build # sdist + wheel into dist/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
CI runs exactly these on every pull request, plus the test suite against
|
|
153
|
+
Python 3.10 through 3.14.
|
|
154
|
+
|
|
155
|
+
## Releasing
|
|
156
|
+
|
|
157
|
+
The version in `pyproject.toml` is the single source of truth. To cut a
|
|
158
|
+
release, bump it, add the matching `## [x.y.z]` section to `CHANGELOG.md`, and
|
|
159
|
+
merge to `master`. The release workflow then tags `v<version>`, creates a
|
|
160
|
+
GitHub Release with those notes, and publishes the sdist and wheel to
|
|
161
|
+
[PyPI](https://pypi.org/project/walsh/) using Trusted Publishing — no API token
|
|
162
|
+
is stored in this repository.
|
|
163
|
+
|
|
164
|
+
Merges that do not change the version are a no-op, since PyPI permanently
|
|
165
|
+
refuses to accept the same version twice.
|
|
166
|
+
|
|
167
|
+
## File format
|
|
168
|
+
|
|
169
|
+
`compress` writes a `.cim` file: an atypical, project-specific container, so
|
|
170
|
+
most commercial tools will not be able to read it. It stores the image
|
|
171
|
+
dimensions, three block-layout descriptions (Y, Cb, Cr), and the retained
|
|
172
|
+
Walsh-Hadamard coefficients as little-endian `int16`.
|
|
173
|
+
|
|
174
|
+
## Effects
|
|
175
|
+
|
|
176
|
+

|
walsh-0.1.1/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Walsh-hadamard transform
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/walsh/)
|
|
4
|
+
[](https://pypi.org/project/walsh/)
|
|
5
|
+
[](https://github.com/oskar-j/walsh-hadamard-transform/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Compressing images with a Hadamard transform
|
|
9
|
+
|
|
10
|
+
## Description
|
|
11
|
+
|
|
12
|
+
**From Wikipedia:** The Hadamard transform (also known as the *Walsh–Hadamard transform*,
|
|
13
|
+
*Hadamard–Rademacher–Walsh transform*, *Walsh transform*, or *Walsh–Fourier transform*) is an example
|
|
14
|
+
of a generalized class of Fourier transforms. It performs an orthogonal, symmetric,
|
|
15
|
+
involutive, linear operation on 2m real numbers (or complex numbers, although the
|
|
16
|
+
Hadamard matrices themselves are purely real).
|
|
17
|
+
|
|
18
|
+
The Hadamard transform can be regarded as being built out of *size-2
|
|
19
|
+
discrete Fourier transforms* (DFTs), and is in fact equivalent to a
|
|
20
|
+
multidimensional DFT of size `2 × 2 × ⋯ × 2 × 2`. It decomposes an
|
|
21
|
+
arbitrary input vector into a superposition of *Walsh functions*.
|
|
22
|
+
|
|
23
|
+
The transform is named for the French mathematician Jacques Hadamard,
|
|
24
|
+
the German-American mathematician Hans Rademacher, and the
|
|
25
|
+
American mathematician Joseph L. Walsh.
|
|
26
|
+
|
|
27
|
+
The Hadamard transform is also used in data encryption, as well as many signal processing
|
|
28
|
+
and data compression algorithms, such as `JPEG XR` and `MPEG-4 AVC`. In video compression
|
|
29
|
+
applications, it is usually used in the form of the sum of absolute transformed differences.
|
|
30
|
+
It is also a crucial part of *Grover's algorithm* and *Shor's algorithm* in quantum computing.
|
|
31
|
+
|
|
32
|
+
## Acknowledgement
|
|
33
|
+
|
|
34
|
+
This code is partially based on the solution from [ktisha/python2012](https://github.com/ktisha/python2012/tree/dee4beda8e22f3a66a3e31384d4b72ab66102e88/avereshchagin)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Requires Python 3.10 or newer.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
pip install walsh
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or, with [uv](https://docs.astral.sh/uv/):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
uv add walsh # into a project
|
|
48
|
+
uv tool install walsh # just the command line tool
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The example script additionally needs matplotlib and Pillow, which are the
|
|
52
|
+
`demo` extra: `pip install "walsh[demo]"`.
|
|
53
|
+
|
|
54
|
+
### Development
|
|
55
|
+
|
|
56
|
+
`uv.lock` is committed, so a checkout reproduces exactly the environment CI
|
|
57
|
+
uses:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
uv sync --group dev --all-extras
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`--group dev` brings in pytest, ruff and mypy; `--all-extras` adds the `demo`
|
|
64
|
+
extra so `examples/roundtrip.py` runs too. Without uv:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pip install -e ".[demo]" -r requirements-dev.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## How to run
|
|
71
|
+
|
|
72
|
+
### Command line
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
walsh compress data/image.bmp data/transformed.cim
|
|
76
|
+
walsh extract data/transformed.cim data/recreated.bmp
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`compress` accepts `--packed-block-size` (how many low-frequency coefficients
|
|
80
|
+
per axis to keep -- lower is smaller and lossier), `--y-block-size`,
|
|
81
|
+
`--chroma-block-size` and `--coeff-removal`. Add `-v`/`-vv` for progress
|
|
82
|
+
logging, and see `walsh --help` for the full list.
|
|
83
|
+
|
|
84
|
+
### As a library
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from walsh import Task
|
|
88
|
+
|
|
89
|
+
Task().with_action("compress").with_input("data/image.bmp").with_output("out.cim").run()
|
|
90
|
+
Task().with_action("extract").with_input("out.cim").with_output("back.bmp").run()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Example
|
|
94
|
+
|
|
95
|
+
`examples/roundtrip.py` compresses the sample image, restores it, and plots
|
|
96
|
+
both images with their histograms side by side (needs the `demo` extra):
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
python examples/roundtrip.py
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
The package itself needs only `numpy` -- BMP parsing is done by hand with
|
|
105
|
+
`struct`. `matplotlib` and `Pillow` are needed only by the example script, and
|
|
106
|
+
are declared as the `demo` extra. Versions are pinned in `pyproject.toml`;
|
|
107
|
+
`requirements.txt`, `requirements-demo.txt` and `requirements-dev.txt` mirror
|
|
108
|
+
them for plain `pip install -r` workflows.
|
|
109
|
+
|
|
110
|
+
## Development commands
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
uv run pytest # test suite
|
|
114
|
+
uv run ruff check . # lint
|
|
115
|
+
uv run ruff format . # format
|
|
116
|
+
uv run mypy # strict type check
|
|
117
|
+
uv build # sdist + wheel into dist/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
CI runs exactly these on every pull request, plus the test suite against
|
|
121
|
+
Python 3.10 through 3.14.
|
|
122
|
+
|
|
123
|
+
## Releasing
|
|
124
|
+
|
|
125
|
+
The version in `pyproject.toml` is the single source of truth. To cut a
|
|
126
|
+
release, bump it, add the matching `## [x.y.z]` section to `CHANGELOG.md`, and
|
|
127
|
+
merge to `master`. The release workflow then tags `v<version>`, creates a
|
|
128
|
+
GitHub Release with those notes, and publishes the sdist and wheel to
|
|
129
|
+
[PyPI](https://pypi.org/project/walsh/) using Trusted Publishing — no API token
|
|
130
|
+
is stored in this repository.
|
|
131
|
+
|
|
132
|
+
Merges that do not change the version are a no-op, since PyPI permanently
|
|
133
|
+
refuses to accept the same version twice.
|
|
134
|
+
|
|
135
|
+
## File format
|
|
136
|
+
|
|
137
|
+
`compress` writes a `.cim` file: an atypical, project-specific container, so
|
|
138
|
+
most commercial tools will not be able to read it. It stores the image
|
|
139
|
+
dimensions, three block-layout descriptions (Y, Cb, Cr), and the retained
|
|
140
|
+
Walsh-Hadamard coefficients as little-endian `int16`.
|
|
141
|
+
|
|
142
|
+
## Effects
|
|
143
|
+
|
|
144
|
+

|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Compress an image and restore it, showing both plus their histograms.
|
|
2
|
+
|
|
3
|
+
Run from the repository root after ``pip install -e .[demo]``::
|
|
4
|
+
|
|
5
|
+
python examples/roundtrip.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
import numpy as np
|
|
15
|
+
from PIL import Image
|
|
16
|
+
|
|
17
|
+
from walsh import Task
|
|
18
|
+
|
|
19
|
+
DATA = Path(__file__).resolve().parent.parent / "data"
|
|
20
|
+
SOURCE = DATA / "image.bmp"
|
|
21
|
+
COMPRESSED = DATA / "transformed.cim" # raw post-transform data, not a standard format
|
|
22
|
+
RESTORED = DATA / "recreated.bmp"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> None:
|
|
26
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
|
|
27
|
+
|
|
28
|
+
Task().with_action("compress").with_input(str(SOURCE)).with_output(str(COMPRESSED)).run()
|
|
29
|
+
|
|
30
|
+
Task().with_action("extract").with_input(str(COMPRESSED)).with_output(str(RESTORED)).run()
|
|
31
|
+
|
|
32
|
+
original_bytes = SOURCE.stat().st_size
|
|
33
|
+
compressed_bytes = COMPRESSED.stat().st_size
|
|
34
|
+
print(
|
|
35
|
+
f"{original_bytes:,} B -> {compressed_bytes:,} B "
|
|
36
|
+
f"({compressed_bytes / original_bytes:.1%} of the original)"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# The image after transformation shows a visible loss of quality, and its
|
|
40
|
+
# colour profile (the per-pixel-value histogram) has shifted as well.
|
|
41
|
+
before = Image.open(SOURCE)
|
|
42
|
+
after = Image.open(RESTORED)
|
|
43
|
+
|
|
44
|
+
figure, axes = plt.subplots(2, 2, figsize=(14, 10))
|
|
45
|
+
for column, (title, image) in enumerate((("original", before), ("restored", after))):
|
|
46
|
+
axes[0][column].imshow(np.asarray(image))
|
|
47
|
+
axes[0][column].set_title(title)
|
|
48
|
+
axes[0][column].axis("off")
|
|
49
|
+
axes[1][column].hist(image.histogram(), bins=40)
|
|
50
|
+
axes[1][column].set_title(f"{title} histogram")
|
|
51
|
+
|
|
52
|
+
figure.tight_layout()
|
|
53
|
+
plt.show()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "walsh"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Image compression with the Walsh-Hadamard transform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Oskar Jarczyk", email = "oskar.jarczyk@gmail.com" }]
|
|
14
|
+
maintainers = [{ name = "Oskar Jarczyk", email = "oskar.jarczyk@gmail.com" }]
|
|
15
|
+
keywords = ["walsh", "hadamard", "transform", "image", "compression", "dsp"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Topic :: Multimedia :: Graphics",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
28
|
+
]
|
|
29
|
+
dependencies = ["numpy>=1.24,<3"]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
# A real extra, because `pip install walsh[demo]` is something a user may want:
|
|
33
|
+
# it is what examples/roundtrip.py needs. The package itself parses BMP by hand.
|
|
34
|
+
demo = ["matplotlib>=3.7,<4", "Pillow>=10.0,<13"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
walsh = "walsh.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/oskar-j/walsh-hadamard-transform"
|
|
41
|
+
Repository = "https://github.com/oskar-j/walsh-hadamard-transform"
|
|
42
|
+
Issues = "https://github.com/oskar-j/walsh-hadamard-transform/issues"
|
|
43
|
+
Changelog = "https://github.com/oskar-j/walsh-hadamard-transform/blob/master/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
# PEP 735 groups rather than extras: these are for working on the project, not
|
|
46
|
+
# for installing it. `uv sync --group dev` pulls them in; pip 25.1+ can too,
|
|
47
|
+
# with `pip install --group dev`.
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest>=8.0,<10",
|
|
51
|
+
"ruff>=0.6,<1",
|
|
52
|
+
"mypy>=1.10,<2",
|
|
53
|
+
"build>=1.2,<2",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.package-data]
|
|
60
|
+
walsh = ["py.typed"]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests"]
|
|
64
|
+
addopts = "-ra"
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 100
|
|
68
|
+
src = ["src", "tests"]
|
|
69
|
+
target-version = "py310"
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint]
|
|
72
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
# No python_version pin: numpy's bundled stubs require mypy to target 3.12+.
|
|
76
|
+
# Minimum-version compatibility is enforced by ruff's target-version instead.
|
|
77
|
+
packages = ["walsh"]
|
|
78
|
+
strict = true
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Runtime dependency of the `walsh` package itself.
|
|
2
|
+
# Authoritative versions live in pyproject.toml; this file mirrors them so the
|
|
3
|
+
# project can also be set up with a plain `pip install -r requirements.txt`.
|
|
4
|
+
#
|
|
5
|
+
# For the example script (examples/roundtrip.py) also install requirements-demo.txt.
|
|
6
|
+
# For tests and linting, install requirements-dev.txt.
|
|
7
|
+
|
|
8
|
+
numpy>=1.24,<3
|
walsh-0.1.1/setup.cfg
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Image compression with the Walsh-Hadamard transform.
|
|
2
|
+
|
|
3
|
+
Typical use::
|
|
4
|
+
|
|
5
|
+
from walsh import Task
|
|
6
|
+
|
|
7
|
+
Task().with_action("compress").with_input("image.bmp").with_output("out.cim").run()
|
|
8
|
+
Task().with_action("extract").with_input("out.cim").with_output("back.bmp").run()
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
14
|
+
|
|
15
|
+
from walsh.colors import ColorModel, RgbColorModel, YCbCrColorModel
|
|
16
|
+
from walsh.image import (
|
|
17
|
+
BlockDescription,
|
|
18
|
+
BMPImage,
|
|
19
|
+
CustomizableImage,
|
|
20
|
+
UnsupportedFileFormatError,
|
|
21
|
+
)
|
|
22
|
+
from walsh.task import Action, Task
|
|
23
|
+
from walsh.transforms import Transform, WalshHadamardTransform
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
__version__ = version("walsh")
|
|
27
|
+
except PackageNotFoundError: # pragma: no cover - source checkout without install
|
|
28
|
+
__version__ = "0.0.0.dev0"
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Action",
|
|
32
|
+
"BMPImage",
|
|
33
|
+
"BlockDescription",
|
|
34
|
+
"ColorModel",
|
|
35
|
+
"CustomizableImage",
|
|
36
|
+
"RgbColorModel",
|
|
37
|
+
"Task",
|
|
38
|
+
"Transform",
|
|
39
|
+
"UnsupportedFileFormatError",
|
|
40
|
+
"WalshHadamardTransform",
|
|
41
|
+
"YCbCrColorModel",
|
|
42
|
+
"__version__",
|
|
43
|
+
]
|