Skip to content

Commit a0d7add

Browse files
authored
Merge pull request #324 from JuliaImages/teh/return-types
Use return types, not Dicts
2 parents 3073599 + 27671b5 commit a0d7add

13 files changed

Lines changed: 386 additions & 146 deletions

NEWS.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# New in 0.13
22

3+
- `imshow` now returns an `ImageDisplay` struct rather than a nested
4+
`Dict{String,Any}`. The nested GUI and ROI groupings are exposed as
5+
the structs `ImageViewGUI` (returned by `imshow_gui`) and `ImageROI`
6+
(returned by the low-level `imshow(::Canvas, ...)`). Field access
7+
is flattened on `ImageDisplay`, so where you previously wrote
8+
`result["gui"]["window"]`, `result["roi"]["zoomregion"]`, or
9+
`result["roi"]["image roi"]`, you can now write `result.window`,
10+
`result.zoomregion`, `result.image_roi`. Call `propertynames(result)`
11+
to see every accessible field.
12+
13+
This change fixes a long-standing usability problem: the default
14+
`show` for the old `Dict` recursively printed the underlying image
15+
array, which could hang the REPL for several seconds on a large
16+
image when the trailing semicolon was forgotten. The new types have
17+
compact `show` methods that print only summary information.
18+
19+
The string-key API (`result["gui"]["window"]` etc.) still works and
20+
routes through a deprecation shim; each call emits a
21+
`Base.depwarn`. The shim will be removed in the next breaking
22+
release. Downstream packages can dispatch on `ImageView.ImageDisplay`,
23+
`ImageView.ImageViewGUI`, or `ImageView.ImageROI` directly.
324
- Julia 1.10 is now required
425
- MultiChannelColors, FileIO support now in extensions
526

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ImageView"
22
uuid = "86fae568-95e7-573e-a6b2-d8a6b900c9ef"
33
author = ["Tim Holy <tim.holy@gmail.com", "Jared Wahlstrand <jwahlstrand@gmail.com>"]
4-
version = "0.13.1"
4+
version = "0.13.2"
55

66
[deps]
77
AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9"

README.md

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -112,47 +112,53 @@ You can place multiple images in the same window using `imshow_gui`:
112112
```
113113
using ImageView, TestImages, Gtk4
114114
gui = imshow_gui((300, 300), (2, 1)) # 2 columns, 1 row of images (each initially 300×300)
115-
canvases = gui["canvas"]
115+
canvases = gui.canvas
116116
imshow(canvases[1,1], testimage("lighthouse"))
117117
imshow(canvases[1,2], testimage("mandrill"))
118-
show(gui["window"])
118+
show(gui.window)
119119
```
120120

121121
![canvasgrid snapshot](readme_images/canvasgrid.jpg)
122122

123-
`gui["window"]` returns the window; `gui["canvas"]` either returns a single canvas
123+
`gui.window` returns the window; `gui.canvas` either returns a single canvas
124124
(if there is just one), or an array if you've specified a grid of canvases.
125125

126126
`Gtk4.show(win)` is sometimes needed when using the lower-level utilities of this
127127
package. Generally you should call it after you've finished assembling the entire window,
128128
so as to avoid redraws with each subsequent change.
129129

130-
### The dictionary and region-of-interest manipulations
130+
### The display handle and region-of-interest manipulations
131131

132-
`imshow` returns a dictionary containing a wealth of information about
133-
the state of the viewer. Perhaps most interesting is the `"roi"`
134-
entry, which itself is another dictionary containing information about
135-
the current selected region or interest. These are
136-
[Observables signals](https://juliagizmos.github.io/Observables.jl/), and consequently you can even manipulate the
137-
state of the GUI by `push!`ing new values to these signals.
132+
`imshow` returns an `ImageDisplay`, a struct holding the GTK widgets and
133+
Observables that drive the viewer. The most interesting subgroup is the
134+
region-of-interest (accessible as `disp.roi` or directly via hoisted fields
135+
like `disp.zoomregion`, `disp.image_roi`, `disp.slicedata`). The signals are
136+
[Observables](https://juliagizmos.github.io/Observables.jl/), and you can
137+
manipulate the state of the GUI by `push!`ing (or assigning) new values to
138+
them.
138139

139140
For example, using the `"mri"` image above, you can select the 5th slice with
140141
```julia
141-
guidict = imshow(img)
142-
guidict["roi"]["slicedata"].signals[1][] = 5
142+
disp = imshow(img)
143+
disp.slicedata.signals[1][] = 5
143144
```
144145

145146
`SliceData` objects contain information about which axes are displayed
146147
and the current slice indices of those axes perpendicular to the view
147-
plane. Likewise, `"image roi"` contains the actual image data
148+
plane. Likewise, `disp.image_roi` contains the actual image data
148149
currently being shown in the window (including all zoom/slice
149150
settings).
150151

151-
The `"zoom"`- and `"pan"`-related signals originate from
152+
The `zoom_*` and `pan_*` signals originate from
152153
[GtkObservables](https://juliagizmos.github.io/GtkObservables.jl/stable/),
153154
and users should see the documentation for that package for more
154155
information.
155156

157+
`propertynames(disp)` lists every accessible field. For backwards
158+
compatibility, the older string-key syntax (`disp["gui"]["window"]`,
159+
`disp["roi"]["zoomregion"]`, etc.) still works but emits a deprecation
160+
warning; it will be removed in the next breaking release.
161+
156162
### Coupling two or more images together
157163

158164
`imshow` allows you to pass many more arguments; please use `?imshow`
@@ -171,9 +177,9 @@ mriseg[mri .> 0.5] .= colorant"red"
171177
Now we display the images:
172178

173179
```julia
174-
guidata = imshow(mri, axes=(1,2))
175-
zr = guidata["roi"]["zoomregion"]
176-
slicedata = guidata["roi"]["slicedata"]
180+
disp = imshow(mri, axes=(1,2))
181+
zr = disp.zoomregion
182+
slicedata = disp.slicedata
177183
imshow(mriseg, nothing, zr, slicedata)
178184
```
179185

@@ -187,8 +193,8 @@ Alternatively, you can place both displays in a single window:
187193
```julia
188194
zr, slicedata = roi(mri, (1,2))
189195
gd = imshow_gui((200, 200), (2, 1); slicedata=slicedata)
190-
imshow(gd["frame"][1,1], gd["canvas"][1,1], mri, nothing, zr, slicedata)
191-
imshow(gd["frame"][1,2], gd["canvas"][1,2], mriseg, nothing, zr, slicedata)
196+
imshow(gd.frame[1,1], gd.canvas[1,1], mri, nothing, zr, slicedata)
197+
imshow(gd.frame[1,2], gd.canvas[1,2], mriseg, nothing, zr, slicedata)
192198
```
193199

194200
You should see something like this:
@@ -208,8 +214,8 @@ and "floating" annotations are best for things like scalebars.
208214

209215
Here's an example of adding a 30-pixel scale bar to an image:
210216
```julia
211-
guidict = imshow(img)
212-
scalebar(guidict, 30; x = 0.1, y = 0.05)
217+
disp = imshow(img)
218+
scalebar(disp, 30; x = 0.1, y = 0.05)
213219
```
214220

215221
`x` and `y` describe the center of the scale bar in normalized
@@ -226,14 +232,14 @@ using Images, ImageView
226232
z = ones(10,50);
227233
y = 8; x = 2;
228234
z[y,x] = 0
229-
guidict = imshow(z)
230-
idx = annotate!(guidict, AnnotationText(x, y, "x", color=RGB(0,0,1), fontsize=3))
231-
idx2 = annotate!(guidict, AnnotationPoint(x+10, y, shape='.', size=4, color=RGB(1,0,0)))
232-
idx3 = annotate!(guidict, AnnotationPoint(x+20, y-6, shape='.', size=1, color=RGB(1,0,0), linecolor=RGB(0,0,0), scale=true))
233-
idx4 = annotate!(guidict, AnnotationLine(x+10, y, x+20, y-6, linewidth=2, color=RGB(0,1,0)))
234-
idx5 = annotate!(guidict, AnnotationBox(x+10, y, x+20, y-6, linewidth=2, color=RGB(0,0,1)))
235+
disp = imshow(z)
236+
idx = annotate!(disp, AnnotationText(x, y, "x", color=RGB(0,0,1), fontsize=3))
237+
idx2 = annotate!(disp, AnnotationPoint(x+10, y, shape='.', size=4, color=RGB(1,0,0)))
238+
idx3 = annotate!(disp, AnnotationPoint(x+20, y-6, shape='.', size=1, color=RGB(1,0,0), linecolor=RGB(0,0,0), scale=true))
239+
idx4 = annotate!(disp, AnnotationLine(x+10, y, x+20, y-6, linewidth=2, color=RGB(0,1,0)))
240+
idx5 = annotate!(disp, AnnotationBox(x+10, y, x+20, y-6, linewidth=2, color=RGB(0,0,1)))
235241
# This shows that you can remove annotations, too:
236-
delete!(guidict, idx)
242+
delete!(disp, idx)
237243
```
238244

239245
This is what this looks like before `delete!`ing the first annotation:
@@ -247,7 +253,7 @@ by calling `annotations()`:
247253
using ImageView, Images, Gtk4
248254
# Create the window and a 2x2 grid of canvases, each 300x300 pixels in size
249255
gui = imshow_gui((300, 300), (2, 2))
250-
canvases = gui["canvas"]
256+
canvases = gui.canvas
251257
# Create some single-color images (just for testing purposes)
252258
makeimage(color) = fill(color, 100, 100)
253259
imgs = makeimage.([colorant"red" colorant"green";
@@ -256,10 +262,10 @@ imgs = makeimage.([colorant"red" colorant"green";
256262
anns = [annotations() annotations();
257263
annotations() annotations()]
258264
# Draw the images on the canvases. Note that we supply the annotation object for each.
259-
roidict = [imshow(canvases[1,1], imgs[1,1], anns[1,1]) imshow(canvases[1,2], imgs[1,2], anns[1,2]);
260-
imshow(canvases[2,1], imgs[2,1], anns[2,1]) imshow(canvases[2,2], imgs[2,2], anns[2,2])]
265+
rois = [imshow(canvases[1,1], imgs[1,1], anns[1,1]) imshow(canvases[1,2], imgs[1,2], anns[1,2]);
266+
imshow(canvases[2,1], imgs[2,1], anns[2,1]) imshow(canvases[2,2], imgs[2,2], anns[2,2])]
261267
# Now we'll add an annotation to the lower-right image
262-
annotate!(anns[2,2], canvases[2,2], roidict[2,2], AnnotationBox(5, 5, 30, 80, linewidth=3, color=RGB(1,1,0)))
268+
annotate!(anns[2,2], canvases[2,2], rois[2,2], AnnotationBox(5, 5, 30, 80, linewidth=3, color=RGB(1,1,0)))
263269
```
264270

265271
![grid annotations](readme_images/grid_annotations.png)
@@ -352,7 +358,7 @@ If you call Julia from a script file, the GLib main loop (which is responsible f
352358
using Images, ImageView, TestImages, Gtk4
353359
354360
img = testimage("mandrill")
355-
guidict = imshow(img);
361+
disp = imshow(img);
356362
357363
#If we are not in a REPL
358364
if (!isinteractive())
@@ -361,7 +367,7 @@ if (!isinteractive())
361367
c = Condition()
362368
363369
# Get the window
364-
win = guidict["gui"]["window"]
370+
win = disp.window
365371
366372
# Start the GLib main loop
367373
@async Gtk4.GLib.glib_main()

0 commit comments

Comments
 (0)