Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
21b2933
A bitmask stored as a quadtree (or n-dimensional equivalent).
tpietzsch Jan 7, 2019
933b6be
Add SparseBitmask:
tpietzsch Jan 8, 2019
3d53a1f
Add SparseBitmask usage examples.
tpietzsch Jan 8, 2019
64cfdcc
Make Bitmask a outer class
maarzt Jan 9, 2019
5eb9114
WIP: add tiny tests + THERE'S A BUG
maarzt Jan 9, 2019
e9e97ca
"Fix" circular test dependency. Thanks @imagejan for this solution!!!
tpietzsch Jan 9, 2019
525a222
bugfix
tpietzsch Jan 9, 2019
ad4ad4e
Renamings:
maarzt Jan 10, 2019
388e956
Rename bbox to boundingBox
maarzt Jan 10, 2019
64ae2ae
Add FIXME comment for DefaultInterval
maarzt Jan 10, 2019
20f4377
Important to avoid wrong usage of this method
maarzt Jan 10, 2019
fee32f6
TODO comment implement Intervals.wrap()
maarzt Jan 10, 2019
f8f8d98
Rename BitMask to LeafBitmask
maarzt Jan 10, 2019
643ac4a
Tree: introduce method getChildIndex
maarzt Jan 10, 2019
76ed17a
Add Benchmark to compare performance of NTree agains SparseBitmask
maarzt Jan 11, 2019
d956e54
Add Benchmark against Labkit's sparse bitmaps.
maarzt Jan 11, 2019
5f1381f
Tree: move operations that modify NodeData into NodeData
maarzt Jan 11, 2019
0778e5d
Make NodeData an outer class
maarzt Jan 11, 2019
802463c
NodeData: rename data to bitmask
maarzt Jan 15, 2019
49c2187
Add final
maarzt Jan 15, 2019
d58b8c4
Improve thread-safety of Tree.get and Tree.getNode
maarzt Jan 15, 2019
87244a5
Make tree a tree with fixed height
maarzt Jan 15, 2019
34994a6
WIP
maarzt Jan 15, 2019
bc2b8f2
GrowableTree: some renamings
maarzt Jan 15, 2019
d4e2def
Make GrowableTree thread safe
maarzt Jan 15, 2019
820ea7a
Add read benchmark
maarzt Jan 15, 2019
6dfc807
Remove ReentrantReadWriteLock
maarzt Jan 15, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand All @@ -197,5 +209,16 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
<artifactId>junit-benchmarks</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>sc.fiji</groupId>
<artifactId>bigdataviewer-vistools</artifactId>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Maven build on Travis now fails because bigdataviewer-vistools contains a transient dependency on imglib2-roi, creating a circular dependency.

As this is a test scope dependency only, the issue can be avoided by adding an exclusion:

		<dependency>
			<groupId>sc.fiji</groupId>
			<artifactId>bigdataviewer-vistools</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>net.imglib2</groupId>
					<artifactId>imglib2-roi</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

I'm not sure though if it is the best way to address this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting hack—I didn't know that worked.Personally, I'd vote for the usage examples requiring vistools to move downstream. Perhaps into vistools itself, since it already depends on imglib2-roi transitively.

<scope>test</scope>
<exclusions>
<exclusion>
<groupId>net.imglib2</groupId>
<artifactId>imglib2-roi</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
293 changes: 293 additions & 0 deletions src/main/java/net/imglib2/roi/sparse/GrowableTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
package net.imglib2.roi.sparse;

import java.util.function.Predicate;

import net.imglib2.Interval;
import net.imglib2.roi.sparse.util.DefaultInterval;

