# 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: 3%{?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. # # aarch64 only (COPR 11194684, fedora-44-aarch64, python3-torch 2.9.1-10.fc44): # every test that resamples an image through torch's affine_grid/grid_sample or # a rotation fails, and nothing else does: 362 failed, 28,337 passed, against # 28,699 passed / 0 failed for the same tree on x86_64. The failures are not # tolerance drift -- errors of 0.8 to 7.3 against bounds of 0.05 to 0.65, some # NaN, and even test_rotate_90 (an exact 90-degree rotation) is wrong. They are # test_physics::test_tomography (309 of 619 cases; radon.py is affine_grid + # grid_sample) and the 2DParallelBeamCT / 2DFanBeamCT operator tests, # VirtualLinearPhysics (Inpainting composed with Rotate), # test_transform (shift / scale / rotate), test_loss::test_losses[es] # (equivariance loss, built on the same transforms), # test_physics_functional::test_gaussian_blur_non_regression (rotated # anisotropic Gaussian filters) and test_tomography_subsets. # That is the torch aarch64 kernel (see also rhbz#2431943, torch 2.9.1 aarch64 # segfaults), not deepinv, and this package is noarch: the same bytes pass the # full suite on x86_64. The affected cases are deselected on aarch64 so the # remaining 28,337 still gate the build there. # # The arch test is done by the shell, not by %%ifnarch: for a BuildArch: noarch # package rpmbuild sets the target to "noarch", so %%ifnarch x86_64 is true on # every builder. Build 11195596 (0.4.2-2) proved it: x86_64 ran 28,252 tests # instead of 28,699, the same count as aarch64. arch_deselect=() if [ "$(uname -m)" != x86_64 ]; then arch_deselect=( --deselect test_physics.py::test_tomography --deselect 'test_physics.py::test_operators_adjointness[device0-2DParallelBeamCT]' --deselect 'test_physics.py::test_operators_adjointness[device0-2DFanBeamCT]' --deselect 'test_physics.py::test_operators_adjointness[device0-VirtualLinearPhysics]' --deselect 'test_physics.py::test_multiscale_coarse_adjointness[device0-2DParallelBeamCT]' --deselect 'test_physics.py::test_multiscale_coarse_adjointness[device0-2DFanBeamCT]' --deselect 'test_physics.py::test_multiscale_coarse_adjointness[device0-VirtualLinearPhysics]' --deselect 'test_physics.py::test_operators_norm[device0-True-VirtualLinearPhysics]' --deselect 'test_physics.py::test_operators_norm[device0-False-VirtualLinearPhysics]' --deselect 'test_physics.py::test_pseudo_inverse[device0-True-VirtualLinearPhysics]' --deselect 'test_physics.py::test_pseudo_inverse[device0-False-VirtualLinearPhysics]' --deselect 'test_physics.py::test_concatenation[device0-VirtualLinearPhysics]' --deselect 'test_physics_functional.py::test_tomography_subsets[device0-Tomography]' --deselect test_physics_functional.py::test_gaussian_blur_non_regression --deselect test_transform.py::test_transform_identity --deselect test_transform.py::test_rotate_90 --deselect test_transform.py::test_batch_size --deselect 'test_loss.py::test_losses[device0-es]' ) fi 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 \ "${arch_deselect[@]}" cd - %files -n python3-%{pypi_name} -f %{pyproject_files} %doc README.rst NOTICE %changelog * Thu Oct 22 2026 Morgan Hough - 0.4.2-3 - The aarch64 deselects were applied on x86_64 too: for a noarch package rpmbuild evaluates the %%ifnarch conditional against "noarch", so build 11195596 ran 28,252 tests on both arches instead of 28,699 on x86_64. The condition is now `uname -m` in the %%check shell. * Thu Oct 22 2026 Morgan Hough - 0.4.2-2 - First build through make_srpm (COPR 11194684): the SRPM stage made the cleaned tarball in COPR's chroot and its md5 matched; f43 and f44 x86_64 passed the full suite (28,669 and 28,699 tests). - %%check: on aarch64 deselect the 362 tests that resample through torch's grid_sample / rotate (tomography, transforms, rotated Gaussian filters); they fail there with errors far outside tolerance while the identical noarch tree passes on x86_64. torch's aarch64 kernel, documented in the spec. - Not built for Fedora 45 or rawhide: their python3-torchvision 0.27.1 cannot be imported at all (Fedora's 0001-A-better-cuda-version.patch registers the _cuda_version op only under WITH_HIP, and torchvision/extension.py calls it unconditionally -> AttributeError on `import torchvision`). Nothing in this package can work around an import error in its dependency. * 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.