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

Standalone viewer app defaults to downsample on Z #362

Merged
merged 15 commits into from
Oct 22, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Changelog
## vx.x.x
- Standalone app defaults to downsample in all dimensions.

## v23.1.0
- Raise error if try to add multiple widgets with the same name to CILViewer or CILViewer2D.
Expand Down
2 changes: 1 addition & 1 deletion Wrappers/Python/ccpi/viewer/ui/main_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def setViewersInput(self, image, viewers, input_num=1, image_name=None):
raw_image_attrs = self.raw_attrs
hdf5_image_attrs = self.hdf5_attrs
dataset_name = hdf5_image_attrs.get('dataset_name')
resample_z = hdf5_image_attrs.get('resample_z')
resample_z = hdf5_image_attrs.get('resample_z', True)
target_size = self.getTargetImageSize()
if isinstance(image, str) or isinstance(image, list):
image_reader = ImageReader(file_name=image)
Expand Down
92 changes: 65 additions & 27 deletions Wrappers/Python/ccpi/viewer/utils/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,63 @@ def ReadDataSetInfo(self):


# ---------------------- RESAMPLE READERS -------------------------------------------------------------
def calculate_target_downsample_magnification(max_size, total_size, acq=False):
'''calculate the magnification of each axis and the number of slices per chunk

Parameters
----------
max_size: int
target size of the image in number of pixels
total_size: int
actual size of the image in number of pixels
acq: bool, default: False
whether the data is acquisiton data or not

Returns
-------
slice_per_chunk: int
number of slices per chunk
xy_axes_magnification: float
magnification of the xy axes
'''
if not acq:
# scaling is going to be similar in every axis
# (xy the same, z possibly different)
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
slice_per_chunk = int(np.round(1 / xy_axes_magnification))
else:
# If we have acquisition data we don't want to resample in the z
# direction because then we would be averaging projections together,
# so we have one slice per chunk
slice_per_chunk = 1
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)

return (slice_per_chunk, xy_axes_magnification)

def calculate_target_downsample_shape(max_size, total_size, shape, acq=False):
'''calculate the magnification of each axis and the number of slices per chunk

Parameters
----------
max_size: int
target size of the image in number of pixels
total_size: int
actual size of the image in number of pixels
acq: bool, default: False
whether the data is acquisition data or not

Returns
-------
target_image_shape: tuple
shape of the resampled image
'''
slice_per_chunk, xy_axes_magnification = \
calculate_target_downsample_magnification(max_size, total_size, acq)
num_chunks = 1 + len([i for i in range(slice_per_chunk, shape[2], slice_per_chunk)])

target_image_shape = (int(xy_axes_magnification * shape[0]), int(xy_axes_magnification * shape[1]), num_chunks)
return target_image_shape

