GithubHelp home page GithubHelp logo

ray's Introduction

ray: segmentation of nD images

Ray is a python library for performance and evaluation of image segmentation, distributed under the open-source MIT license. It supports n-dimensional images (images, volumes, videos, videos of volumes...) and multiple channels per image.

Requirements (tested versions)

  • Python 2.x (2.6, 2.7)
  • numpy (1.5.1, 1.6.0)
  • Image (a.k.a. Python Imaging Library or PIL) (1.3.1)
  • networkx (1.4, 1.5, 1.6)
  • h5py (1.5.0)
  • scipy (0.7.0, 0.9.0, 0.10.0)

All of the above are included in the Enthought Python Distribution, so I would recommend you just install that if you can.

Recommended

progressbar is in PyPi and trivial to install:

sudo easy_install progressbar

For vigra, you are on your own. It is used for the random forest classifier, but if you don't install it you can still use SVM or AdaBoost classifiers.

Installation

Well, there's nothing to install per se (distutils support coming at some point in the far future). Download the source and add whatever path you downloaded it to to your Python path.

Testing

The test coverage is rather tiny, but it is still a nice way to check you haven't completely screwed up your installation. From the Ray root directory, run python test/test_ray.py to run some regression tests.

Usage

Agglomeration

Suppose you have already trained a pixel level boundary detector, and want to perform mean agglomeration on it. This is the simplest form of agglomeration and was the initial design spec for Ray. Now:

from ray import imio, agglo, morpho
# prob is a numpy ndarray
# probabilities-* can be one file for 2D segmentation, or many files for 3D.
prob = imio.read_image_stack('probabilities-*.png') 
label_field = morpho.watershed(prob)
# Make the region adjacency graph (RAG)
g = agglo.Rag(label_field, prob)
threshold = 0.5
# agglomerate until the given threshold
g.agglomerate(threshold)
# get the label field resulting from the agglomeration
seg = g.get_segmentation() 
# now agglomerate to completion and get the UCM
from numpy import inf
g.agglomerate(inf)
ucm = g.get_ucm()

An ultrametric contour map (UCM) can be thresholded to provide the segmentation at any threshold of agglomeration. (It may, however, result in a split when a segment becomes thinner than one pixel.)

The mean agglomeration may be too simple. What if we want to use the median? We can specify this with the merge_priority_function argument to the RAG constructor:

# merge by boundary median instead of mean
g = agglo.Rag(label_field, prob, merge_priority_function=agglo.boundary_median)

A user can specify their own merge priority function. A valid merge priority function is a callable Python object that takes a graph and two nodes from that graph as input, and returns a real number. (Technically, any object that satisfies the basic comparison operations, such as __lt__, will work.)

Learning agglomeration

A whole new set of tools is needed to apply machine learning to agglomeration. These are provided by the classify module, and built into the agglo.Rag class.

from ray import classify
gs = imio.read_h5_stack('gold-standard-segmentation.h5')
fm = classify.MomentsFeatureManager()
fh = classify.HistogramFeatureManager()
fc = classify.CompositeFeatureManager(children=[fm, fh])

A feature manager is a callable object that computes feature vectors from graph edges. The object has the following responsibilities, which it can inherit from classify.NullFeatureManager:

  • create a (possibly empty) feature cache on each edge and node, precomputing some of the calculations needed for feature computation;
  • maintain the feature cache throughout node merges during agglomeration;
  • compute the feature vector from the feature caches when called with the inputs of a graph and two nodes.

Feature managers can be chained through the classify.CompositeFeatureManager class.

We can then extract feature vectors from the graph as follows:

g = agglo.Rag(label_field, prob, feature_manager=fc)
n1, n2 = 1, 2
feature_vector = fc(g, n1, n2)

This gives us the rudimentary tools to do some machine learning, when combined with a labeling system. Given a gold standard segmentation (gs, above) assumed to be a correct segmentation of the image, do:

training_data, all_training_data = g.learn_agglomerate(gs, fc)

The training data is a tuple with four elements:

  • an nsamples x nfeatures numpy array with the feature vectors for each learned edge.
  • an nsamples x 4 numpy array with the associated lables for each edge: -1 for "correct merge", and +1 for "incorrect merge". The four columns are four different labeling systems. They mostly agree (and certainly do in the case of a perfect oversegmentation); using column 0 is fine for most purposes.
  • an nsamples x 2 numpy array of weights (VI and RI) associated with each learned edge, for weighted learning.
  • an nsamples x 2 numpy array of edge ids, the sample history during learning.

all_training_data is a list of such tuples, one for each training epoch. This will be ignored for this tutorial. Briefly, learning takes place by agglomerating while comparing the present segmentation to the gold standard. Once the volume has been fully agglomerated, learning starts over, which can result in repeated elements. training_data is the set of unique learning samples encountered over all epochs, while all_training_data is a list of the samples encountered in each epoch (including repeats).

Now that we have a training sample, we can train a classifier, such as classify.RandomForest, which is a wrapper of vigra.learning.RandomForest to match the classifier interface in scikits.learn:

features, labels, weights, history = training_data
rf = classify.RandomForest()
rf = rf.fit(features, labels[:,0])
rf.save_to_disk('my-random-forest.rf.h5')

As seen above, the RF can be saved in HDF5 format for future use. It's easy to reload later:

rf = classify.RandomForest()
rf.load_from_disk('my-random-forest.rf.h5')

Let's use it right now though:

# use agglo.classifier_probability to create a closure over the feature map and
# classifier that satisfies the definition of a merge priority function.
learned_priority_function = agglo.classifier_probability(fc, rf)
test_prob = imio.read_image_stack('test-probabilities-*.png')
test_label_field = morpho.watershed(test_prob)
gtest = agglo.Rag(test_label_field, test_prob, learned_priority_function,
    feature_manager=fc)
gtest.agglomerate(inf)
test_ucm = gtest.get_ucm()

It's probably a good idea to save the UCM for later:

imio.write_h5_stack(test_ucm, 'test-ucm.h5')

By default, imio puts datasets in the 'stack' group in the HDF5 file, but you can specify your own.

imio.write_h5_stack(test_ucm, 'test-ucm.h5', group='volume')

We have now done our first learned agglomeration. But how do we know how well we have done?

Evaluation

We can use the evaluate submodule to check our performance.

from ray import evaluate
from scipy.ndimage.measurements import label
t = imio.read_h5_stack('test-gold-standard.h5')
s = label(ucm_test < 0.5)[0]
# variation of information, including decomposition, and multiple thresholds
vi = evaluate.vi(s, t)
svi = evaluate.split_vi(s, t)
vit = evaluate.vi_by_threshold(test_ucm, t)
# draw the split-vi plot
from matplotlib import pyplot as plt
plt.plot(vit[1], vit[2])
plt.show()
# rand index and adjusted rand index
ri = evaluate.rand_index(s, t)
ari = evaluate.adj_rand_index(s, t)
# Fowlkes-Mallows index
fm = evaluate.fm_index(s, t)
# pixel-wise precision-recall
pr = evaluate.pixel_wise_precision_recall(s, t)

That's a quick summary of the capabilities of Ray. There are of course many options under the hood, many of which are undocumented... Feel free to push me to update the documentation of your favorite function!

ray's People

Contributors

anirbanchakraborty avatar docsavage avatar jni avatar stephenplaza avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ray's Issues

Reorganize files

All source files are currently in the root directory. Everything should be reorganized so that docs and figures can be more easily added to the repository. Suggested structures:

ray/
    ray.py
    modules/
        *.py
    doc/
        figures/

or:

ray/
    src/
        *.py
    doc/
        figures/

Need to look into Python modules to see what makes sense.

refine_post_merge_boundaries() could remove double-counted pixels

refine_post_merge_boundaries in agglo.py was created for the purpose of maintaining the integrity of 0-labeled boundaries in cases such as the following:

1 0 2 0
1 0 0 3
0 4 0 3

When we have a merge of body 3 into body 2, a new pixel is added to the boundary between 1 and 2. If this addition is not noted, when 1 and 4 are merged, that pixel will be removed and the boundary between 1 and 2 will have a leak.

