Skip to content

Commit

Permalink
Add build variants for vtk 8.1.2 and vtk 9 and fix tiff orientation i…
Browse files Browse the repository at this point in the history
…ssues (#294)

* Build variants for vtk 8.1.2 and vtk 9

* add SetOrientationType methods to tiff reader, and set default orientation type

* Add tests for setting tiff orientation types
  • Loading branch information
lauramurgatroyd authored Aug 1, 2022
1 parent a75b1a0 commit abd04b0
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 33 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
- change signature of creator, and defaults to empty list of items to be clipped on init
- renamed Get/SetInteractor->Get/SetInteractorStyle
- Renamed all classes bearing name baseReader to ReaderInterface as they provide the interface not a abstract reader class.
- Added TIFF resample and cropped readers with unit tests
- Added TIFF resample and cropped readers with unit tests.
- Add Set/GetOrientationType methods to TIFF resample and cropped readers, with default value 1: ORIENTATION_TOPLEFT (row 0 top, col 0 lhs)
- web_viewer:
- added web viewer using trame for viewer3D and viewer2D
- add entry point for running the web viewers
Expand Down Expand Up @@ -35,6 +36,9 @@
- colormaps: added get_color_transfer_function
- added examples of running the 2D and 3D viewers without Qt
- Added CILViewer base class
- Conda recipe:
- Fix windows build recipe
- Add build variants for VTK to prevent need for multiple viewer release numbers for different VTK versions

## v22.1.2
* Changes released in 22.0.2, but also requires vtk version >= 9.0.3
Expand Down
61 changes: 59 additions & 2 deletions Wrappers/Python/ccpi/viewer/utils/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ def __init__(self):
VTKPythonAlgorithmBase.__init__(self, nInputPorts=0, nOutputPorts=1)
super(cilTIFFImageReaderInterface, self).__init__()
self._CompressedData = False
# Set orientation type due to issue:
# https://github.com/vais-ral/CILViewer/issues/296
# https://gitlab.kitware.com/vtk/vtk/-/merge_requests/6155
self._OrientationType = 1

def SetFileName(self, value):
''' Set the file name or path from which to read the image data
Expand Down Expand Up @@ -1061,13 +1065,56 @@ def SetIsCompressedData(self, value):
whether the file is compressed'''
self._CompressedData = value

def SetOrientationType(self, value):
'''
Parameters
----------
value: int {1, 2, 3, 4, 5, 6, 7, 8}, default: 1
ORIENTATION_TOPLEFT 1 (row 0 top, col 0 lhs)
ORIENTATION_TOPRIGHT 2 (row 0 top, col 0 rhs)
ORIENTATION_BOTRIGHT 3 (row 0 bottom, col 0 rhs)
ORIENTATION_BOTLEFT 4 (row 0 bottom, col 0 lhs)
ORIENTATION_LEFTTOP 5 (row 0 lhs, col 0 top)
ORIENTATION_RIGHTTOP 6 (row 0 rhs, col 0 top)
ORIENTATION_RIGHTBOT 7 (row 0 rhs, col 0 bottom)
ORIENTATION_LEFTBOT 8 (row 0 lhs, col 0 bottom)
Notes
-----
Relevant issues:
https://github.com/vais-ral/CILViewer/issues/296
https://gitlab.kitware.com/vtk/vtk/-/merge_requests/6155
'''

self._OrientationType = value

def GetOrientationType(self):
''' Gets the orientation type.
Returns
----------
value: int {1, 2, 3, 4, 5, 6, 7, 8}, default: 1
ORIENTATION_TOPLEFT 1 (row 0 top, col 0 lhs)
ORIENTATION_TOPRIGHT 2 (row 0 top, col 0 rhs)
ORIENTATION_BOTRIGHT 3 (row 0 bottom, col 0 rhs)
ORIENTATION_BOTLEFT 4 (row 0 bottom, col 0 lhs)
ORIENTATION_LEFTTOP 5 (row 0 lhs, col 0 top)
ORIENTATION_RIGHTTOP 6 (row 0 rhs, col 0 top)
ORIENTATION_RIGHTBOT 7 (row 0 rhs, col 0 bottom)
ORIENTATION_LEFTBOT 8 (row 0 lhs, col 0 bottom)
'''
return self._OrientationType

def ReadDataSetInfo(self):
# this should set or do nothing
self.SetIsFortran(True)
self.SetBigEndian(False)
# get one slice size
reader = vtk.vtkTIFFReader()
reader.SetFileName(self.GetFileName()[0])
# Set orientation type due to issue:
# https://github.com/vais-ral/CILViewer/issues/296
# https://gitlab.kitware.com/vtk/vtk/-/merge_requests/6155
reader.SetOrientationType(self.GetOrientationType())
reader.Update()
dimensions = reader.GetOutput().GetDimensions()
zdim = len(self.GetFileName())
Expand Down Expand Up @@ -1232,7 +1279,7 @@ def RequestData(self, request, inInfo, outInfo):
element_spacing[1] / xy_axes_magnification,
element_spacing[2] / z_axis_magnification)
# resampled data
resampled_image = outData
resampled_image = vtk.vtkImageData()