class cilBaseResampleReader(cilReaderInterface):
'''vtkAlgorithm to load and resample a file to an approximate memory footprint.
This BaseClass provides the methods needed to resample a file, if the filename
Expand Down Expand Up @@ -1007,20 +1064,10 @@ def RequestData(self, request, inInfo, outInfo):
outData.ShallowCopy(reader.GetOutput())

else:

# scaling is going to be similar in every axis
# (xy the same, z possibly different)
if not self.GetIsAcquisitionData():
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
num_slices_per_chunk = int(
1 / xy_axes_magnification) # number of slices in the z direction we are resampling together.
else:
# If we have acquisition data we don't want to resample in the z
# direction because then we would be averaging projections together,
# so we have one slice per chunk
num_slices_per_chunk = 1 # number of slices in the z direction we are resampling together.
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)

num_slices_per_chunk, xy_axes_magnification = \
calculate_target_downsample_magnification(max_size,
total_size,
self.GetIsAcquisitionData())
# Each chunk will be the z slices that we will resample together to form one new slice.
# Each chunk will contain num_slices_per_chunk number of slices.
self._SetNumSlicesPerChunk(num_slices_per_chunk)
Expand Down Expand Up @@ -1850,19 +1897,10 @@ def RequestData(self, request, inInfo, outInfo):
outData.ShallowCopy(inData)

else:

# scaling is going to be similar in every axis
# (xy the same, z possibly different)
if not self.GetIsAcquisitionData():
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
num_slices_per_chunk = int(
1 / xy_axes_magnification) # number of slices in the z direction we are resampling together.
else:
# If we have acquisition data we don't want to resample in the z
# direction because then we would be averaging projections together,
# so we have one slice per chunk
num_slices_per_chunk = 1 # number of slices in the z direction we are resampling together.
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)
num_slices_per_chunk, xy_axes_magnification = \
calculate_target_downsample_magnification(max_size,
total_size,
self.GetIsAcquisitionData())

# Each chunk will be the z slices that we will resample together to form one new slice.
# Each chunk will contain num_slices_per_chunk number of slices.
Expand Down
5 changes: 2 additions & 3 deletions Wrappers/Python/conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ build:
test:
requires:
- pillow
- pytest
source_files:
- ./Wrappers/Python/test

commands:
- ls # [not win]
- python -c "import os; print ('TESTING IN THIS DIRECTORY' , os.getcwd())" # [not win]
- python -m unittest discover -s Wrappers/Python/test -v # [not win]
- python -m pytest Wrappers/Python/test # [not win]

requirements:
build:
Expand Down
15 changes: 1 addition & 14 deletions Wrappers/Python/test/test_hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,10 @@
import h5py
import numpy as np
import vtk
from ccpi.viewer.utils.conversion import Converter, cilHDF5ResampleReader, cilHDF5CroppedReader
from ccpi.viewer.utils.conversion import *
from ccpi.viewer.utils.hdf5_io import (HDF5Reader, HDF5SubsetReader, write_image_data_to_hdf5)


def calculate_target_downsample_shape(max_size, total_size, shape, acq=False):
if not acq:
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
slice_per_chunk = int(1 / xy_axes_magnification)
else:
slice_per_chunk = 1
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)
num_chunks = 1 + len([i for i in range(slice_per_chunk, shape[2], slice_per_chunk)])

target_image_shape = (int(xy_axes_magnification * shape[0]), int(xy_axes_magnification * shape[1]), num_chunks)
return target_image_shape


class TestHDF5IO(unittest.TestCase):

def setUp(self):
Expand Down
21 changes: 4 additions & 17 deletions Wrappers/Python/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,9 @@
import h5py
import numpy as np
import vtk
from ccpi.viewer.utils.conversion import Converter
from ccpi.viewer.utils.conversion import *
paskino marked this conversation as resolved.
Show resolved Hide resolved
from ccpi.viewer.utils.io import ImageReader, cilviewerHDF5Writer, cilviewerHDF5Reader


def calculate_target_downsample_shape(max_size, total_size, shape, acq=False):
if not acq:
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
slice_per_chunk = int(1 / xy_axes_magnification)
else:
slice_per_chunk = 1
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)
num_chunks = 1 + len([i for i in range(slice_per_chunk, shape[2], slice_per_chunk)])

target_image_shape = (int(xy_axes_magnification * shape[0]), int(xy_axes_magnification * shape[1]), num_chunks)
return target_image_shape


class TestImageReaderAndWriter(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -85,7 +71,7 @@ def _test_read_full_size_data(self, reader):
def _test_resampling_not_acq_data(self, reader, target_size):
image = reader.Read()
extent = image.GetExtent()
resulting_shape = (extent[1] + 1, (extent[3] + 1), (extent[5] + 1))
resulting_shape = tuple(image.GetDimensions())
og_shape = np.shape(self.input_3D_array)
og_shape = (og_shape[2], og_shape[1], og_shape[0])
og_size = og_shape[0] * og_shape[1] * og_shape[2] * self.bytes_per_element
Expand Down Expand Up @@ -307,7 +293,8 @@ def test_write_read_hdf5(self):
self.assertEqual(value, read_original_image_attrs[key])

def tearDown(self):
files = [self.hdf5_filename_3D] + self.tiff_fnames
files = [self.hdf5_filename_3D, self.numpy_filename_3D,
self.mha_filename_3D, self.raw_filename_3D] + self.tiff_fnames
for f in files:
os.remove(f)
os.rmdir('tiff_files')
Expand Down
15 changes: 1 addition & 14 deletions Wrappers/Python/test/test_resample_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,9 @@

import numpy as np
import vtk
from ccpi.viewer.utils.conversion import (Converter, cilMetaImageResampleReader, cilNumpyMETAImageWriter,
cilNumpyResampleReader, cilRawResampleReader, cilTIFFResampleReader)
from ccpi.viewer.utils.conversion import *


def calculate_target_downsample_shape(max_size, total_size, shape, acq=False):
if not acq:
xy_axes_magnification = np.power(max_size / total_size, 1 / 3)
slice_per_chunk = int(1 / xy_axes_magnification)
else:
slice_per_chunk = 1
xy_axes_magnification = np.power(max_size / total_size, 1 / 2)
num_chunks = 1 + len([i for i in range(slice_per_chunk, shape[2], slice_per_chunk)])

target_image_shape = (int(xy_axes_magnification * shape[0]), int(xy_axes_magnification * shape[1]), num_chunks)
return target_image_shape


class TestResampleReaders(unittest.TestCase):

Expand Down
53 changes: 39 additions & 14 deletions Wrappers/Python/test/test_viewer_main_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,55 @@ class TestViewerMainWindow(unittest.TestCase):
'''

