seana-ssl 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: "If you use SEANA, please cite the accompanying paper. Publication metadata will be updated for the camera-ready release."
3
+ type: software
4
+ title: "SEANA: Noise-Aligned View Generation for Self-Supervised Learning"
5
+ version: 0.1.0
6
+ authors:
7
+ - family-names: Patil
8
+ given-names: Oankar R.
9
+ - family-names: Wang
10
+ given-names: May D.
11
+ preferred-citation:
12
+ type: manuscript
13
+ title: "Breaking Noise Shortcuts in Self-Supervised Learning via Noise-Aligned View Generation"
14
+ authors:
15
+ - family-names: Patil
16
+ given-names: Oankar R.
17
+ - family-names: Wang
18
+ given-names: May D.
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SEANA 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.
22
+
23
+ Scope: this license covers original SEANA code and documentation. Third-party
24
+ code, dependencies, datasets and weights retain their own terms. In particular,
25
+ cifar10/lejepa.py and denoisers/nsp.py are not covered by this MIT grant; see
26
+ THIRD_PARTY_NOTICES.md.
@@ -0,0 +1,7 @@
1
+ # Library source distribution: experiment repository assets are released separately.
2
+ global-exclude *
3
+ include pyproject.toml MANIFEST.in LICENSE CITATION.cff THIRD_PARTY_NOTICES.md
4
+ include seana.py _seana_api.py uv.lock
5
+ include docs/library.md
6
+ include examples/ssl_pipeline.py
7
+ include tests/conftest.py tests/test_library.py
@@ -0,0 +1,313 @@
1
+ Metadata-Version: 2.4
2
+ Name: seana-ssl
3
+ Version: 0.1.0
4
+ Summary: Learned variance stabilization and noise-aligned views for self-supervised learning
5
+ Author: Oankar R. Patil, May D. Wang
6
+ License-Expression: MIT
7
+ Keywords: self-supervised-learning,augmentation,denoising,pytorch
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: torch<3,>=2.5.1
12
+ Provides-Extra: drunet
13
+ Requires-Dist: deepinv==0.4.1; extra == "drunet"
14
+ Provides-Extra: experiments
15
+ Requires-Dist: torch==2.5.1; extra == "experiments"
16
+ Requires-Dist: torchvision==0.20.1; extra == "experiments"
17
+ Requires-Dist: lightly==1.5.25; extra == "experiments"
18
+ Requires-Dist: deepinv==0.4.1; extra == "experiments"
19
+ Requires-Dist: numpy<3,>=1.26; extra == "experiments"
20
+ Requires-Dist: pillow<14,>=10; extra == "experiments"
21
+ Provides-Extra: imagenet100
22
+ Requires-Dist: datasets==4.4.1; extra == "imagenet100"
23
+ Provides-Extra: bbbc048
24
+ Requires-Dist: tifffile==2024.8.30; extra == "bbbc048"
25
+ Provides-Extra: demo
26
+ Requires-Dist: ipywidgets<9,>=8; extra == "demo"
27
+ Requires-Dist: matplotlib<4,>=3.8; extra == "demo"
28
+ Requires-Dist: tqdm<5,>=4.66; extra == "demo"
29
+ Dynamic: license-file
30
+
31
+ # SEANA for your SSL pipeline
32
+
33
+ SEANA learns a variance-stabilizing transform (VST) around a frozen Gaussian
34
+ denoiser and generates independently resampled views for self-supervised learning.
35
+ The Python distribution is **`seana-ssl`**; its import is **`seana`**. The core
36
+ implementation remains in `seana.py` at the repository root.
37
+
38
+ ## Install with uv
39
+
40
+ Add SEANA to your uv project:
41
+
42
+ ```bash
43
+ uv add seana-ssl
44
+ # Optional pretrained DRUNet support:
45
+ uv add 'seana-ssl[drunet]'
46
+ ```
47
+
48
+ For an existing environment using uv's pip interface:
49
+
50
+ ```bash
51
+ uv pip install 'seana-ssl[drunet]'
52
+ # Standard pip works too:
53
+ python -m pip install 'seana-ssl[drunet]'
54
+ ```
55
+
56
+ For local development from a source checkout:
57
+
58
+ ```bash
59
+ uv sync --extra drunet
60
+ uv run --extra drunet python -c "from seana import SEANA; print(SEANA)"
61
+ # Or add the checkout to a different project:
62
+ uv add /path/to/SEANA --extra drunet
63
+ ```
64
+
65
+ Only **PyTorch** is required by the core library. The `drunet` extra adds
66
+ DeepInverse. Importing `seana` does not import DeepInverse or download weights.
67
+ Choose the PyTorch CPU/CUDA build appropriate for your environment; the library
68
+ does not impose a CUDA index. See [uv's PyTorch guide](https://docs.astral.sh/uv/guides/integration/pytorch/).
69
+ Python 3.10+ is required. Research dependencies retain their Python 3.12 recipe.
70
+
71
+ To reproduce this package release, pin `seana-ssl==0.1.0` (or
72
+ `seana-ssl[drunet]==0.1.0`) in your environment.
73
+
74
+ The wheel and source distribution contain the reusable core/API, without research
75
+ datasets, comparison-model code or cluster artifacts. Experiments remain in the
76
+ full repository checkout. The source distribution also includes this guide, a
77
+ runnable example, library tests and the development lockfile.
78
+
79
+ ## Fit once, generate views
80
+
81
+ ```python
82
+ import torch
83
+ from seana import SEANA
84
+
85
+ device = "cuda" if torch.cuda.is_available() else "cpu"
86
+ seana = SEANA.from_drunet(
87
+ sigma_d=0.08, # Gaussian denoiser's noise-level input
88
+ sigma_vst=0.1, # Resampling standard deviation in VST space
89
+ channels=3,
90
+ device=device,
91
+ )
92
+
93
+ # train_loader yields raw floating [B,3,H,W] images, optionally followed by labels.
94
+ # Fit on training images only, before ImageNet/encoder mean-std normalization.
95
+ seana.fit(train_loader, steps=5000, patch_size=64, seed=0)
96
+
97
+ for images, _ in train_loader:
98
+ source_a, source_b = seana(images.to(device))
99
+ # Apply independent SSL transforms to each source, as shown below.
100
+ ```
101
+
102
+ These noise levels demonstrate the API; appropriate values depend on the dataset,
103
+ fitted transform and SSL objective. The library does not automatically tune them
104
+ against downstream labels. `sigma_d` and `sigma_vst` have different roles.
105
+
106
+ Calling `seana(images)` denoises once and returns two independent noise draws
107
+ **at the same spatial geometry**. It does not add an implicit crop or normalization
108
+ policy. For a dataset of already-noisy images, use this order:
109
+
110
+ **Original noisy image → SEANA denoising and independent resampling → independent
111
+ SSL crops/photometric transforms → encoder normalization → SSL model.**
112
+
113
+ The fitting loader and the SSL source loader should yield original noisy images,
114
+ converted to floating tensors at the intended intensity scale, before SSL
115
+ augmentations. Apply your single-view transforms to the two SEANA outputs:
116
+
117
+ ```python
118
+ source_a, source_b = seana(images)
119
+ view_a = torch.stack([ssl_view_a(image) for image in source_a])
120
+ view_b = torch.stack([ssl_view_b(image) for image in source_b])
121
+ loss = ssl_loss(encoder(view_a), encoder(view_b))
122
+ loss.backward()
123
+ ```
124
+
125
+ Here `ssl_view_a` and `ssl_view_b` are your single-view augmentation pipelines,
126
+ including normalization as their last step. They can share a stochastic transform
127
+ or use different branch settings, as in BYOL. Calling them per image gives
128
+ independent transform draws across examples as well as across branches.
129
+
130
+ This order lets SEANA denoise the original acquisition noise before resizing,
131
+ blur or color operations alter it. Those operations then act on the resampled
132
+ sources, so noise after the SSL transforms need not retain its pre-transform
133
+ statistics. A crop without interpolation or a flip preserves retained pixel
134
+ values, but a crop still changes the denoiser's available context.
135
+
136
+ The API also permits `.augment(crop)` for an explicitly chosen alternate protocol;
137
+ the orders are not generally equivalent. The captured ImageNet runner uses
138
+ SEANA before SSL transforms. The captured CIFAR runner applies SEANA after its
139
+ view transforms, and microscopy has several explicit order controls. Existing
140
+ research recipes retain those protocols; the library example follows the
141
+ SEANA-first workflow described here.
142
+
143
+ ## LightlySSL integration
144
+
145
+ Use a **single-view** Lightly transform on each independently resampled source.
146
+ Its normal final normalization can stay enabled because SEANA runs first:
147
+
148
+ ```python
149
+ from lightly.transforms import SimCLRViewTransform
150
+
151
+ ssl_view = SimCLRViewTransform(input_size=224)
152
+ for images, _ in raw_train_loader:
153
+ source_a, source_b = seana(images.to(device))
154
+ view_a = torch.stack([ssl_view(image) for image in source_a])
155
+ view_b = torch.stack([ssl_view(image) for image in source_b])
156
+ loss = ssl_loss(encoder(view_a), encoder(view_b))
157
+ loss.backward()
158
+ ```
159
+
160
+ `SimCLRViewTransform` includes crop/resize, flipping, color jitter, grayscale,
161
+ blur and normalization. Tensor inputs require torchvision transforms v2, available
162
+ in the tested torchvision 0.20.1 / Lightly 1.5.25 research environment.
163
+ See the [Lightly transform API](https://docs.lightly.ai/self-supervised-learning/lightly.transforms.html#lightly.transforms.simclr_transform.SimCLRViewTransform).
164
+ Keep the raw-image loader free of the usual two-view dataset transform and run
165
+ SEANA in the training process, after batching. Using one SEANA noise draw as the
166
+ shared input to a two-view `SimCLRTransform` would share that draw across both
167
+ views, so it is not the independent-resampling path shown above.
168
+
169
+ The augmenter stays in evaluation mode with frozen parameters, even when it is
170
+ a child of a module on which `.train()` is called. Its outputs are detached
171
+ regular tensors, so the encoder still receives parameter gradients. Do not put
172
+ the complete SSL encoder/loss computation inside `torch.no_grad()`.
173
+
174
+ A complete, download-free example is included:
175
+
176
+ ```bash
177
+ uv run examples/ssl_pipeline.py
178
+ ```
179
+
180
+ It uses synthetic images, a toy averaging filter and a small contrastive encoder
181
+ to demonstrate integration, not scientific performance. To use pretrained DRUNet:
182
+
183
+ ```bash
184
+ uv run --extra drunet examples/ssl_pipeline.py --drunet --device cuda
185
+ ```
186
+
187
+ To exercise Lightly's actual transforms after SEANA with the research dependencies:
188
+
189
+ ```bash
190
+ uv run --python 3.12 --extra experiments examples/ssl_pipeline.py --lightly \
191
+ --checkpoint runs/library_example/lightly_seana.pt
192
+ ```
193
+
194
+ Add `--drunet --device cuda` to use pretrained DRUNet in that example. Lightly is
195
+ optional; it is not imported by the core library.
196
+
197
+ The root BBBC048 notebook provides a real-data demo in the full repository.
198
+
199
+ ## Supply your own denoiser
200
+
201
+ ```python
202
+ seana = SEANA(my_denoiser, channels=1, sigma_vst=0.05, device="cuda")
203
+ seana.fit(train_loader, steps=5000, patch_size=64)
204
+ ```
205
+
206
+ The denoiser must be an `nn.Module` mapping floating `[B,C,H,W]` tensors to the
207
+ same shape/device. Its forward pass must be differentiable with respect to its
208
+ **input** during VST fitting, although its own weights and BatchNorm statistics
209
+ stay frozen. A denoiser whose forward uses `torch.no_grad()` cannot train a VST
210
+ through that method.
211
+
212
+ SEANA owns the module by reference: it moves and freezes it. Pass a separate copy
213
+ if another part of your application trains the same denoiser. Inputs may be CHW
214
+ or BCHW for inference. Denoiser-specific image/patch-size constraints still apply.
215
+
216
+ ## Fitting options
217
+
218
+ `fit()` accepts a re-iterable loader of tensors or `(images, labels)` batches.
219
+ For dictionaries, pass `batch_key="image"`; other fields are ignored. One-shot
220
+ generators and empty loaders are rejected. Training images must have the configured
221
+ channel count and be at least as large as `patch_size`.
222
+
223
+ By default, the first 50 training batches calibrate one dataset-level intensity
224
+ range. To use a known range:
225
+
226
+ ```python
227
+ seana = SEANA(my_denoiser, input_range=(0.0, 1.0), sigma_vst=0.1)
228
+ seana.fit(train_loader, calibrate=False, steps=5000, patch_size=64,
229
+ patch_batch_size=4, stride=4, lr=0.01)
230
+ ```
231
+
232
+ The default spline has 128 knots and uses the captured global fitting objective.
233
+ Use `variant="conditional"` for the captured conditional VST, with the correct
234
+ `channels` specified at construction. Existing low-level imports
235
+ `GlobalNoise2VST`, `ConditionalGlobalNoise2VST` and `DeepInvDRUNetWrapper` remain
236
+ available for research code.
237
+
238
+ An optional `seed` isolates the loop's Python/PyTorch RNG state; separate loader
239
+ generators and stochastic worker transforms remain the caller's responsibility.
240
+ The `progress` callback receives percent completion. Repeated `fit()` calls
241
+ continue the current VST weights; construct a new augmenter for a fresh fit.
242
+ A failed fit remains unusable until fitting succeeds. Fitting needs autograd
243
+ and must run outside `torch.inference_mode()`.
244
+
245
+ ## Denoising and reproducible resampling
246
+
247
+ ```python
248
+ estimate = seana.denoise(images) # No noise draw; does not advance the RNG.
249
+ a, b = seana(images, sigma_vst=0.0) # Equal views with resampling disabled.
250
+ a, b = seana(images, sigma_vst=(0.05, 0.15)) # One scale per image, shared by the pair.
251
+
252
+ generator = torch.Generator(device=device).manual_seed(123)
253
+ a, b = seana(images, sigma_vst=0.1, generator=generator)
254
+ ```
255
+
256
+ Use a generator on the augmenter's device. Recreating it with the same seed
257
+ repeats the noise; reusing it advances the stream. Each view receives independent
258
+ noise. Per-image strengths may be tensors shaped `[B]` or `[B,1,1,1]`. All
259
+ strengths must be finite and nonnegative. Outputs are clamped to the fitted
260
+ physical intensity range by default; `clamp=False` disables both input and output
261
+ clamps. `denoise()` is an estimate, not clean ground truth.
262
+
263
+ ## Save and reload
264
+
265
+ ```python
266
+ seana.save("weights/seana.pt")
267
+ restored = SEANA.load("weights/seana.pt", device=device)
268
+ ```
269
+
270
+ For a custom denoiser, supply the same configured module:
271
+
272
+ ```python
273
+ restored = SEANA.load("weights/seana.pt", denoiser=my_reconstructed_denoiser,
274
+ device=device)
275
+ ```
276
+
277
+ Checkpoints store the VST, denoiser tensors, settings and fit summary. DRUNet
278
+ reloads with `pretrained=None`, without another download. Custom denoisers are
279
+ never dynamically imported or unpickled: the provided class/representation is
280
+ checked and tensor states load strictly. Preserve any custom behavior not encoded
281
+ in `state_dict()` or `extra_repr()` yourself. Loading uses a typed payload and
282
+ `torch.load(weights_only=True)`. Saving refuses an existing file. These are
283
+ inference artifacts, not optimizer-state resume checkpoints.
284
+
285
+ ## Development and research checkout
286
+
287
+ ```bash
288
+ uv lock --check
289
+ uv build
290
+ uv sync --group dev
291
+ uv run --group dev pytest -q tests/test_library.py
292
+ ```
293
+
294
+ `uv.lock` records the development resolution. Consumer projects resolve the
295
+ package dependency bounds in their own environment, using their own lockfile.
296
+
297
+ For the complete research checkout and test suite:
298
+
299
+ ```bash
300
+ uv sync --python 3.12 --extra experiments --extra imagenet100 --extra bbbc048 \
301
+ --extra demo --group dev --group notebooks
302
+ uv run --no-sync pytest -q
303
+ ```
304
+
305
+ Legacy `requirements*.txt` files retain the research/Colab installation paths.
306
+ Run experiment scripts from the full checkout; installing the core into another
307
+ project does not install those scripts or datasets.
308
+
309
+ Original SEANA code is MIT licensed. Dependencies, pretrained weights and the
310
+ repository's comparison methods/datasets retain their own terms. Please cite
311
+ *Breaking Noise Shortcuts in Self-Supervised Learning via Noise-Aligned View
312
+ Generation*, Oankar R. Patil and May D. Wang; the repository's `CITATION.cff`
313
+ tracks publication details.
@@ -0,0 +1,88 @@
1
+ # Third-party components
2
+
3
+ The original SEANA code is MIT licensed. The following components retain separate
4
+ terms. This repository has not yet been published.
5
+
6
+ The `seana-ssl` library wheel contains only the original SEANA core and its
7
+ practitioner API, under MIT. Comparison implementations, datasets and pretrained
8
+ weights are excluded from the library distributions. The following comparison
9
+ and dataset notices apply to their files in the full research repository.
10
+
11
+ ## LeJEPA / SIGReg
12
+
13
+ `cifar10/lejepa.py` is adapted from the project's microscopy implementation,
14
+ which identifies its Epps–Pulley and slicing routines as ports of
15
+ [galilai-group/lejepa](https://github.com/galilai-group/lejepa), by Randall
16
+ Balestriero and Yann LeCun. The upstream code is licensed under
17
+ [CC BY-NC 4.0](https://github.com/galilai-group/lejepa/blob/main/LICENSE).
18
+ The full license is retained in [third_party/lejepa-LICENSE.txt](third_party/lejepa-LICENSE.txt).
19
+ It is excluded from the MIT grant for original SEANA code.
20
+
21
+ The release adaptation runs on one process, uses two views at the selected dataset's size, and
22
+ exposes the upstream `(1-lambda)*invariance + lambda*SIGReg` weighting and the
23
+ captured project's `invariance + lambda*SIGReg` weighting as separate settings.
24
+ The upstream weighting is the default. Source reference:
25
+ [minimal example](https://github.com/galilai-group/lejepa/blob/main/MINIMAL.md).
26
+
27
+ ## Next-Scale Prediction (NSP)
28
+
29
+ `denoisers/nsp.py` is the project's adaptation of
30
+ [XLearning-SCU/2026-CVPR-NSP](https://github.com/XLearning-SCU/2026-CVPR-NSP),
31
+ by Yiwen Shan, Haiyu Zhao, Peng Hu, Xi Peng and Yuanbiao Gou.
32
+ Its DBSNl architecture also references Wu et al., *Unpaired Learning of Deep
33
+ Image Denoising* (ECCV 2020).
34
+
35
+ No license file was present in the inspected NSP repository on 2026-11-06.
36
+ This local preparation preserves the user's existing adaptation; it does not
37
+ assert redistribution permission. **Resolve upstream redistribution terms before
38
+ publishing this file.** The root MIT license does not cover it. Adaptations
39
+ include `tds=4` for 32x32 images, a shorter fit schedule, functional masked
40
+ convolution, vectorized pair sampling and averaged sub-image inference.
41
+ The new ImageNet-100 recipe uses 160x160 fitting patches and tds=5.
42
+ The BBBC048 and fetal ultrasound recipes retain 64x64 fitting patches and tds=4.
43
+
44
+ ## Neighbor2Neighbor (N2N)
45
+
46
+ `denoisers/n2n.py` preserves the project's own SmallUNet and loss. It is inspired
47
+ by Tao Huang, Songjiang Li, Xu Jia, Huchuan Lu and Jianzhuang Liu,
48
+ *Neighbor2Neighbor: Self-Supervised Denoising from Single Noisy Images* (CVPR 2021).
49
+ The [official implementation](https://github.com/TaoHuang2018/Neighbor2Neighbor)
50
+ uses a different sampling/regularization scheme. No official implementation is
51
+ vendored here; [protocol.md](docs/protocol.md) states the project's exact loss.
52
+
53
+ ## Dependencies, data and pretrained weights
54
+
55
+ PyTorch is the library's required dependency. DeepInverse is an optional extra;
56
+ torchvision, Lightly, Hugging Face Datasets and tifffile support the research workflows.
57
+ Their source code is not bundled into this repository. Model heads and standard
58
+ losses use Lightly. The SEANA experiment uses DeepInverse's pretrained DRUNet;
59
+ its weights are obtained separately and retain their applicable terms.
60
+
61
+ CIFAR-10 data are obtained through torchvision or supplied by the user and are
62
+ not distributed here. ImageNet-100 is obtained separately through
63
+ [clane9/imagenet-100](https://huggingface.co/datasets/clane9/imagenet-100), whose
64
+ dataset card provides its terms. ImageNet images are not redistributed.
65
+ BBBC048's original pre-merged images and ground-truth list are included in the
66
+ single-file [dataset archive](fluorescence_imaging_bbbc048/dataset/bbbc048_merged_v1.zip).
67
+ The [dataset page](https://bbbc.broadinstitute.org/BBBC048) identifies Andrew Filby
68
+ as the licensor under **CC BY-NC-SA 3.0**. The archive retains that license and
69
+ attribution to the Flow Cytometry Core Facility, Newcastle University, with
70
+ citations to Eulenberg et al. (2017) and Ljosa et al. (2012). See
71
+ [the dataset notice](fluorescence_imaging_bbbc048/dataset/NOTICE.md) and
72
+ [full license](fluorescence_imaging_bbbc048/dataset/LICENSE.txt).
73
+ BBBC048 images are redistributed without pixel changes; SEANA adds the manifest,
74
+ hashes and reproducible split assignments.
75
+
76
+ FETAL_PLANES_DB's prepared native-resolution regions are included as the single
77
+ [fetal dataset file](fetal_ultrasound/dataset/fetal_planes_native192_v1.zip).
78
+ [Burgos-Artizzu et al.'s dataset v1](https://doi.org/10.5281/zenodo.3904280) is
79
+ licensed under **CC BY 4.0**. The file preserves the original metadata and adds
80
+ 192x192 grayscale crops, provenance hashes and patient partitions. Cropping
81
+ matches the experiment loader without interpolation; the full source frames
82
+ are not bundled. See [attribution and modification notice](fetal_ultrasound/dataset/NOTICE.md)
83
+ and [full license](fetal_ultrasound/dataset/LICENSE.txt). The GitHub dataset file
84
+ uses Git LFS and is excluded from Python package distributions.
85
+
86
+ Experiment CSVs are the author's measurements.
87
+ The repository's MIT license does not grant rights to
88
+ datasets or pretrained weights.