resampled_image.SetExtent(0, target_image_shape[0] - 1, 0, target_image_shape[1] - 1, 0,
target_image_shape[2] - 1)
Expand Down Expand Up @@ -1283,8 +1330,13 @@ def RequestData(self, request, inInfo, outInfo):
# print(i, resampler.GetOutput().GetScalarComponentAsDouble(0,0,i,0))

################# vtk way ####################
resampled_image.CopyAndCastFrom(resampler.GetOutput(), extent)

data = resampler.GetOutput()
resampled_image.CopyAndCastFrom(data, extent)
self.UpdateProgress(i / num_chunks)

outData.ShallowCopy(resampled_image)

except Exception as e:
raise Exception(e)

Expand Down Expand Up @@ -1545,6 +1597,7 @@ def _GetInternalChunkReader(self):
'''returns a reader which will only read a specific chunk of the data.
This is a chunk which will get resampled into a single slice.'''
reader = vtk.vtkTIFFReader()
reader.SetOrientationType(self.GetOrientationType())
self._ChunkReader = reader
return reader

Expand Down Expand Up @@ -1881,6 +1934,10 @@ def RequestData(self, request, inInfo, outInfo):
shape = list(readshape)[::-1]

reader = vtk.vtkTIFFReader()
# Set orientation type due to issue:
# https://github.com/vais-ral/CILViewer/issues/296
# https://gitlab.kitware.com/vtk/vtk/-/merge_requests/6155
reader.SetOrientationType(self.GetOrientationType())
sa = vtk.vtkStringArray()

extent = [0, -1, 0, -1, self.GetTargetZExtent()[0], self.GetTargetZExtent()[1]]
Expand Down
2 changes: 1 addition & 1 deletion Wrappers/Python/conda-recipe/bld.bat
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

xcopy /e "%RECIPE_DIR%\.." "%SRC_DIR%"
cd %RECIPE_DIR%/..

pip install .
if errorlevel 1 exit 1
3 changes: 3 additions & 0 deletions Wrappers/Python/conda-recipe/conda_build_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vtk:
- 8.1.2
- 9.*
3 changes: 2 additions & 1 deletion Wrappers/Python/conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ test:
requirements:
build:
- python
- vtk {{ vtk }}

run:
- python
- numpy
- vtk >=9.0.3
- vtk {{ vtk }}
- pyside2
- eqt
- importlib_metadata # [py<38]
Expand Down
31 changes: 23 additions & 8 deletions Wrappers/Python/test/test_cropped_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def setUp(self):
self.input_3D_array = np.random.randint(10, size=shape, dtype=eval(f"np.uint{bits}"))
self.input_3D_array = np.reshape(np.arange(self.input_3D_array.size),
newshape=shape).astype(dtype=eval(f"np.uint{bits}"))
self.raw_type_code = str(self.input_3D_array.dtype)
bytes_3D_array = bytes(self.input_3D_array)
self.raw_filename_3D = 'test_3D_data.raw'
with open(self.raw_filename_3D, 'wb') as f:
Expand Down Expand Up @@ -60,9 +61,11 @@ def check_extent(self, reader, target_z_extent):
expected_extent[5] = target_z_extent[1]
self.assertEqual(extent, expected_extent)

