Skip to content

Commit 9dfcc82

Browse files
authored
Merge pull request #230 from saalfeldlab/feature/stack-straightening
Feature: Stack straightening
2 parents 9d2a1a6 + 2913198 commit 9dfcc82

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package org.janelia.render.client;
2+
3+
import com.beust.jcommander.Parameter;
4+
import com.beust.jcommander.ParametersDelegate;
5+
6+
import java.io.IOException;
7+
import java.util.ArrayList;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import mpicbg.trakem2.transform.AffineModel2D;
13+
14+
import org.janelia.alignment.spec.Bounds;
15+
import org.janelia.alignment.spec.LeafTransformSpec;
16+
import org.janelia.alignment.spec.ResolvedTileSpecCollection;
17+
import org.janelia.alignment.spec.TileSpec;
18+
import org.janelia.alignment.spec.stack.StackMetaData;
19+
import org.janelia.render.client.parameter.CommandLineParameters;
20+
import org.janelia.render.client.parameter.RenderWebServiceParameters;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
24+
/**
25+
* Java client for straightening a stack by gradually applying an offset
26+
* computed from the midpoints of the first and last layer bounding boxes.
27+
* <br>
28+
* The offset is linearly interpolated across layers:
29+
* layer z is moved by (z - zmin) / (zmax - zmin) * offset
30+
*/
31+
public class StackStraighteningClient {
32+
33+
public static class Parameters extends CommandLineParameters {
34+
35+
@ParametersDelegate
36+
public RenderWebServiceParameters renderWeb = new RenderWebServiceParameters();
37+
38+
@Parameter(
39+
names = "--stack",
40+
description = "Name of source stack",
41+
required = true)
42+
public String stack;
43+
44+
@Parameter(
45+
names = "--targetStack",
46+
description = "Name of target stack",
47+
required = true)
48+
public String targetStack;
49+
50+
@Parameter(
51+
names = "--numberOfZLayersPerChunk",
52+
description = "Number of Z layers to process per chunk")
53+
public int numberOfZLayersPerChunk = 1000;
54+
}
55+
56+
public static void main(final String[] args) {
57+
final ClientRunner clientRunner = new ClientRunner(args) {
58+
@Override
59+
public void runClient(final String[] args) throws Exception {
60+
61+
final Parameters parameters = new Parameters();
62+
parameters.parse(args);
63+
64+
LOG.info("runClient: entry, parameters={}", parameters);
65+
66+
final StackStraighteningClient client = new StackStraighteningClient(parameters);
67+
client.straightenStack();
68+
}
69+
};
70+
clientRunner.run();
71+
}
72+
73+
private final Parameters parameters;
74+
private final RenderDataClient renderDataClient;
75+
private final List<Double> zValues;
76+
private final double minZ;
77+
private final double maxZ;
78+
private final double zRange;
79+
80+
private StackStraighteningClient(final Parameters parameters)
81+
throws IOException {
82+
83+
this.parameters = parameters;
84+
this.renderDataClient = parameters.renderWeb.getDataClient();
85+
this.zValues = renderDataClient.getStackZValues(parameters.stack);
86+
87+
if (zValues.size() < 2) {
88+
throw new IllegalArgumentException("Stack must have at least 2 layers for straightening");
89+
}
90+
91+
this.minZ = zValues.get(0);
92+
this.maxZ = zValues.get(zValues.size() - 1);
93+
this.zRange = maxZ - minZ;
94+
95+
if (parameters.numberOfZLayersPerChunk < 1) {
96+
throw new IllegalArgumentException("numberOfZLayersPerChunk must be at least 1");
97+
}
98+
}
99+
100+
private void straightenStack() throws Exception {
101+
102+
final StackMetaData sourceStackMetaData = renderDataClient.getStackMetaData(parameters.stack);
103+
renderDataClient.setupDerivedStack(sourceStackMetaData, parameters.targetStack);
104+
105+
LOG.info("straightenStack: minZ={}, maxZ={}", minZ, maxZ);
106+
107+
// Get bounding box midpoints for first and last layers
108+
final double[] firstLayerMidpoint = getLayerBoundingBoxMidpoint(minZ);
109+
final double[] lastLayerMidpoint = getLayerBoundingBoxMidpoint(maxZ);
110+
111+
// Compute the total offset between first and last layer midpoints
112+
final double totalOffsetX = lastLayerMidpoint[0] - firstLayerMidpoint[0];
113+
final double totalOffsetY = lastLayerMidpoint[1] - firstLayerMidpoint[1];
114+
115+
LOG.info("straightenStack: firstLayerMidpoint=({}, {}), lastLayerMidpoint=({}, {}), totalOffset=({}, {})",
116+
firstLayerMidpoint[0], firstLayerMidpoint[1],
117+
lastLayerMidpoint[0], lastLayerMidpoint[1],
118+
totalOffsetX, totalOffsetY);
119+
120+
for (int start = 0; start < zValues.size(); start += parameters.numberOfZLayersPerChunk) {
121+
final int end = Math.min(start + parameters.numberOfZLayersPerChunk, zValues.size());
122+
final List<Double> zLayersChunk = zValues.subList(start, end);
123+
straightenLayers(zLayersChunk, totalOffsetX, totalOffsetY);
124+
}
125+
126+
renderDataClient.setStackState(parameters.targetStack, StackMetaData.StackState.COMPLETE);
127+
128+
LOG.info("straightenStack: exit, processed {} layers", zValues.size());
129+
}
130+
131+
private double[] getLayerBoundingBoxMidpoint(final double z) throws Exception {
132+
final Bounds layerBounds = renderDataClient.getLayerBounds(parameters.stack, z);
133+
134+
if (layerBounds == null || layerBounds.getMinX() == null) {
135+
throw new IllegalArgumentException("Cannot compute bounding box for layer z=" + z);
136+
}
137+
138+
final double midX = (layerBounds.getMinX() + layerBounds.getMaxX()) / 2.0;
139+
final double midY = (layerBounds.getMinY() + layerBounds.getMaxY()) / 2.0;
140+
141+
LOG.info("getLayerBoundingBoxMidpoint: z={}, bounds={}, midpoint=({}, {})",
142+
z, layerBounds, midX, midY);
143+
144+
return new double[] { midX, midY };
145+
}
146+
147+
private void straightenLayers(final List<Double> zLayersChunk,
148+
final double totalOffsetX,
149+
final double totalOffsetY)
150+
throws Exception {
151+
152+
final Double firstZ = zLayersChunk.get(0);
153+
final Double lastZ = zLayersChunk.get(zLayersChunk.size() - 1);
154+
155+
LOG.info("straightenLayers: entry, firstZ={}, lastZ={}", firstZ, lastZ);
156+
157+
final ResolvedTileSpecCollection resolvedTiles = renderDataClient.getResolvedTiles(parameters.stack,
158+
firstZ,
159+
lastZ,
160+
null,
161+
null,
162+
null,
163+
null,
164+
null,
165+
null);
166+
167+
final Map<Double, List<TileSpec>> zToTileSpecs = new HashMap<>();
168+
final Map<Double, LeafTransformSpec> zToTranslation = new HashMap<>();
169+
170+
for (final TileSpec tileSpec : resolvedTiles.getTileSpecs()) {
171+
172+
final List<TileSpec> tileSpecsForZ = zToTileSpecs.computeIfAbsent(tileSpec.getZ(),
173+
k -> new ArrayList<>());
174+
tileSpecsForZ.add(tileSpec);
175+
176+
if (! zToTranslation.containsKey(tileSpec.getZ())) {
177+
178+
final Double z = tileSpec.getZ();
179+
180+
// Compute the interpolation factor for this layer
181+
// At zMin, factor = 0 (no movement)
182+
// At zMax, factor = 1 (full offset applied, but negated to bring it back to first layer position)
183+
final double factor = (z - minZ) / zRange;
184+
185+
// The translation needed to straighten this layer
186+
final double translateX = -factor * totalOffsetX;
187+
final double translateY = -factor * totalOffsetY;
188+
189+
final AffineModel2D translationModel = new AffineModel2D();
190+
translationModel.set(1, 0, 0, 1, translateX, translateY);
191+
192+
zToTranslation.put(z, new LeafTransformSpec(translationModel.getClass().getName(),
193+
translationModel.toDataString()));
194+
}
195+
}
196+
197+
for (final Double z : zLayersChunk) {
198+
199+
final List<TileSpec> layerTileSpecs = zToTileSpecs.get(z);
200+
final LeafTransformSpec transformSpec = zToTranslation.get(z);
201+
202+
for (final TileSpec tileSpec : layerTileSpecs) {
203+
resolvedTiles.addTransformSpecToTile(tileSpec.getTileId(),
204+
transformSpec,
205+
ResolvedTileSpecCollection.TransformApplicationMethod.PRE_CONCATENATE_LAST);
206+
}
207+
208+
LOG.debug("straightenLayers: applied affine transform {} to {} tiles for z={}",
209+
transformSpec.getDataString(), layerTileSpecs.size(), z);
210+
}
211+
212+
renderDataClient.saveResolvedTiles(resolvedTiles, parameters.targetStack, null);
213+
214+
LOG.info("straightenLayers: exit, saved {} tiles for z {} to {}", resolvedTiles.getTileCount(), firstZ, lastZ);
215+
}
216+
217+
private static final Logger LOG = LoggerFactory.getLogger(StackStraighteningClient.class);
218+
}

0 commit comments

Comments
 (0)