RAG thresholding in ImagePy

I have a working method to perform RAG thresholding using scikit-image:

from skimage.future import graph

original_image = ... # unmodified image
segmented_image = ... # result of a segmentation
threshold = ...

g = graph.rag_mean_color(original_image, segmented_image)
thresholded = graph.cut_threshold(segmented_image, g, threshold, in_place=False)

How can I achieve the same effect in ImagePy, using Process -> Segment -> RAG Threshold? This is what I did so far: open original_image, make a copy of it, perform the segmentation on it to obtain segmented_image. Looking into the RagThreshold class, I don’t see how I can use the two opened images original_image and segmented_image to perform the thresholding from the ImagePy GUI.

This specific question has a generalization. Given some opened images in ImagePy, how can I pass them to the ImagePy plugin system?

there are two method:

  1. merge the two step in one plugin, you can du segment and ragthreshold in run.
  2. there are some advanced parameter, such as ‘img’, ‘tab’, ‘fields’. which you can get a list of image opened. Then use ImageManager.get(name), you can get the ips object.

ips.img is the current ndarray.
ips.imgs is the ndarray sequence.

Thank you, I understand now the logic. However, I have a related question.
Can we return an arbitrary object from the run method and use this returned object as a parameter in another class. E.g.

class A(Simple):
    def __init__(self):
        obj = ... # construct an object

    def run(self, ips, imgs, para=None):
        ... # do some image processing
        ... # modify the state of `obj` and optionally return
class B(Simple):
    def __init__(self, obj):
        # Somehow access the object from class `A`. How to do that?

    def run(self, ips, imgs, para=None):
        # Even though the user does not provide more images with
        # ImageManager.get(para['key']), this class can now operate on `obj`
        # (which can e.g. be a numpy array representing a previous image)

This strategy would allow us to combine the advantages of the two methods you described above:

  • don’t need to perform multiple steps in one plugin class, so I can separate the algorithms
  • I can internally deal with arbitrary objects (e.g. numpy arrays) without the need to have open images and use the mouse

What I currently do is defining a class in __init__.py of the package and instantiate an object:

class Transfer:

tr = Transfer()

The role of this class is to dynamically add attributes to it. So

class A(Simple):
    def run(self, ips, imgs, para=None):
        from . import tr
        ... # do some image processing
        tr.new_instance = ... # modify the state of `tr`

Now I can transfer the data to a new class object:

class B(Simple):
    def run(self, ips, imgs, para=None):
        from . import tr
        ... # use `tr.new_instance`
        # optionally further mutate the `tr` object

I don’t know how “Pythonic” it is…

I think you want to share data between plugins.
there are several method:

  1. Write a Manager class, and use @cls method to record. (like imagepy.core.manager ImageManager)
  2. Use pandas.DataFrame, if you have many objects, you can put them in a DataFrame, then use show_table to show it. Then you can use ‘tab’ parameter to get the table, then use it.
  3. ips.data is a {}, you can attach some user data in, and next plugin can got it by ips.data[‘xxx’].

That’s exactly what I want, very nice answer! I will stick to option 3 as I only have little data for now. It would be nice to have documentation on it, I would never have thought that ips.data served this purpose.

the BuildGraph is a demo:

class BuildGraph(Filter):
    title = 'Build Graph'
    note = ['8-bit', 'not_slice', 'not_channel', 'auto_snap']

    def run(self, ips, snap, img, para = None):
        ips.data = sknw.build_sknw(img, True)
        sknw.draw_graph(img, ips.data)
        ips.mark = Mark(ips.data)

attach the graph object to ips.data, and use it later in graph statistic.

class Statistic(Simple):
    title = 'Graph Statistic'
    note = ['all']

    def load(self, ips):
        if not isinstance(ips.data, nx.MultiGraph):
            IPy.alert("Please build graph!");
            return False;
        return True;

    def run(self, ips, imgs, para = None):
        edges, nodes = [], []
        ntitles = ['PartID', 'NodeID', 'Degree','X', 'Y']
        etitles = ['PartID', 'StartID', 'EndID', 'Length']
        k, unit = ips.unit
        comid = 0
        for g in nx.connected_component_subgraphs(ips.data, False):
            for idx in g.nodes():
                o = g.node[idx]['o']
                print(idx, g.degree(idx))
                nodes.append([comid, idx, g.degree(idx), round(o[1]*k,2), round(o[0]*k,2)])
            for (s, e) in g.edges():
                eds = g[s][e]
                for i in eds:
                    edges.append([comid, s, e, round(eds[i]['weight']*k, 2)])
            comid += 1

        IPy.show_table(pd.DataFrame(nodes, columns=ntitles), ips.title+'-nodes')
        IPy.show_table(pd.DataFrame(edges, columns=etitles), ips.title+'-edges')

may be a data check in load is needed.