Skip to content
This repository was archived by the owner on Feb 4, 2022. It is now read-only.

Commit fe978af

Browse files
committed
Integrate imagej-notebook code
1 parent 2b10a3b commit fe978af

8 files changed

Lines changed: 739 additions & 2 deletions

File tree

pom.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<groupId>com.github.jupyter</groupId>
125125
<artifactId>jvm-repr</artifactId>
126126
<version>master-SNAPSHOT</version>
127+
<type>jar</type>
127128
</dependency>
128129

129130
<!-- Supported languages -->
@@ -172,7 +173,6 @@
172173
<artifactId>scripting-scala</artifactId>
173174
</dependency>
174175

175-
176176
<!-- Test dependencies -->
177177
<dependency>
178178
<groupId>junit</groupId>
@@ -185,6 +185,17 @@
185185
<scope>test</scope>
186186
</dependency>
187187

188+
<!-- Temporary deps while stabilizing image-notebook code -->
189+
<dependency>
190+
<groupId>net.imglib2</groupId>
191+
<artifactId>imglib2</artifactId>
192+
</dependency>
193+
194+
<dependency>
195+
<groupId>net.imagej</groupId>
196+
<artifactId>imagej-ops</artifactId>
197+
</dependency>
198+
188199
</dependencies>
189200