def check_values(self, target_z_extent, read_cropped_image):
def check_values(self, target_z_extent, read_cropped_image, expected_array=None):
if expected_array is None:
expected_array = self.input_3D_array
read_cropped_array = Converter.vtk2numpy(read_cropped_image)
cropped_array = self.input_3D_array[target_z_extent[0]:target_z_extent[1] + 1, :, :]
cropped_array = expected_array[target_z_extent[0]:target_z_extent[1] + 1, :, :]
np.testing.assert_array_equal(cropped_array, read_cropped_array)

def test_raw_cropped_reader(self):
Expand Down Expand Up @@ -94,18 +97,30 @@ def test_meta_and_numpy_cropped_readers(self):
self.check_extent(reader, target_z_extent)
self.check_values(target_z_extent, reader.GetOutput())

def test_tiff_cropped_reader(self):
target_z_extent = [1, 3]
def _setup_tiff_cropped_reader(self, target_z_extent):
reader = cilTIFFCroppedReader()
# og_shape = np.shape(self.input_3D_array)
reader.SetFileName(self.tiff_fnames)
reader.SetTargetZExtent(tuple(target_z_extent))
raw_type_code = str(self.input_3D_array.dtype)
reader.SetTargetZExtent(target_z_extent)
return reader

def test_tiff_cropped_reader(self):
target_z_extent = [1, 3]
reader = self._setup_tiff_cropped_reader(tuple(target_z_extent))
self.check_extent(reader, target_z_extent)
# Check raw type code was set correctly:
self.assertEqual(raw_type_code, reader.GetTypeCodeName())
self.assertEqual(self.raw_type_code, reader.GetTypeCodeName())
self.check_values(target_z_extent, reader.GetOutput())

def test_tiff_cropped_reader_when_orientation_set(self):
target_z_extent = [1, 3]
reader = self._setup_tiff_cropped_reader(tuple(target_z_extent))
reader.SetOrientationType(4) # this flips the y axis
expected_array = np.flip(np.copy(self.input_3D_array), axis=1)
self.check_extent(reader, target_z_extent)
# Check raw type code was set correctly:
self.assertEqual(self.raw_type_code, reader.GetTypeCodeName())
self.check_values(target_z_extent, reader.GetOutput(), expected_array)

def tearDown(self):
files = [self.raw_filename_3D, self.numpy_filename_3D, self.meta_filename_3D] + self.tiff_fnames
for f in files:
Expand Down
89 changes: 69 additions & 20 deletions Wrappers/Python/test/test_resample_readers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import os
import unittest
import warnings

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


def calculate_target_downsample_shape(max_size, total_size, shape, acq=False):
Expand All @@ -29,6 +29,8 @@ def setUp(self):
bits = 16
self.bytes_per_element = int(bits / 8)
shape = (5, 10, 6)
self.size_to_resample_to = 100
self.size_greater_than_input_size = 10000
self.input_3D_array = np.random.randint(10, size=shape, dtype=eval(f"np.uint{bits}"))
self.input_3D_array = np.reshape(np.arange(self.input_3D_array.size),
newshape=shape).astype(dtype=eval(f"np.uint{bits}"))
Expand Down Expand Up @@ -81,16 +83,20 @@ def setUp(self):

self.tiff_fnames = fnames

def resample_reader_test1(self, reader, target_size):
def resample_reader_test1(self, reader, target_size, expected_array=None):
# Tests image with correct target size is generated by resample reader:
# Not a great test, but at least checks the resample reader runs
# without crashing
# TODO: improve this test

if expected_array is None:
expected_array = self.input_3D_array

reader.SetTargetSize(target_size)
reader.Modified()
reader.Update()
og_shape = np.shape(self.input_3D_array)
raw_type_code = str(self.input_3D_array.dtype)
og_shape = np.shape(expected_array)
raw_type_code = str(expected_array.dtype)

if target_size < self.input_3D_array.size * self.bytes_per_element:
# Check raw type code was set correctly:
Expand Down Expand Up @@ -120,18 +126,18 @@ def resample_reader_test1(self, reader, target_size):
# angle (z direction) is first index in numpy array, and in cil
# but it is the last in vtk.
resulting_z_shape = extent[5] + 1
og_z_shape = np.shape(self.input_3D_array)[0]
og_z_shape = og_shape[2]
self.assertEqual(resulting_size, expected_size)
self.assertEqual(resulting_z_shape, og_z_shape)
else:
reader.Modified()
reader.Update()
image = reader.GetOutput()
resulting_shape = image.GetDimensions()
expected_shape = np.shape(self.input_3D_array)
expected_shape = np.shape(expected_array)
self.assertEqual(resulting_shape, expected_shape[::-1])
resulting_array = Converter.vtk2numpy(image)
np.testing.assert_array_equal(np.asfortranarray(self.input_3D_array), resulting_array)
np.testing.assert_array_equal(np.asfortranarray(expected_array), resulting_array)