def setUp(self):
pass

def tearDown(self) -> None:
pass

def test_all(self):
global _instance
if _instance is None:
_instance = QApplication(sys.argv)

def test_init(self):
# https://stackoverflow.com/questions/5387299/python-unittest-testcase-execution-order
# https://stackoverflow.com/questions/11145583/unit-and-functional-testing-a-pyside-based-application
self._test_init()
self._test_init_calls_super_init()
self._test_createAppSettingsDialog_calls_setAppSettingsDialogWidgets()
self._test_acceptViewerSettings_when_gpu_checked()
self._test_acceptViewerSettings_when_gpu_unchecked()
self._test_setDefaultDownsampledSize()
self._test_getDefaultDownsampledSize()
self._test_getTargetImageSize_when_vis_size_is_None()
self._test_getTargetImageSize_when_vis_size_is_not_None()
self._test_updateViewerCoords_with_display_unsampled_coords_selected()
self._test_updateViewerCoords_with_display_downsampled_coords_selected()
self._test_updateViewerCoords_with_3D_viewer()
self._test_updateViewerCoords_with_no_img3D()

del _instance


def _test_init(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
assert vmw is not None

def test_init_calls_super_init(self):
def _test_init_calls_super_init(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
# If the super init is called, then the following attributes should be set:
assert vmw.app_name == "testing app name"
assert vmw.threadpool is not None
assert vmw.progress_windows == {}

def test_createAppSettingsDialog_calls_setAppSettingsDialogWidgets(self):
def _test_createAppSettingsDialog_calls_setAppSettingsDialogWidgets(self):
paskino marked this conversation as resolved.
Show resolved Hide resolved
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
vmw.setAppSettingsDialogWidgets = mock.MagicMock()
vmw.setViewerSettingsDialogWidgets = mock.MagicMock()
vmw.createAppSettingsDialog()
vmw.setAppSettingsDialogWidgets.assert_called_once()
vmw.setViewerSettingsDialogWidgets.assert_called_once()

def test_acceptViewerSettings_when_gpu_checked(self):
def _test_acceptViewerSettings_when_gpu_checked(self):

vmw = self._setup_acceptViewerSettings_tests()

Expand All @@ -66,7 +91,7 @@ def test_acceptViewerSettings_when_gpu_checked(self):

assert isinstance(vmw.viewers[0].volume_mapper, vtk.vtkSmartVolumeMapper)

def test_acceptViewerSettings_when_gpu_unchecked(self):
def _test_acceptViewerSettings_when_gpu_unchecked(self):

vmw, settings_dialog = self._setup_acceptViewerSettings_tests()

Expand Down Expand Up @@ -100,7 +125,7 @@ def _setup_acceptViewerSettings_tests(self):
vmw._vs_dialog = settings_dialog
return vmw

def test_acceptViewerSettings_when_gpu_unchecked(self):
def _test_acceptViewerSettings_when_gpu_unchecked(self):
vmw = self._setup_acceptViewerSettings_tests()
vmw._vs_dialog.widgets['gpu_checkbox_field'].isChecked.return_value = False

Expand All @@ -111,19 +136,19 @@ def test_acceptViewerSettings_when_gpu_unchecked(self):

assert isinstance(vmw.viewers[0].volume_mapper, vtk.vtkFixedPointVolumeRayCastMapper)

def test_setDefaultDownsampledSize(self):
def _test_setDefaultDownsampledSize(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
vmw.setDefaultDownsampledSize(5)
assert vmw.default_downsampled_size == 5

def test_getDefaultDownsampledSize(self):
def _test_getDefaultDownsampledSize(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
# Test what the default value is:
assert vmw.getDefaultDownsampledSize() == 512**3
vmw.default_downsampled_size = 5
assert vmw.getDefaultDownsampledSize() == 5

def test_getTargetImageSize_when_vis_size_is_None(self):
def _test_getTargetImageSize_when_vis_size_is_None(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
vmw.settings.setValue("vis_size", None)
vmw.getDefaultDownsampledSize = mock.MagicMock()
Expand All @@ -132,15 +157,15 @@ def test_getTargetImageSize_when_vis_size_is_None(self):
vmw.getDefaultDownsampledSize.assert_called_once()
assert (returned_target_size == 512**3)

def test_getTargetImageSize_when_vis_size_is_not_None(self):
def _test_getTargetImageSize_when_vis_size_is_not_None(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
vmw.settings.setValue("vis_size", 5)
vmw.getDefaultDownsampledSize = mock.MagicMock()
returned_target_size = vmw.getTargetImageSize()
vmw.getDefaultDownsampledSize.assert_not_called()
assert (returned_target_size == 5 * (1024**3))

def test_updateViewerCoords_with_display_unsampled_coords_selected(self):
def _test_updateViewerCoords_with_display_unsampled_coords_selected(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
viewer2D = CILViewer2D()
viewer2D.visualisation_downsampling = [2, 2, 2]
Expand All @@ -166,7 +191,7 @@ def test_updateViewerCoords_with_display_unsampled_coords_selected(self):
viewer_coords_widgets['coords_warning_field'].setVisible.assert_called_with(False)
viewer2D.updatePipeline.assert_called_once()

def test_updateViewerCoords_with_display_downsampled_coords_selected(self):
def _test_updateViewerCoords_with_display_downsampled_coords_selected(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
viewer2D = CILViewer2D()
viewer2D.visualisation_downsampling = [2, 2, 2]
Expand All @@ -185,7 +210,7 @@ def test_updateViewerCoords_with_display_downsampled_coords_selected(self):
viewer_coords_widgets['coords_warning_field'].setVisible.assert_called_once_with(False)
viewer2D.updatePipeline.assert_called()

def test_updateViewerCoords_with_3D_viewer(self):
def _test_updateViewerCoords_with_3D_viewer(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
viewer3D = CILViewer()
viewer3D.visualisation_downsampling = [2, 2, 2]
Expand All @@ -202,7 +227,7 @@ def test_updateViewerCoords_with_3D_viewer(self):
viewer_coords_widgets['coords_warning_field'].setVisible.assert_not_called()
viewer3D.updatePipeline.assert_not_called()

def test_updateViewerCoords_with_no_img3D(self):
def _test_updateViewerCoords_with_no_img3D(self):
vmw = ViewerMainWindow(title="Testing Title", app_name="testing app name")
viewer2D = CILViewer2D()
viewer2D.visualisation_downsampling = [2, 2, 2]
Expand Down
Loading