190201
</project>
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*-
2+
* #%L
3+
* ImageJ software for multidimensional image processing and analysis.
4+
* %%
5+
* Copyright (C) 2017 Board of Regents of the University of
6+
* Wisconsin-Madison.
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
package net.imagej.notebook;
31+
32+
import com.twosigma.beaker.SerializeToString;
33+
import com.twosigma.beaker.mimetype.MIMEContainer;
34+
import java.lang.reflect.Method;
35+
import java.util.ArrayList;
36+
import java.util.Arrays;
37+
import java.util.Comparator;
38+
import java.util.List;
39+
import java.util.Map;
40+
import java.util.stream.Collectors;
41+
import jupyter.Displayer;
42+
import jupyter.Displayers;
43+
import jupyter.Registration;
44+
45+
import net.imagej.display.ColorTables;
46+
import net.imagej.notebook.displayer.ListDisplayer;
47+
import net.imagej.notebook.displayer.StringDisplayer;
48+
import net.imagej.ops.OpService;
49+
import net.imagej.ops.Ops;
50+
import net.imagej.ops.special.inplace.Inplaces;
51+
import net.imglib2.FinalInterval;
52+
import net.imglib2.IterableInterval;
53+
import net.imglib2.RandomAccessible;
54+
import net.imglib2.RandomAccessibleInterval;
55+
import net.imglib2.converter.Converter;
56+
import net.imglib2.converter.RealLUTConverter;
57+
import net.imglib2.display.ColorTable8;
58+
import net.imglib2.display.projector.composite.CompositeXYProjector;
59+
import net.imglib2.display.screenimage.awt.ARGBScreenImage;
60+
import net.imglib2.img.Img;
61+
import net.imglib2.type.NativeType;
62+
import net.imglib2.type.numeric.ARGBType;
63+
import net.imglib2.type.numeric.RealType;
64+
import net.imglib2.util.IntervalIndexer;
65+
import net.imglib2.util.Pair;
66+
import net.imglib2.util.Util;
67+
import net.imglib2.view.IntervalView;
68+
import net.imglib2.view.MixedTransformView;
69+
import org.scijava.log.LogService;
70+
71+
import org.scijava.plugin.Parameter;
72+
import org.scijava.plugin.Plugin;
73+
import org.scijava.service.AbstractService;
74+
import org.scijava.service.Service;
75+
import org.scijava.util.ClassUtils;
76+
77+
/**
78+
* AWT-driven implementation of {@link NotebookService}.
79+
*
80+
* @author Curtis Rueden
81+
*/
82+
@Plugin(type = Service.class)
83+
public class DefaultNotebookService extends AbstractService implements
84+
NotebookService {
85+
86+
@Parameter
87+
private OpService ops;
88+
89+
@Parameter
90+
private LogService log;
91+
92+
public Object test() {
93+
94+
Displayers.register(String.class, (Displayer) StringDisplayer.get());
95+
Displayers.register(List.class, (Displayer) ListDisplayer.get());
96+
97+
Object result = "salut ceci est un test";
98+
Object result2 = Arrays.asList("jjj", "llll", "dsdsdds");
99+
100+
Map<String, String> richResult = Displayers.display(result2);
101+
return richResult;
102+
}
103+
104+
public Object test2() {
105+
Object result = "salut ceci est un test";
106+
MIMEContainer niceResult = SerializeToString.doit(result);
107+
return niceResult;
108+
}
109+
110+
@Override
111+
public <T extends RealType<T>> Object display(
112+
final RandomAccessibleInterval<T> source, //
113+
final int xAxis, final int yAxis, final int cAxis, //
114+
final ValueScaling scaling, final long... pos) {
115+
final IntervalView<T> image = ops.transform().zeroMin(source);
116+
117+
final int w = xAxis >= 0 ? (int) image.dimension(xAxis) : 1;
118+
final int h = yAxis >= 0 ? (int) image.dimension(yAxis) : 1;
119+
final int c = cAxis >= 0 ? (int) image.dimension(cAxis) : 1;
120+
final ARGBScreenImage target = new ARGBScreenImage(w, h);
121+
final ArrayList<Converter<T, ARGBType>> converters = new ArrayList<>(c);
122+
123+
final double min, max;
124+
final boolean full = scaling == ValueScaling.FULL
125+
|| //
126+
scaling == ValueScaling.AUTO && isNarrowType(source);
127+
128+
if (full) {
129+
// scale the intensities based on the full range of the type
130+
min = image.firstElement().getMinValue();
131+
max = image.firstElement().getMaxValue();
132+
} else {
133+
// scale the intensities based on the sample values
134+
final IterableInterval<T> ii = ops.transform().flatIterable(source);
135+
final Pair<T, T> minMax = ops.stats().minMax(ii);
136+
min = minMax.getA().getRealDouble();
137+
max = minMax.getB().getRealDouble();
138+
}
139+
140+
for (int i = 0; i < c; i++) {
141+
final ColorTable8 lut = c == 1
142+
? //
143+
ColorTables.GRAYS : ColorTables.getDefaultColorTable(i);
144+
converters.add(new RealLUTConverter<T>(min, max, lut));
145+
}
146+
final CompositeXYProjector<T> proj = new CompositeXYProjector<>(image,
147+
target, converters, cAxis);
148+
if (pos != null && pos.length > 0) {
149+
proj.setPosition(pos);
150+
}
151+
proj.setComposite(true);
152+
proj.map();
153+
return target.image();
154+
}
155+
156+
@Override
157+
public <T extends RealType<T> & NativeType<T>> RandomAccessibleInterval<T>
158+
mosaic(final int[] gridLayout,
159+
@SuppressWarnings("unchecked") final RandomAccessibleInterval<T>... images) {
160+
// Count the actual number of image dimensions.
161+
int numDims = 0;
162+
for (int i = 0; i < images.length; i++) {
163+
numDims = Math.max(numDims, images[i].numDimensions());
164+
}
165+
166+
// Pad any missing grid dimensions.
167+
final int[] grid = new int[numDims];
168+
for (int d = 0; d < numDims; d++) {
169+
grid[d] = d < gridLayout.length ? gridLayout[d] : 1;
170+
}
171+
172+
// Define a buffer for holding multidimensional position indices.
173+
final int[] pos = new int[numDims];
174+
175+
// Compute grid box extents (width, height, etc.).
176+
final long[][] extents = new long[numDims][];
177+
for (int d = 0; d < numDims; d++) {
178+
extents[d] = new long[grid[d]];
179+
}
180+
for (int i = 0; i < images.length; i++) {
181+
IntervalIndexer.indexToPosition(i, grid, pos);
182+
for (int d = 0; d < numDims; d++) {
183+
if (pos[d] < grid[d]) {
184+
extents[d][pos[d]]
185+
= //
186+
Math.max(extents[d][pos[d]], images[i].dimension(d));
187+
}
188+
}
189+
}
190+
191+
// Compute grid box offsets.
192+
final long[][] offsets = new long[numDims][];
193+
for (int d = 0; d < numDims; d++) {
194+
offsets[d] = new long[grid[d] + 1];
195+
}
196+
for (int d = 0; d < numDims; d++) {
197+
for (int g = 0; g < grid[d]; g++) {
198+
offsets[d][g + 1] = offsets[d][g] + extents[d][g];
199+
}
200+
}
201+
202+
// Compute total mosaic dimensions.
203+
final long[] mosaicDims = new long[numDims];
204+
for (int d = 0; d < numDims; d++) {
205+
mosaicDims[d] = offsets[d][offsets[d].length - 1];
206+
}
207+
final FinalInterval mosaicBox = new FinalInterval(mosaicDims);
208+
209+
final Img<T> result
210+
= //
211+
ops.create().img(mosaicBox, Util.getTypeFromInterval(images[0]));
212+
213+
for (int i = 0; i < images.length; i++) {
214+
IntervalIndexer.indexToPosition(i, grid, pos);
215+
216+
// Skip images which will not appear on the grid.
217+
boolean outOfBounds = false;
218+
for (int d = 0; d < numDims; d++) {
219+
if (pos[d] >= grid[d]) {
220+
outOfBounds = true;
221+
break;
222+
}
223+
}
224+
if (outOfBounds) {
225+
continue;
226+
}
227+
228+
// Translate the origin of each image to match its position in the mosaic.
229+
final long[] offset = new long[numDims];
230+
for (int d = 0; d < numDims; d++) {
231+
offset[d] = offsets[d][pos[d]];
232+
}
233+
final MixedTransformView<T> translated
234+
= //
235+
ops.transform().translate(ops.transform().zeroMin(images[i]), offset);
236+
237+
// Unfortunately, this operation loses the "Interval" from the RAI:
238+
// translated objects are RAs, not RAIs.
239+
// So, we readd the bounds to match the newly translated coordinates.
240+
// NB: The max bound is _inclusive_, so we must subtract 1.
241+
final long[] max = new long[numDims];
242+
for (int d = 0; d < numDims; d++) {
243+
max[d] = offset[d] + images[i].dimension(d) - 1;
244+
}
245+
final FinalInterval bounds = new FinalInterval(offset, max);
246+
final RandomAccessibleInterval<T> bounded
247+
= //
248+
ops.transform().interval(translated, bounds);
249+
250+
// Declare that all values outside the interval proper will be 0.
251+
// If we do not perform this step, we will get an error when querying
252+
// out-of-bounds coordinates.
253+
final RandomAccessible<T> extended = ops.transform().extendZero(bounded);
254+
255+
// Define the interval of the image to match the size of the mosaic.
256+
final RandomAccessibleInterval<T> expanded
257+
= //
258+
ops.transform().interval(extended, mosaicBox);
259+
260+
// Add the full-size zero-padded translated image into the mosaic.
261+
Inplaces.binary1(ops, Ops.Math.Add.class, result, expanded).mutate1(
262+
result, expanded);
263+
}
264+
265+
// TODO: Some day, use Views.arrange, Views.tile or Views.combine instead.
266+
return result;
267+
}
268+
269+
@Override
270+
public NotebookTable methods(final Class<?> type, final String prefix) {
271+
final NotebookTable table = new NotebookTable();
272+
273+
final Method[] methods = type.getMethods();
274+
// NB: Methods are returned in inconsistent order.
275+
Arrays.sort(methods, new Comparator<Method>() {
276+
277+
@Override
278+
public int compare(final Method m1, final Method m2) {
279+
final int nameComp = m1.getName().compareTo(m2.getName());
280+
if (nameComp != 0) {
281+
return nameComp;
282+
}
283+
final int pCount1 = m1.getParameterCount();
284+
final int pCount2 = m2.getParameterCount();
285+
if (pCount1 != pCount2) {
286+
return pCount1 - pCount2;
287+
}
288+
final Class<?>[] pTypes1 = m1.getParameterTypes();
289+
final Class<?>[] pTypes2 = m2.getParameterTypes();
290+
for (int i = 0; i < pTypes1.length; i++) {
291+
final int typeComp = ClassUtils.compare(pTypes1[i], pTypes2[i]);
292+
if (typeComp != 0) {
293+
return typeComp;
294+
}
295+
}
296+
return ClassUtils.compare(m1.getReturnType(), m2.getReturnType());
297+
}
298+
});
299+
300+
for (final Method m : methods) {
301+
final String name = m.getName();
302+
if (!name.startsWith(prefix)) {
303+
continue;
304+
}
305+
306+
final Class<?>[] paramTypes = m.getParameterTypes();
307+
final List<String> args = Arrays.asList(paramTypes).stream().map(//
308+
c -> c.getName() //
309+
).collect(Collectors.toList());
310+
String arguments = args.toString();
311+
arguments = arguments.substring(1, arguments.length() - 1);
312+
if (arguments.isEmpty()) {
313+
arguments = "<none>";
314+
}
315+
316+
final String returns = m.getReturnType().getName();
317+
318+
table.addRow(//
319+
"name", name, //
320+
"arguments", arguments, //
321+
"returns", returns //
322+
);
323+
}
324+
return table;
325+
}
326+
327+
// -- Helper methods --
328+
private <T extends RealType<T>> boolean isNarrowType(
329+
final RandomAccessibleInterval<T> source) {
330+
return Util.getTypeFromInterval(source).getBitsPerPixel() <= 8;
331+
}
332+
}

0 commit comments

Comments
 (0)