# Synchronizing the camera view of multiple Image3D panels within a Manipulate interface

I'm trying to synchronize the camera view of multiple Image3D panels displayed within a Manipulate interface.

A random 3D image will do for demonstration purposes.

theimg = Image3D[RandomReal[1, {20, 30, 40}], ImageSize -> Medium]


Here is the code I have which attempts to synchronize the camera views of two Image3D panels. The code is based upon the StackExchange answers Extract current viewing parameters from a 3D view and Synchronizing manual rotations for multiple Graphics3D outputs.

Options[imgcompare] = {SphericalRegion -> True};
imgcompare[img1_, img2_, opts : OptionsPattern[{imgcompare, Image3D}]] :=
DynamicModule[{vv = {0.0, 0.0, 1.0}, vp = {1.3, -2.4, 2.0}},
Grid[
{{Image3D[img1, opts, Sequence @@ Options[imgcompare],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv]],
Image3D[img2, opts, Sequence @@ Options[imgcompare],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv]]}}]]


The code works fine outside of a Manipulate statement as demonstrated by the code below. You can manually rotate one of the 3D images and the other one rotates in synch.

imgcompare[theimg, Binarize[Erosion[theimg, 1]]]


However, synchronization doesn't work if you try to call the function within a Manipulate statement.

Manipulate[imgcompare[theimg, Binarize[Erosion[theimg, n]]],
{{n, 1, "Erosion"}, 1, 4, 1, SetterBar}]


Initially, the Manipulate window gives an error. In the message window, the errors given are:

Viewpoint \$CellContextvp$13957 is not a triple of numbers or a recognized symbolic form.

and

The ViewVertical vector is too short to establish the up direction.

If you inspect the value of the variable vp\$13957 (the number part of the variable name changes for each execution of the code), you can see it is undefined (the output just repeats the symbol name). I'm baffled as to how this variable could be undefined as I explicitly initialize vp and vv in the DynamicModule.

Despite the error, if I ignore the error and try to manually rotate one of the images, it seems to work at first and the panels are synchronized.

However, if I click the SetterBar to change the value, the error returns.

If I again ignore the error and manually rotate one of the panels, the error goes away again, but synchronization is broken. The two panels rotate independently.

Why does the camera view synchronization fail when the imgcompare function is called within a Manipulate expression?

• Minimal example of your problem can be observed with: Dynamic@DynamicModule[{vp = {2.0, 2.0, 1.}}, Image3D[RandomReal[1, {2, 2, 2}], ViewPoint -> Dynamic[vp]]]. – Kuba Jan 13 '15 at 19:12
• And since this works: Dynamic@DynamicModule[{vp = {2.0, 2.0, 1.}}, Plot3D[Sin[x y], {x, 0, 1}, {y, 0, 1}, ViewPoint -> Dynamic[vp]]] I don't know what to say... – Kuba Jan 13 '15 at 19:12

I have not fully diagnosed the problem, but it appears as if vv and vp are not initialized when you effectively wrap a Dyanmic module within a dynamic module (which is what you've done with the Manipulate.

Changing your DynamicModule into the Manipulate seems to get the result you desire if you Initialize vv and vp:

Clear[vv, vp]
Manipulate[
With[{img1 = theimg, img2 = Binarize[Erosion[theimg, n]]},
Grid[{{Image3D[img1, Sequence @@ Options[imgcompare],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv]],
Image3D[img2, Sequence @@ Options[imgcompare],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv]]}}]], {{n,
1, "Erosion"}, 1, 4, 1, SetterBar},
Initialization -> (vv = {0.0, 0.0, 1.0}; vp = {1.3, -2.4, 2.0};)]


Here's another workaround: Use Show to wrap the Image3D with Graphics3D. This changes how the graphics are processed by the Front End (I suspect). Evidently the FE is grabbing the graphics parameters such as the view point, and dealing with it. I don't know enough to give a complete explanation. Certain things are optimized, such as using the GPU efficiently, and the FE always has had to control the View* properties.

There are two things that complicate things: how Image3D graphics are handled, and the nested DynamicModules (Manipulate creates a DynamicModule to hold the variables and code). Kuba's comments shows something different happening with Image3D and Graphics3D; bobthechemist's answer shows that having everything in one module works, too. The workaround I'm proposing takes advantage of Kuba's observation.

The changes to imgcompare are to use Show and to add the view options to the Show. This puts them with the Graphics3D, and for whatever reason, the Front End plays nicely with them. (It's not clear to me how the OP would want to handle the options here, so I just left the code alone. Users should adapt it to their own requirements)

Options[imgcompare] = {SphericalRegion -> True};
imgcompare[img1_, img2_, opts : OptionsPattern[{imgcompare, Image3D}]] :=
DynamicModule[{vv = {0.0, 0.0, 1.0}, vp = {1.3, -2.4, 2.0}},
Grid[{{
Show[Image3D[img1, opts, Sequence @@ Options[imgcompare]],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv],
Boxed -> False, SphericalRegion -> True],
Show[Image3D[img2, opts, Sequence @@ Options[imgcompare]],
ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv],
Boxed -> False, SphericalRegion -> True]}}]]


Notes on the difficulties

Some observations. It might be a bug with how Image3D is handled.

1. In dynamic settings such as ViewPoint -> Dynamic[vp], the viewpoint is set to the code Dynamic[vp], which is in itself not a list of numbers or a symbol like Front. The dynamic updating system (FE/Kernel) is supposed to replace this with the value of vp when the graphics are typeset/displayed. Is the value of vp is not being substituted? Or is the variable is uninitialized? Sticking a Print in shows that it seems to be initialized.

2. Adding a row {ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv]}, to the Grid in the OP's imgcompare shows that vp in the image(s) and in the row are not updated together. The row is never updated. However they are updated together with the Show workaround above.

3. Changing n causes a new DynamicModule to be instantiated (by a call to imgcompare). This should reset the view points. It does in the above workaround, but...

4. In the OP's version: Rotating just one of the images seems to cause the FE to take control of the view point for that image and keep control of it. If n is changed, the other's view point is reset, but the view point of the rotated one stays the same. The view points are now unlinked and unsynchronized. Presumably, they are now vps from different DynamicModules. If both images are rotated before changing n, then the view points will remain synchronized whenever n` is changed.

I cannot see a way to blame this behavior on an error in the OP's code. It is probably worth reporting the problem to Wolfram support to see what they say. If it is a bug, they will appreciate hearing about it. (And if they point out an error, maybe Matt could answer his own question.)