dual-vade 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.
@@ -0,0 +1,18 @@
1
+ cff-version: 1.2.0
2
+ message: "Please cite this software when using the package."
3
+ type: software
4
+ title: "Dual-VaDE: unsupervised neural image clustering"
5
+ version: 0.1.0
6
+ authors:
7
+ - family-names: Zheng
8
+ given-names: Wugeng
9
+ repository-code: "https://github.com/ZhengWugeng/dual-vade"
10
+ license: MIT
11
+ abstract: >-
12
+ Unsupervised image clustering with VaDE, Freq-VaDE, and Dual-VaDE.
13
+ Train neural networks on user-provided images, fit a post-hoc GMM,
14
+ and save complete local models for later inference.
15
+ keywords:
16
+ - clustering
17
+ - variational autoencoder
18
+ - unsupervised learning
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wugeng Zheng
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,19 @@
1
+ include README.md pyproject.toml MANIFEST.in CITATION.cff LICENSE
2
+ include release-approval.json requirements-release.txt
3
+ recursive-include src *.py
4
+ include examples/train_local.py
5
+ include tools/check_release.py
6
+ include docs/BUNDLE_FORMAT.md docs/TRAINING.md docs/source_provenance.json
7
+ include docs/RELEASING.md
8
+ prune model_bundles
9
+ prune research_inputs
10
+ prune .git
11
+ prune .github
12
+ prune .codex
13
+ prune build
14
+ prune dist
15
+ global-exclude __pycache__ *.py[cod]
16
+ global-exclude *.pth *.pt *.npz *.npy *.joblib *.pkl
17
+ # No data, model assets, previews, private credentials, or mailbox files.
18
+ global-exclude *.nii *.nii.gz *.dcm *.png *.jpg *.jpeg *.eml
19
+ global-exclude .env .env.* .pypirc *.pem *.key
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: dual-vade
3
+ Version: 0.1.0
4
+ Summary: Unsupervised image clustering with trainable VaDE, Freq-VaDE, and Dual-VaDE
5
+ Author: Wugeng Zheng
6
+ Maintainer: Wugeng Zheng
7
+ License-Expression: MIT
8
+ Project-URL: Repository, https://github.com/ZhengWugeng/dual-vade
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy>=1.24
13
+ Requires-Dist: torch>=2.6
14
+ Requires-Dist: scikit-learn>=1.3
15
+ Dynamic: license-file
16
+
17
+ # Dual-VaDE
18
+
19
+ Unsupervised neural image clustering with VaDE, Freq-VaDE, and Dual-VaDE.
20
+ Author and maintainer: Wugeng Zheng. Code license: MIT.
21
+
22
+ ## Installation
23
+
24
+ ```text
25
+ python -m pip install dual-vade
26
+ ```
27
+
28
+ Requires Python 3.10 or newer. Runtime dependencies are NumPy, PyTorch, and
29
+ scikit-learn. For GPU use, install a PyTorch build compatible with your GPU and
30
+ driver before installing this package. Use `device="cpu"` for CPU operation.
31
+
32
+ Give the model your prepared images, train without labels, save the complete
33
+ model, and reload it for inference on new images. There is no required dataset,
34
+ official pretrained model, model registry, or automatic model download.
35
+ The interface encapsulates neural training and GMM fitting; it does not claim
36
+ source-code or weight secrecy.
37
+
38
+ ## Train, cluster, and save
39
+
40
+ ```python
41
+ from dual_vade import DualVaDE
42
+
43
+ # images: a float array shaped (N, 128, 128), with values in [0, 1].
44
+ # No ground-truth labels are passed to fit.
45
+ model = DualVaDE(n_clusters=3, device="cuda")
46
+ model.fit(images, epochs=200, verbose=True)
47
+ labels = model.predict(images)
48
+ model.save("my_model")
49
+
50
+ # Later, with no training data or further fitting:
51
+ restored = DualVaDE.load("my_model", device="cpu")
52
+ new_labels = restored.predict(new_images)
53
+ probabilities = restored.predict_proba(new_images)
54
+ embeddings = restored.transform(new_images)
55
+ ```
56
+
57
+ `fit` trains the neural encoders, decoder, and native mixture prior, then fits a
58
+ separate diagonal GMM to the final training posterior means. It is not a GMM-only
59
+ refit. A new constructor starts from random neural weights. Calling `fit` again,
60
+ or after loading your own saved model, continues those weights with a new
61
+ optimizer and final GMM. `fit` returns `self`; `predict` returns cluster IDs.
62
+ The user chooses `n_clusters`; the package does not select it automatically or
63
+ assign semantic names to clusters.
64
+
65
+ ## Model choices
66
+
67
+ - `VaDE(n_clusters=3, ...)`: spatial-only.
68
+ - `FreqVaDE(n_clusters=3, spectral="reim", ...)`: frequency-only.
69
+ - `DualVaDE(n_clusters=3, spectral="reim", ...)`: spatial and frequency branches.
70
+
71
+ The four spectral choices are `fullfreq`, `lowfreq`, `polarfreq`, and `reim`.
72
+ ReIm is the default for FreqVaDE and DualVaDE. The old internal FullCplx source
73
+ is named PolarFreq publicly. Together with VaDE, this provides nine training
74
+ configurations. The preserved network implementations are unchanged.
75
+
76
+ ## Input requirements
77
+
78
+ Supply finite, real-valued grayscale image arrays shaped `(N,H,W)` or
79
+ `(N,1,H,W)`, already scaled to `[0,1]`. All images must match the constructor's
80
+ `image_size`: default 128, or a power of two at least 64. Training requires at
81
+ least `max(2, n_clusters)` images; practical training usually needs more data.
82
+
83
+ The same format applies to medical or non-medical images; no specific dataset is
84
+ required. RGB images, 3D volumes, and file-directory paths are not direct inputs.
85
+ The package does not silently resize, normalize, register, or convert raw images.
86
+ Use the same preparation at training and prediction time. Train only on your
87
+ training split when evaluating held-out generalization.
88
+
89
+ `examples/train_local.py` reads your prepared `.npy` array or a `.npz` archive
90
+ containing `images`. See `docs/TRAINING.md` for the API, training recipe, and
91
+ lifecycle, and `docs/BUNDLE_FORMAT.md` for saved model contents.
92
+
93
+ ## Saved models and scope
94
+
95
+ `save("my_model")` creates a new directory containing `config.json`,
96
+ `model_state.pth`, `clusterer.npz`, and `manifest.json`. A network `.pth` alone
97
+ cannot restore the full clustering pipeline. Loading and prediction never train
98
+ or fit a GMM. The package does not upload your images or saved models.
99
+
100
+ The MIT license covers this software, not rights to anyone's input data. This
101
+ release distributes neither datasets nor official pretrained weights/GMMs.
102
+ Users are responsible for the permissions applicable to their data and any
103
+ models they choose to share. Outputs are cluster IDs, not clinical diagnoses.
104
+
105
+ ## Validation
106
+
107
+ The installed code-only rc3 wheel passed HPG job `46757892` on RTX PRO 6000.
108
+ All nine configurations trained on generated non-medical grayscale images,
109
+ without external data or pretrained assets. Neural parameter updates, final GMM,
110
+ held-out inference, saved-model GPU/CPU reload, CPU training, continued training,
111
+ and the CLI example passed. See `docs/TRAINING_VALIDATION_CODE_ONLY.json`.
112
+ The published TestPyPI wheel then passed the same nine-configuration checks in
113
+ job `46761432`, installed into a fresh package venv with the validated HPG
114
+ scientific dependencies. Its origin and API hash were verified, with no source
115
+ checkout used for package imports. See `docs/TESTPYPI_INSTALL_VALIDATION.json`.
116
+ Short functional checks do not establish clustering quality on a user's dataset.
117
+ Historical results are retained in `docs/VALIDATION_STATUS.md`.
@@ -0,0 +1,101 @@
1
+ # Dual-VaDE
2
+
3
+ Unsupervised neural image clustering with VaDE, Freq-VaDE, and Dual-VaDE.
4
+ Author and maintainer: Wugeng Zheng. Code license: MIT.
5
+
6
+ ## Installation
7
+
8
+ ```text
9
+ python -m pip install dual-vade
10
+ ```
11
+
12
+ Requires Python 3.10 or newer. Runtime dependencies are NumPy, PyTorch, and
13
+ scikit-learn. For GPU use, install a PyTorch build compatible with your GPU and
14
+ driver before installing this package. Use `device="cpu"` for CPU operation.
15
+
16
+ Give the model your prepared images, train without labels, save the complete
17
+ model, and reload it for inference on new images. There is no required dataset,
18
+ official pretrained model, model registry, or automatic model download.
19
+ The interface encapsulates neural training and GMM fitting; it does not claim
20
+ source-code or weight secrecy.
21
+
22
+ ## Train, cluster, and save
23
+
24
+ ```python
25
+ from dual_vade import DualVaDE
26
+
27
+ # images: a float array shaped (N, 128, 128), with values in [0, 1].
28
+ # No ground-truth labels are passed to fit.
29
+ model = DualVaDE(n_clusters=3, device="cuda")
30
+ model.fit(images, epochs=200, verbose=True)
31
+ labels = model.predict(images)
32
+ model.save("my_model")
33
+
34
+ # Later, with no training data or further fitting:
35
+ restored = DualVaDE.load("my_model", device="cpu")
36
+ new_labels = restored.predict(new_images)
37
+ probabilities = restored.predict_proba(new_images)
38
+ embeddings = restored.transform(new_images)
39
+ ```
40
+
41
+ `fit` trains the neural encoders, decoder, and native mixture prior, then fits a
42
+ separate diagonal GMM to the final training posterior means. It is not a GMM-only
43
+ refit. A new constructor starts from random neural weights. Calling `fit` again,
44
+ or after loading your own saved model, continues those weights with a new
45
+ optimizer and final GMM. `fit` returns `self`; `predict` returns cluster IDs.
46
+ The user chooses `n_clusters`; the package does not select it automatically or
47
+ assign semantic names to clusters.
48
+
49
+ ## Model choices
50
+
51
+ - `VaDE(n_clusters=3, ...)`: spatial-only.
52
+ - `FreqVaDE(n_clusters=3, spectral="reim", ...)`: frequency-only.
53
+ - `DualVaDE(n_clusters=3, spectral="reim", ...)`: spatial and frequency branches.
54
+
55
+ The four spectral choices are `fullfreq`, `lowfreq`, `polarfreq`, and `reim`.
56
+ ReIm is the default for FreqVaDE and DualVaDE. The old internal FullCplx source
57
+ is named PolarFreq publicly. Together with VaDE, this provides nine training
58
+ configurations. The preserved network implementations are unchanged.
59
+
60
+ ## Input requirements
61
+
62
+ Supply finite, real-valued grayscale image arrays shaped `(N,H,W)` or
63
+ `(N,1,H,W)`, already scaled to `[0,1]`. All images must match the constructor's
64
+ `image_size`: default 128, or a power of two at least 64. Training requires at
65
+ least `max(2, n_clusters)` images; practical training usually needs more data.
66
+
67
+ The same format applies to medical or non-medical images; no specific dataset is
68
+ required. RGB images, 3D volumes, and file-directory paths are not direct inputs.
69
+ The package does not silently resize, normalize, register, or convert raw images.
70
+ Use the same preparation at training and prediction time. Train only on your
71
+ training split when evaluating held-out generalization.
72
+
73
+ `examples/train_local.py` reads your prepared `.npy` array or a `.npz` archive
74
+ containing `images`. See `docs/TRAINING.md` for the API, training recipe, and
75
+ lifecycle, and `docs/BUNDLE_FORMAT.md` for saved model contents.
76
+
77
+ ## Saved models and scope
78
+
79
+ `save("my_model")` creates a new directory containing `config.json`,
80
+ `model_state.pth`, `clusterer.npz`, and `manifest.json`. A network `.pth` alone
81
+ cannot restore the full clustering pipeline. Loading and prediction never train
82
+ or fit a GMM. The package does not upload your images or saved models.
83
+
84
+ The MIT license covers this software, not rights to anyone's input data. This
85
+ release distributes neither datasets nor official pretrained weights/GMMs.
86
+ Users are responsible for the permissions applicable to their data and any
87
+ models they choose to share. Outputs are cluster IDs, not clinical diagnoses.
88
+
89
+ ## Validation
90
+
91
+ The installed code-only rc3 wheel passed HPG job `46757892` on RTX PRO 6000.
92
+ All nine configurations trained on generated non-medical grayscale images,
93
+ without external data or pretrained assets. Neural parameter updates, final GMM,
94
+ held-out inference, saved-model GPU/CPU reload, CPU training, continued training,
95
+ and the CLI example passed. See `docs/TRAINING_VALIDATION_CODE_ONLY.json`.
96
+ The published TestPyPI wheel then passed the same nine-configuration checks in
97
+ job `46761432`, installed into a fresh package venv with the validated HPG
98
+ scientific dependencies. Its origin and API hash were verified, with no source
99
+ checkout used for package imports. See `docs/TESTPYPI_INSTALL_VALIDATION.json`.
100
+ Short functional checks do not establish clustering quality on a user's dataset.
101
+ Historical results are retained in `docs/VALIDATION_STATUS.md`.
@@ -0,0 +1,31 @@
1
+ # User model bundle, schema version 1
2
+
3
+ A complete saved model contains four files:
4
+
5
+ - `config.json`: schema version, model ID, method (`vade`, `freq`, `dual`),
6
+ spectral representation (`none`, `fullfreq`, `lowfreq`, `polarfreq`, `reim`),
7
+ `n_clusters`, `latent_dim`, `image_size`, random seed, and input metadata.
8
+ - `model_state.pth`: a plain PyTorch state dict, including inference buffers.
9
+ - `clusterer.npz`: the fitted post-hoc diagonal GMM, with numeric arrays
10
+ `weights`, `means`, and `covariances`, in fixed component order.
11
+ - `manifest.json`: schema version, SHA-256 checksums, training origin, history,
12
+ GMM details, and parent-bundle identity when training continues from a saved model.
13
+
14
+ Input metadata declares `intensity_range: [0,1]`, `normalization: "none"`,
15
+ and `resize: "none"`. Inputs must already satisfy that contract. This format
16
+ is not restricted to a particular dataset and is not a raw-image preprocessing
17
+ pipeline. Prediction must use the same input preparation as training.
18
+
19
+ The post-hoc clusterer is distinct from the neural network's native mixture prior.
20
+ Both network and clusterer must be retained. Loading never fits either component.
21
+ Training images, labels, subject IDs, per-image embeddings, and optimizer states
22
+ are not saved. A later fit continues weights, not an interrupted optimizer run.
23
+
24
+ The loader verifies checksums, uses `weights_only=True` for the state dict and
25
+ `allow_pickle=False` for the GMM arrays, and validates dimensions and finite
26
+ parameters. A local manifest detects corruption; it is not an independent proof
27
+ that a model from someone else is trustworthy. Load models from trusted sources.
28
+
29
+ `save` never overwrites an existing destination. Saving a loaded model without
30
+ further training copies its four original files byte-for-byte. Further training
31
+ changes only the in-memory model and any new export, not the source folder.
@@ -0,0 +1,113 @@
1
+ # Code-only release checklist
2
+
3
+ ## Scope
4
+
5
+ Stable release preparation `0.1.0`, author and maintainer Wugeng Zheng. The only supported
6
+ workflow is user images -> full neural fit -> post-hoc GMM -> save/load/predict.
7
+ The three classes expose nine training configurations. There is no official
8
+ pretrained entry point, registry, model hosting requirement, or dataset helper.
9
+ The package contains no dataset, trained weights, or fitted GMM assets.
10
+
11
+ The user confirmed team authorship and the supervisor's request to publish on
12
+ 2026-10-19. MIT applies to the package code. This is not a license for user data
13
+ or a blanket license for user-trained model assets. A separate OASIS agreement
14
+ or official-weight redistribution check is not a prerequisite for this code-only
15
+ release, which distributes neither. Users must comply with terms of their own data.
16
+
17
+ `release-approval.json` records code rights and the code-only scope. On 2026-10-19,
18
+ the user separately approved formal PyPI `0.1.0` after TestPyPI validation passed.
19
+ Per-index and per-version allowlists limit authorization to TestPyPI `0.1.0rc3`
20
+ and PyPI `0.1.0`. A later version needs a new approval record. Model redistribution,
21
+ public asset URLs, and pretrained download checks are not release gates. Do not
22
+ upload historical private model bundles.
23
+
24
+ ## Private verification
25
+
26
+ The prior rc2 fit API passed HPG job 46754905. Removing download interfaces changes
27
+ the inference source hash, so that record does not authorize the rc3 candidate.
28
+ `tools/run_hpg_training.sbatch` builds and installs the current wheel in a Slurm
29
+ Pro 6000 allocation and trains all nine configurations on generated non-medical
30
+ grayscale images. No reference dataset or pretrained assets are needed. It also
31
+ checks CPU training, model save/reload, continued training from a user's saved
32
+ model, held-out probability contracts, the CLI example, source-wheel rebuild,
33
+ MIT metadata, and expected public-upload refusal.
34
+
35
+ The code-only validation passed as HPG job 46757892 (COMPLETED, exit 0:0).
36
+ `docs/TRAINING_VALIDATION_CODE_ONLY.json` retains the report, and the matching
37
+ source hash/job are recorded in `release-approval.json`. The original wheel and
38
+ source distribution were downloaded, hash-verified, and retained in ignored
39
+ `dist/0.1.0rc3-hpg46757892/`. Citation, MIT metadata, strict Twine checks, source
40
+ rebuild, and the expected refusal of both public upload gates passed as well.
41
+ Short validation is not an accuracy claim. Public test upload is now separately
42
+ authorized. TestPyPI installation and full training/save-load verification then
43
+ passed GPU job 46761432; the evidence is recorded in `testpypi_install_validation`.
44
+ Formal PyPI 0.1.0 is now separately authorized; its final build and upload are pending.
45
+
46
+ ## Publication sequence
47
+
48
+ 1. Review package contents and Git history for private files, credentials, data,
49
+ old model assets, and required upstream notices before making anything public.
50
+ 2. Complete the TestPyPI pending publisher, if still missing. The user enters any
51
+ password/MFA themselves. Never read, store, or request these values.
52
+ 3. Configure GitHub environments `testpypi` and `pypi`, matching Trusted Publishing.
53
+ Review branch/environment protections before enabling upload.
54
+ 4. With explicit authorization, publish the reviewed code repository and set
55
+ `public_release_authorized` with a non-sensitive approval reference.
56
+ 5. Dispatch `publish.yml` on main, target `testpypi`, with confirmation
57
+ `PUBLISH dual-vade testpypi`. TestPyPI is public, not private storage.
58
+ 6. In an HPG GPU allocation, install dependencies from PyPI, install this package
59
+ from TestPyPI using `--no-deps`, and verify training, save/load, and prediction.
60
+ Record the job ID and version in `testpypi_install_validation`.
61
+ 7. Set the package, citation, and public version to `0.1.0`; rebuild and review.
62
+ Obtain formal release approval, then dispatch target `pypi` with confirmation
63
+ `PUBLISH dual-vade pypi`. Verify clean installation of the published package.
64
+
65
+ The workflow remains manual-only and build-only by default. No push/tag trigger
66
+ publishes anything. No passwords or long-lived API tokens are needed by the
67
+ prepared OIDC upload workflow. Do not replace an already uploaded file/version.
68
+
69
+ ## Account setup last observed: 2026-10-19
70
+
71
+ PyPI account `zhengwugeng` registered a pending publisher for project `dual-vade`,
72
+ owner `ZhengWugeng`, repository `dual-vade`, workflow `publish.yml`, environment
73
+ `pypi`. TestPyPI has the matching publisher with environment `testpypi`;
74
+ the user completed sensitive confirmations personally. The GitHub code repository
75
+ is public. Its `testpypi` environment permits only the `main` branch. Workflow run
76
+ 39357159452 on commit 145461c successfully built and published `0.1.0rc3` to
77
+ TestPyPI on 2026-10-19 at 22:07 EDT. The formal PyPI job was skipped. Published
78
+ artifact hashes and matching runtime sources were verified; the release receipt
79
+ is `docs/TESTPYPI_RELEASE.json`. HPG index-install validation passed job 46761432;
80
+ its public summary is `docs/TESTPYPI_INSTALL_VALIDATION.json` and its detailed
81
+ training report is `docs/TESTPYPI_TRAINING_VALIDATION.json`.
82
+ Formal PyPI 0.1.0 is now explicitly authorized; build/upload are being prepared.
83
+
84
+ After either index upload, `tools/run_hpg_testpypi.sbatch` must use the exact published version,
85
+ public wheel SHA-256, and validated training API hash. It installs a fresh copy of
86
+ the package from the explicitly selected index (`--no-deps`, no fallback index), reusing the validated
87
+ HPG scientific dependencies. The package itself must resolve inside the new venv,
88
+ not the checkout. The full nine-configuration verification runs on Pro 6000. This
89
+ is not a claim of reinstalling the entire CUDA/PyTorch dependency stack.
90
+
91
+ ## HPG runtime reuse detail
92
+
93
+ The selected HPG Python is already a virtual environment. A child venv with
94
+ `--system-site-packages` does not expose the parent venv's installed packages;
95
+ job 46760982 therefore installed the test wheel but failed before training.
96
+ The corrected installer uses a fresh package venv and a .pth entry for only the
97
+ selected parent's site-packages. It records exact dependency locations/versions
98
+ and checks visibility first. User-site and checkout PYTHONPATH stay disabled.
99
+ The package-origin and pinned wheel/API checks remain unchanged. Do not interpret
100
+ successful pip installation alone as successful model/runtime validation.
101
+
102
+ ## Formal 0.1.0 authorization
103
+
104
+ The user approved formal publication after job 46761432 passed. Package, citation,
105
+ and public version are now 0.1.0; inference.py, training.py, and all four network
106
+ implementations are unchanged. The formal gate additionally binds the TestPyPI
107
+ installation record to this exact training API hash. The workflow still requires
108
+ manual dispatch on main with `PUBLISH dual-vade pypi`; no push/tag auto-publishes.
109
+
110
+ The existing HPG installer supports `DUAL_VADE_INDEX_TARGET=pypi` as well as its
111
+ original `testpypi` default. It uses a fixed official index/JSON endpoint for each,
112
+ checks the public wheel hash before installation, and reruns all nine configurations
113
+ from the installed package. Formal-index results use `pypi-install-validation.json`.
@@ -0,0 +1,104 @@
1
+ # Neural training and complete-model inference
2
+
3
+ ## Public API
4
+
5
+ ```python
6
+ from dual_vade import DualVaDE, FreqVaDE, VaDE
7
+
8
+ model = DualVaDE(
9
+ n_clusters=3, spectral="reim", latent_dim=32, image_size=128,
10
+ device="cuda", batch_size=16, random_state=123,
11
+ )
12
+ model.fit(train_images, epochs=200, learning_rate=1.5e-4, verbose=True)
13
+ labels = model.predict(test_images)
14
+ model.save("my_model")
15
+ restored = DualVaDE.load("my_model", device="cpu")
16
+ labels = restored.predict(test_images)
17
+ ```
18
+
19
+ `FreqVaDE` has the same spectral choices. Use `VaDE(n_clusters=3, ...)` without
20
+ a spectral argument for the spatial baseline. Methods use sklearn-style
21
+ fit/predict calls; full sklearn estimator/cloning/GridSearchCV compatibility is
22
+ not claimed.
23
+
24
+ Only images explicitly passed to `fit` are used for neural optimization or final
25
+ GMM fitting. Do not concatenate held-out/test data into that call. The local training
26
+ example treats every image in its input file as training data; separate your
27
+ held-out data before invoking it. `predict`,
28
+ `predict_proba`, `transform`, `reconstruct`, `save`, and `load` never train a model.
29
+ No external dataset, pretrained weights, labels, or model download is needed.
30
+ Train on your own compatible images; choose the cluster count with n_clusters.
31
+
32
+ ## Input contract
33
+
34
+ Prepared finite grayscale arrays, `(N,H,W)` or `(N,1,H,W)`, with intensities in
35
+ [0,1]. Constructor image_size is a power of two >=64, default 128. At least
36
+ `max(2, n_clusters)` training images are required. No registration, resizing,
37
+ RGB conversion, normalization, or raw-volume preprocessing is hidden in fit.
38
+ Maintain the same input convention at prediction time.
39
+
40
+ ## What fit actually does
41
+
42
+ 1. Put the neural network in training mode and enable all neural parameters.
43
+ 2. For each epoch, shuffle only the supplied training images; evaluate the
44
+ variational encoder/decoder and trainable native mixture-prior objective;
45
+ backpropagate, clip gradients, and take AdamW optimizer steps.
46
+ 3. After the final neural epoch, switch to evaluation mode and collect posterior
47
+ means for those same training images, in their original supplied order.
48
+ 4. Fit a separate sklearn diagonal GMM to those means, with the requested K.
49
+ 5. Commit the fitted GMM and training history to the model and return `self`.
50
+
51
+ Defaults: AdamW learning rate 1.5e-4, weight decay 2e-4, gradient norm clip 5,
52
+ cosine decay to min(5e-5, initial learning rate). Loss weights are reconstruction
53
+ BCE 0.2, fused/spatial KL 0.01, native GMM negative log-likelihood 0.05, and mixture
54
+ balance 0.01. Dual FullFreq/ReIm/PolarFreq also use branch-level KL 0.01 per branch;
55
+ LowFreq retains its original concat recipe without those extra branch terms.
56
+ The post-hoc GMM uses float64 embeddings, diagonal covariance, ten restarts,
57
+ reg_covar 1e-4, and at most 200 EM iterations. Non-finite gradients/losses or a
58
+ non-converged final GMM fail rather than silently creating a different K model.
59
+
60
+ This is a shared user-training pipeline, not a promise to reproduce every original
61
+ family/seed's complete epoch-dependent research schedule, early stopping, or paper
62
+ metrics. It does not select checkpoints using test data and does not repeatedly
63
+ refit GMMs during neural epochs. Training quality on a new dataset needs its own
64
+ evaluation.
65
+
66
+ ## Lifecycle and reproducibility
67
+
68
+ - New constructors create random networks; inference/save raises NotFittedError
69
+ until fit succeeds. `epochs=0` is rejected: fit cannot be a GMM-only shortcut.
70
+ - A loaded user-saved model already supports inference. Calling fit on
71
+ it continues its current neural weights, using a fresh optimizer and final GMM.
72
+ Repeated fit likewise continues weights; construct a new object for a fresh start.
73
+ - Prediction never updates weights, BatchNorm statistics, or GMM parameters.
74
+ - Basic invalid options/input are rejected before invalidating an existing model.
75
+ Once training starts, failure invalidates inference/save until a successful fit
76
+ or a new load; original on-disk bundles are never changed by fitting.
77
+ - `history_` records epoch losses and learning rates; `n_iter_` records the most
78
+ recent successful fit's epoch count. They survive saving/loading.
79
+ - `random_state` defaults to 123 and controls initialization, shuffling, sampling,
80
+ dropout, and GMM initialization. Seeded initialization/training restores the
81
+ caller's NumPy and relevant Torch RNG states. None uses unseeded randomness.
82
+
83
+ ## Saved model
84
+
85
+ `save` creates a new directory and never overwrites an existing one:
86
+
87
+ ```text
88
+ my_model/
89
+ config.json
90
+ model_state.pth
91
+ clusterer.npz
92
+ manifest.json
93
+ ```
94
+
95
+ The config stores architecture/input settings. The state dict includes network
96
+ parameters and inference buffers. NPZ contains fixed post-hoc weights, means, and
97
+ diagonal covariances. Manifest records SHA-256 values, training origin, epochs,
98
+ optimizer-step count, history, GMM details, and parent bundle identity when relevant.
99
+ It does not store training images, subject IDs, or per-sample embeddings/labels.
100
+
101
+ Loading requires no training dataset, gradient updates, or GMM re-fitting.
102
+ Optimizer/scheduler states are not saved: a later fit continues weights, not an
103
+ exact interrupted optimizer run. Continued-training outputs retain user-trained provenance. Saving an unmodified
104
+ loaded model copies its verified bundle byte-for-byte and preserves its history.
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "source": "1_FullFreq-GMVAE/models/vae.py",
4
+ "package_file": "src/dual_vade/_models/fullfreq.py",
5
+ "sha256": "893f5f157cf13b03cdb8eca4b2e0c91e14116ee37050f7f0db3c700c970e78e9"
6
+ },
7
+ {
8
+ "source": "2_LowFreq-GMVAE/models/vae.py",
9
+ "package_file": "src/dual_vade/_models/lowfreq.py",
10
+ "sha256": "329ed5758cf89478f35d2dbefbffe9de94d5589a3c4883e2ceb41fb7a11cb1d7"
11
+ },
12
+ {
13
+ "source": "3_ReIm-GMVAE/models/vae.py",
14
+ "package_file": "src/dual_vade/_models/reim.py",
15
+ "sha256": "8332a9d1cac901edcb3e3676d1662335e43a7adef056d755e351c19a8f52668f"
16
+ },
17
+ {
18
+ "source": "4_FullCplx-GMVAE/models/vae.py",
19
+ "package_file": "src/dual_vade/_models/polarfreq.py",
20
+ "sha256": "fda98c02074974b0564347b26cc9efd07b37647421d7c439dc8ecef2c3db1caa"
21
+ }
22
+ ]
@@ -0,0 +1,37 @@
1
+ """Train a new Dual-VaDE on prepared local images, then save the complete model."""
2
+ import argparse
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+
7
+ from dual_vade import DualVaDE
8
+
9
+
10
+ def main():
11
+ parser = argparse.ArgumentParser(description=__doc__)
12
+ parser.add_argument("--images", type=Path, required=True, help="Training images only: prepared .npy array or .npz with an images key")
13
+ parser.add_argument("--output", type=Path, required=True, help="A new model directory")
14
+ parser.add_argument("--n-clusters", type=int, default=3)
15
+ parser.add_argument("--spectral", choices=("fullfreq", "lowfreq", "polarfreq", "reim"), default="reim")
16
+ parser.add_argument("--epochs", type=int, default=200)
17
+ parser.add_argument("--batch-size", type=int, default=16)
18
+ parser.add_argument("--device", choices=("cpu", "cuda"), default="cpu")
19
+ args = parser.parse_args()
20
+ args.images = args.images.expanduser()
21
+ args.output = args.output.expanduser()
22
+ if args.output.exists():
23
+ parser.error("Output directory already exists; choose a new path")
24
+ if args.images.suffix == ".npz":
25
+ with np.load(args.images, allow_pickle=False) as saved:
26
+ images = saved["images"]
27
+ else:
28
+ images = np.load(args.images, allow_pickle=False)
29
+ model = DualVaDE(n_clusters=args.n_clusters, spectral=args.spectral,
30
+ device=args.device, batch_size=args.batch_size)
31
+ model.fit(images, epochs=args.epochs, verbose=True)
32
+ model.save(args.output)
33
+ print(f"Saved complete trained model to {args.output}")
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dual-vade"
7
+ version = "0.1.0"
8
+ description = "Unsupervised image clustering with trainable VaDE, Freq-VaDE, and Dual-VaDE"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{name = "Wugeng Zheng"}]
13
+ maintainers = [{name = "Wugeng Zheng"}]
14
+ requires-python = ">=3.10"
15
+ dependencies = ["numpy>=1.24", "torch>=2.6", "scikit-learn>=1.3"]
16
+
17
+ [project.urls]
18
+ Repository = "https://github.com/ZhengWugeng/dual-vade"
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["src"]
@@ -0,0 +1,25 @@
1
+ {
2
+ "schema_version": 1,
3
+ "release_scope": "trainable-code-only",
4
+ "code_rights_confirmed": true,
5
+ "public_release_authorized": true,
6
+ "approval_record": "2026-10-19: user confirmed team authorship and supervisor instruction to publish code for training user-provided images, with no official model or dataset distribution. User approved public GitHub and TestPyPI 0.1.0rc3, then explicitly approved formal PyPI 0.1.0 after successful HPG TestPyPI install/training validation 46761432. Authorization is limited to these versions.",
7
+ "testpypi_install_validation": {
8
+ "hpg_job_id": "46761432",
9
+ "version": "0.1.0rc3",
10
+ "training_api_sha256": "09f16c8afa85a5ffd999df68b6efeb216b41a97ed9d57f2688ec513421c14459",
11
+ "wheel_sha256": "19343ed5f0b781e0c0fda15711c14eeac811b1283bff3e7a736736feec8377a6"
12
+ },
13
+ "training_validation": {
14
+ "hpg_job_id": "46757892",
15
+ "training_api_sha256": "09f16c8afa85a5ffd999df68b6efeb216b41a97ed9d57f2688ec513421c14459"
16
+ },
17
+ "authorized_publish_targets": [
18
+ "testpypi",
19
+ "pypi"
20
+ ],
21
+ "authorized_publish_versions": {
22
+ "testpypi": "0.1.0rc3",
23
+ "pypi": "0.1.0"
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ # Build/validation tools only; these are not inference dependencies.
2
+ build==1.3.0
3
+ twine==6.2.0
4
+ cffconvert==2.0.0
5
+ PyYAML==6.0.2
6
+ packaging==25.0
7
+ setuptools==80.9.0
8
+ wheel==0.46.3
9
+ tomli>=2,<3; python_version < "3.11"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Unsupervised neural image clustering: fit, predict, save, and load."""
2
+ from .inference import DualVaDE, FreqVaDE, VaDE
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["DualVaDE", "FreqVaDE", "VaDE"]