/**
* A unbounded {@link SparseBitmaskNTree}, based on a {@link Tree} that grows
* when out-of-bounds pixels are set.
* <p>
* This class is thread-safe!
*
* @author Tobias Pietzsch
*/
public class GrowableTree implements SparseBitmaskNTree
{
private TreeAndOffset treeAndOffset;

/**
* @param leafDims
* Dimensions of a leaf bit-mask. <em>Every element must be a
* power of 2!</em>
*/
public GrowableTree( final int[] leafDims )
{
treeAndOffset = new TreeAndOffset( new Tree( leafDims, 0 ), new long[ leafDims.length ] );
}

@Override
public boolean get( final long[] position )
{
return get( position, new long[ numDimensions() ] );
}

@Override
public void set( final long[] position, final boolean value )
{
set( position, new long[ numDimensions() ], value );
}


/**
* Set the value at the specified position. If necessary, new nodes will be
* created. If possible, nodes will be merged.
* <p>
* {@code position} must be within bounds of this tree, i.e., within the
* interval covered by the root node.
*
* @param position
* coordinates within bounds of this tree.
* @param tmp
* pre-allocated array to store translated coordinates.
* @param value
* value to store at {@code position}.
*/
public synchronized void set( final long[] position, final long[] tmp, final boolean value )
{
final TreeAndOffset o = treeAndOffset;
final int n = o.tree.numDimensions();
int childindex = 0;
boolean outOfBounds = false;
long[] newOffset = null;
for ( int d = 0; d < n; ++d )
{
final long p = position[ d ] - o.offset[ d ];
tmp[ d ] = p;
outOfBounds = outOfBounds || p < 0 || p > o.tree.bounds().max( d );
if ( p < 0 )
{
if(newOffset == null)
newOffset = new long[n];
childindex |= 1 << d;
newOffset[ d ] -= o.tree.bounds().dimension( d );
}
}
if ( outOfBounds )
{
if ( value )
{
if(newOffset == null)
newOffset = new long[n];
treeAndOffset = new TreeAndOffset( Tree.newParentTree( o.tree, childindex ), newOffset );
set( position, tmp, value );
}
}
else
o.tree.set( tmp, value );
}

/**
* Get the value at the specified position.
* <p>
* {@code position} must be within bounds of this tree, i.e., within the
* interval covered by the root node.
*
* @param position
* coordinates within bounds of this tree.
* @param tmp
* pre-allocated array to store translated coordinates.
* @return the value at {@code position}.
*/
public boolean get( final long[] position, final long[] tmp )
{
TreeAndOffset o = treeAndOffset;
final int n = o.tree.numDimensions();
for ( int d = 0; d < n; ++d )
{
final long p = position[ d ] - o.offset[ d ];
if ( p < 0 || p > o.tree.bounds().max( d ) )
return false;
tmp[ d ] = p;
}
return o.tree.get( tmp );
}

/**
* Returns the current height of the tree.
* <p>
* The height is the length of the path from the root to a leaf (containing
* a bit mask). E.g., a tree comprising only a root nodeData has
* {@code height = 0}.
*
* @return the current height of the tree.
*/
@Override
public int height()
{
return treeAndOffset.tree.height();
}

@Override
public int numDimensions()
{
return treeAndOffset.tree.numDimensions();
}

@Override
public void forEach( final Predicate< Node > op )
{
TreeAndOffset o = treeAndOffset;
final TranslatedNode w = new TranslatedNode( o.offset );
o.tree.forEach( ( final Node nd ) -> op.test( w.wrap( nd ) ) );
}

@Override
public NodeIterator iterator()
{
return new TranslatedNodeIterator( treeAndOffset );
}

private static class TreeAndOffset
{
private final Tree tree;
private final long[] offset;

private TreeAndOffset( Tree tree, long[] offset )
{
this.tree = tree;
this.offset = offset;
}
}

private static class TranslatedNodeIterator implements NodeIterator
{
private final TranslatedNode w;

private final NodeIterator wi;

private TranslatedNodeIterator( TreeAndOffset tree )
{
w = new TranslatedNode( tree.offset );
wi = tree.tree.iterator();
}

private TranslatedNodeIterator( final TranslatedNodeIterator other )
{
w = new TranslatedNode( other.w );
wi = other.wi.copy();
}

@Override
public void reset()
{
wi.reset();
}

@Override
public boolean hasNext()
{
return wi.hasNext();
}

@Override
public Node next()
{
return w.wrap( wi.next() );
}

@Override
public Node current()
{
return w.wrap( wi.current() );
}

@Override
public NodeIterator copy()
{
return new TranslatedNodeIterator( this );
}
}

private static class TranslatedNode implements Node
{
private Node source;

private final long[] offset;

private TranslatedNode( final long[] offset )
{
this.offset = offset;
}

private TranslatedNode( final TranslatedNode other )
{
this.source = other.source;
this.offset = other.offset;
}

Node wrap( final Node source )
{
this.source = source;
return this;
}

private final Interval interval = new DefaultInterval()
{
@Override
public long min( final int d )
{
return source.interval().min( d ) + offset[ d ];
}

@Override
public long max( final int d )
{
return source.interval().max( d ) + offset[ d ];
}

@Override
public long dimension( final int d )
{
return source.interval().dimension( d );
}

@Override
public int numDimensions()
{
return source.interval().numDimensions();
}
};

@Override
public boolean hasChildren()
{
return source.hasChildren();
}

@Override
public boolean value()
{
return source.value();
}

@Override
public LeafBitmask bitmask()
{
return source.bitmask();
}

@Override
public Interval interval()
{
return interval;
}

@Override
public int level()
{
return source.level();
}
}
}
Loading