From f281c6e1ca4467ea1a1228e449cd0bf50e12939e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 17 May 2022 22:52:40 +0100 Subject: [PATCH 1/3] fix get_lookup_table closes #182 --- .../Python/ccpi/viewer/utils/colormaps.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Wrappers/Python/ccpi/viewer/utils/colormaps.py b/Wrappers/Python/ccpi/viewer/utils/colormaps.py index d301291b..4098adc4 100644 --- a/Wrappers/Python/ccpi/viewer/utils/colormaps.py +++ b/Wrappers/Python/ccpi/viewer/utils/colormaps.py @@ -1144,6 +1144,60 @@ def get_color_transfer_function(cmap, color_range): return tf + @staticmethod + def get_lookup_table(cmap, color_range): + + tf = vtk.vtkLookupTable() + + + if not cmap in _color_map_dict.keys(): + + try: + from matplotlib import cm + + colors = [] + for x in range(0, 255): + color = cm.get_cmap(cmap)(x) + colors.append([color[0], color[1], color[2]]) + except ImportError: + print("To use colormaps other than: ", + "{}, please install matplotlib.".format( + str(list(_color_map_dict.keys())))) + + else: + try: + colors = _color_map_dict[cmap] + except KeyError as e: + raise KeyError("Colormap: {} could not be found. \ + Installing matplotlib might resolve this.".format(e)) + + try: + from matplotlib import cm + + colors = [] + for x in range(0, 255): + color = cm.get_cmap(cmap)(x) + colors.append([color[0], color[1], color[2]]) + except ImportError: + print("To use colormaps other than: ", + "{}, please install matplotlib.".format( + str(list(_color_map_dict.keys())))) + + else: + colors = _color_map_dict[cmap] + + N = len(colors) + x = numpy.linspace(0, N, num=N) + scaling = 0.1 + opacity = CILColorMaps.get_opacity_transfer_function(x, relu, color_range[0], color_range[1], scaling) + + for i, color in enumerate(colors): + # level = color_range[0] + \ + # (color_range[1] - color_range[0]) * i / (N-1) + tf.SetTableValue(i, color[0], color[1], color[2], opacity.GetValue(i)) + + return tf + @staticmethod From 9dab1d00d868d2586d4964c59e2c518c48325dec Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 12 Jul 2022 22:21:08 +0100 Subject: [PATCH 2/3] working example of slice-by-slice viewer requires fixing for input2 and also viewer3D --- Wrappers/Python/ccpi/viewer/CILViewer2D.py | 36 +++++++++++++++--- .../Python/ccpi/viewer/utils/conversion.py | 25 +++++++++---- .../Python/examples/display_full_slice.py | 26 +++++++++++++ Wrappers/Python/notes.md | 37 +++++++++++++++++++ 4 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 Wrappers/Python/examples/display_full_slice.py create mode 100644 Wrappers/Python/notes.md diff --git a/Wrappers/Python/ccpi/viewer/CILViewer2D.py b/Wrappers/Python/ccpi/viewer/CILViewer2D.py index 998ba74e..60cf4685 100644 --- a/Wrappers/Python/ccpi/viewer/CILViewer2D.py +++ b/Wrappers/Python/ccpi/viewer/CILViewer2D.py @@ -334,7 +334,10 @@ def InitialiseBox(self, clickPosition): def OnMouseWheelForward(self, interactor, event): if self.GetInputData() is None: return - maxSlice = self.GetInputData().GetExtent()[self.GetSliceOrientation() * 2 + 1] + if self._viewer._reader is None: + maxSlice = self.GetInputData().GetExtent()[self.GetSliceOrientation() * 2 + 1] + else: + maxSlice = self._viewer._reader.GetWholeExtent()[self.GetSliceOrientation() * 2 + 1] shift = interactor.GetShiftKey() advance = 1 if shift: @@ -354,6 +357,10 @@ def OnMouseWheelBackward(self, interactor, event): if self.GetInputData() is None: return minSlice = self.GetInputData().GetExtent()[self.GetSliceOrientation() * 2] + if self._viewer._reader is None: + minSlice = self.GetInputData().GetExtent()[self.GetSliceOrientation() * 2] + else: + minSlice = self._viewer._reader.GetWholeExtent()[self.GetSliceOrientation() * 2] shift = interactor.GetShiftKey() advance = 1 if shift: @@ -1244,6 +1251,9 @@ def __init__(self, dimx=600, dimy=600, ren=None, renWin=None, iren=None, debug=T self.imageTracer.AutoCloseOn() self.imageTracer.AddObserver(vtk.vtkWidgetEvent.Select, self.style.OnTracerModifiedEvent, 1.0) + # input reader + self._reader = None + self.__vis_mode = CILViewer2D.IMAGE_WITH_OVERLAY self.setVisualisationToImageWithOverlay() @@ -1257,10 +1267,23 @@ def setInput3DData(self, imageData): def setInputData(self, imageData): self.log("setInputData") - self.img3D = imageData + self._img3D = imageData + self.installPipeline() + self.axes_initialised = True + + def setInputDataReader(self, reader): + self._reader = reader self.installPipeline() self.axes_initialised = True + @property + def img3D(self): + if self._reader is None: + return self._img3D + else: + self._reader.Update() + return self._reader.GetOutput() + def setInputData2(self, imageData): self.image2 = imageData # TODO resample on image1 @@ -1300,9 +1323,9 @@ def setInputAsNumpy(self, shiftScaler.SetShift(-iMin) shiftScaler.SetOutputScalarType(dtype) shiftScaler.Update() - self.img3D = shiftScaler.GetOutput() + self._img3D = shiftScaler.GetOutput() else: - self.img3D = importer.GetOutput() + self._img3D = importer.GetOutput() self.installPipeline() @@ -1332,6 +1355,9 @@ def updateMainVOI(self): extent = [i for i in self.img3D.GetExtent()] extent[self.sliceOrientation * 2] = self.getActiveSlice() extent[self.sliceOrientation * 2 + 1] = self.getActiveSlice() + if self._reader is not None: + self._reader.SetTargetZExtent((extent[4], extent[5])) + self._reader.Update() self.voi.SetVOI(extent[0], extent[1], extent[2], extent[3], extent[4], extent[5]) self.log("extent {0}".format(extent)) self.voi.Update() @@ -1456,8 +1482,6 @@ def installImageWithOverlayPipeline(self): self.voi.SetVOI(extent[0], extent[1], extent[2], extent[3], extent[4], extent[5]) - self.voi.SetVOI(extent[0], extent[1], extent[2], extent[3], extent[4], extent[5]) - self.voi.Update() # set window/level for current slices self.ia.SetInputData(self.voi.GetOutput()) diff --git a/Wrappers/Python/ccpi/viewer/utils/conversion.py b/Wrappers/Python/ccpi/viewer/utils/conversion.py index 3e625ffa..520077fd 100644 --- a/Wrappers/Python/ccpi/viewer/utils/conversion.py +++ b/Wrappers/Python/ccpi/viewer/utils/conversion.py @@ -22,7 +22,7 @@ import os import math import numpy -import vtk +import vtkmodules.all as vtk from vtk.util.vtkAlgorithm import VTKPythonAlgorithmBase from vtk.util import numpy_support @@ -1600,6 +1600,21 @@ def GetTargetZExtent(self): Get the target extent to crop to on the z axis. ''' return self._TargetZExtent + + def GetShape(self): + readshape = self.GetStoredArrayShape() + is_fortran = self.GetIsFortran() + + if is_fortran: + shape = list(readshape) + else: + shape = list(readshape)[::-1] + return shape + + def GetWholeExtent(self): + shape = self.GetShape() + extent = (0, shape[0] - 1, 0, shape[1] - 1, 0, shape[2] - 1) + return extent def RequestData(self, request, inInfo, outInfo): outData = vtk.vtkImageData.GetData(outInfo) @@ -1608,14 +1623,8 @@ def RequestData(self, request, inInfo, outInfo): # get basic info big_endian = self.GetBigEndian() - readshape = self.GetStoredArrayShape() file_header_length = self.GetFileHeaderLength() - is_fortran = self.GetIsFortran() - - if is_fortran: - shape = list(readshape) - else: - shape = list(readshape)[::-1] + shape = self.GetShape() tmpdir = tempfile.mkdtemp() header_filename = os.path.join(tmpdir, "header.mhd") diff --git a/Wrappers/Python/examples/display_full_slice.py b/Wrappers/Python/examples/display_full_slice.py new file mode 100644 index 00000000..b55a5bb2 --- /dev/null +++ b/Wrappers/Python/examples/display_full_slice.py @@ -0,0 +1,26 @@ +from ccpi.viewer import viewer2D +from ccpi.viewer.utils.conversion import cilTIFFCroppedReader, cilNumpyCroppedReader +import os, glob + + +data_dir = os.path.abspath('C:/Users/ofn77899/Data/dvc/') + +ftype = 'tiff' + +if ftype == 'npy': + fname = os.path.abspath('C:/Users/ofn77899/Data/dvc/frame_000_f.npy') + reader = cilNumpyCroppedReader() + reader.SetFileName(fname) +elif ftype == 'tiff': + reader = cilTIFFCroppedReader() + data_dir = os.path.abspath('C:/Users/ofn77899/Data/dvc/frame_000') + fnames = glob.glob(os.path.join(data_dir, '*.tiff')) + reader.SetFileName(fnames) + + +v = viewer2D() + +v.setInputDataReader(reader) + +v.startRenderLoop() + diff --git a/Wrappers/Python/notes.md b/Wrappers/Python/notes.md new file mode 100644 index 00000000..e5b68212 --- /dev/null +++ b/Wrappers/Python/notes.md @@ -0,0 +1,37 @@ +# Slice by slice viewer + +To be able to display a huge image data in full scale we could display one slice at a time. +This would imply that we rely on the data to not be fully loaded in the viewer, rather +the viewer gets passed a reader and the reader provides the appropriate slice on request. +This could be achieved with cropped readers. + +However, this also requires some changes in the viewer: + +1. The viewer assumes that the member `img3D` is a `vtkImageData` +2. `img3D` is then passed as input to a `vtkExtractVOI` filter to create the `vtkSlice` object +that is rendered on the scene +3. The actual slicing is triggered by mouse wheel event, which do the following: + a. `setActiveSlice` is called with the new slice number that one wants to select. This actually just stores an integer + in list of length 3. + b. `updatePipeline` is called, which in turn calls either `updateImageWithOverlayPipeline` or `updateRectilinearWipePipeline` + depending on which visualisation mode we are using. Both methods trigger `updateMainVOI` which sets the extent + for `vtkExtractVOI` to the slice that we want to show and the appropriate slice is extracted and rendered. + +## Possible way forward + +If we need to rely on a file reader to render only part of the data we need to be able to pass +a reader output port to the viewer. The viewer should be able to understand if it's passed a +`vtkImageData` or a reader, and in the latter case modify `img3D` with the slice that's requested. + +This requires to make `img3D` a property along these lines + +```python + +@property +def img3D(self): + if self._input_reader is None: + return self._img3D + else: + self._input_reader.Update() + return self._input_reader.GetOutput() +``` \ No newline at end of file From 2c6db348c471adaaebc1885ab55a6ceeaa8ad349 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 12 Jul 2022 22:42:07 +0100 Subject: [PATCH 3/3] remove notes.md as added to the PR discussion --- Wrappers/Python/notes.md | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 Wrappers/Python/notes.md diff --git a/Wrappers/Python/notes.md b/Wrappers/Python/notes.md deleted file mode 100644 index e5b68212..00000000 --- a/Wrappers/Python/notes.md +++ /dev/null @@ -1,37 +0,0 @@ -# Slice by slice viewer - -To be able to display a huge image data in full scale we could display one slice at a time. -This would imply that we rely on the data to not be fully loaded in the viewer, rather -the viewer gets passed a reader and the reader provides the appropriate slice on request. -This could be achieved with cropped readers. - -However, this also requires some changes in the viewer: - -1. The viewer assumes that the member `img3D` is a `vtkImageData` -2. `img3D` is then passed as input to a `vtkExtractVOI` filter to create the `vtkSlice` object -that is rendered on the scene -3. The actual slicing is triggered by mouse wheel event, which do the following: - a. `setActiveSlice` is called with the new slice number that one wants to select. This actually just stores an integer - in list of length 3. - b. `updatePipeline` is called, which in turn calls either `updateImageWithOverlayPipeline` or `updateRectilinearWipePipeline` - depending on which visualisation mode we are using. Both methods trigger `updateMainVOI` which sets the extent - for `vtkExtractVOI` to the slice that we want to show and the appropriate slice is extracted and rendered. - -## Possible way forward - -If we need to rely on a file reader to render only part of the data we need to be able to pass -a reader output port to the viewer. The viewer should be able to understand if it's passed a -`vtkImageData` or a reader, and in the latter case modify `img3D` with the slice that's requested. - -This requires to make `img3D` a property along these lines - -```python - -@property -def img3D(self): - if self._input_reader is None: - return self._img3D - else: - self._input_reader.Update() - return self._input_reader.GetOutput() -``` \ No newline at end of file