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

Issue with using callback to show intermediate results in iterative solvers #1636

Closed
ozanoktem opened this issue Feb 12, 2024 · 3 comments · Fixed by #1638
Closed

Issue with using callback to show intermediate results in iterative solvers #1636

ozanoktem opened this issue Feb 12, 2024 · 3 comments · Fixed by #1638

Comments

@ozanoktem
Copy link
Contributor

Most solvers in ODL are iterative and my understanding is that setting a callback offers a way to pass instructions to such a solver that can be executed in each iterate. There now seems to be an issue with the way the callback-function in ODL interfaces with the show-function. More specifically, one can use callback to show intermediate reconstructions in each iterate by setting

callback_settings = (odl.solvers.CallbackPrintIteration() & odl.solvers.CallbackShow())

and then calling the ODL solver with callback=callback_settings. However, this yields an attribute error that allow the first iterate to be shown, but then iterates are terminated with the message

iter = 0
iter = 1
Traceback (most recent call last):

File ~/opt/anaconda3/envs/ODL/lib/python3.9/site-packages/spyder_kernels/py3compat.py:356 in compat_exec
exec(code, globals, locals)

File ~/Library/CloudStorage/Dropbox/Teaching/KTH/SF3582 Inverse Problems/Laborations/issue_callback.py:42
odl.solvers.iterative.iterative.landweber(fwd_op, reco, data, niter=10, callback=callback_settings)

File ~/odl/odl/solvers/iterative/iterative.py:120 in landweber
callback(x)

File ~/odl/odl/solvers/util/callback.py:133 in call
p(result)

File ~/odl/odl/solvers/util/callback.py:666 in call
self.fig = x.show(title, fig=self.fig,

File ~/odl/odl/discr/discr_space.py:1515 in show
return show_discrete_data(values, part, title=title, method=method,

File ~/odl/odl/util/graphics.py:439 in show_discrete_data
csub.colorbar.set_clim(minval, maxval)

AttributeError: 'Colorbar' object has no attribute 'set_clim'

Below is a small example demonstrating the issue.

import numpy as np
import odl
      
# Reconstruction space: discretized functions on the rectangle
# [-20, 20]x[-20,20] with 300 samples per dimension.
reco_space = odl.uniform_discr(
    min_pt=[-1, -1], max_pt=[1, 1], shape=[256, 256], dtype='float32')

# Create a discrete Shepp-Logan phantom (modified version)
phantom = odl.phantom.shepp_logan(reco_space, modified=True)

# Data space: Parallel-beam geometry with flat detector
# Angles: uniformly spaced, n = 1000, min = 0, max = pi
angle_partition = odl.uniform_partition(0, np.pi, 1000)
# Detector: uniformly sampled, n = 500, min = -30, max = 30
detector_partition = odl.uniform_partition(-1.5, 1.5, 300)
# Make a parallel beam geometry with flat detector
geometry = odl.tomo.Parallel2dGeometry(angle_partition, detector_partition)

# Forward operator: Ray transform 
fwd_op = odl.tomo.RayTransform(reco_space, geometry)

# Create projection data by calling the ray transform on the phantom
data = fwd_op(phantom)

# Optionally pass callback to the solver to display intermediate results
callback_settings = (odl.solvers.CallbackPrintIteration() & odl.solvers.CallbackShow())

# Built in Landweber reconstruction 
reco = fwd_op.domain.zero()
odl.solvers.iterative.iterative.landweber(fwd_op, reco, data, niter=10, callback= callback_settings)
@paulhausner
Copy link
Contributor

I think this is just a problem with odl.utils.graphics usage of matplotlib. Rescaling the colorbar seems to not work anymore but it should be possible to instead rescale the plot (https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.clim.html). See #1635

@leftaroundabout
Copy link
Contributor

leftaroundabout commented Feb 15, 2024

So, this particular issue is indeed easily fixed by

--- a/odl/util/graphics.py
+++ b/odl/util/graphics.py
@@ -436,14 +436,13 @@ def show_discrete_data(values, grid, title=None, method='',
                 plt.colorbar(mappable=csub, ticks=ticks, format=fmt)
             elif update_in_place:
                 # If it exists and we should update it
-                csub.colorbar.set_clim(minval, maxval)
+                csub.set_clim(minval, maxval)
                 csub.colorbar.set_ticks(ticks)
                 if '%' not in fmt:
                     labels = [fmt] * len(ticks)
                 else:

There is then another error a few lines down:

  File "/home/js/progwrit/python/odl/odl/util/graphics.py", line 446, in show_discrete_data
    csub.colorbar.draw_all()
AttributeError: 'Colorbar' object has no attribute 'draw_all'

Colorbar.draw_all has been deprecated since 3.6 and is gone in 3.8. The Matplotlib documentation recommends this replacement:

--- a/odl/util/graphics.py
+++ b/odl/util/graphics.py
@@ -443,7 +443,7 @@ def show_discrete_data(values, grid, title=None, method='',
                 else:
                     labels = [fmt % t for t in ticks]
                 csub.colorbar.set_ticklabels(labels)
-                csub.colorbar.draw_all()
+                fig.draw_without_rendering()
 
     # Set title of window
     if title is not None:

This does not work right though: all axis labels and also the colorbar itself vanish.

Honestly I don't see why this line is required at all: if I remove it completely the plot seems to update as desired.
plottingcallback_landweber_noexplicitredraw

Correction: actually the colorbar is not updated without the line.

@leftaroundabout
Copy link
Contributor

fig.canvas.draw_idle() seems to do the trick:

odl_plotcallback_colorbarupdate

leftaroundabout added a commit to leftaroundabout/odl that referenced this issue Feb 15, 2024
The update methods for colorbars have been removed in favour of more global updates.

Applying the change to the `clim`s simply on the `csub` was suggested by paulhausner
in odlgroup#1635 and seems to work fine.

Actually updating the drawing of the colorbar (specifically, the ticks on it) did
not work with the fix suggested in the Matplotlib documentation, but `canvas.draw_idle()`
does seem to have the intended effect.

Fixes odlgroup#1636.
JevgenijaAksjonova pushed a commit that referenced this issue Feb 22, 2024
The update methods for colorbars have been removed in favour of more global updates.

Applying the change to the `clim`s simply on the `csub` was suggested by paulhausner
in #1635 and seems to work fine.

Actually updating the drawing of the colorbar (specifically, the ticks on it) did
not work with the fix suggested in the Matplotlib documentation, but `canvas.draw_idle()`
does seem to have the intended effect.

Fixes #1636.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants