Skip to content

Commit 7f5dc6f

Browse files
committed
Merge branch 'editor-overlay' of github.com:trackmate-sc/TrackMate into editor-overlay
2 parents fdb6eb2 + 9ddd6fb commit 7f5dc6f

4 files changed

Lines changed: 401 additions & 12 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
</properties>
184184

185185
<dependencies>
186+
187+
<dependency>
188+
<groupId>org.slf4j</groupId>
189+
<artifactId>slf4j-simple</artifactId>
190+
</dependency>
191+
186192
<!-- Fiji dependencies -->
187193
<dependency>
188194
<groupId>sc.fiji</groupId>
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package fiji.plugin.trackmate.gui.editor.labkit.component;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.concurrent.ExecutorService;
6+
import java.util.stream.Collectors;
7+
8+
import bdv.viewer.SourceAndConverter;
9+
import bdv.viewer.render.AccumulateProjectorFactory;
10+
import bdv.viewer.render.ProjectorUtils;
11+
import bdv.viewer.render.ProjectorUtils.ArrayData;
12+
import bdv.viewer.render.VolatileProjector;
13+
import net.imglib2.Interval;
14+
import net.imglib2.RandomAccessible;
15+
import net.imglib2.RandomAccessibleInterval;
16+
import net.imglib2.type.numeric.ARGBType;
17+
import net.imglib2.util.Intervals;
18+
import net.imglib2.util.StopWatch;
19+
20+
public class SrcOverAccumulateProjectorARGB
21+
{
22+
23+
public static class Factory implements AccumulateProjectorFactory< ARGBType >
24+
{
25+
26+
private float[] sourceAlphas;
27+
28+
public Factory( final float[] sourceAlphas )
29+
{
30+
this.sourceAlphas = sourceAlphas;
31+
}
32+
33+
public void setSourceAlpha( final int sourceIndex, final float alpha )
34+
{
35+
if ( sourceIndex >= 0 && sourceIndex < sourceAlphas.length )
36+
{
37+
this.sourceAlphas[ sourceIndex ] = Math.max( 0, Math.min( 1, alpha ) );
38+
}
39+
}
40+
41+
public float getSourceAlpha( final int sourceIndex )
42+
{
43+
return ( sourceIndex >= 0 && sourceIndex < sourceAlphas.length )
44+
? sourceAlphas[ sourceIndex ]
45+
: 1.0f;
46+
}
47+
48+
@Override
49+
public VolatileProjector createProjector(
50+
final List< VolatileProjector > sourceProjectors,
51+
final List< SourceAndConverter< ? > > sources,
52+
final List< ? extends RandomAccessible< ? extends ARGBType > > sourceScreenImages,
53+
final RandomAccessibleInterval< ARGBType > targetScreenImage,
54+
final int numThreads,
55+
final ExecutorService executorService )
56+
{
57+
58+
final ProjectorData projectorData = getProjectorData( sourceScreenImages, targetScreenImage );
59+
if ( projectorData == null )
60+
{
61+
// Fallback to generic (not optimized)
62+
return null;
63+
}
64+
65+
return new SrcOverProjectorARGBArrayData( sourceProjectors, projectorData, sourceAlphas );
66+
}
67+
}
68+
69+
private static class ProjectorData
70+
{
71+
private final ArrayData targetData;
72+
73+
private final List< int[] > sourceData;
74+
75+
ProjectorData( final ArrayData targetData, final List< int[] > sourceData )
76+
{
77+
this.targetData = targetData;
78+
this.sourceData = sourceData;
79+
}
80+
81+
ArrayData targetData()
82+
{
83+
return targetData;
84+
}
85+
86+
List< int[] > sourceData()
87+
{
88+
return sourceData;
89+
}
90+
}
91+
92+
private static ProjectorData getProjectorData(
93+
final List< ? extends RandomAccessible< ? extends ARGBType > > sources,
94+
final RandomAccessibleInterval< ARGBType > target )
95+
{
96+
97+
final ArrayData targetData = ProjectorUtils.getARGBArrayData( target );
98+
if ( targetData == null )
99+
return null;
100+
101+
final int numSources = sources.size();
102+
final List< int[] > sourceData = new ArrayList<>( numSources );
103+
for ( int i = 0; i < numSources; ++i )
104+
{
105+
final RandomAccessible< ? extends ARGBType > source = sources.get( i );
106+
if ( !( source instanceof RandomAccessibleInterval ) )
107+
return null;
108+
if ( !Intervals.equals( target, ( Interval ) source ) )
109+
return null;
110+
final int[] data = ProjectorUtils.getARGBArrayImgData( source );
111+
if ( data == null )
112+
return null;
113+
sourceData.add( data );
114+
}
115+
116+
return new ProjectorData( targetData, sourceData );
117+
}
118+
119+
private static class SrcOverProjectorARGBArrayData implements VolatileProjector
120+
{
121+
122+
private List< VolatileProjector > sourceProjectors;
123+
124+
private final List< int[] > sources;
125+
126+
private final ArrayData target;
127+
128+
private final float[] sourceAlphas;
129+
130+
private long lastFrameRenderNanoTime;
131+
132+
private volatile boolean canceled = false;
133+
134+
private volatile boolean valid = false;
135+
136+
public SrcOverProjectorARGBArrayData(
137+
final List< VolatileProjector > sourceProjectors,
138+
final ProjectorData projectorData,
139+
final float[] sourceAlphas )
140+
{
141+
142+
this.sourceProjectors = sourceProjectors;
143+
this.target = projectorData.targetData();
144+
this.sources = projectorData.sourceData();
145+
this.sourceAlphas = sourceAlphas;
146+
}
147+
148+
@Override
149+
public boolean map( final boolean clearUntouchedTargetPixels )
150+
{
151+
if ( canceled )
152+
return false;
153+
154+
if ( isValid() )
155+
return true;
156+
157+
final StopWatch stopWatch = StopWatch.createAndStart();
158+
159+
// Render each source
160+
sourceProjectors.forEach( p -> p.map( clearUntouchedTargetPixels ) );
161+
162+
if ( canceled )
163+
return false;
164+
165+
// Composite with SRC_OVER
166+
mapAccumulate();
167+
168+
sourceProjectors = sourceProjectors.stream()
169+
.filter( p -> !p.isValid() )
170+
.collect( Collectors.toList() );
171+
172+
lastFrameRenderNanoTime = stopWatch.nanoTime();
173+
valid = sourceProjectors.isEmpty();
174+
return !canceled;
175+
}
176+
177+
/**
178+
* Accumulate pixels of all sources to target using SRC_OVER blending.
179+
*/
180+
private void mapAccumulate()
181+
{
182+
if ( canceled )
183+
return;
184+
185+
final int numSources = sources.size();
186+
final int width = target.width();
187+
final int height = target.height();
188+
final int[] targetData = target.data();
189+
final int targetStride = target.stride();
190+
final int targetOx = target.ox();
191+
final int targetOy = target.oy();
192+
193+
// Process each row
194+
for ( int y = 0; y < height; ++y )
195+
{
196+
final int oTarget = ( y + targetOy ) * targetStride + targetOx;
197+
final int oSource = y * width;
198+
199+
// Process each pixel in the row
200+
for ( int x = 0; x < width; ++x )
201+
{
202+
int resultARGB = 0;
203+
204+
// Composite each source front-to-back
205+
for ( int s = 0; s < numSources; ++s )
206+
{
207+
final int[] source = sources.get( s );
208+
final int sourceARGB = source[ oSource + x ];
209+
210+
// Get global alpha for this source
211+
final float globalAlpha = ( s < sourceAlphas.length )
212+
? sourceAlphas[ s ]
213+
: 1.0f;
214+
215+
// Extract source components
216+
int srcA = ARGBType.alpha( sourceARGB );
217+
final int srcR = ARGBType.red( sourceARGB );
218+
final int srcG = ARGBType.green( sourceARGB );
219+
final int srcB = ARGBType.blue( sourceARGB );
220+
221+
// Apply global alpha to per-pixel alpha
222+
srcA = ( int ) ( srcA * globalAlpha );
223+
224+
// SRC_OVER blending
225+
if ( s == 0 || resultARGB == 0 )
226+
{
227+
// First layer or nothing underneath
228+
resultARGB = ARGBType.rgba( srcR, srcG, srcB, srcA );
229+
}
230+
else
231+
{
232+
// SRC_OVER: C_out = C_src + C_dst * (1 - alpha_src)
233+
final int dstA = ARGBType.alpha( resultARGB );
234+
final int dstR = ARGBType.red( resultARGB );
235+
final int dstG = ARGBType.green( resultARGB );
236+
final int dstB = ARGBType.blue( resultARGB );
237+
238+
final float srcAlphaF = srcA / 255.0f;
239+
final float oneMinusSrcAlpha = 1.0f - srcAlphaF;
240+
241+
final int outR = ( int ) ( srcR * srcAlphaF + dstR * oneMinusSrcAlpha );
242+
final int outG = ( int ) ( srcG * srcAlphaF + dstG * oneMinusSrcAlpha );
243+
final int outB = ( int ) ( srcB * srcAlphaF + dstB * oneMinusSrcAlpha );
244+
final int outA = ( int ) ( srcA + dstA * oneMinusSrcAlpha );
245+
246+
resultARGB = ARGBType.rgba( outR, outG, outB, Math.min( 255, outA ) );
247+
}
248+
}
249+
250+
targetData[ oTarget + x ] = resultARGB;
251+
}
252+
}
253+
}
254+
255+
@Override
256+
public void cancel()
257+
{
258+
canceled = true;
259+
for ( final VolatileProjector p : sourceProjectors )
260+
p.cancel();
261+
}
262+
263+
@Override
264+
public long getLastFrameRenderNanoTime()
265+
{
266+
return lastFrameRenderNanoTime;
267+
}
268+
269+
@Override
270+
public boolean isValid()
271+
{
272+
return valid;
273+
}
274+
}
275+
}

src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
import bdv.util.BdvOptions;
6767
import fiji.plugin.trackmate.gui.Icons;
6868
import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel;
69+
import net.imagej.ImgPlus;
70+
import net.imagej.axis.Axes;
6971
import net.imglib2.Dimensions;
7072
import net.imglib2.util.Intervals;
7173
import net.miginfocom.swing.MigLayout;
@@ -97,6 +99,12 @@ public TMLabKitFrame( final TMLabKitModel model )
9799
{
98100
final ImageLabelingModel imageLabelingModel = model.imageLabelingModel();
99101

102+
// How many sources?
103+
final ImgPlus< ? > img = imageLabelingModel.imageForSegmentation().get();
104+
final int cAxis = img.dimensionIndex( Axes.CHANNEL );
105+
final int nChannels = cAxis < 0 ? 1 : ( int ) img.dimension( cAxis );
106+
final int nSources = nChannels + 1; // for the labels
107+
100108
/*
101109
* Here we create a specific config for BDV, so that we can use a custom
102110
* keymap selected not to interfere with the TrackMate-Labkit keymap. I

0 commit comments

Comments
 (0)