torch-transform-image 0.0.2__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.
- torch_transform_image-0.0.2/.copier-answers.yml +11 -0
- torch_transform_image-0.0.2/.github/ISSUE_TEMPLATE.md +15 -0
- torch_transform_image-0.0.2/.github/TEST_FAIL_TEMPLATE.md +12 -0
- torch_transform_image-0.0.2/.github/dependabot.yml +10 -0
- torch_transform_image-0.0.2/.github/workflows/ci.yml +83 -0
- torch_transform_image-0.0.2/.gitignore +111 -0
- torch_transform_image-0.0.2/LICENSE +28 -0
- torch_transform_image-0.0.2/PKG-INFO +157 -0
- torch_transform_image-0.0.2/README.md +125 -0
- torch_transform_image-0.0.2/pyproject.toml +102 -0
- torch_transform_image-0.0.2/src/torch_transform_image/__init__.py +18 -0
- torch_transform_image-0.0.2/src/torch_transform_image/py.typed +5 -0
- torch_transform_image-0.0.2/src/torch_transform_image/transforms_2d.py +36 -0
- torch_transform_image-0.0.2/src/torch_transform_image/transforms_3d.py +36 -0
- torch_transform_image-0.0.2/tests/test_torch_image_transform.py +50 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Do not edit - changes here will be overwritten by Copier
|
|
2
|
+
_commit: v1
|
|
3
|
+
_src_path: gh:pydev-guide/pyrepo-copier
|
|
4
|
+
author_email: alisterburt@gmail.com
|
|
5
|
+
author_name: Alister Burt
|
|
6
|
+
github_username: teamtomo
|
|
7
|
+
mode: simple
|
|
8
|
+
module_name: torch_transform_image
|
|
9
|
+
project_name: torch-transform-image
|
|
10
|
+
project_short_description: Real space transformations of 2D/3D images in PyTorch
|
|
11
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
* torch-transform-image version:
|
|
2
|
+
* Python version:
|
|
3
|
+
* Operating System:
|
|
4
|
+
|
|
5
|
+
### Description
|
|
6
|
+
|
|
7
|
+
Describe what you were trying to get done.
|
|
8
|
+
Tell us what happened, what went wrong, and what you expected to happen.
|
|
9
|
+
|
|
10
|
+
### What I Did
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Paste the command(s) you ran and the output.
|
|
14
|
+
If there was a crash, please include the traceback here.
|
|
15
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "{{ env.TITLE }}"
|
|
3
|
+
labels: [bug]
|
|
4
|
+
---
|
|
5
|
+
The {{ workflow }} workflow failed on {{ date | date("YYYY-MM-DD HH:mm") }} UTC
|
|
6
|
+
|
|
7
|
+
The most recent failing test was on {{ env.PLATFORM }} py{{ env.PYTHON }}
|
|
8
|
+
with commit: {{ sha }}
|
|
9
|
+
|
|
10
|
+
Full run: https://github.com/{{ repo }}/actions/runs/{{ env.RUN_ID }}
|
|
11
|
+
|
|
12
|
+
(This post will be updated if another test fails, as long as this issue remains open.)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
2
|
+
|
|
3
|
+
version: 2
|
|
4
|
+
updates:
|
|
5
|
+
- package-ecosystem: "github-actions"
|
|
6
|
+
directory: "/"
|
|
7
|
+
schedule:
|
|
8
|
+
interval: "weekly"
|
|
9
|
+
commit-message:
|
|
10
|
+
prefix: "ci(dependabot):"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
pull_request:
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# cancel in-progress runs that use the same workflow and branch
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
name: ${{ matrix.platform }} (${{ matrix.python-version }})
|
|
21
|
+
runs-on: ${{ matrix.platform }}
|
|
22
|
+
strategy:
|
|
23
|
+
fail-fast: false
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: [ "3.10", "3.11", "3.12" ] # , "3.13"]
|
|
26
|
+
platform: [ ubuntu-latest, macos-latest ] # windows-latest]
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- name: 🐍 Set up Python ${{ matrix.python-version }}
|
|
32
|
+
uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ matrix.python-version }}
|
|
35
|
+
cache-dependency-path: "pyproject.toml"
|
|
36
|
+
cache: "pip"
|
|
37
|
+
|
|
38
|
+
- name: Install Dependencies
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install -U pip
|
|
41
|
+
python -m pip install .[test]
|
|
42
|
+
|
|
43
|
+
- name: 🧪 Run Tests
|
|
44
|
+
run: pytest --color=yes --cov --cov-report=xml --cov-report=term-missing
|
|
45
|
+
|
|
46
|
+
- name: Coverage
|
|
47
|
+
uses: codecov/codecov-action@v5
|
|
48
|
+
|
|
49
|
+
deploy:
|
|
50
|
+
name: Deploy
|
|
51
|
+
needs: test
|
|
52
|
+
if: success() && startsWith(github.ref, 'refs/tags/')
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
|
|
55
|
+
permissions:
|
|
56
|
+
# IMPORTANT: this permission is mandatory for trusted publishing on PyPi
|
|
57
|
+
# see https://docs.pypi.org/trusted-publishers/
|
|
58
|
+
id-token: write
|
|
59
|
+
# This permission allows writing releases
|
|
60
|
+
contents: write
|
|
61
|
+
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
with:
|
|
65
|
+
fetch-depth: 0
|
|
66
|
+
|
|
67
|
+
- name: 🐍 Set up Python
|
|
68
|
+
uses: actions/setup-python@v5
|
|
69
|
+
with:
|
|
70
|
+
python-version: "3.x"
|
|
71
|
+
|
|
72
|
+
- name: 👷 Build
|
|
73
|
+
run: |
|
|
74
|
+
python -m pip install build
|
|
75
|
+
python -m build
|
|
76
|
+
|
|
77
|
+
- name: 🚢 Publish to PyPI
|
|
78
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
79
|
+
|
|
80
|
+
- uses: softprops/action-gh-release@v2
|
|
81
|
+
with:
|
|
82
|
+
generate_release_notes: true
|
|
83
|
+
files: './dist/*'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
|
|
60
|
+
# Flask stuff:
|
|
61
|
+
instance/
|
|
62
|
+
.webassets-cache
|
|
63
|
+
|
|
64
|
+
# Scrapy stuff:
|
|
65
|
+
.scrapy
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# PyBuilder
|
|
71
|
+
target/
|
|
72
|
+
|
|
73
|
+
# Jupyter Notebook
|
|
74
|
+
.ipynb_checkpoints
|
|
75
|
+
|
|
76
|
+
# pyenv
|
|
77
|
+
.python-version
|
|
78
|
+
|
|
79
|
+
# celery beat schedule file
|
|
80
|
+
celerybeat-schedule
|
|
81
|
+
|
|
82
|
+
# SageMath parsed files
|
|
83
|
+
*.sage.py
|
|
84
|
+
|
|
85
|
+
# dotenv
|
|
86
|
+
.env
|
|
87
|
+
|
|
88
|
+
# virtualenv
|
|
89
|
+
.venv
|
|
90
|
+
venv/
|
|
91
|
+
ENV/
|
|
92
|
+
|
|
93
|
+
# Spyder project settings
|
|
94
|
+
.spyderproject
|
|
95
|
+
.spyproject
|
|
96
|
+
|
|
97
|
+
# Rope project settings
|
|
98
|
+
.ropeproject
|
|
99
|
+
|
|
100
|
+
# mkdocs documentation
|
|
101
|
+
/site
|
|
102
|
+
|
|
103
|
+
# mypy
|
|
104
|
+
.mypy_cache/
|
|
105
|
+
|
|
106
|
+
# ruff
|
|
107
|
+
.ruff_cache/
|
|
108
|
+
|
|
109
|
+
# IDE settings
|
|
110
|
+
.vscode/
|
|
111
|
+
.idea/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Alister Burt
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch-transform-image
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Real space transformations of 2D/3D images in PyTorch
|
|
5
|
+
Project-URL: homepage, https://github.com/teamtomo/torch-transform-image
|
|
6
|
+
Project-URL: repository, https://github.com/teamtomo/torch-transform-image
|
|
7
|
+
Author-email: Alister Burt <alisterburt@gmail.com>
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: torch
|
|
21
|
+
Requires-Dist: torch-affine-utils
|
|
22
|
+
Requires-Dist: torch-grid-utils
|
|
23
|
+
Requires-Dist: torch-image-interpolation
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: ipython; extra == 'dev'
|
|
26
|
+
Requires-Dist: pdbpp; extra == 'dev'
|
|
27
|
+
Requires-Dist: rich; extra == 'dev'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# torch-transform-image
|
|
34
|
+
|
|
35
|
+
[](https://github.com/teamtomo/torch-transform-image/raw/main/LICENSE)
|
|
36
|
+
[](https://pypi.org/project/torch-transform-image)
|
|
37
|
+
[](https://python.org)
|
|
38
|
+
[](https://github.com/teamtomo/torch-transform-image/actions/workflows/ci.yml)
|
|
39
|
+
[](https://codecov.io/gh/teamtomo/torch-transform-image)
|
|
40
|
+
|
|
41
|
+
Real space transformations of 2D/3D images in PyTorch
|
|
42
|
+
|
|
43
|
+
## Motivation
|
|
44
|
+
|
|
45
|
+
This package provides a simple, consistent API for applying affine transformations to 2D/3D images in PyTorch.
|
|
46
|
+
It enables efficient, GPU-accelerated geometric transformations of images.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install torch-transform-image
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- Apply arbitrary affine transformations to 2D and 3D images
|
|
57
|
+
- Support for various interpolation methods (nearest, bilinear, bicubic for 2D; nearest, trilinear for 3D)
|
|
58
|
+
- Batched operations for efficient processing
|
|
59
|
+
- Fully differentiable operations compatible with PyTorch's autograd
|
|
60
|
+
|
|
61
|
+
## Coordinate System
|
|
62
|
+
|
|
63
|
+
This package uses the same coordinate system as NumPy/PyTorch array indexing:
|
|
64
|
+
- For 2D images: coordinates are ordered as `[y, x]` for dimensions `(height, width)`
|
|
65
|
+
- For 3D images: coordinates are ordered as `[z, y, x]` for dimensions `(depth, height, width)`
|
|
66
|
+
|
|
67
|
+
Transformation matrices left-multiply homogeneous pixel coordinates (`[y, x, 1]` for 2D and `[z, y, x, 1]` for 3D).
|
|
68
|
+
|
|
69
|
+
### Generating Transformation Matrices
|
|
70
|
+
|
|
71
|
+
The companion package [torch-affine-utils](https://github.com/teamtomo/torch-affine-utils) provides convenient functions
|
|
72
|
+
to generate transformation matrices that work with homogenous pixel coordinates (`yxw`/`zyxw`):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from torch_affine_utils.transforms_2d import R, T, S # Rotation, Translation, Scale for 2D
|
|
76
|
+
from torch_affine_utils.transforms_3d import Rx, Ry, Rz, T, S # Rotation, Translation, Scale for 3D
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### 2D Transformations
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import torch
|
|
85
|
+
from torch_transform_image import affine_transform_image_2d
|
|
86
|
+
from torch_affine_utils.transforms_2d import R, T, S # Rotation, Translation, Scale
|
|
87
|
+
|
|
88
|
+
# Create a test image (28×28)
|
|
89
|
+
image = torch.zeros((28, 28), dtype=torch.float32)
|
|
90
|
+
image[14, 14] = 1 # Place a dot at the center
|
|
91
|
+
|
|
92
|
+
# Create a transformation matrix to translate coordinates 4 pixels in y direction
|
|
93
|
+
translation = T([4, 0]) # Uses [y, x] coordinate order matching dimensions (h, w)
|
|
94
|
+
|
|
95
|
+
# Apply the transformation
|
|
96
|
+
result = affine_transform_image_2d(
|
|
97
|
+
image=image,
|
|
98
|
+
matrices=translation,
|
|
99
|
+
interpolation='bilinear', # Options: 'nearest', 'bilinear', 'bicubic'
|
|
100
|
+
yx_matrices=True, # The generated translations have [y, x] order
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Compose multiple transformations
|
|
104
|
+
# First translate to origin, then rotate, then translate back
|
|
105
|
+
T1 = T([-14, -14]) # Move center to origin
|
|
106
|
+
R1 = R(45, yx=True) # Rotate 45 degrees
|
|
107
|
+
T2 = T([14, 14]) # Move back
|
|
108
|
+
transform = T2 @ R1 @ T1 # Matrix composition (applied right-to-left)
|
|
109
|
+
|
|
110
|
+
# Apply the composed transformation
|
|
111
|
+
rotated = affine_transform_image_2d(
|
|
112
|
+
image=image,
|
|
113
|
+
matrices=transform,
|
|
114
|
+
interpolation='bicubic',
|
|
115
|
+
yx_matrices=True,
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3D Transformations
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import torch
|
|
123
|
+
from torch_transform_image import affine_transform_image_3d
|
|
124
|
+
from torch_affine_utils.transforms_3d import R, T, S # Rotation, Translation, Scale
|
|
125
|
+
|
|
126
|
+
# Create a test volume (64×64×64)
|
|
127
|
+
volume = torch.zeros((64, 64, 64), dtype=torch.float32)
|
|
128
|
+
volume[32, 32, 32] = 1 # Place a dot at the center
|
|
129
|
+
|
|
130
|
+
# Create a transformation matrix (translate coordinates 5 voxels in z direction)
|
|
131
|
+
translation = T([5, 0, 0]) # Uses [z, y, x] coordinate order matching dimensions (d, h, w)
|
|
132
|
+
|
|
133
|
+
# Apply the transformation
|
|
134
|
+
result = affine_transform_image_3d(
|
|
135
|
+
image=volume,
|
|
136
|
+
matrices=translation,
|
|
137
|
+
interpolation='trilinear', # Options: 'nearest', 'trilinear'
|
|
138
|
+
zyx_matrices=True, # The generated translations have [z, y, x] order
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## How It Works
|
|
143
|
+
|
|
144
|
+
Under the hood, the package:
|
|
145
|
+
1. Creates a coordinate grid for the input image
|
|
146
|
+
2. Applies the transformation matrix to these coordinates
|
|
147
|
+
3. Samples the original image at the transformed coordinates using the specified interpolation method
|
|
148
|
+
|
|
149
|
+
All operations are performed in PyTorch, making them fully differentiable and GPU-compatible.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
|
|
154
|
+
|
|
155
|
+
## Contributing
|
|
156
|
+
|
|
157
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# torch-transform-image
|
|
2
|
+
|
|
3
|
+
[](https://github.com/teamtomo/torch-transform-image/raw/main/LICENSE)
|
|
4
|
+
[](https://pypi.org/project/torch-transform-image)
|
|
5
|
+
[](https://python.org)
|
|
6
|
+
[](https://github.com/teamtomo/torch-transform-image/actions/workflows/ci.yml)
|
|
7
|
+
[](https://codecov.io/gh/teamtomo/torch-transform-image)
|
|
8
|
+
|
|
9
|
+
Real space transformations of 2D/3D images in PyTorch
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
This package provides a simple, consistent API for applying affine transformations to 2D/3D images in PyTorch.
|
|
14
|
+
It enables efficient, GPU-accelerated geometric transformations of images.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install torch-transform-image
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Apply arbitrary affine transformations to 2D and 3D images
|
|
25
|
+
- Support for various interpolation methods (nearest, bilinear, bicubic for 2D; nearest, trilinear for 3D)
|
|
26
|
+
- Batched operations for efficient processing
|
|
27
|
+
- Fully differentiable operations compatible with PyTorch's autograd
|
|
28
|
+
|
|
29
|
+
## Coordinate System
|
|
30
|
+
|
|
31
|
+
This package uses the same coordinate system as NumPy/PyTorch array indexing:
|
|
32
|
+
- For 2D images: coordinates are ordered as `[y, x]` for dimensions `(height, width)`
|
|
33
|
+
- For 3D images: coordinates are ordered as `[z, y, x]` for dimensions `(depth, height, width)`
|
|
34
|
+
|
|
35
|
+
Transformation matrices left-multiply homogeneous pixel coordinates (`[y, x, 1]` for 2D and `[z, y, x, 1]` for 3D).
|
|
36
|
+
|
|
37
|
+
### Generating Transformation Matrices
|
|
38
|
+
|
|
39
|
+
The companion package [torch-affine-utils](https://github.com/teamtomo/torch-affine-utils) provides convenient functions
|
|
40
|
+
to generate transformation matrices that work with homogenous pixel coordinates (`yxw`/`zyxw`):
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from torch_affine_utils.transforms_2d import R, T, S # Rotation, Translation, Scale for 2D
|
|
44
|
+
from torch_affine_utils.transforms_3d import Rx, Ry, Rz, T, S # Rotation, Translation, Scale for 3D
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### 2D Transformations
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import torch
|
|
53
|
+
from torch_transform_image import affine_transform_image_2d
|
|
54
|
+
from torch_affine_utils.transforms_2d import R, T, S # Rotation, Translation, Scale
|
|
55
|
+
|
|
56
|
+
# Create a test image (28×28)
|
|
57
|
+
image = torch.zeros((28, 28), dtype=torch.float32)
|
|
58
|
+
image[14, 14] = 1 # Place a dot at the center
|
|
59
|
+
|
|
60
|
+
# Create a transformation matrix to translate coordinates 4 pixels in y direction
|
|
61
|
+
translation = T([4, 0]) # Uses [y, x] coordinate order matching dimensions (h, w)
|
|
62
|
+
|
|
63
|
+
# Apply the transformation
|
|
64
|
+
result = affine_transform_image_2d(
|
|
65
|
+
image=image,
|
|
66
|
+
matrices=translation,
|
|
67
|
+
interpolation='bilinear', # Options: 'nearest', 'bilinear', 'bicubic'
|
|
68
|
+
yx_matrices=True, # The generated translations have [y, x] order
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Compose multiple transformations
|
|
72
|
+
# First translate to origin, then rotate, then translate back
|
|
73
|
+
T1 = T([-14, -14]) # Move center to origin
|
|
74
|
+
R1 = R(45, yx=True) # Rotate 45 degrees
|
|
75
|
+
T2 = T([14, 14]) # Move back
|
|
76
|
+
transform = T2 @ R1 @ T1 # Matrix composition (applied right-to-left)
|
|
77
|
+
|
|
78
|
+
# Apply the composed transformation
|
|
79
|
+
rotated = affine_transform_image_2d(
|
|
80
|
+
image=image,
|
|
81
|
+
matrices=transform,
|
|
82
|
+
interpolation='bicubic',
|
|
83
|
+
yx_matrices=True,
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3D Transformations
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import torch
|
|
91
|
+
from torch_transform_image import affine_transform_image_3d
|
|
92
|
+
from torch_affine_utils.transforms_3d import R, T, S # Rotation, Translation, Scale
|
|
93
|
+
|
|
94
|
+
# Create a test volume (64×64×64)
|
|
95
|
+
volume = torch.zeros((64, 64, 64), dtype=torch.float32)
|
|
96
|
+
volume[32, 32, 32] = 1 # Place a dot at the center
|
|
97
|
+
|
|
98
|
+
# Create a transformation matrix (translate coordinates 5 voxels in z direction)
|
|
99
|
+
translation = T([5, 0, 0]) # Uses [z, y, x] coordinate order matching dimensions (d, h, w)
|
|
100
|
+
|
|
101
|
+
# Apply the transformation
|
|
102
|
+
result = affine_transform_image_3d(
|
|
103
|
+
image=volume,
|
|
104
|
+
matrices=translation,
|
|
105
|
+
interpolation='trilinear', # Options: 'nearest', 'trilinear'
|
|
106
|
+
zyx_matrices=True, # The generated translations have [z, y, x] order
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## How It Works
|
|
111
|
+
|
|
112
|
+
Under the hood, the package:
|
|
113
|
+
1. Creates a coordinate grid for the input image
|
|
114
|
+
2. Applies the transformation matrix to these coordinates
|
|
115
|
+
3. Samples the original image at the transformed coordinates using the specified interpolation method
|
|
116
|
+
|
|
117
|
+
All operations are performed in PyTorch, making them fully differentiable and GPU-compatible.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# https://peps.python.org/pep-0517/
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
# https://hatch.pypa.io/latest/config/metadata/
|
|
7
|
+
[tool.hatch.version]
|
|
8
|
+
source = "vcs"
|
|
9
|
+
|
|
10
|
+
# read more about configuring hatch at:
|
|
11
|
+
# https://hatch.pypa.io/latest/config/build/
|
|
12
|
+
[tool.hatch.build.targets.wheel]
|
|
13
|
+
only-include = ["src"]
|
|
14
|
+
sources = ["src"]
|
|
15
|
+
|
|
16
|
+
# https://peps.python.org/pep-0621/
|
|
17
|
+
[project]
|
|
18
|
+
name = "torch-transform-image"
|
|
19
|
+
dynamic = ["version"]
|
|
20
|
+
description = "Real space transformations of 2D/3D images in PyTorch"
|
|
21
|
+
readme = "README.md"
|
|
22
|
+
requires-python = ">=3.9"
|
|
23
|
+
license = { text = "BSD-3-Clause" }
|
|
24
|
+
authors = [{ name = "Alister Burt", email = "alisterburt@gmail.com" }]
|
|
25
|
+
# https://pypi.org/classifiers/
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 3 - Alpha",
|
|
28
|
+
"License :: OSI Approved :: BSD License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
# add your package dependencies here
|
|
38
|
+
dependencies = [
|
|
39
|
+
"torch",
|
|
40
|
+
"torch-image-interpolation",
|
|
41
|
+
"torch-grid-utils",
|
|
42
|
+
"torch-affine-utils",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
|
|
46
|
+
# "extras" (e.g. for `pip install .[test]`)
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
# add dependencies used for testing here
|
|
49
|
+
test = ["pytest", "pytest-cov"]
|
|
50
|
+
# add anything else you like to have in your dev environment here
|
|
51
|
+
dev = [
|
|
52
|
+
"ipython",
|
|
53
|
+
"pdbpp", # https://github.com/pdbpp/pdbpp
|
|
54
|
+
"rich", # https://github.com/Textualize/rich
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
homepage = "https://github.com/teamtomo/torch-transform-image"
|
|
59
|
+
repository = "https://github.com/teamtomo/torch-transform-image"
|
|
60
|
+
|
|
61
|
+
# Entry points
|
|
62
|
+
# https://peps.python.org/pep-0621/#entry-points
|
|
63
|
+
# same as console_scripts entry point
|
|
64
|
+
# [project.scripts]
|
|
65
|
+
# torch-transform-image-cli = "torch_transform_image:main_cli"
|
|
66
|
+
|
|
67
|
+
# [project.entry-points."some.group"]
|
|
68
|
+
# tomatoes = "torch_transform_image:main_tomatoes"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# https://docs.pytest.org/
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
minversion = "7.0"
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
filterwarnings = ["error"]
|
|
77
|
+
|
|
78
|
+
# https://coverage.readthedocs.io/
|
|
79
|
+
[tool.coverage.report]
|
|
80
|
+
show_missing = true
|
|
81
|
+
exclude_lines = [
|
|
82
|
+
"pragma: no cover",
|
|
83
|
+
"if TYPE_CHECKING:",
|
|
84
|
+
"@overload",
|
|
85
|
+
"except ImportError",
|
|
86
|
+
"\\.\\.\\.",
|
|
87
|
+
"raise NotImplementedError()",
|
|
88
|
+
"pass",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[tool.coverage.run]
|
|
92
|
+
source = ["torch_transform_image"]
|
|
93
|
+
|
|
94
|
+
# https://github.com/mgedmin/check-manifest#configuration
|
|
95
|
+
# add files that you want check-manifest to explicitly ignore here
|
|
96
|
+
# (files that are in the repo but shouldn't go in the package)
|
|
97
|
+
[tool.check-manifest]
|
|
98
|
+
ignore = [
|
|
99
|
+
".pre-commit-config.yaml",
|
|
100
|
+
".ruff_cache/**/*",
|
|
101
|
+
"tests/**/*",
|
|
102
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Real space transformations of 2D/3D images in PyTorch"""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("torch-transform-image")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
__version__ = "uninstalled"
|
|
9
|
+
__author__ = "Alister Burt"
|
|
10
|
+
__email__ = "alisterburt@gmail.com"
|
|
11
|
+
|
|
12
|
+
from torch_transform_image.transforms_2d import affine_transform_image_2d
|
|
13
|
+
from torch_transform_image.transforms_3d import affine_transform_image_3d
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'affine_transform_image_2d',
|
|
17
|
+
'affine_transform_image_3d',
|
|
18
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import einops
|
|
4
|
+
import torch
|
|
5
|
+
from torch_affine_utils import homogenise_coordinates
|
|
6
|
+
from torch_grid_utils import coordinate_grid
|
|
7
|
+
from torch_image_interpolation import sample_image_2d
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def affine_transform_image_2d(
|
|
11
|
+
image: torch.Tensor,
|
|
12
|
+
matrices: torch.Tensor,
|
|
13
|
+
interpolation: Literal['nearest', 'bilinear', 'bicubic'],
|
|
14
|
+
yx_matrices: bool = False,
|
|
15
|
+
) -> torch.Tensor:
|
|
16
|
+
# grab image dimensions
|
|
17
|
+
h, w = image.shape[-2:]
|
|
18
|
+
|
|
19
|
+
if not yx_matrices:
|
|
20
|
+
matrices[..., :2, :2] = (
|
|
21
|
+
torch.flip(matrices[..., :2, :2], dims=(-2, -1))
|
|
22
|
+
)
|
|
23
|
+
matrices[..., :2, 2] = torch.flip(matrices[..., :2, 2], dims=(-1,))
|
|
24
|
+
|
|
25
|
+
# generate grid of pixel coordinates
|
|
26
|
+
grid = coordinate_grid(image_shape=(h, w), device=image.device)
|
|
27
|
+
|
|
28
|
+
# apply matrix to coordinates
|
|
29
|
+
grid = homogenise_coordinates(grid) # (h, w, yxw)
|
|
30
|
+
grid = einops.rearrange(grid, 'h w yxw -> h w yxw 1')
|
|
31
|
+
grid = matrices @ grid
|
|
32
|
+
grid = grid[..., :2, 0] # dehomogenise coordinates: (..., h, w, yxw, 1) -> (..., h, w, yx)
|
|
33
|
+
|
|
34
|
+
# sample image at transformed positions
|
|
35
|
+
result = sample_image_2d(image, coordinates=grid, interpolation=interpolation)
|
|
36
|
+
return result
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import einops
|
|
4
|
+
import torch
|
|
5
|
+
from torch_affine_utils import homogenise_coordinates
|
|
6
|
+
from torch_grid_utils import coordinate_grid
|
|
7
|
+
from torch_image_interpolation import sample_image_3d
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def affine_transform_image_3d(
|
|
11
|
+
image: torch.Tensor,
|
|
12
|
+
matrices: torch.Tensor,
|
|
13
|
+
interpolation: Literal['nearest', 'trilinear'],
|
|
14
|
+
zyx_matrices: bool = False,
|
|
15
|
+
) -> torch.Tensor:
|
|
16
|
+
# grab image dimensions
|
|
17
|
+
d, h, w = image.shape[-3:]
|
|
18
|
+
|
|
19
|
+
if not zyx_matrices:
|
|
20
|
+
matrices[..., :3, :3] = (
|
|
21
|
+
torch.flip(matrices[..., :3, :3], dims=(-2, -1))
|
|
22
|
+
)
|
|
23
|
+
matrices[..., :3, 3] = torch.flip(matrices[..., :3, 3], dims=(-1,))
|
|
24
|
+
|
|
25
|
+
# generate grid of pixel coordinates
|
|
26
|
+
grid = coordinate_grid(image_shape=(d, h, w), device=image.device)
|
|
27
|
+
|
|
28
|
+
# apply matrix to coordinates
|
|
29
|
+
grid = homogenise_coordinates(grid) # (d, h, w, zyxw)
|
|
30
|
+
grid = einops.rearrange(grid, 'd h w zyxw -> d h w zyxw 1')
|
|
31
|
+
grid = matrices @ grid
|
|
32
|
+
grid = grid[..., :3, 0] # dehomogenise coordinates: (..., d, h, w, zyxw, 1) -> (..., d, h, w, zyx)
|
|
33
|
+
|
|
34
|
+
# sample image at transformed positions
|
|
35
|
+
result = sample_image_3d(image, coordinates=grid, interpolation=interpolation)
|
|
36
|
+
return result
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch_transform_image import affine_transform_image_2d, affine_transform_image_3d
|
|
3
|
+
from torch_affine_utils.transforms_2d import T as T_2d
|
|
4
|
+
from torch_affine_utils.transforms_3d import T as T_3d
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_affine_transform_image_2d():
|
|
8
|
+
# set up test image with dot at (18, 14)
|
|
9
|
+
image = torch.zeros((28, 28), dtype=torch.float32)
|
|
10
|
+
image[18, 14] = 1
|
|
11
|
+
image = image.float()
|
|
12
|
+
|
|
13
|
+
# check that image is zero at center
|
|
14
|
+
assert image[14, 14] == 0
|
|
15
|
+
|
|
16
|
+
# define transform
|
|
17
|
+
M = T_2d([4, 0]) # move coordinates up 4 in h dim
|
|
18
|
+
|
|
19
|
+
# sample
|
|
20
|
+
result = affine_transform_image_2d(
|
|
21
|
+
image, M, interpolation='bicubic', yx_matrices=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# sanity check, array center which was 4 voxels below the dot should now be 1
|
|
25
|
+
assert result.shape == image.shape
|
|
26
|
+
assert result[14, 14] == 1
|
|
27
|
+
assert result[18, 14] == 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_affine_transform_image_3d():
|
|
31
|
+
# set up test image with dot at (18, 14)
|
|
32
|
+
image = torch.zeros((28, 28, 28), dtype=torch.float32)
|
|
33
|
+
image[18, 14, 14] = 1
|
|
34
|
+
image = image.float()
|
|
35
|
+
|
|
36
|
+
# check that image is zero at center
|
|
37
|
+
assert image[14, 14, 14] == 0
|
|
38
|
+
|
|
39
|
+
# define transform
|
|
40
|
+
M = T_3d([4, 0, 0]) # move coordinates up 4 in d dim
|
|
41
|
+
|
|
42
|
+
# sample
|
|
43
|
+
result = affine_transform_image_3d(
|
|
44
|
+
image, M, interpolation='trilinear', zyx_matrices=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# sanity check, array center which was 4 voxels below the dot should now be 1
|
|
48
|
+
assert result.shape == image.shape
|
|
49
|
+
assert result[14, 14, 14] == 1
|
|
50
|
+
assert result[18, 14, 14] == 0
|