Skip to content

Commit

Permalink
Merge 5efdb55 into f6ce749
Browse files Browse the repository at this point in the history
  • Loading branch information
jni committed Feb 17, 2015
2 parents f6ce749 + 5efdb55 commit 7189f1e
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 106 deletions.
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# gala: segmentation of nD images [![Picture](https://raw.github.com/janelia-flyem/janelia-flyem.github.com/master/images/gray_janelia_logo.png)](http://janelia.org/)

Gala is a python library for performing and evaluating image segmentation,
distributed under the open-source [Janelia Farm license](http://janelia-flyem.github.com/janelia_farm_license.html). It implements the algorithm
described in [Nunez-Iglesias *et al*.](http://arxiv.org/abs/1303.6163), PLOS
ONE, 2013.
Gala is a Python library for performing and evaluating image segmentation,
distributed under the open-source, BSD-like
[Janelia Farm license](http://janelia-flyem.github.com/janelia_farm_license.html).
It implements the algorithm described in
[Nunez-Iglesias *et al*.](http://arxiv.org/abs/1303.6163), PLOS ONE, 2013.

If you use this library in your research, please cite:

Expand All @@ -21,17 +22,18 @@ If you use or compare to the GALA algorithm in your research, please cite:
> *PLoS ONE 8(8): e71715.* doi:10.1371/journal.pone.0071715
Gala supports n-dimensional images (images, volumes, videos, videos of
volumes...) and multiple channels per image.
volumes...) and multiple channels per image. It is compatible with both
Python 3.4 and Python 2.7.

[![Build Status](https://travis-ci.org/janelia-flyem/gala.png?branch=master)](https://travis-ci.org/janelia-flyem/gala)
[![Coverage Status](https://img.shields.io/coveralls/janelia-flyem/gala.svg)](https://coveralls.io/r/janelia-flyem/gala)

## Requirements

* Python 2.7
* Python 3.4 or 2.7
* numpy 1.7+
* scipy 0.10+
* Image (a.k.a. Python Imaging Library or PIL) 1.1.7
* Image (a.k.a. Python Imaging Library or PIL) 1.1.7 or Pillow 2.5+
* networkx 1.6+
* HDF5 and h5py 1.5+
* cython 0.17+
Expand All @@ -46,17 +48,18 @@ volumes...) and multiple channels per image.

In its original incarnation, this project used Vigra for the random forest
classifier. Installation is less simple than scikit-learn, which has emerged
in the last year as a truly excellent implementation and is now recommended.
in recent years as a truly excellent implementation and is now recommended.
Tests in the test suite expect scikit-learn rather than Vigra.
You can also use any of the scikit-learn classifiers,
including their newly-excellent random forest.
including their world-class random forest implementation.

## Installation

### Installing gala

Gala is a Python library with limited Cython extensions and can be
installed in two ways:
installed in three ways:
* Use pip: `pip install gala`.
* Add the gala directory to your PYTHONPATH environment variable, or
* Use distutils to install it into your preferred python environment:

Expand Down Expand Up @@ -93,19 +96,17 @@ On Mac, you might have to install compilers (such as gcc, g++, and gfortran).

### Testing

The test coverage is rather tiny, but it is still a nice way to check you
haven't completely screwed up your installation. Note: the test scripts
*must* be run from the `tests` directory.
The test coverage is rather small, but it is still a nice way to check you
haven't completely screwed up your installation. After installing gala, go
to the code directory and type:

```bash
$ cd tests
$ python test_agglo.py
$ python test_features.py
$ python test_watershed.py
$ python test_optimized.py
$ python test_gala.py
$ py.test
```

You need to have pytest and pytest-cov installed, both of which are
available through PyPI.

## Usage

An example script, `example.py`, exists in the `tests/example-data`
Expand Down
34 changes: 31 additions & 3 deletions gala/agglo.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,33 @@ def contingency_table(a, b):


def conditional_countdown(seq, start=1, pred=bool):
"""Count down from 'start' each time pred(elem) is true for elem in seq."""
"""Count down from 'start' each time pred(elem) is true for elem in seq.
Used to know how many elements of a sequence remain that satisfy a
predicate.
Parameters
----------
seq : iterable
Any sequence.
start : int, optional
The starting element.
pred : function, type(next(seq)) -> bool
A predicate acting on the elements of `seq`.
Examples
--------
>>> seq = range(10)
>>> cc = conditional_countdown(seq, start=5, pred=lambda x: x % 2 == 1)
>>> next(cc)
5
>>> next(cc)
4
>>> next(cc)
4
>>> next(cc)
3
"""
remaining = start
for elem in seq:
if pred(elem):
Expand Down Expand Up @@ -141,8 +167,10 @@ def ladder_function(g, n1, n2):

def no_mito_merge(priority_function):
def predict(g, n1, n2):
if n1 in g.frozen_nodes or n2 in g.frozen_nodes \
or (n1, n2) in g.frozen_edges:
frozen = (n1 in g.frozen_nodes or
n2 in g.frozen_nodes or
(n1, n2) in g.frozen_edges)
if frozen:
return np.inf
else:
return priority_function(g, n1, n2)
Expand Down
130 changes: 51 additions & 79 deletions gala/annotefinder.py
Original file line number Diff line number Diff line change
@@ -1,90 +1,62 @@
from __future__ import absolute_import
import math
from six.moves import zip
import numpy as np
from scipy import spatial
from matplotlib import pyplot as plt

try:
import pylab
except ImportError:
""

class AnnoteFinder:
"""
callback for matplotlib to display an annotation when points are clicked on. The
point which is closest to the click and within xtol and ytol is identified.
class AnnotationFinder:
"""MPL callback to display an annotation when points are clicked.
Register this function like this:
scatter(xdata, ydata)
af = AnnoteFinder(xdata, ydata, annotes)
connect('button_press_event', af)
"""
The nearest point within xtol and ytol is identified.
def __init__(self, xdata, ydata, annotes, axis=None, xtol=None, ytol=None, xmin=None,ymin=None,xmax=None,ymax=None):
self.data = list(zip(xdata, ydata, annotes))
if xtol is None:
xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
if ytol is None:
ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
self.xtol = xtol
self.ytol = ytol
if axis is None:
self.axis = pylab.gca()
else:
self.axis= axis
self.drawnAnnotations = {}
self.links = []
self.xmin=xmin
self.ymin=ymin
self.xmax=xmax
self.ymax=ymax
Register this function like this:
def distance(self, x1, x2, y1, y2):
"""
return the distance between two points
plt.scatter(xdata, ydata)
af = AnnotationFinder(xdata, ydata, annotations)
plt.connect('button_press_event', af)
"""
return math.hypot(x1 - x2, y1 - y2)

def __call__(self, event):
if event.inaxes:
clickX = event.xdata
clickY = event.ydata
if self.axis is None or self.axis==event.inaxes:
annotes = []
for x,y,a in self.data:
if clickX-self.xtol < x < clickX+self.xtol and clickY-self.ytol < y < clickY+self.ytol :
annotes.append((self.distance(x,clickX,y,clickY),x,y, a) )
if annotes:
annotes.sort()
distance, x, y, annote = annotes[0]
self.drawAnnote(event.inaxes, x, y, annote)
for l in self.links:
l.drawSpecificAnnote(annote)
def __init__(self, xdata, ydata, annotations, axis=None):
self.points = np.array(zip(xdata, ydata))
self.annotations = annotations
self.nntree = spatial.cKDTree(self.points)
if axis is None:
self.axis = plt.gca()
else:
self.axis = axis
self.drawn_annotations = {}
# links to other AnnotationFinder instances
self.links = []

def drawAnnote(self, axis, x, y, annote):
"""
Draw the annotation on the plot
"""
if (x,y) in self.drawnAnnotations:
markers = self.drawnAnnotations[(x,y)]
for m in markers:
m.set_visible(not m.get_visible())
pylab.axes(self.axis)
if self.xmin is not None: pylab.xlim(xmin=self.xmin)
if self.ymin is not None: pylab.ylim(ymin=self.ymin)
if self.xmax is not None: pylab.xlim(xmax=self.xmax)
if self.ymax is not None: pylab.ylim(ymax=self.ymax)
self.axis.figure.canvas.draw()
else:
t = axis.text(x,y, "(%3.2f, %3.2f) - %s"%(x,y,annote), )
self.drawnAnnotations[(x,y)] = [t]
pylab.axes(self.axis)
if self.xmin is not None: pylab.xlim(xmin=self.xmin)
if self.ymin is not None: pylab.ylim(ymin=self.ymin)
if self.xmax is not None: pylab.xlim(xmax=self.xmax)
if self.ymax is not None: pylab.ylim(ymax=self.ymax)
self.axis.figure.canvas.draw()
def __call__(self, event):
if event.inaxes:
clicked = np.array([event.xdata, event.ydata])
if self.axis is None or self.axis == event.inaxes:
nnidx = self.nntree.query(clicked)[1]
x, y = self.points[nnidx]
annotation = self.annotations[nnidx]
self.draw_annotation(event.inaxes, x, y, annotation)
for link in self.links:
link.draw_specific_annotation(annotation)

def draw_annotation(self, axis, x, y, annotation):
"""Draw the annotation on the plot."""
if (x, y) in self.drawn_annotations:
markers = self.drawn_annotations[(x, y)]
for m in markers:
m.set_visible(not m.get_visible())
plt.axes(self.axis)
self.axis.figure.canvas.draw()
else:
t = axis.text(x, y, "(%3.2f, %3.2f) - %s" % (x, y, annotation), )
self.drawn_annotations[(x, y)] = [t]
plt.axes(self.axis)
self.axis.figure.canvas.draw()

def drawSpecificAnnote(self, annote):
annotesToDraw = [(x,y,a) for x,y,a in self.data if a==annote]
for x,y,a in annotesToDraw:
self.drawAnnote(self.axis, x, y, a)
def draw_specific_annotation(self, annotation):
to_draw = [(x, y, a)
for (x, y), a in zip(self.points, self.annotations)
if a == annotation]
for x, y, a in to_draw:
self.draw_annotation(self.axis, x, y, a)
6 changes: 2 additions & 4 deletions gala/viz.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import absolute_import
from .annotefinder import AnnoteFinder
from .annotefinder import AnnotationFinder
from math import ceil
import numpy as np
import scipy
Expand Down Expand Up @@ -233,9 +233,7 @@ def plot_vi_breakdown_panel(px, h, title, xlab, ylab, hlines, scatter_size,
plt.plot(x, val/x, color='gray', ls=':', **kwargs)
plt.scatter(px, h, label=title, s=scatter_size, **kwargs)
# Make points clickable to identify ID. This section needs work.
af = AnnoteFinder(px, h, [str(i) for i in range(len(px))],
xtol=0.005, ytol=0.005, xmin=-0.05*max(px), ymin=-0.05*max(px),
xmax = 1.05*max(px), ymax=1.05*max(h))
af = AnnotationFinder(px, h, [str(i) for i in range(len(px))])
plt.connect('button_press_event', af)
plt.xlim(xmin=-0.05*max(px), xmax=1.05*max(px))
plt.ylim(ymin=-0.05*max(h), ymax=1.05*max(h))
Expand Down
18 changes: 17 additions & 1 deletion tests/test_agglo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from gala import evaluate as ev


test_idxs = list(range(4))
test_idxs = list(range(6))
num_tests = len(test_idxs)
fns = [D + 'toy-data/test-%02i-probabilities.txt' % i for i in test_idxs]
probs = list(map(np.loadtxt, fns))
Expand Down Expand Up @@ -60,6 +60,22 @@ def test_no_dam_agglomeration():
assert_allclose(ev.vi(g.get_segmentation(), results[i]), 0.0,
err_msg='No dam agglomeration failed.')


def test_mito():
i = 5
def frozen(g, i):
"hardcoded frozen nodes representing mitochondria"
return i in [3, 4]
g = agglo.Rag(wss[i], probs[i], agglo.no_mito_merge(agglo.boundary_mean),
normalize_probabilities=True, isfrozennode=frozen)
g.agglomerate(0.15)
g.merge_priority_function = agglo.mito_merge()
g.rebuild_merge_queue()
g.agglomerate(1.0)
assert_allclose(ev.vi(g.get_segmentation(), results[i]), 0.0,
err_msg='Mito merge failed')


if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
Expand Down
3 changes: 3 additions & 0 deletions tests/toy-data/test-04-groundtruth.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 0 3 0
1 0 0 4
0 2 0 4
3 changes: 3 additions & 0 deletions tests/toy-data/test-04-probabilities.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0.1 0.6 0.2 0.8
0.3 1.0 0.8 0.2
0.8 0.0 1.0 0.4
3 changes: 3 additions & 0 deletions tests/toy-data/test-05-groundtruth.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 1 1 1 2
1 1 1 1 2
1 1 1 1 2
3 changes: 3 additions & 0 deletions tests/toy-data/test-05-probabilities.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0. 0. 0. 1. 1.
0. 5. 5. 1. 1.
.9 0. 0. 1. 1.
3 changes: 3 additions & 0 deletions tests/toy-data/test-05-watershed.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 1 1 1 2
1 3 4 1 2
5 1 1 1 2

0 comments on commit 7189f1e

Please sign in to comment.