diff --git a/src/main/java/net/imglib2/img/sparse/SparseCSCImg.java b/src/main/java/net/imglib2/img/sparse/SparseCSCImg.java
new file mode 100644
index 000000000..5916f5592
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseCSCImg.java
@@ -0,0 +1,70 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.Cursor;
+import net.imglib2.Interval;
+import net.imglib2.RandomAccess;
+import net.imglib2.img.Img;
+import net.imglib2.img.ImgFactory;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+
+public class SparseCSCImg<
+		D extends NumericType<D> & NativeType<D>,
+		I extends IntegerType<I> & NativeType<I>> extends SparseImg<D,I> {
+
+    public SparseCSCImg(final long numCols, final long numRows, final Img<D> data, final Img<I> indices, final Img<I> indptr) {
+        super(numCols, numRows, data, indices, indptr);
+    }
+
+	@Override
+	public RandomAccess<D> randomAccess() {
+		return new SparseRandomAccess<D, I>(this, 1);
+	}
+
+	@Override
+	public Cursor<D> localizingCursor() {
+		return new SparseLocalizingCursor<>(this, 1, data.firstElement());
+	}
+
+	@Override
+	public SparseCSCImg<D,I> copy() {
+		Img<D> dataCopy = data.copy();
+		Img<I> indicesCopy = indices.copy();
+		Img<I> indptrCopy = indptr.copy();
+		return new SparseCSCImg<>(dimension(0), dimension(1), dataCopy, indicesCopy, indptrCopy);
+	}
+
+	@Override
+	public ImgFactory<D> factory() {
+		return new SparseImgFactory<>(data.getAt(0), indices.getAt(0), 1);
+	}
+
+	@Override
+	public ColumnMajorIterationOrder2D iterationOrder() {
+		return new ColumnMajorIterationOrder2D(this);
+	}
+
+	/**
+	 * An iteration order that scans a 2D image in column-major order.
+	 * I.e., cursors iterate column by column and row by row. For instance a
+	 * sparse img ranging from <em>(0,0)</em> to <em>(1,1)</em> is iterated like
+	 * <em>(0,0), (0,1), (1,0), (1,1)</em>
+	 */
+	public static class ColumnMajorIterationOrder2D {
+
+		private final Interval interval;
+		public ColumnMajorIterationOrder2D(final Interval interval) {
+			this.interval = interval;
+		}
+
+		@Override
+		public boolean equals(final Object obj) {
+
+			if (!(obj instanceof SparseCSCImg.ColumnMajorIterationOrder2D))
+				return false;
+
+			return SparseImg.haveSameIterationSpace(interval, ((ColumnMajorIterationOrder2D) obj).interval);
+		}
+	}
+}
diff --git a/src/main/java/net/imglib2/img/sparse/SparseCSRImg.java b/src/main/java/net/imglib2/img/sparse/SparseCSRImg.java
new file mode 100644
index 000000000..fcf155757
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseCSRImg.java
@@ -0,0 +1,70 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.Cursor;
+import net.imglib2.Interval;
+import net.imglib2.RandomAccess;
+import net.imglib2.img.Img;
+import net.imglib2.img.ImgFactory;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+
+public class SparseCSRImg<
+	D extends NumericType<D> & NativeType<D>,
+	I extends IntegerType<I> & NativeType<I>> extends SparseImg<D,I> {
+
+    public SparseCSRImg(final long numCols, final long numRows, final Img<D> data, final Img<I> indices, final Img<I> indptr) {
+        super(numCols, numRows, data, indices, indptr);
+    }
+
+	@Override
+	public RandomAccess<D> randomAccess() {
+		return new SparseRandomAccess<D, I>(this, 0);
+	}
+
+	@Override
+	public Cursor<D> localizingCursor() {
+		return new SparseLocalizingCursor<>(this, 0, data.firstElement());
+	}
+
+	@Override
+	public RowMajorIterationOrder2D iterationOrder() {
+		return new RowMajorIterationOrder2D(this);
+	}
+
+	@Override
+	public SparseCSRImg<D,I> copy() {
+		Img<D> dataCopy = data.copy();
+		Img<I> indicesCopy = indices.copy();
+		Img<I> indptrCopy = indptr.copy();
+		return new SparseCSRImg<>(dimension(0), dimension(1), dataCopy, indicesCopy, indptrCopy);
+	}
+
+	@Override
+	public ImgFactory<D> factory() {
+		return new SparseImgFactory<>(data.getAt(0), indices.getAt(0), 0);
+	}
+
+	/**
+	 * An iteration order that scans a 2D image in row-major order.
+	 * I.e., cursors iterate row by row and column by column. For instance a
+	 * sparse img ranging from <em>(0,0)</em> to <em>(1,1)</em> is iterated like
+	 * <em>(0,0), (1,0), (0,1), (1,1)</em>
+	 */
+	public static class RowMajorIterationOrder2D {
+
+		private final Interval interval;
+		public RowMajorIterationOrder2D(final Interval interval) {
+			this.interval = interval;
+		}
+
+		@Override
+		public boolean equals(final Object obj) {
+
+			if (!(obj instanceof RowMajorIterationOrder2D))
+				return false;
+
+			return SparseImg.haveSameIterationSpace(interval, ((RowMajorIterationOrder2D) obj).interval);
+		}
+	}
+}
diff --git a/src/main/java/net/imglib2/img/sparse/SparseImg.java b/src/main/java/net/imglib2/img/sparse/SparseImg.java
new file mode 100644
index 000000000..6a1ee94ca
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseImg.java
@@ -0,0 +1,173 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.Cursor;
+import net.imglib2.Interval;
+import net.imglib2.RandomAccess;
+import net.imglib2.img.Img;
+import net.imglib2.img.array.ArrayImgFactory;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+import net.imglib2.type.numeric.integer.LongType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+abstract public class SparseImg<
+        D extends NumericType<D> & NativeType<D>,
+        I extends IntegerType<I> & NativeType<I>> implements Img<D> {
+
+    protected final long[] max;
+    protected final Img<D> data;
+    protected final Img<I> indices;
+    protected final Img<I> indptr;
+
+    public SparseImg(long numCols, long numRows, Img<D> data, Img<I> indices, Img<I> indptr) {
+
+        this.data = data;
+        this.indices = indices;
+        this.indptr = indptr;
+        this.max = new long[]{numCols-1, numRows-1};
+
+        if (data.numDimensions() != 1 || indices.numDimensions() != 1 || indptr.numDimensions() != 1)
+            throw new IllegalArgumentException("Data, index, and indptr Img must be one dimensional.");
+        if (data.min(0) != 0 || indices.min(0) != 0 || indptr.min(0) != 0)
+            throw new IllegalArgumentException("Data, index, and indptr arrays must start from 0.");
+        if (data.max(0) != indices.max(0))
+            throw new IllegalArgumentException("Data and index array must be of the same size.");
+        if (indptr.max(0) != max[0]+1 && indptr.max(0) != max[1]+1)
+            throw new IllegalArgumentException("Indptr array does not fit number of slices.");
+    }
+
+    public static <T extends NumericType<T> & NativeType<T>> SparseImg<T, LongType> convertToSparse(Img<T> img) {
+        return convertToSparse(img, 0); // CSR per default
+    }
+
+    public static <T extends NumericType<T> & NativeType<T>> SparseImg<T, LongType> convertToSparse(Img<T> img, int leadingDimension) {
+        if (leadingDimension != 0 && leadingDimension != 1)
+            throw new IllegalArgumentException("Leading dimension in sparse array must be 0 or 1.");
+
+        T zeroValue = img.getAt(0, 0).copy();
+        zeroValue.setZero();
+
+        int nnz = getNumberOfNonzeros(img);
+        int ptrDimension = 1 - leadingDimension;
+        Img<T> data = new ArrayImgFactory<>(zeroValue).create(nnz);
+        Img<LongType> indices = new ArrayImgFactory<>(new LongType()).create(nnz);
+        Img<LongType> indptr = new ArrayImgFactory<>(new LongType()).create(img.dimension(ptrDimension) + 1);
+
+        long count = 0;
+        T actualValue;
+        RandomAccess<T> ra = img.randomAccess();
+        RandomAccess<T> dataAccess = data.randomAccess();
+        RandomAccess<LongType> indicesAccess = indices.randomAccess();
+        RandomAccess<LongType> indptrAccess = indptr.randomAccess();
+        indptrAccess.setPosition(0,0);
+        indptrAccess.get().setLong(0L);
+
+        for (long j = 0; j < img.dimension(ptrDimension); j++) {
+            ra.setPosition(j, ptrDimension);
+            for (long i = 0; i < img.dimension(leadingDimension); i++) {
+                ra.setPosition(i, leadingDimension);
+                actualValue = ra.get();
+                if (!actualValue.valueEquals(zeroValue)) {
+                    dataAccess.setPosition(count, 0);
+                    dataAccess.get().set(actualValue);
+                    indicesAccess.setPosition(count, 0);
+                    indicesAccess.get().setLong(i);
+                    count++;
+                }
+            }
+            indptrAccess.fwd(0);
+            indptrAccess.get().setLong(count);
+        }
+
+        return (leadingDimension == 0) ? new SparseCSRImg<>(img.dimension(0), img.dimension(1), data, indices, indptr)
+            : new SparseCSCImg<>(img.dimension(0), img.dimension(1), data, indices, indptr);
+    }
+
+    public static <T extends NumericType<T>> int getNumberOfNonzeros(Img<T> img) {
+        T zeroValue = img.getAt(0, 0).copy();
+        zeroValue.setZero();
+
+        int nnz = 0;
+        for (T pixel : img)
+            if (!pixel.valueEquals(zeroValue))
+                ++nnz;
+        return nnz;
+    }
+
+    @Override
+    public long min(int d) {
+        return 0L;
+    }
+
+    @Override
+    public long max(int d) {
+        return max[d];
+    }
+
+    @Override
+    public int numDimensions() {
+        return 2;
+    }
+
+    @Override
+    public RandomAccess<D> randomAccess(Interval interval) {
+        return randomAccess();
+    }
+
+    public Img<D> getDataArray() {
+        return data;
+    }
+
+    public Img<I> getIndicesArray() {
+        return indices;
+    }
+
+    public Img<I> getIndexPointerArray() {
+        return indptr;
+    }
+
+    @Override
+    public Cursor<D> cursor() {
+        return localizingCursor();
+    }
+
+    @Override
+    public long size() {
+        return max[0] * max[1];
+    }
+
+    /**
+     * Checks if two intervals have the same iteration space.
+     *
+     * @param a One interval
+     * @param b Other interval
+     * @return true if both intervals have compatible non-singleton dimensions, false otherwise
+     */
+    protected static boolean haveSameIterationSpace(Interval a, Interval b) {
+        List<Integer> nonSingletonDimA = nonSingletonDimensions(a);
+        List<Integer> nonSingletonDimB = nonSingletonDimensions(b);
+
+        if (nonSingletonDimA.size() != nonSingletonDimB.size())
+            return false;
+
+        for (int i = 0; i < nonSingletonDimA.size(); i++) {
+            Integer dimA = nonSingletonDimA.get(i);
+            Integer dimB = nonSingletonDimB.get(i);
+            if (a.min(dimA) != b.min(dimB) || a.max(dimA) != b.max(dimB))
+                return false;
+        }
+
+        return true;
+    }
+
+    protected static List<Integer> nonSingletonDimensions(Interval interval) {
+        List<Integer> nonSingletonDim = new ArrayList<>();
+        for (int i = 0; i < interval.numDimensions(); i++)
+            if (interval.dimension(i) > 1)
+                nonSingletonDim.add(i);
+        return nonSingletonDim;
+    }
+}
diff --git a/src/main/java/net/imglib2/img/sparse/SparseImgFactory.java b/src/main/java/net/imglib2/img/sparse/SparseImgFactory.java
new file mode 100644
index 000000000..50c96c42b
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseImgFactory.java
@@ -0,0 +1,60 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.Dimensions;
+import net.imglib2.exception.IncompatibleTypeException;
+import net.imglib2.img.Img;
+import net.imglib2.img.ImgFactory;
+import net.imglib2.img.array.ArrayImg;
+import net.imglib2.img.array.ArrayImgFactory;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+
+/**
+ * Factory for {@link SparseImg}s.
+ * @param <D> type of data
+ * @param <I> type of indices
+ */
+public class SparseImgFactory<
+		D extends NumericType<D> & NativeType<D>,
+		I extends IntegerType<I> & NativeType<I>> extends ImgFactory<D> {
+
+	protected final int leadingDimension;
+	protected final I indexType;
+
+
+	protected SparseImgFactory(D type, I indexType, int leadingDimension) {
+		super(type);
+		this.leadingDimension = leadingDimension;
+		this.indexType = indexType;
+	}
+
+	@Override
+	public SparseImg<D, I> create(long... dimensions) {
+		if (dimensions.length != 2)
+			throw new IllegalArgumentException("Only 2D images are supported");
+
+		Dimensions.verify(dimensions);
+		ArrayImg<D, ?> data = new ArrayImgFactory<>(type()).create(1);
+		ArrayImg<I, ?> indices = new ArrayImgFactory<>(indexType).create(1);
+		int secondaryDimension = 1 - leadingDimension;
+		ArrayImg<I, ?> indptr = new ArrayImgFactory<>(indexType).create(dimensions[secondaryDimension] + 1);
+
+		return (leadingDimension == 0) ? new SparseCSRImg<>(dimensions[0], dimensions[1], data, indices, indptr)
+			: new SparseCSCImg<>(dimensions[0], dimensions[1], data, indices, indptr);
+	}
+
+	@Override
+	@SuppressWarnings({"unchecked", "rawtypes"})
+	public <S> ImgFactory<S> imgFactory(S type) throws IncompatibleTypeException {
+		if (type instanceof NumericType && type instanceof NativeType)
+			return new SparseImgFactory<>((NumericType & NativeType) type, indexType, leadingDimension);
+		else
+			throw new IncompatibleTypeException(this, type.getClass().getCanonicalName() + " does not implement NumericType & NativeType.");
+	}
+
+	@Override
+	public Img<D> create(long[] dim, D type) {
+		return create(dim);
+	}
+}
diff --git a/src/main/java/net/imglib2/img/sparse/SparseLocalizingCursor.java b/src/main/java/net/imglib2/img/sparse/SparseLocalizingCursor.java
new file mode 100644
index 000000000..1ad75ed70
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseLocalizingCursor.java
@@ -0,0 +1,116 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.AbstractLocalizingCursor;
+import net.imglib2.Cursor;
+import net.imglib2.RandomAccess;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+
+public class SparseLocalizingCursor<T extends NumericType<T> & NativeType<T>> extends AbstractLocalizingCursor<T> {
+
+	private final long[] max;
+	private boolean isHit = false;
+	private final int leadingDim;
+	private final int secondaryDim;
+	private final T fillValue;
+
+	private SparseImg<T,?> img = null;
+	private Cursor<T> dataCursor;
+	private Cursor<? extends IntegerType<?>> indicesCursor;
+	private RandomAccess<? extends IntegerType<?>> indptrAccess;
+
+
+	public SparseLocalizingCursor(int n) {
+		super(n);
+		if (n != 2)
+			throw new IllegalArgumentException("Only 2D images are supported");
+
+		max = new long[]{0L, 0L};
+		img = null;
+		dataCursor = null;
+		indicesCursor = null;
+		indptrAccess = null;
+		fillValue = null;
+		leadingDim = 0;
+		secondaryDim = 0;
+	}
+
+	public SparseLocalizingCursor(SparseImg<T,?> img, int leadingDimension, T fillValue) {
+		super(img.numDimensions());
+		if (n != 2)
+			throw new IllegalArgumentException("Only 2D images are supported");
+
+		this.img = img;
+		max = new long[]{img.dimension(0)-1, img.dimension(1)-1};
+		this.leadingDim = leadingDimension;
+		this.secondaryDim = 1 - leadingDimension;
+
+		this.dataCursor = img.data.cursor();
+		this.indicesCursor = img.indices.localizingCursor();
+		this.indptrAccess = img.indptr.randomAccess();
+		dataCursor.fwd();
+		indicesCursor.fwd();
+		indptrAccess.setPosition(0, 0);
+
+		this.fillValue = fillValue.copy();
+		this.fillValue.setZero();
+
+	}
+
+	@Override
+	public T get() {
+		if (isHit)
+			return dataCursor.get();
+		return fillValue;
+	}
+
+	@Override
+	public SparseLocalizingCursor<T> copy() {
+		return new SparseLocalizingCursor<>(img, leadingDim, fillValue);
+	}
+
+	@Override
+	public void fwd() {
+		if (isHit)
+			advanceToNextNonzeroElement();
+
+		// always: advance to next element in picture ...
+		if (position[leadingDim] < max[leadingDim]) {
+			++position[leadingDim];
+		} else {
+			position[leadingDim] = 0;
+			++position[secondaryDim];
+		}
+
+		// ... and check if it is a hit
+		isHit = indicesCursor.get().getIntegerLong() == position[leadingDim]
+				&& indptrAccess.getLongPosition(0) == position[secondaryDim];
+	}
+
+	protected void advanceToNextNonzeroElement() {
+		if (indicesCursor.hasNext()) {
+			dataCursor.fwd();
+			indicesCursor.fwd();
+		}
+		long currentIndexPosition = indicesCursor.getLongPosition(0);
+		indptrAccess.fwd(0);
+		while (indptrAccess.get().getIntegerLong() <= currentIndexPosition)
+			indptrAccess.fwd(0);
+		indptrAccess.bck(0);
+	}
+
+	@Override
+	public void reset() {
+		position[leadingDim] = -1;
+		position[secondaryDim] = 0;
+		dataCursor.reset();
+		indicesCursor.reset();
+		indptrAccess.setPosition(0,0);
+	}
+
+	@Override
+	public boolean hasNext() {
+		return (position[0] < max[0] || position[1] < max[1]);
+	}
+}
diff --git a/src/main/java/net/imglib2/img/sparse/SparseRandomAccess.java b/src/main/java/net/imglib2/img/sparse/SparseRandomAccess.java
new file mode 100644
index 000000000..25071192a
--- /dev/null
+++ b/src/main/java/net/imglib2/img/sparse/SparseRandomAccess.java
@@ -0,0 +1,158 @@
+package net.imglib2.img.sparse;
+
+import net.imglib2.AbstractLocalizable;
+import net.imglib2.Localizable;
+import net.imglib2.RandomAccess;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.NumericType;
+
+public class SparseRandomAccess<
+        D extends NumericType<D> & NativeType<D>,
+        I extends IntegerType<I> & NativeType<I>> extends AbstractLocalizable implements RandomAccess<D> {
+
+    protected final SparseImg<D, I> rai;
+    protected final RandomAccess<D> dataAccess;
+    protected final RandomAccess<I> indicesAccess;
+    protected final RandomAccess<I> indptrAccess;
+    protected final int leadingDim;
+    protected final int secondaryDim;
+    protected final D fillValue;
+
+    public SparseRandomAccess(SparseImg<D, I> rai, int leadingDim) {
+        super(rai.numDimensions());
+        this.rai = rai;
+        this.dataAccess = rai.data.randomAccess();
+        this.indicesAccess = rai.indices.randomAccess();
+        this.indptrAccess = rai.indptr.randomAccess();
+        this.leadingDim = leadingDim;
+        this.secondaryDim = 1 - leadingDim;
+
+        this.fillValue = dataAccess.get().createVariable();
+        this.fillValue.setZero();
+    }
+
+    public SparseRandomAccess(SparseRandomAccess<D, I> ra) {
+        super(ra.rai.numDimensions());
+
+        this.rai = ra.rai;
+        this.leadingDim = ra.leadingDim;
+        this.secondaryDim = ra.secondaryDim;
+        this.setPosition( ra );
+
+        // not implementing copy() methods here had no effect since only setPosition() is used
+        this.indicesAccess = ra.indicesAccess.copy();
+        this.indptrAccess = ra.indptrAccess.copy();
+        this.dataAccess = ra.dataAccess.copy();
+        this.fillValue = ra.fillValue.createVariable();
+        this.fillValue.setZero();
+    }
+
+    @Override
+    public void fwd(int d) {
+        ++position[d];
+    }
+
+    @Override
+    public void bck(int d) {
+        --position[d];
+    }
+
+    @Override
+    public void move(int distance, int d) {
+        position[d] += distance;
+    }
+
+    @Override
+    public void move(long distance, int d) {
+        position[d] += distance;
+    }
+
+    @Override
+    public void move(Localizable localizable) {
+        for (int d = 0; d < n; ++d) {
+            position[d] += localizable.getLongPosition(d);
+        }
+    }
+
+    @Override
+    public void move(int[] distance) {
+        for (int d = 0; d < n; ++d) {
+            position[d] += distance[d];
+        }
+    }
+
+    @Override
+    public void move(long[] distance) {
+        for (int d = 0; d < n; ++d) {
+            position[d] += distance[d];
+        }
+    }
+
+    @Override
+    public void setPosition(Localizable localizable) {
+        for (int d = 0; d < n; ++d) {
+            position[d]  = localizable.getLongPosition(d);
+        }
+    }
+
+    @Override
+    public void setPosition(int[] position) {
+        for (int d = 0; d < n; ++d) {
+            this.position[d] = position[d];
+        }
+    }
+
+    @Override
+    public void setPosition(long[] position) {
+        for (int d = 0; d < n; ++d) {
+            this.position[d] = position[d];
+        }
+    }
+
+    @Override
+    public void setPosition(int position, int d) {
+        this.position[d] = position;
+    }
+
+    @Override
+    public void setPosition(long position, int d) {
+        this.position[d] = position;
+    }
+
+    @Override
+    public D get() {
+
+        // determine range of indices to search
+        indptrAccess.setPosition(position[secondaryDim], 0);
+        long start = indptrAccess.get().getIntegerLong();
+        indptrAccess.fwd(0);
+        long end = indptrAccess.get().getIntegerLong();
+
+        if (start == end)
+            return fillValue;
+
+        long current, currentInd;
+        do {
+            current = (start + end) / 2L;
+            indicesAccess.setPosition(current, 0);
+            currentInd = indicesAccess.get().getIntegerLong();
+
+            if (currentInd == position[leadingDim]) {
+                dataAccess.setPosition(indicesAccess);
+                return dataAccess.get();
+            }
+            if (currentInd < position[leadingDim])
+                start = current;
+            if (currentInd > position[leadingDim])
+                end = current;
+        } while (current != start || (end - start) > 1L);
+
+        return fillValue;
+    }
+
+    @Override
+    public RandomAccess<D> copy() {
+        return new SparseRandomAccess<>(this);
+    }
+}
diff --git a/src/test/java/net/imglib2/img/sparse/SparseImageTest.java b/src/test/java/net/imglib2/img/sparse/SparseImageTest.java
new file mode 100644
index 000000000..cfb378de6
--- /dev/null
+++ b/src/test/java/net/imglib2/img/sparse/SparseImageTest.java
@@ -0,0 +1,113 @@
+package net.imglib2.img.sparse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.Img;
+import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.type.Type;
+import net.imglib2.type.numeric.integer.LongType;
+import net.imglib2.type.numeric.real.DoubleType;
+import net.imglib2.view.Views;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+
+public class SparseImageTest {
+
+	protected static Map<String, SparseImg<DoubleType, LongType>> sparseImgs;
+
+	@Test
+	public void CsrSetupIsCorrect() {
+		SparseCSRImg<DoubleType, LongType> csr = setupCsr();
+		assertEquals(2, csr.numDimensions());
+		assertArrayEquals(new long[]{0, 0}, csr.minAsLongArray());
+		assertArrayEquals(new long[]{9, 8}, csr.maxAsLongArray());
+	}
+
+	@Test
+	public void iterationOrderEqualityTestIsCorrect() {
+		SparseCSRImg<DoubleType, LongType> csr = setupCsr();
+		SparseCSRImg<DoubleType, LongType> csr2 = csr.copy();
+		SparseCSCImg<DoubleType, LongType> csc = setupCsc();
+
+		assertEquals(csr.iterationOrder(), csr.iterationOrder());
+		assertEquals(csr.iterationOrder(), csr2.iterationOrder());
+		assertNotEquals(csr.iterationOrder(), csc.iterationOrder());
+	}
+
+	@Test
+	public void CsrNonzeroEntriesAreCorrect() {
+		int[] x = new int[]{2, 5, 0, 6, 9};
+		int[] y = new int[]{0, 1, 2, 8, 8};
+
+		for (int i = 0; i < x.length; i++) {
+			RandomAccess<DoubleType> ra = setupCsr().randomAccess();
+			assertEquals("Mismatch for x=" + x[i] + ", y=" + y[i], 1.0, ra.setPositionAndGet(x[i],y[i]).getRealDouble(), 1e-6);
+		}
+	}
+
+	@Test
+	public void sparseHasCorrectNumberOfNonzeros() {
+		for (Map.Entry<String, SparseImg<DoubleType, LongType>> entry : sparseImgs.entrySet()) {
+			assertEquals("Mismatch for " + entry.getKey(), 5, SparseImg.getNumberOfNonzeros(entry.getValue()));
+		}
+	}
+
+	@Test
+	public void conversionToSparseIsCorrect() {
+		for (SparseImg<DoubleType, LongType> sparse : sparseImgs.values()) {
+			assertEquals(5, SparseImg.getNumberOfNonzeros(sparse));
+			SparseImg<DoubleType, LongType> newCsr = SparseImg.convertToSparse(sparse, 0);
+			assertTrue(newCsr instanceof SparseCSRImg);
+			assert2DRaiEquals(sparse, newCsr);
+			SparseImg<DoubleType, LongType> newCsc = SparseImg.convertToSparse(sparse, 1);
+			assertTrue(newCsc instanceof SparseCSCImg);
+			assert2DRaiEquals(sparse, newCsc);
+		}
+	}
+
+	@Test
+	public void CscIsCsrTransposed() {
+		SparseCSRImg<DoubleType, LongType> csr = setupCsr();
+		SparseCSCImg<DoubleType, LongType> csc = setupCsc();
+		assert2DRaiEquals(csr, Views.permute(csc, 0, 1));
+	}
+
+	protected SparseCSRImg<DoubleType, LongType> setupCsr() {
+		return (SparseCSRImg<DoubleType, LongType>) sparseImgs.get("CSR");
+	}
+
+	protected SparseCSCImg<DoubleType, LongType> setupCsc() {
+		return (SparseCSCImg<DoubleType, LongType>) sparseImgs.get("CSC");
+	}
+
+	@BeforeClass
+	public static void setupSparseImages() {
+		Img<DoubleType> data = ArrayImgs.doubles(new double[]{1.0, 1.0, 1.0, 1.0, 1.0}, 5);
+		Img<LongType> indices = ArrayImgs.longs(new long[]{2L, 5L, 0L, 6L, 9L}, 5);
+		Img<LongType> indptr = ArrayImgs.longs(new long[]{0L, 1L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 5L}, 10);
+
+		sparseImgs = new HashMap<>();
+		sparseImgs.put("CSR", new SparseCSRImg<>(10, 9, data, indices, indptr));
+		sparseImgs.put("CSC", new SparseCSCImg<>(9, 10, data, indices, indptr));
+	}
+
+	protected static <T extends Type<T>> void assert2DRaiEquals(RandomAccessibleInterval<T> expected, RandomAccessibleInterval<T> actual) {
+		assertEquals("Number of columns not the same.", expected.dimension(0), actual.dimension(0));
+		assertEquals("Number of rows not the same.", expected.dimension(1), actual.dimension(1));
+
+		RandomAccess<T> raExpected = expected.randomAccess();
+		RandomAccess<T> raActual = actual.randomAccess();
+		for (int i = 0; i < expected.dimension(0); ++i)
+			for (int j = 0; j < expected.dimension(1); ++j)
+				assertEquals("Rai's differ on entry (" + i + "," + j +")", raExpected.setPositionAndGet(i, j), raActual.setPositionAndGet(i, j));
+	}
+}
\ No newline at end of file