Skip to content
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

Move from 2d to 3d array operations #12

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open

Conversation

PinkShnack
Copy link

@PinkShnack PinkShnack commented Jan 7, 2025

About

We should allow stacks of 2D arrays as inputs. This will likely speed up processing of large datasets, and certainly will when we add Cupy as an FFTFilter (#10). Several things to note:

  • Allowed formats e.g. RGB, 2d, 3d (see below)
  • What should be returned to the user: currently, a 3d array with the shape (z, y, x), with z being the number of images in the stack.
    • We can provide convenience functions for users to convert between formats. I think we should limit this as a "per image" basis. In other words, the functions should handle image input, not qpretrieve classes. We could f course have some nice methods that do the conversion within the class, and return the image to the user.
    • Important to have a method that returns the original and current image formats.

To do

  • FFTFilter Base class work with 3D arrays
  • QLSI init - fix the instanciation
  • run_pipeline steps for OAH and QLSI should also work with 3D arrays.
  • Benchmark single 2d array prcessed n times against stack of n images all processed at once. Put in docs. See test test_fft_comparison_data_input_fmt
  • Image formats. The following formats should continue to work.
    • 2D images (y, x) should be converted internally to (z, y, x)
    • 3D images (image stacks) (z, y, x) will work with above
    • 2D RGB images (y, x, 3) or RGBA (y, x, 4). The data will be assumed to be greyscale, and each channel equal. The first channel will be taken and converted as in the 2D images case above.
      • A warning should be passed to the user.
    • The shape of the returned field will be 3D, and we will provide convenience functions for conversion back to the original format.
      • tests for each data format and each data_attr
  • docs
    • compare old (3d input -> 3d output) and new versions (2d input -> 3d output)
    • sphinx not collecting get_array_with_input_layout in code reference
    • show image format conversion
    • base class data argument should be documented clearly
    • show benchmark for 2d vs 3d.
    • Clearly describe what data shapes are handled internally, and what is not (yet) handled e.g. stack of rgb: (20, 128, 128, 3).
    • add type hints
  • tests:
    • data array layouts from 3d (test_data_array_layout_to_3d.py)
    • hologram fixture not inputting size argument Solution was to use "request" as hologram size param for the fixture. This is required as learned here.
      • Make PR on pytest describing this
    • compare 2d and 3d FFT algorithms, ensure consistency
  • cicd passed
  • update changelog

@PinkShnack PinkShnack added the enhancement New feature or request label Jan 7, 2025
@PinkShnack PinkShnack changed the title 9 feat br 3d arrays Move from 2d to 3d array operations Jan 14, 2025
@codecov-commenter
Copy link

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@PinkShnack PinkShnack mentioned this pull request Jan 21, 2025
@PinkShnack PinkShnack changed the base branch from main to ci_and_doc_fixes January 21, 2025 12:55
@PinkShnack PinkShnack changed the base branch from ci_and_doc_fixes to main January 21, 2025 12:55
@PinkShnack PinkShnack changed the base branch from main to cupy_gpu_fft_backend January 21, 2025 12:56
@paulmueller paulmueller changed the base branch from cupy_gpu_fft_backend to main January 21, 2025 12:56
@@ -58,6 +101,17 @@ def __init__(self, data, subtract_mean=True, padding=2, copy=True,
self._phase = None
self._amplitude = None

def get_array_with_input_layout(self, data):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this should be get_data_with_input_layout

@PinkShnack
Copy link
Author

Hey @paulmueller, when you get a chance please go through this PR. I added docs which you should look through. Main things left are "QLSI init - fix the instanciation" and updating the changelog.

Copy link
Member

@paulmueller paulmueller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a few comments. It's not yet waterproof, but it's almost there.
Thanks!

.. _sec_doc_array_layout:

Since version 0.4.0, `qpretrieve` accepts 3D (z,y,x) arrays as input.
Additionally, it **always** returns in data as the 3D array layout.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"returns in data" -> typo?

channel is taken as the image to process. In other words, it is assumed that
all channels contain the same information, so the first channel is used.

If you use the `oah.get_array_with_input_layout("phase")` method for
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use this here. Sphinx will automatically add a cross-reference to the API.

:func:`.oah.get_array_with_input_layout`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and you can describe the RGBA behavior there as well.

oah.run_pipeline()
assert oah.field.shape == (1, 256, 256) # <- now a 3D array is returned
# if you want the original array layout (2d)
field_2d = oah.get_array_with_input_layout("field")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add an assert for the field_2d case

data_3d_prep, _ = convert_data_to_3d_array_layout(data_2d)
data_3d_bg_prep, _ = convert_data_to_3d_array_layout(data_2d_bg)

for fft_interface in fft_interfaces:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that you are not taking into account FFTW wisdom (https://en.wikipedia.org/wiki/FFTW). FFTW should be much faster than numpy, because it initially tests several FFTs on the input data shape and then takes the fastest one. The wisdom is forgotten unless you store it locally on disk everytime you use pyfftw (#5).

I assume that if you added PyFFTW a second time to the fft_interfaces list, you will get faster results (because the wisdom is already there from the first run).

data = data_input[:, :, 0]
data = data[np.newaxis, :, :]
array_layout = "rgb"
warnings.warn(f"Format of input data detected as {array_layout}. "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a warning here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that some of the tests are testing that the warning is there. I think we can live without the warning. The docs are pretty clear.

@@ -29,3 +30,138 @@ def test_qlsi_phase():
assert qlsi.phase.argmax() == 242294
assert np.allclose(qlsi.phase.max(), 0.9343997734657971,
atol=0, rtol=1e-12)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a 2d-3d full pipeline comparison. I.e. test_qlsi_phase_3d where you stack h5["0"] ten times and let it run through the pipeline. The assert statements from test_qlsi_phase should be True here for every slice in the output.

holo = qpretrieve.OffAxisHologram(data)

with pytest.raises(ValueError,
match="Invalid value for `filter_size_interpretation`"):
holo.run_pipeline(filter_size_interpretation="blequency")


def test_get_field_filter_names():
data = hologram()
def test_get_field_filter_names(hologram):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a 3D test similar to this one for one filter (e.g. "disk") where the input data is a 3D stack of then holograms or so.

assert not np.array_equal(px_2d, px_3d[0])


def test_qlsi_rotate_2d_3d(hologram):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hologram fixture is technically incorrect here. Please add this information as a comment. QLSI data are different from hologram data.

assert np.array_equal(rot_2d, rot_3d_2[0])


def test_qlsi_pad_2d_3d(hologram):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hologram see above

assert np.array_equal(gradpad_2d, gradpad_3d[0])


def test_fxy_complex_mul(hologram):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hologram see above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants