mpl-extensions / mpl-interactions

Sliders to control matplotlib and other interactive goodies. Works in any interactive backend and even uses ipywidgets when in a Jupyter notebook
https://mpl-interactions.rtfd.io
BSD 3-Clause "New" or "Revised" License
132 stars 20 forks source link

image_segmenter: Enable saving of verts or path coordinates for a image_segmenter object #263

Open wgottwald opened 1 year ago

wgottwald commented 1 year ago

Problem:

Paths of the ROIs are not returned by image_segmenter

Explanantion:

After segmenting an image, the .verts attributes are not saved. It would be good to access them later, not just the mask.

Proposed Solution

Add the verts and the paths of the drawn ROI as a property to an image_segmenter object. Similar to mask.

ianhi commented 1 year ago

One complication is how to deal with difference between erasing and drawing paths. Would probalby have to return something like

{"drawing": [path, path, path....], "erasing": [path, path, path]}

how's that sound?

wgottwald commented 1 year ago

Yes thats probably an issue. But in the end the interesting output are just the coordinates of the final roi that one has drawn. The motivation here is that I can display that later onto the image I have drawn it on or any other image with the same dimensions for that matter.

ianhi commented 1 year ago

But in the end the interesting output are just the coordinates of the final roi that one has drawn.

So you basically want the outline of a mask? Can you take the mask you drawn and take it's outline using some skimage functions?

wgottwald commented 1 year ago

Yes thats also a solution. I just thought if its an easy fix to include the path coordinates as a property that would be cleaner.

ianhi commented 1 year ago

Just to disambiguate: When you say path coordinates, do you mean the sort of sum of paths, or all the individual paths that were used to create the masks?

For example this is a valid mask to draw: image

but it's not obvious to me that there's a nice way to represent that with a single path

wgottwald commented 1 year ago

I mean something like this: (where the red line is the path that has been extracted from the verts parameter)

Figure 1(4)

ianhi commented 1 year ago

I guess my thought is that that is really two different red lines. Also how did you generate the red line, programaticcaly from verts? If there is an easy way to combine the paths into one shape that I'm not aware of I'd be stoked to include that

ianhi commented 1 year ago

I thought about this a bit more and while there is a make_compount_path function that only works for purely additive paths, since this allows erasing I don't think there's a super easy way to combine them into one (or multiple paths.

based on your image i think what you want is contours which should be easy to create directly from the mask:

borrowing from this example I did this

fig, ax = plt.subplots()
contours = measure.find_contours(segmenter.mask)
ax.imshow(segmenter.mask)
for contour in contours:
    ax.plot(contour[:, 1], contour[:, 0], linewidth=2, color='r')

which gives this:

image

wgottwald commented 1 year ago

I guess my thought is that that is really two different red lines. Also how did you generate the red line, programaticcaly from verts? If there is an easy way to combine the paths into one shape that I'm not aware of I'd be stoked to include that

I generated them like this (in a Jupyter-lab notebook):

import sys
from mpl_interactions import image_segmenter
import numpy as np
import matplotlib.pyplot as plt
%matplotlib widget
import ipywidgets as widgets
test_array = np.zeros((128,128,4))
bar = image_segmenter(test_array[:,:,2],nclasses=2)

Call that in a new cell to draw the ROI:

bar

Save the verts as a list in a new cell:

path_coords = bar.verts

Set erase to True in a new cell:

bar.erasing = True

Go back to cell that calls bar and execute that:

bar

Draw a erasing ROI on it and then save that into a new parameter in yet another cell:

eraser_coords = bar.verts

Plot the mask and the boundaries:

plt.close('all')
fig,ax=plt.subplots(1)
ax.imshow(bar.mask)

for n in range(len(path_coords)-1):
    ax.plot([path_coords[n][0],path_coords[n+1][0]],[path_coords[n][1],path_coords[n+1][1]],color='r')
for n in range(len(eraser_coords)-1):
    ax.plot([eraser_coords[n][0],eraser_coords[n+1][0]],[eraser_coords[n][1],eraser_coords[n+1][1]],color='r')
wgottwald commented 1 year ago

I thought about this a bit more and while there is a make_compount_path function that only works for ...

Thanks for pointing this out, that makes the most sense to me actually. Will try to implement it like this.

wgottwald commented 1 year ago

I wrote a function that accomplishes extracting the contours from the mask, where would you place such a function within the package? Into generic.py under the segmenter?