Skip to content

Commit

Permalink
Merge pull request #22 from mrphys/develop
Browse files Browse the repository at this point in the history
Release 0.17.0
  • Loading branch information
jmontalt authored Apr 22, 2022
2 parents ad9b5dd + 7911dd1 commit bc61299
Show file tree
Hide file tree
Showing 27 changed files with 1,653 additions and 225 deletions.
3 changes: 3 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ ARG PYBIN=/usr/local/bin/python
ARG PYVERSIONS="3.7 3.8 3.9 3.10"
RUN for PYVER in ${PYVERSIONS}; do ${PYBIN}${PYVER} -m pip install pycairo PyGObject; done

# For Jupyter notebooks.
RUN for PYVER in ${PYVERSIONS}; do ${PYBIN}${PYVER} -m pip install ipykernel; done

# Install TFMRI dependencies.
COPY requirements.txt /tmp/requirements.txt
RUN for PYVER in ${PYVERSIONS}; do ${PYBIN}${PYVER} -m pip install -r /tmp/requirements.txt; done
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: docs
path: ./tools/docs/_build/html
path: ./tools/docs/_build/dirhtml

- name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
*.pyc
*.so

.ipynb_checkpoints/
__pycache__/
artifacts/
third_party/spiral_waveform
tools/docs/_build
tools/docs/_templates
tools/docs/api_docs
tools/docs/tfmri
tools/docs/*.rst
tools/docs/index.rst
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ wheel: $(TARGET)
docs: $(TARGET)
rm -rf tools/docs/_build
rm -rf tools/docs/_templates
rm -rf tools/docs/tfmri
rm -rf tools/docs/api_docs
$(PYTHON) tools/docs/generate_templates.py
$(PYTHON) tools/docs/generate_documents.py
$(MAKE) -C tools/docs html PY_VERSION=$(PY_VERSION)
$(MAKE) -C tools/docs dirhtml PY_VERSION=$(PY_VERSION)

test: $(wildcard tensorflow_mri/python/ops/*.py)
$(PYTHON) -m unittest discover -v -p *_test.py
Expand Down
88 changes: 49 additions & 39 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,7 @@

.. start-intro
TensorFlow MRI is a library of TensorFlow operators for computational MRI which
includes:

* A fast, native non-uniform fast Fourier transform (NUFFT) operator (see
also `TensorFlow NUFFT <https://github.com/mrphys/tensorflow-nufft>`_).
* MR image reconstruction functions, which support parallel imaging, compressed
sensing, machine learning and partial Fourier methods.
* Common linear and nonlinear operators, such as Fourier operators and
regularizers, to aid in the development of image reconstruction techniques.
* Multicoil imaging operators, such as coil combination, coil compression and
estimation of coil sensitivity maps.
* Calculation of non-Cartesian k-space trajectories and sampling density
estimation.
* A collection of Keras objects including models, layers, metrics, loss
functions and callbacks for rapid development of neural networks.
* Many other differentiable operators for common tasks such as array
manipulation and image/signal processing.

TensorFlow MRI is a library of TensorFlow operators for computational MRI.
The library has a Python interface and is mostly written in Python. However,
computations are efficiently performed by the TensorFlow backend (implemented in
C++/CUDA), which brings together the ease of use and fast prototyping of Python
Expand All @@ -51,7 +34,49 @@ Whether an application involves ML or not, TensorFlow MRI operators can take
full advantage of the TensorFlow framework, with capabilities including
automatic differentiation, multi-device support (CPUs and GPUs), automatic
device placement and copying of tensor data, and conversion to fast,
serializable graphs.
serializable graphs.

TensorFlow MRI contains operators for:

* Multicoil arrays
(`tfmri.coils <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/coils>`_):
coil combination, coil compression and estimation of coil sensitivity
maps.
* Convex optimization
(`tfmri.convex <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/convex>`_):
convex functions (quadratic, L1, L2, Tikhonov, total variation, etc.) and
optimizers (ADMM).
* Keras layers
(`tfmri.layers <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/layers>`_):
layers and building blocks for neural networks.
* Linear algebra
(`tfmri.linalg <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/linalg>`_):
linear operators specialized for image processing and MRI.
* Loss functions
(`tfmri.losses <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/losses>`_):
for classification, segmentation and image restoration.
* Metrics
(`tfmri.metrics <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/metrics>`_):
for classification, segmentation and image restoration.
* Image processing
(`tfmri.image <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/image>`_):
filtering, gradients, phantoms, image quality assessment, etc.
* Image reconstruction
(`tfmri.recon <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/recon>`_):
Cartesian/non-Cartesian, 2D/3D, parallel imaging, compressed sensing.
* *k*-space sampling
(`tfmri.sampling <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/sampling>`_):
Cartesian masks, non-Cartesian trajectories, sampling density compensation,
etc.
* Signal processing
(`tfmri.signal <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/signal>`_):
N-dimensional FFT, non-uniform FFT
(see also `TensorFlow NUFFT <https://github.com/mrphys/tensorflow-nufft>`_),
*k*-space filtering, etc.
* Unconstrained optimization
(`tfmri.optimize <https://mrphys.github.io/tensorflow-mri/api_docs/tfmri/optimize>`_):
gradient descent, L-BFGS.
* Supporting array manipulation and math tasks.

.. end-intro
Expand All @@ -75,9 +100,12 @@ Each TensorFlow MRI release is compiled against a specific version of
TensorFlow. To ensure compatibility, it is recommended to install matching
versions of TensorFlow and TensorFlow MRI according to the table below.

.. start-compatibility-table
====================== ======================== ============
TensorFlow MRI Version TensorFlow Compatibility Release Date
====================== ======================== ============
v0.17.0 v2.8.x Apr 22, 2022
v0.16.0 v2.8.x Apr 13, 2022
v0.15.0 v2.8.x Apr 1, 2022
v0.14.0 v2.8.x Mar 29, 2022
Expand All @@ -95,6 +123,8 @@ v0.5.0 v2.6.x Aug 29, 2021
v0.4.0 v2.6.x Aug 18, 2021
====================== ======================== ============

.. end-compatibility-table
.. end-install
Documentation
Expand All @@ -118,23 +148,3 @@ Citation

If you find this software useful in your work, please
`cite us <https://doi.org/10.5281/zenodo.5151590>`_.

FAQ
---

.. start-faq
**When trying to install TensorFlow MRI, I get an error about OpenEXR which
includes:
``OpenEXR.cpp:36:10: fatal error: ImathBox.h: No such file or directory``. What
do I do?**

OpenEXR is needed by TensorFlow Graphics, which is a dependency of TensorFlow
MRI. This issue can be fixed by installing the OpenEXR library. On
Debian/Ubuntu:

.. code-block:: console
$ apt install libopenexr-dev
.. end-faq
70 changes: 6 additions & 64 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,76 +1,18 @@
Release 0.16.0
Release 0.17.0
==============

Breaking Changes
----------------

* ``tfmri.convex``:
* ``tfmri.signal``:

* Renamed keyword argument ``traj`` of ``filter_kspace`` and ``crop_kspace``
to ``trajectory``.

* Several of the inputs and outputs of ``admm_minimize`` have been renamed
to improve clarity and to make the interface more consistent with the
``tfmri.optimize`` module.

Major Features and Improvements
-------------------------------

* ``tfmri.convex``:

* ``admm_minimize`` now supports batches of inputs.

* ``admm_minimize`` has two new arguments ``f_prox_kwargs`` and
``g_prox_kwargs`` that allow passing additional arguments to the proximal
operators of ``f`` and ``g``.

* ``admm_minimize`` has a new argument ``name`` that allows specifying
the name of the operation.

* Method ``_prox`` of ``ConvexFunctionQuadratic`` has a new argument
``solver_kwargs`` that allows passing additional arguments to the
internal solver.

* New properties ``shape`` and ``batch_shape`` for ``ConvexFunction`` and
its subclasses. These allow retrieval of static shape information.

* New methods ``ndim_tensor``, ``shape_tensor`` and ``batch_shape_tensor``
for ``ConvexFunction`` and its subclasses. These allow retrieval of the
dynamic shape information.

* ``tfmri.linalg``:

* New argument ``bypass_gradient`` for ``conjugate_gradient``.

* ``tfmri.recon``:

* ``lstsq`` has a new argument ``return_optimizer_state`` which allows the
user to retrieve the internal optimizer state.

* ``tfmri.optimize``:

* New function ``gradient_descent`` implementing the gradient descent method
for minimization of differentiable functions.

* ``tfmri.plot``:

* New argument ``dpi`` for functions ``image_sequence`` and
``tiled_image_sequence``.
* New function ``close``, alias of ``matplotlib.pyplot.close``.


Bug Fixes and Other Changes
---------------------------

* ``tfmri.convex``:

* Fixed a bug in method ``prox`` of ``ConvexFunctionNorm``,
``ConvexFunctionL2Squared`` and ``ConvexFunctionQuadratic`` that caused
errors when running in graph mode.

* ``tfmri.linalg``:

* Fixed a bug in internal linear algebra framework that would cause errors
when running in graph mode.

* ``tfmri.plot``:

* Removed lazy import mechanism which was causing problems when using
``matplotlib`` outside TFMRI. For now we use regular imports.
* New utility ``tiled_image``.
2 changes: 1 addition & 1 deletion tensorflow_mri/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
__summary__ = "A collection of TensorFlow add-ons for computational MRI."
__uri__ = "https://github.com/mrphys/tensorflow-mri"

__version__ = "0.16.0"
__version__ = "0.17.0"

__author__ = "Javier Montalt Tordera"
__email__ = "javier.montalt@outlook.com"
Expand Down
32 changes: 17 additions & 15 deletions tensorflow_mri/python/ops/signal_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def atanfilt(arg, cutoff=np.pi, beta=100.0, name=None):

@api_util.export("signal.filter_kspace")
def filter_kspace(kspace,
traj=None,
trajectory=None,
filter_fn='hamming',
filter_rank=None,
filter_kwargs=None):
Expand All @@ -111,8 +111,9 @@ def filter_kspace(kspace,
Args:
kspace: A `Tensor` of any shape. The input *k*-space.
traj: A `Tensor` of shape `kspace.shape + [N]`, where `N` is the number of
spatial dimensions. If `None`, `kspace` is assumed to be Cartesian.
trajectory: A `Tensor` of shape `kspace.shape + [N]`, where `N` is the
number of spatial dimensions. If `None`, `kspace` is assumed to be
Cartesian.
filter_fn: A `str` (one of `'hamming'`, `'hann'` or `'atanfilt'`) or a
callable that accepts a coordinate array and returns corresponding filter
values.
Expand All @@ -125,16 +126,17 @@ def filter_kspace(kspace,
A `Tensor` of shape `kspace.shape`. The filtered *k*-space.
"""
kspace = tf.convert_to_tensor(kspace)
if traj is not None:
kspace, traj = check_util.verify_compatible_trajectory(kspace, traj)
if trajectory is not None:
kspace, trajectory = check_util.verify_compatible_trajectory(
kspace, trajectory)

# Make a "trajectory" for Cartesian k-spaces.
is_cartesian = traj is None
is_cartesian = trajectory is None
if is_cartesian:
filter_rank = filter_rank or kspace.shape.rank
vecs = [tf.linspace(-np.pi, np.pi - (2.0 * np.pi / s), s)
for s in kspace.shape[-filter_rank:]] # pylint: disable=invalid-unary-operand-type
traj = array_ops.meshgrid(*vecs)
trajectory = array_ops.meshgrid(*vecs)

if not callable(filter_fn):
# filter_fn not a callable, so should be an enum value. Get the
Expand All @@ -149,37 +151,37 @@ def filter_kspace(kspace,
}[filter_fn]
filter_kwargs = filter_kwargs or {}

traj_norm = tf.norm(traj, axis=-1)
traj_norm = tf.norm(trajectory, axis=-1)
return kspace * tf.cast(filter_fn(traj_norm, **filter_kwargs), kspace.dtype)


@api_util.export("signal.crop_kspace")
def crop_kspace(kspace, traj=None, cutoff=None, mode='low_pass'): # pylint: disable=missing-raises-doc
def crop_kspace(kspace, trajectory=None, cutoff=None, mode='low_pass'): # pylint: disable=missing-raises-doc
"""Crop *k*-space.
Crops all frequencies above or below the specified frequency.
Args:
kspace: A `Tensor` of any shape. The input *k*-space.
traj: A `Tensor` of shape `kspace.shape + [N]`, where `N` is the number of
spatial dimensions.
trajectory: A `Tensor` of shape `kspace.shape + [N]`, where `N` is the
number of spatial dimensions.
cutoff: A `float` between `-pi` and `pi`. The cutoff frequency.
mode: A `str`. Must be one of `low_pass` or `high_pass`.
Returns:
A `Tensor`. The cropped *k*-space.
"""
# TODO: add support for Cartesian *k*-space.
if traj is None:
raise ValueError('`traj` must be specified.')
if trajectory is None:
raise ValueError('`trajectory` must be specified.')
mode = check_util.validate_enum(mode, {'low_pass', 'high_pass'}, 'mode')
if cutoff is None:
cutoff = 0.0 if mode == 'high_pass' else np.pi
traj_norm = tf.norm(traj, axis=-1)
traj_norm = tf.norm(trajectory, axis=-1)
if mode == 'low_pass':
mask = traj_norm < cutoff
elif mode == 'high_pass':
mask = traj_norm > cutoff
filt_kspace = tf.gather_nd(kspace, tf.where(mask))
filt_traj = tf.gather_nd(traj, tf.where(mask))
filt_traj = tf.gather_nd(trajectory, tf.where(mask))
return filt_kspace, filt_traj
8 changes: 4 additions & 4 deletions tensorflow_mri/python/ops/signal_ops_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ def test_crop(self):
kspace = [1. + 2.j, 2. + 2.j, 3. - 4.j]
traj = [[0.4, 1.0], [3.0, 2.0], [0, 0.5]]

res_kspace, res_traj = signal_ops.crop_kspace(kspace, traj=traj,
res_kspace, res_traj = signal_ops.crop_kspace(kspace, trajectory=traj,
cutoff=np.pi / 2,
mode='low_pass')
self.assertAllClose(res_kspace, [1. + 2.j, 3. - 4.j])
self.assertAllClose(res_traj, [[0.4, 1.0], [0, 0.5]])

res_kspace, res_traj = signal_ops.crop_kspace(kspace, traj=traj,
res_kspace, res_traj = signal_ops.crop_kspace(kspace, trajectory=traj,
cutoff=np.pi / 2,
mode='high_pass')
self.assertAllClose(res_kspace, [2. + 2.j])
Expand All @@ -97,7 +97,7 @@ def test_filter_noncart(self, filter_fn): # pylint: disable=missing-param-doc

expected = kspace * tf.cast(filt_fn[filter_fn](radius), tf.complex64)
result = signal_ops.filter_kspace(
kspace, traj=traj, filter_fn=filter_fn)
kspace, trajectory=traj, filter_fn=filter_fn)
self.assertAllClose(expected, result)

def test_filter_cart(self):
Expand Down Expand Up @@ -140,7 +140,7 @@ def test_filter_custom_fn(self):
expected = [2. + 4.j, 4. + 4.j, 0. + 0.j]

result = signal_ops.filter_kspace(
kspace, traj=traj, filter_fn=filter_fn)
kspace, trajectory=traj, filter_fn=filter_fn)
self.assertAllClose(expected, result)

if __name__ == '__main__':
Expand Down
Loading

0 comments on commit bc61299

Please sign in to comment.