# DeepInverse -- a PyTorch library for imaging inverse problems: forward # operators (MRI, CT, deblurring, inpainting, super-resolution, ...), noise # models, classical and learned reconstruction (unrolled networks, plug-and-play, # diffusion and other generative priors), losses including self-supervised ones, # datasets and a trainer. # # WHY IT IS HERE: AI image reconstruction is the step BEFORE the segmentation # stack this COPR already carries (MONAI, nnU-Net, MITK, Slicer), and it reads # what we ship: raw k-space via our ismrmrd, NIfTI/DICOM via Fedora's nibabel # and pydicom, and has a napari viewer (napari is in this COPR). DeepInverse # ran a hands-on reconstruction tutorial at MICCAI 2026 (27 Sep, Strasbourg). # python-lightning-utilities -> python-torchmetrics -> python-deepinv # # Class 1 under docs/PACKAGING-POLICY.md: nothing in Fedora provides # python3dist(deepinv). Verified 2026-10-11: # dnf repoquery --whatprovides 'python3dist(deepinv)' -> empty # Every required runtime dependency is in Fedora 44 proper except torchmetrics. %global pypi_name deepinv Name: python-%{pypi_name} Version: 0.4.2 Release: 1%{?dist} Summary: PyTorch library for solving imaging inverse problems with deep learning # All of the shipped code is BSD-3-Clause. The one file under another licence, # PromptIR (non-commercial "Academic Public License"), is removed from the # source tarball itself -- see Source0. License: BSD-3-Clause URL: https://deepinv.org/ # NOT the PyPI sdist. deepinv-clean.sh (in this repo) removes # deepinv/models/third_party/promptir.py, PromptIR adapted from # github.com/va1shn9v/PromptIR, whose terms are non-commercial only. The SRPM is # published, so it has to be absent from the source, not merely from the RPM. # The script's output is reproducible (sorted, fixed owner and mtime, gzip -n): # two runs give the same md5. # # Produced at SRPM time, not seeded: the package is registered in COPR with the # make_srpm method, so .copr/Makefile -> ci/make-srpm.sh runs deepinv-clean.sh # for %%version and refuses to continue unless the result's md5 is %%cleanmd5. # Nothing is uploaded or put into the lookaside by hand (PACKAGING-POLICY ยง5; # invesalius and fluorender still carry the older seeded-once procedure). COPR # imports every built SRPM into its dist-git, so after the first build this # URL serves the same tarball, and the /md5/ segment pins which one. %global copr_lookaside https://copr-dist-git.fedorainfracloud.org/repo/pkgs/mhough/neurofedora/%{name} %global cleanmd5 28d11eab7898264fd00f60ded193bee8 Source0: %{copr_lookaside}/%{pypi_name}-%{version}-clean.tar.gz/md5/%{cleanmd5}/%{pypi_name}-%{version}-clean.tar.gz # Upstream conftest turns DownloadError failures into skips but sets # report.longrepr to a plain string. pytest asserts a (path, lineno, message) # tuple for skipped reports in both the -rs summary and the junitxml writer, so # offline (as in a build root) the first such skip became an INTERNALERROR that # aborted the session (seen in both: _folded_skips and append_skipped). Not # Fedora-specific; for upstream. Patch0: python-deepinv-conftest-skip-longrepr-tuple.patch BuildArch: noarch BuildRequires: python3-devel # %%check BuildRequires: python3dist(pytest) BuildRequires: python3dist(pytest-xdist) # Optional backends with tests in the upstream suite (importorskip or direct # use); with them installed those tests run instead of skipping. BuildRequires: python3dist(ptwt) BuildRequires: python3dist(torchkbnufft) # sigpy needs numba, which neither Fedora 43 nor this COPR has for f43. %if 0%{?fedora} >= 44 BuildRequires: python3dist(sigpy) %endif %global _description %{expand: DeepInverse is a PyTorch library for imaging inverse problems with deep learning. It provides forward operators for MRI (Cartesian and multi-coil), computed tomography, deblurring, inpainting, super-resolution, compressed sensing and more; noise models; classical variational solvers; learned reconstructions including unrolled networks, plug-and-play and diffusion priors; supervised and self-supervised losses; dataset loaders; and a training loop. Pretrained weights are downloaded at first use and are not part of this package.} %description %_description %package -n python3-%{pypi_name} Summary: %{summary} # deepinv imports PIL directly at module scope (utils/plotting.py, utils/demo.py, # transform/projective.py) but does not declare pillow; torchvision pulls it in # today, which is an accident rather than a contract. Requires: python3dist(pillow) # Optional backends. Every one is imported inside a function, never at module # scope (checked over deepinv/ excluding tests), so none is needed to import or # to use the core operators. Recommends only where Fedora or this COPR has the # package; the pip-only ones (kornia, mri-nufft, astra-toolbox, diffusers, # parallelproj) are absent and listing them would be dead text. # scipy, pywt classical solvers # ptwt WaveletDenoiser/WaveletDictDenoiser priors, wavelet noise # estimator (this COPR) # sigpy MultiCoilMRI ESPIRiT and birdcage coil maps (this COPR) # torchkbnufft RadioInterferometry operator (this COPR) # nibabel, pydicom load_nifti / load_dicom in deepinv.utils.io Recommends: python3-scipy Recommends: python3-pywt Recommends: python3-ptwt Recommends: python3-sigpy Recommends: python3-torchkbnufft Recommends: python3-nibabel Recommends: python3-pydicom # brainweb-dl deepinv.datasets.BrainWebMRI (downloads at use; this COPR) Suggests: python3-brainweb-dl # napari deepinv.utils.plot_napari (this COPR) # timm, transformers backbones for some learned models (this COPR) Suggests: python3-napari Suggests: python3-timm Suggests: python3-transformers %description -n python3-%{pypi_name} %_description %prep %autosetup -p1 -n %{pypi_name}-%{version} # The cleaned tarball has no promptir.py; drop the two imports of it so # `import deepinv` works. Each sed is followed by an assertion, because a sed # that matches nothing is silent. test ! -e deepinv/models/third_party/promptir.py sed -i '/^from deepinv\.models\.third_party\.promptir import PromptIR$/d' \ deepinv/models/third_party/__init__.py sed -i '/^from \.third_party import PromptIR$/d' deepinv/models/__init__.py ! grep -rn --include='*.py' -E 'import PromptIR' deepinv # ...and from the tests: the "promptir" entry of the denoiser parametrisation and # the PromptIR row of the pretrained-denoiser table (line ~1363). sed -i '/^ "promptir",$/d' deepinv/tests/test_models.py sed -i '/dinv\.models\.PromptIR(pretrained="download")/d' deepinv/tests/test_models.py ! grep -n -E '"promptir",$|models\.PromptIR\(pretrained' deepinv/tests/test_models.py # Upstream's NOTICE (shipped as %%doc) describes PromptIR as a non-commercial # component of the package. Here it is absent, so say that instead: a notice # claiming a non-commercial component that is not there misleads both ways. sed -i '/^1\. PromptIR$/,/^ Contact for commercial licensing:/c\ 1. PromptIR -- NOT INCLUDED in this package.\ Upstream ships deepinv/models/third_party/promptir.py under a\ non-commercial licence. It is removed from the source tarball this package\ is built from (deepinv-clean.sh in the neurofedora repository), so\ deepinv.models.PromptIR does not exist here. Everything in this package is\ BSD-3-Clause.' NOTICE grep -q 'NOT INCLUDED in this package' NOTICE ! grep -qi -E 'Non-Commercial Use Only|commercial licensing|requires separate licensing' NOTICE # Package discovery is `find = { where = ["."] }` with no include filter, and # setuptools' namespace discovery then picks up the sdist's examples/ and docs/ # trees as top-level packages: the first local build installed # site-packages/examples/unfolded/demo_DEQ.py and friends (rpmbuild failed on # them as unpackaged files). A top-level "examples" package in site-packages # would shadow anyone else's. Limit discovery to deepinv itself. sed -i 's/^find = { where = \["\."\] }$/find = { where = ["."], include = ["deepinv*"] }/' pyproject.toml grep -qxF 'find = { where = ["."], include = ["deepinv*"] }' pyproject.toml %generate_buildrequires %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files -l %{pypi_name} %check # The package ships its own test suite as deepinv.tests; it imports pytest and # the test-only `dummy` helper, so it is left out of the import check. %pyproject_check_import -e 'deepinv.tests*' # Upstream's suite against the installed tree, offline. The suite ships inside # the package as deepinv.tests; several tests write into the working directory, # so they run from a writable copy, importing deepinv from the buildroot. # # --dist loadfile: test_physics_state_dict (57 cases) shares ./cache_test_physics # across cases and tears it down, so with the default per-test distribution # xdist workers delete each other's directory (FileNotFoundError, "Parent # directory ./cache_test_physics does not exist"). One file per worker: 57/57. # # The build root has no network. Downloads raise deepinv.utils.DownloadError, # which the (patched, Patch0) conftest turns into skips. The exclusions below # are the cases where that cannot happen, each with the failure it produced in # a local f44 mock (2026-10-11, 29,980 tests): # # test_distributed.py Fedora's python3-torch is built without Gloo: # torch.distributed.is_gloo_available() is False, and # every multi-process CPU test dies in # init_process_group with "Distributed package # doesn't have Gloo built in" (43 tests). deepinv's # single-process code paths are unaffected. # test_datasets.py the dataset_output_context tests wrap the download # (6 loaders + generate) in pytest.warns(DeprecationWarning), so the # DownloadError surfaces as "DID NOT WARN"; # Kohler and FMD use urllib/requests directly and # raise URLError/ConnectionError, not DownloadError. # test_sampling::test_sde the pretrained denoiser download fails inside a # try whose finally does `del denoiser`, so the # DownloadError is replaced by UnboundLocalError. # test_utils::test_load_dataset set3c is downloaded; offline the folder is # empty and indexing it raises IndexError. # test_models::test_anscombe_transform statistical (std of GAT(y) and # IGAT(GAT(y)) ~ y within 10%) on noise drawn from # torch's unseeded global RNG; the one run where it # executed rather than skipping on its image download # missed by ~0.13. cp -a %{buildroot}%{python3_sitelib}/deepinv/tests %{_builddir}/deepinv-tests cd %{_builddir}/deepinv-tests # PYTHONDONTWRITEBYTECODE: test_datasets and test_physics are also imported as # deepinv.tests.* from the buildroot, and pytest's assertion rewriter then # writes *-pytest-*.pyc there (rpmbuild failed on them as unpackaged files). OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 MPLBACKEND=Agg HOME=%{_builddir}/home \ PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=%{buildroot}%{python3_sitelib} \ %{python3} -m pytest -c /dev/null --rootdir=. -p no:cacheprovider \ -n %{_smp_build_ncpus} --dist loadfile -m 'not slow' -q -rfE \ --ignore=test_distributed.py \ --deselect test_datasets.py::test_load_div2k_dataset \ --deselect test_datasets.py::test_load_urban100_dataset \ --deselect test_datasets.py::test_load_set14_dataset \ --deselect test_datasets.py::test_load_Flickr2kHR_dataset \ --deselect test_datasets.py::test_load_lsdir_dataset \ --deselect test_datasets.py::test_load_fmd_dataset \ --deselect test_datasets.py::test_load_Kohler_dataset \ --deselect test_datasets.py::test_generate_dataset \ --deselect test_sampling.py::test_sde \ --deselect test_utils.py::test_load_dataset \ --deselect test_models.py::test_anscombe_transform cd - %files -n python3-%{pypi_name} -f %{pyproject_files} %doc README.rst NOTICE %changelog * Sun Oct 11 2026 Morgan Hough - 0.4.2-1 - Initial package. - Source is repacked by deepinv-clean.sh without PromptIR (non-commercial licence); the two imports of it and its two test entries are removed in %%prep. - Built with COPR's make_srpm method (.copr/Makefile, ci/make-srpm.sh): the cleaned tarball is produced at SRPM time from the script in git and checked against its md5, so nothing is uploaded or seeded by hand. - NOTICE: the PromptIR section is replaced by a statement that the component is absent from this package. - Recommends ptwt, sigpy and torchkbnufft, and builds against them so their tests run (packaged in this COPR 2026-10-12). - Package discovery limited to deepinv*: upstream's bare find() also installed top-level examples/ and docs/ packages into site-packages. - Patch0: upstream conftest set a string longrepr on DownloadError skips, which crashes pytest's -rs summary and junitxml writer at the first offline skip. - %%check runs upstream's suite offline from a writable copy with --dist loadfile: 28,699 passed, 1,037 skipped (downloads), 0 failed in a local f44 mock. Excluded with reasons in the spec: test_distributed.py (Fedora's torch has no Gloo) and eleven tests that hide their download error offline. - Upstream notes: docs/upstream/deepinv-packaging-and-offline-tests.md.