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

Viewer slider #365

Merged
merged 29 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c4f90dd
added example to add slider for slicing
paskino Jul 24, 2023
0ca3a8e
added comment
paskino Jul 24, 2023
2f5ef9e
update slider after orientation changed
paskino Jul 25, 2023
e34f054
finds the appropriate number of slices to bin on resampling
paskino Jul 28, 2023
f4f24ff
adds a horizontal slider to slice the volume
paskino Sep 8, 2023
172698f
fix limits on new image loaded
paskino Sep 8, 2023
be2839a
use instance member for slider properties
paskino Sep 8, 2023
0c145a6
allow orientation horizontal and vertical
paskino Sep 13, 2023
a4b1bb1
update to file
paskino Sep 13, 2023
b8897d5
revert unrelated changes
paskino Sep 14, 2023
f4ece42
add facility to enable/disable slider
paskino Sep 15, 2023
f8b254c
remove paskino channel (#368)
paskino Sep 27, 2023
283e9f0
Standalone viewer app defaults to downsample on Z (#362)
paskino Oct 22, 2023
b86f9f9
Automated autoyapf fixes
invalid-email-address Oct 22, 2023
b811547
Update conda_build_and_publish.yml (#371)
lauramurgatroyd Jan 8, 2024
7ac2930
Move PR template (#373)
lauramurgatroyd Jan 15, 2024
daf3117
Fix dependency versions (#369)
lauramurgatroyd Jan 17, 2024
c33ace9
Remove vtk8 variants and updates Python variants (#375)
paskino Jan 17, 2024
31bd112
Merge branch 'viewer_slider' of https://github.com/paskino/CILViewer …
DanicaSTFC Feb 19, 2024
ace5f0c
refactor to have a custom slider representation
paskino Feb 26, 2024
922a342
simplified code
paskino Feb 26, 2024
33083cb
simplified code, change colors to CIL colors
paskino Feb 27, 2024
b3acb66
add docstrings
paskino Feb 27, 2024
8a35c7d
deleted example used to develop the functionality
paskino Feb 27, 2024
77f3b02
Merge branch 'viewer_slider' of https://github.com/paskino/CILViewer …
DanicaSTFC Apr 5, 2024
4d643ac
Change bar color to gray
DanicaSTFC Apr 5, 2024
acdbe1e
Edit docstrings
DanicaSTFC Apr 5, 2024
5e7aac8
Edit changelog
DanicaSTFC Apr 5, 2024
ecad4f0
word 'Slice' removed
DanicaSTFC Apr 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/conda_build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # All history for use later with the meta.yaml file for conda-recipes
- name: publish-to-conda
uses: paskino/conda-package-publish-action@v1.4.4
uses: TomographicImaging/conda-package-publish-action@v2
with:
subDir: 'Wrappers/Python/conda-recipe'
channels: '-c conda-forge -c paskino -c ccpi'
channels: '-c conda-forge -c ccpi'
AnacondaToken: ${{ secrets.ANACONDA_TOKEN }}
publish: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }}
test_all: ${{(github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || (github.ref == 'refs/heads/master')}}
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Changelog

## vx.x.x
- Add slider widget #365
- Removed VTK 8 variants from conda recipe.
DanicaSTFC marked this conversation as resolved.
Show resolved Hide resolved
- Change Python variants: removed 3.6 and 3.7, added 3.11 and 3.12.
- Bugfix on resample reader #359. Standalone viewer app defaults to downsample in all dimensions.
- Fix bug with setInputAsNumpy() using deprecated `numpy2vtkImporter` in `CILViewer2D` - now uses `numpy2vtkImage`
- Removed the `paskino` channel from the install command as eqt is on `conda-forge`

## 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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ An example of use of the viewers in another app is the [iDVC app](https://github
To install via `conda`, create a new environment using:

```bash
conda create --name cilviewer ccpi-viewer=23.1.0 -c ccpi -c paskino -c conda-forge
conda create --name cilviewer ccpi-viewer=23.1.0 -c ccpi -c conda-forge
```

## Running the CILViewer app
Expand Down
80 changes: 78 additions & 2 deletions Wrappers/Python/ccpi/viewer/CILViewer2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ccpi.viewer.CILViewerBase import CILViewerBase
from ccpi.viewer.utils import Converter

from ccpi.viewer.widgets import cilviewerBoxWidget
from ccpi.viewer.widgets import cilviewerBoxWidget, SliceSliderRepresentation, SliderCallback


class CILInteractorStyle(vtk.vtkInteractorStyle):
Expand Down Expand Up @@ -991,7 +991,7 @@ class CILViewer2D(CILViewerBase):
IMAGE_WITH_OVERLAY = 0
RECTILINEAR_WIPE = 1

def __init__(self, dimx=600, dimy=600, ren=None, renWin=None, iren=None, debug=True):
def __init__(self, dimx=600, dimy=600, ren=None, renWin=None, iren=None, debug=False, enableSliderWidget=True):
CILViewerBase.__init__(self, dimx=dimx, dimy=dimy, ren=ren, renWin=renWin, iren=iren, debug=debug)

self.setInteractorStyle(CILInteractorStyle(self))
Expand Down Expand Up @@ -1140,6 +1140,10 @@ 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)

# Slider widget
self.sliderWidget = None
self._sliderWidgetEnabled = enableSliderWidget

self.__vis_mode = CILViewer2D.IMAGE_WITH_OVERLAY
self.setVisualisationToImageWithOverlay()

Expand All @@ -1153,6 +1157,7 @@ def setInput3DData(self, imageData):

def setInputData(self, imageData):
self.log("setInputData")
self.reset()
self.img3D = imageData
self.installPipeline()
self.axes_initialised = True
Expand Down Expand Up @@ -1310,6 +1315,9 @@ def installPipeline(self):
elif self.vis_mode == CILViewer2D.RECTILINEAR_WIPE:
self.installRectilinearWipePipeline()

if self.getSliderWidgetEnabled():
self.installSliceSliderWidgetPipeline()

self.ren.ResetCamera()
self.ren.Render()

Expand Down Expand Up @@ -1460,6 +1468,59 @@ def installRectilinearWipePipeline(self):

self.AddActor(wipeSlice, WIPE_ACTOR)

def installSliceSliderWidgetPipeline(self):
paskino marked this conversation as resolved.
Show resolved Hide resolved
'''Create the pipeline for the slice slider widget

The slider widget and representation are created if not already present.
Currently the slider widget enabled flag is not used.
'''
if self.sliderWidget is not None:
# reset the values to the appropriate ones of the new loaded image
self.sliderCallback.update_orientation(self.style, 'reset')
return

sr = SliceSliderRepresentation()
sr.SetValue(self.getActiveSlice())
sr.SetMaximumValue(self.img3D.GetDimensions()[2] - 1)
sr.SetMinimumValue(0)

sw = vtk.vtkSliderWidget()
sw.SetInteractor(self.getInteractor())
sw.SetRepresentation(sr)
sw.SetAnimationModeToAnimate()
sw.EnabledOn()

cb = SliderCallback(self, sw)

# Add interaction observers
# propagate events from the slider to the viewer
sw.AddObserver(vtk.vtkCommand.InteractionEvent, cb)

# propagate events from the viewer to the slider
self.style.AddObserver("MouseWheelForwardEvent", cb.update_from_viewer, 0.9 )
self.style.AddObserver("MouseWheelBackwardEvent", cb.update_from_viewer, 0.9 )
self.style.AddObserver("CharEvent", cb.update_orientation, 0.9 )

# reset the slider
cb.update_from_viewer(self.style, 'reset')

# save references
self.sliderWidget = sw
self.sliderCallback = cb

def uninstallSliderWidget(self):
'''remove the slider widget from the viewer'''
if self.sliderWidget is not None:
sr = self.sliderWidget.GetRepresentation()
if sr is not None:
sr.RemoveAllObservers()
coll = vtk.vtkPropCollection()
sr.GetActors(coll)
print ("coll", coll)
for actor in coll:
print ("actor", actor)
self.ren.RemoveActor(actor)

def AdjustCamera(self, resetcamera=False):
self.ren.ResetCameraClippingRange()

Expand Down Expand Up @@ -1801,3 +1862,18 @@ def uninstallPipeline2(self):
elif self.vis_mode == CILViewer2D.RECTILINEAR_WIPE:
# rectilinear wipe visualises 2 images in the same pipeline
pass

def reset(self):
self.uninstallPipeline()
if self.image2 is not None:
self.uninstallPipeline2()
if self.getSliderWidgetEnabled():
self.uninstallSliderWidget()

def getSliderWidgetEnabled(self):
return self._sliderWidgetEnabled
def setSliderWidgetEnabled(self, enable):
if enable:
self._sliderWidgetEnabled = enable


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
94 changes: 67 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,65 @@ 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 shape of the resampled image

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 +1066,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 +1899,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
1 change: 1 addition & 0 deletions Wrappers/Python/ccpi/viewer/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .box_widgets import cilviewerBoxWidget, cilviewerLineWidget
from .slider import SliderCallback, SliceSliderRepresentation
Loading
Loading