-
Notifications
You must be signed in to change notification settings - Fork 26
New Line Class with Back Project Method #1164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
baa98d7
Added backproject script and stub in the image folder
170a219
Added One-Dimension Test for Backproject
2f563d9
Stashing
0908786
Fixed Scaling Issue with BackProject and Integrated NRMSE to One Stac…
d374b81
fixed single back_project test
974b039
reorg Line to avoid circ import. Interop Image/Line classes
garrettwrong f3fe20c
adjust tests towards Line/Image interop
garrettwrong 46af4a1
passing the 20/20 test cases and added Attributes + Methods to the Li…
9af2d83
finished multidim test
286b8fe
removed unused statements
3ac2b28
initial fft changes
ac6d747
stashing gpu fixes
3d2a3a9
forward gpu
ad12081
changed backproject to run on gpu (cupy)
d5464a5
revert config
ac201e7
fixed gpu issues
a15df2f
added angles array
e942936
Pr and naming changes
5046aa0
fixup backproject tranform and tests
garrettwrong c2a3502
Changed name Line to Sinogram in dir/code/imports
garrettwrong 9cd4180
cleaned up tox syntax remarks
garrettwrong a6720de
fixed docs
ff02e76
fixed minor errors and created tests for str, repr methods
6028b7e
Back Project ~> Backproject
garrettwrong 4d86b1e
replace `_data` in test_sinagram
garrettwrong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from .sinogram import Sinogram |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| import logging | ||
|
|
||
| import numpy as np | ||
|
|
||
| import aspire.image | ||
| from aspire.nufft import anufft | ||
| from aspire.numeric import fft, xp | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Sinogram: | ||
| def __init__(self, data, dtype=None): | ||
| """ | ||
| Initialize a Sinogram Object. This is a stack of one or more line projections or sinograms. | ||
|
|
||
| The stack can be multidimensional with 'self.n' equal to the product | ||
| of the stack dimensions. Singletons will be expanded into a stack | ||
| with one entry. | ||
|
|
||
| :param data: Numpy array containing image data with shape | ||
| `(..., angles, radial points)`. | ||
| :param dtype: Optionally cast `data` to this dtype. | ||
| Defaults to `data.dtype`. | ||
|
|
||
| :return: Sinogram instance holding `data`. | ||
| """ | ||
| if dtype is None: | ||
| self.dtype = data.dtype | ||
| else: | ||
| self.dtype = np.dtype(dtype) | ||
|
|
||
| if data.ndim == 2: | ||
| data = data[np.newaxis, :, :] | ||
| if data.ndim < 3: | ||
| raise ValueError( | ||
| f"Invalid data shape: {data.shape}. Expected shape: (..., angles, radial_points), where '...' is the stack number." | ||
| ) | ||
|
|
||
| self._data = data.astype(self.dtype, copy=False) | ||
| self.ndim = self._data.ndim | ||
| self.shape = self._data.shape | ||
| self.stack_shape = self._data.shape[:-2] | ||
| self.stack_n_dim = self._data.ndim - 2 | ||
| self.n = np.product(self.stack_shape) | ||
| self.n_angles = self._data.shape[-2] | ||
| self.n_radial_points = self._data.shape[-1] | ||
|
|
||
| # Numpy interop | ||
| # https://numpy.org/devdocs/user/basics.interoperability.html#the-array-interface-protocol | ||
| self.__array_interface__ = self._data.__array_interface__ | ||
| self.__array__ = self._data | ||
|
|
||
| def _check_key_dims(self, key): | ||
| if isinstance(key, tuple) and (len(key) > self._data.ndim): | ||
| raise ValueError( | ||
| f"Sinogram stack_dim is {self.stack_n_dim}, slice length must be =< {self.n_dim}" | ||
| ) | ||
|
|
||
| def __getitem__(self, key): | ||
| self._check_key_dims(key) | ||
| return self.__class__(self._data[key]) | ||
|
|
||
| def __setitem__(self, key, value): | ||
| self._check_key_dims(key) | ||
| self._data[key] = value | ||
|
|
||
| def stack_reshape(self, *args): | ||
| """ | ||
| Reshape the stack axis. | ||
|
|
||
| :*args: Integer(s) or tuple describing the intended shape. | ||
|
|
||
| :return: Sinogram instance | ||
| """ | ||
|
|
||
| # If we're passed a tuple, use that | ||
| if len(args) == 1 and isinstance(args[0], tuple): | ||
| shape = args[0] | ||
| else: | ||
| # Otherwise use the variadic args | ||
| shape = args | ||
|
|
||
| # Sanity check the size | ||
| if shape != (-1,) and np.prod(shape) != self.n: | ||
| raise ValueError( | ||
| f"Number of sinogram images {self.n} cannot be reshaped to {shape}." | ||
| ) | ||
|
|
||
| return self.__class__(self._data.reshape(*shape, *self._data.shape[-2:])) | ||
|
|
||
| def asnumpy(self): | ||
| """ | ||
| Return image data as a (<stack>, angles, radians) | ||
| read-only array view. | ||
|
|
||
| :return: read-only ndarray view | ||
| """ | ||
|
|
||
| view = self._data.view() | ||
| view.flags.writeable = False | ||
| return view | ||
|
|
||
| def copy(self): | ||
| return self.__class__(self._data.copy()) | ||
|
|
||
| def __str__(self): | ||
| return f"Sinogram(n_images = {self.n}, n_angles = {self.n_angles}, n_radial_points = {self.n_radial_points})" | ||
|
|
||
| def __repr__(self): | ||
| msg = f"Sinogram: {self.n} images of dtype {self.dtype}, " | ||
| msg += f"arranged as a stack with shape {self.stack_shape}. " | ||
| msg += f"Each image has {self.n_angles} angles and {self.n_radial_points} radial points." | ||
| return msg | ||
|
|
||
| def backproject(self, angles): | ||
| """ | ||
| Backprojection method for a single stack of lines. | ||
|
|
||
| :param angles: np.ndarray | ||
| 1D array of angles in radians. Each entry in the array | ||
| corresponds to different angles which are used to | ||
| reconstruct the image. | ||
| :return: An Image object containing the original stack size | ||
| with a newly reconstructed numpy array of the images. | ||
| Expected return shape should be (..., n_radial_points, n_radial_points) | ||
| """ | ||
| if len(angles) != self.n_angles: | ||
| raise ValueError("Number of angles must match the number of projections.") | ||
|
|
||
| original_stack_shape = self.stack_shape | ||
| sinogram = xp.asarray(self.stack_reshape(-1)._data) | ||
| L = self.n_radial_points | ||
| sinogram = fft.ifftshift(sinogram, axes=-1) | ||
| sinogram_ft = fft.rfft(sinogram, axis=-1) | ||
| sinogram_ft *= xp.pi # Fix scale to match | ||
| sinogram_ft[..., 0] /= 2 # Fix DC | ||
| angles = xp.asarray(angles) | ||
|
|
||
| # grid generation with real points | ||
| y_idx = fft.rfftfreq(self.n_radial_points) * xp.pi * 2 | ||
| n_real_points = len(y_idx) | ||
| pts = xp.empty((2, len(angles), n_real_points), dtype=self.dtype) | ||
| pts[0] = y_idx[xp.newaxis, :] * xp.sin(angles)[:, xp.newaxis] | ||
| pts[1] = y_idx[xp.newaxis, :] * xp.cos(angles)[:, xp.newaxis] | ||
|
|
||
| imgs = anufft( | ||
| sinogram_ft.reshape(self.n, -1), | ||
| pts.reshape(2, n_real_points * len(angles)), | ||
| sz=(L, L), | ||
| real=True, | ||
| ).reshape(self.n, L, L) | ||
|
|
||
| imgs = imgs / (self.n_radial_points * len(angles)) | ||
| return aspire.image.Image(xp.asnumpy(imgs)).stack_reshape(original_stack_shape) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.