def test_raw_resample_reader(self):
og_shape = np.shape(self.input_3D_array)
Expand All @@ -142,32 +148,75 @@ def test_raw_resample_reader(self):
raw_type_code = str(self.input_3D_array.dtype)
reader.SetTypeCodeName(raw_type_code)
reader.SetStoredArrayShape(og_shape)
self.resample_reader_test1(reader, 100)
self.resample_reader_test1(reader, 100 * 8 * 2)
self.resample_reader_test1(reader, self.size_to_resample_to)

def test_tiff_resample_reader(self):
def test_raw_resample_reader_when_resampling_not_needed(self):
og_shape = np.shape(self.input_3D_array)
reader = cilRawResampleReader()
reader.SetFileName(self.raw_filename_3D)
reader.SetBigEndian(False)
reader.SetIsFortran(False)
raw_type_code = str(self.input_3D_array.dtype)
reader.SetTypeCodeName(raw_type_code)
reader.SetStoredArrayShape(og_shape)
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def _setup_tiff_resample_reader(self):
reader = cilTIFFResampleReader()
reader.SetFileName(self.tiff_fnames)
self.resample_reader_test1(reader, 100)
self.resample_reader_test1(reader, 100 * 8 * 2)
return reader

def test_tiff_resample_reader(self):
reader = self._setup_tiff_resample_reader()
self.resample_reader_test1(reader, self.size_to_resample_to)

def test_tiff_resample_reader_with_orientation_type_set(self):
reader = self._setup_tiff_resample_reader()
reader.SetOrientationType(4) # this flips the y axis
expected_array = np.flip(np.copy(self.input_3D_array), axis=1)
self.resample_reader_test1(reader, self.size_to_resample_to, expected_array)

def test_tiff_resample_reader_when_resampling_not_needed(self):
reader = self._setup_tiff_resample_reader()
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def test_tiff_resample_reader_with_orientation_type_set_when_resampling_not_needed(self):
reader = self._setup_tiff_resample_reader()
reader.SetOrientationType(4) # this flips the y axis
expected_array = np.flip(np.copy(self.input_3D_array), axis=1)
self.resample_reader_test1(reader, self.size_greater_than_input_size, expected_array)

def test_meta_resample_reader_mha(self):
reader = cilMetaImageResampleReader()
reader.SetFileName(self.meta_filename_3D)
self.resample_reader_test1(reader, 100)
self.resample_reader_test1(reader, 100 * 8 * 2)
self.resample_reader_test1(reader, self.size_to_resample_to)

def test_meta_resample_reader_mha_when_resampling_not_needed(self):
reader = cilMetaImageResampleReader()
reader.SetFileName(self.meta_filename_3D)
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def test_meta_resample_reader_mhd(self):
reader = cilMetaImageResampleReader()
reader.SetFileName(self.mhd_filename_3D)
self.resample_reader_test1(reader, 100)
self.resample_reader_test1(reader, 100 * 8 * 2)
self.resample_reader_test1(reader, self.size_to_resample_to)

def test_meta_resample_reader_mhd_when_resampling_not_needed(self):
reader = cilMetaImageResampleReader()
reader.SetFileName(self.mhd_filename_3D)
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def test_npy_resample_reader(self):
reader = cilNumpyResampleReader()
reader.SetFileName(self.numpy_filename_3D)
self.resample_reader_test1(reader, 100)
self.resample_reader_test1(reader, 100 * 8 * 2)
self.resample_reader_test1(reader, self.size_to_resample_to)
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def test_npy_resample_reader_when_resampling_not_needed(self):
reader = cilNumpyResampleReader()
reader.SetFileName(self.numpy_filename_3D)
self.resample_reader_test1(reader, self.size_to_resample_to)
self.resample_reader_test1(reader, self.size_greater_than_input_size)

def tearDown(self):
files = [self.raw_filename_3D, self.numpy_filename_3D, self.meta_filename_3D
Expand Down

0 comments on commit abd04b0

Please sign in to comment.