However, it turns out that this function could be used to detect when boundary pixels are double-counted during a merge and correct the double counting.

    idxs = set(boundaries_to_edit[(u,v)])                            
    if self.has_edge(u, v):                                          
        idxs = idxs - self[u][v]['boundary']                         
        self[u][v]['boundary'].update(idxs)                          
        self.feature_manager.pixelwise_update_edge_cache(self, u, v, 
                            self[u][v]['feature-cache'], list(idxs)) 

Find the intersection of idxs and self[u][v]['boundary'] and use the feature_manager to pixelwise_update with remove=True.

Show errors given a body number and matching volume

Current interactive VOI Breakdown plots are able to show which body is responsible for a particular data point. However, it is difficult to take that information and see what the error was. We need a function in viz that will take as input a body in either GT or Seg, the corresponding segmentation, and the segmentation against which it is different. Then we can figure out where the error is and display it.

Support multiple learning modes

I've been thinking a bit more about these learning modes and there's actually three distinct things going on: a learning mode, which specifies whether, given a 1 (boundary) label we merge or not; a labeling mode, which specifies how to get the label; and a sampling mode, which specifies how to obtain the merge order. For a while I thought that only some combinations of these made sense, but actually all combinations can work conceptually.

Here are the options so far for each of those:
learning modes: strict (my approach) or laissez-faire (Viren's, and RL in general)
labeling modes: assignment (mine), Rand change sign (Viren's), VOI sign (new)
sampling modes: random (mine), active (Viren's), boundary mean (mine when I had a bug, but it still worked well!) [I don't think you were in this discussion, but I found out that all the results on my poster came from training through mean boundary, not random!]

One could also consider a mixed sampling approach, for example, alternating epochs of random and active sampling.

add push-button interface to run entire pipeline

We now have a pretty good idea of the metrics we need when running the ray segmentation pipeline for scientific experiments. In particular:

run same watershed on train and test volumes
train classifier (collect training error and feature importance if possible)
segment test volume (collect VOI and boundary precision-recall metrics)

ucm is incorrectly updated during agglomerate_ladder

agglomerate_ladder() is often called ahead of the actual agglomeration procedure. This will result in max_merge_score to be set to the maximum value before agglomeration has even begun, thereby messing up the UCM. Therefore, special treatment must be given to scores during ladder agglomeration...

Save FeatureManager objects along with classifiers

A classifier is trained for a specific subset of features. If the feature set used is forgotten, then the classifier itself is useless. Therefore the feature manager object should be saved along with the classifier. Information about the number of channels is also necessary.

copy operation is not atomic

if anything should go wrong during a graph copy operation, the original object will most likely be corrupted, since unpickleable member objects are deleted and then restored. Not sure how to fix this... some kind of try: except: wrapping so that the objects can be re-added before re-raising the exception?

Add progress bar to RandomForest

classify.RandomForest uses the VIGRA library, which is written in C++ and there's not a straightforward way to have a progress bar. Instead, we could train a 1-tree random forest before beginning the full training to see how long it takes to train a tree.

On one test dataset, indications are that this would provide a good estimate of total time (i.e., initial overhead and other factors won't mess things up), since we get the following times for training 1, 2, and 3 trees:

1 tree: 118.812502146 seconds
2 trees: 236.317131042 seconds
3 trees: 351.319090128 seconds

Each successive tree is very close to a multiple of the 1-tree training time. The progress bar could then be based on time.

Add support for watershed where shallow minima are removed

Rather than thresholding the probability map and seeding the watershed, we can "smooth" out shallow minima whose depth is less than a threshold. The algorithm is:

  • Take the image complement
  • Subtract the threshold
  • Perform morphological reconstruction
  • Take the image complement
  • Perform an unseeded watershed on the result

add histogram feature manager

Feature managers are responsible for maintaining an auxiliary cache in a graph, as well as computing features from that cache. Currently only NullFeatureManager and MomentsFeatureManager exist. A histogram will also be useful in a machine learning context.

change verbosity using logging module

Current implementation of the --verbose flag in most of the ray pipeline is an ad-hoc implementation that is clunky and difficult to expand upon. The python built-in logging module offers a very natural interface for this use case. Therefore, all --verbose and --debug flags should be implemented using this module.

specify boundary width

The boundary used by agglo.Rag is just one pixel wide. This is not sufficient to cover the boundary given by the Ilastik prediction map. Therefore, we may find less noise and more consistent measurements by using a wider boundary.

add squiggliness feature

Proposed features:
ratio of surface area vs bounding box surface area
ratio of surface area vs maximum and/or minimum flat surface area within bounding box
variance of curvature along surface

add boundary surface area feature

A boundary between two cells should be relatively smooth. Having a very wavy boundary could be indicative of a problem. Thus a suggested feature would be boundary surface area ^ 0.5 / boundary bounding-box volume ^ (1/3).

Replace try: except: clauses with dict.setdefault() method

I had only considered two approaches to default values in dictionaries:

if d.has_key('k'):
    # do something
else:
    # default do something

or

try:
    # do something with d[k]
except KeyError:
    # do something default

The most common in my code:

try:
    d[k].append(v)
except KeyError:
    d[k] = [v]

The dict.setdefault() built-in method allows an easy substitute:

d.setdefault(k, []).append(v)

Benchmarks should be done to verify this is faster in real-use cases.

allow different cache lengths for nodes and for boundaries

FeatureManager objects currently assume that features require equal number of cache items for each node and for the boundary. However, some features only make sense for the boundary, and others only for the nodes. Therefore we should support different cache sizes for nodes and boundaries.

add trainable normalized cut

Timothée Cour, Nicolas Gogin, and Jianbo Shi have a paper describing how to learn a spectral graph segmentation function. This could be implemented in ray.

fix agglo.Rag.refine_post_merge_boundaries() for volumes with no boundary pixels

From Ryan:
Two nodes both have 9 pixels:

In [179]: a.node[1]['feature-cache'][0]
Out[179]: 9.0

In [180]: a.node[2]['feature-cache'][0]
Out[180]: 9.0

When I merge them, I would think we should have 18 pixels in node 1,
and when I look at the len() of the 'extent', this is true:

In [184]: a.merge_nodes(1,2)

In [185]: len(a.node[1]['extent'])
Out[185]: 18

But the feature cache says N is 21:

In [186]: a.node[1]['feature-cache'][0]
Out[186]: 21.0

I traced the point where this changes from 18 to 21 to
refine_post_merge_boundaries.

paper outline

Begin writing paper in order to identify missing data.

Run unique() on training data after compiling

Training proceeds by multiple iterations of random merging in a volume. During training some node pairs may be merged that were encountered in previous iterations, resulting in duplicate entries in the training data, which could adversely affect performance.

We should try ensuring that each data entry is unique and see how classifier performance is affected.

Support backtracking in agglomeration with a tree structure for the history

An agglomeration can be represented as a forest structure (single tree if the agglomeration proceeds to completion) in which each leaf is a superpixel, and inner nodes are the products of agglomerating two child nodes. If agglo.Rag maintains this structure, it will be relatively easy to backtrack and undo merging.

Enable progressbar for agglo.Rag.learn_agglomerate()

Due to a bug in the MergeQueue handling during agglo.Rag.learn_agglomerate(), the progress bar has to be turned off. This is done by the statement, g.show_progress = False. This makes it difficult to predict when the learning process will finish.

The problem has to do with miscounting of remaining valid items. My theory is that items are merged that are not in the merge queue. Therefore the number of nodes decreases but not the number of valid items.

add composite feature manager

Currently only individual feature managers can be used. A composite feature manager would allow arbitrary combinations of feature vectors.

Composite feature managers follow the Composite design pattern from the Gang of Four book.

seeded watershed unconscionably slow when number of levels is large

Seeded watershed currently works by pushing unlabeled voxels at the current level up to the next level. This operation is repeated until the voxels are labeled. This means that an individual voxel is moved up as many times as there are levels between it and the nearest seeded basin. For volumes where the number of levels is more or less continuous, this becomes a ludicrous number.

The correct way is to perform a watershed on the volume after doing an imposemin operation. See Matlab's imimposemin source for implementation.

test performance of node split

Node split is working but it has not been tested on true false merge cases. Testing correct results is essential before trying out machine learning on should-be-split function.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.