Skip to content

Commit

Permalink
ENH: compare widget
Browse files Browse the repository at this point in the history
Compare two images by visualizing them side by side.

Visualization traits, e.g. the view mode, camera, etc., are linked
between the viewers. Optional trait linking can be enabled in
widget's user interface.

Closes #86

Co-authored-by: Wei Ouyang <oeway007@gmail.com>
Co-authored-by: Matt McCormick <matt.mccormick@kitware.com>
  • Loading branch information
3 people committed Jan 15, 2020
1 parent 9249ab0 commit e3cab40
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Data types:
Tasks:

- `Binder: Compare images with a checkerboard pattern <https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/master?urlpath=lab/tree/examples/Checkerboard.ipynb>`_
- `Binder: Compare images side by side <https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/master?urlpath=lab/tree/examples/CompareImages.ipynb>`_
- `Binder: Examine a line profile <https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/master?urlpath=lab/tree/examples/LineProfile.ipynb>`_
- `Binder: Interactively explore algorithm parameters <https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/master?urlpath=lab/tree/examples/InteractiveParameterExploration.ipynb>`_
- `Binder: Record a video <https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/master?urlpath=lab/tree/examples/RecordAVideo.ipynb>`_
Expand Down Expand Up @@ -259,6 +260,7 @@ After installation, try the following examples that demonstrate how to visualize
or how to:

- `Compares images with a checkerboard pattern <https://github.com/InsightSoftwareConsortium/itkwidgets/blob/master/examples/Checkerboard.ipynb>`_
- `Compares images side by side <https://github.com/InsightSoftwareConsortium/itkwidgets/blob/master/examples/CompareImages.ipynb>`_
- `Examine a line profile <https://github.com/InsightSoftwareConsortium/itkwidgets/blob/master/examples/LineProfile.ipynb>`_
- `Interatively explore algorithm parameters <https://github.com/InsightSoftwareConsortium/itkwidgets/blob/master/examples/InteractiveParameterExploration.ipynb>`_
- `Record a video <https://github.com/InsightSoftwareConsortium/itkwidgets/blob/master/examples/RecordAVideo.ipynb>`_
Expand Down
106 changes: 106 additions & 0 deletions examples/CompareImages.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Install dependencies for this example\n",
"# Note: This does not include itkwidgets, itself\n",
"import sys\n",
"!{sys.executable} -m pip install itk-io"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from urllib.request import urlretrieve\n",
"import os\n",
"\n",
"import itk\n",
"\n",
"from itkwidgets import compare, view"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Download data\n",
"file_name = 'engine.nrrd'\n",
"if not os.path.exists(file_name):\n",
" url = 'https://data.kitware.com/api/v1/file/5b843d468d777f43cc8d4f6b/download'\n",
" urlretrieve(url, file_name)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"image1 = itk.imread(file_name, itk.F)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"image2 = itk.gradient_magnitude_image_filter(image1)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "0e78848274c34179be91807943316ae9",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"AppLayout(children=(HBox(children=(Label(value='Link:'), Checkbox(value=True, description='cmap'), Checkbox(va…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"compare(image1, image2, link_cmap=True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
2 changes: 2 additions & 0 deletions itkwidgets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
__all__ = ['version_info', '__version__',
'Viewer', 'view',
'checkerboard',
'compare',
'line_profile',
'cm',
'_jupyter_nbextension_paths']

from ._version import version_info, __version__

from .widget_viewer import Viewer, view
from .widget_compare import compare
from .widget_checkerboard import checkerboard
from .widget_line_profiler import line_profile
from . import cm
Expand Down
69 changes: 69 additions & 0 deletions itkwidgets/widget_compare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import numpy as np
import ipywidgets as widgets
from itkwidgets.widget_viewer import Viewer
from traitlets import CBool
import IPython
from itkwidgets._transform_types import to_itk_image


def compare(image1, image2,
link_cmap=False, link_gradient_opacity=False,
**viewer_kwargs):
"""Compare two images by visualizing them side by side.
Visualization traits, e.g. the view mode, camera, etc., are linked
between the viewers. Optional trait linking can be enabled in widget's
user interface.
"""

viewer1 = Viewer(image=image1, **viewer_kwargs)
# Collapse the second viewer's user interface by default.
if 'ui_collapsed' not in viewer_kwargs:
viewer_kwargs['ui_collapsed'] = True
viewer2 = Viewer(image=image2, **viewer_kwargs)


widgets.jslink((viewer1, 'mode'), (viewer2, 'mode'))
widgets.jslink((viewer1, 'camera'), (viewer2, 'camera'))
widgets.jslink((viewer1, 'roi'), (viewer2, 'roi'))
widgets.jslink((viewer1, 'rotate'), (viewer2, 'rotate'))
widgets.jslink((viewer1, 'annotations'), (viewer2, 'annotations'))
widgets.jslink((viewer1, 'x_slice'), (viewer2, 'x_slice'))
widgets.jslink((viewer1, 'y_slice'), (viewer2, 'y_slice'))
widgets.jslink((viewer1, 'z_slice'), (viewer2, 'z_slice'))

link_widgets = []
link_widgets.append(widgets.Label('Link:'))

class UpdateLink(object):
def __init__(self, enable, name):
self.link = None
self.name = name
if enable:
self.link = widgets.jslink((viewer1, name), (viewer2, name))

def __call__(self, change):
if change.new:
self.link = widgets.jslink((viewer1, self.name), (viewer2, self.name))
else:
self.link.unlink()

link_cmap_widget = widgets.Checkbox(description='cmap', value=link_cmap)
update_cmap_link = UpdateLink(link_cmap, 'cmap')
link_cmap_widget.observe(update_cmap_link, 'value')
link_widgets.append(link_cmap_widget)

link_gradient_opacity_widget = widgets.Checkbox(description='gradient_opacity', value=link_gradient_opacity)
update_gradient_opacity_link = UpdateLink(link_gradient_opacity, 'gradient_opacity')
link_gradient_opacity_widget.observe(update_gradient_opacity_link, 'value')
link_widgets.append(link_gradient_opacity_widget)

link_widget = widgets.HBox(link_widgets)

widget = widgets.AppLayout(header=None,
left_sidebar=viewer1,
center=None,
right_sidebar=viewer2,
footer=link_widget,
pane_heights=[1, 6, 1])
return widget
6 changes: 4 additions & 2 deletions js/lib/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,15 +642,15 @@ const ViewerView = widgets.DOMWidgetView.extend({
}
this.model.itkVtkViewer.subscribeViewModeChanged(onViewModeChanged)

const onCameraChanged = () => {
const onCameraChanged = macro.throttle(() => {
const camera = new Float32Array(9)
const viewProxy = this.model.itkVtkViewer.getViewProxy()
camera.set(viewProxy.getCameraPosition(), 0)
camera.set(viewProxy.getCameraFocalPoint(), 3)
camera.set(viewProxy.getCameraViewUp(), 6)
this.model.set('camera', camera)
this.model.save_changes()
}
}, 50)
// If view-up has not been set, set initial value to itk-vtk-viewer default
const viewUp = this.model.get('camera').slice(6, 9)
if (!!!viewUp[0] && !!!viewUp[1] && !!!viewUp[2]) {
Expand All @@ -663,6 +663,8 @@ const ViewerView = widgets.DOMWidgetView.extend({
interactor.onEndMouseWheel(onCameraChanged)
interactor.onEndPan(onCameraChanged)
interactor.onEndPinch(onCameraChanged)
const vtkCamera = this.model.itkVtkViewer.getViewProxy().getCamera()
vtkCamera.onModified(onCameraChanged)

const onShadowToggle = (enabled) => {
if (enabled !== this.model.get('shadow')) {
Expand Down

0 comments on commit e3cab40

Please sign in to comment.