IntertrabecularAngleWrapper extends BoneJCommand { @Override public void run() { statusService.showStatus("Intertrabecular angles: Initialising..."); statusService.showStatus("Intertrabecular angles: skeletonising"); final ImagePlus skeleton = skeletonise(); statusService.showStatus("Intertrabecular angles: analysing skeletons"); statusService.showProgress(0, PROGRESS_STEPS); final Graph[] graphs = analyzeSkeleton(skeleton); if (graphs == null || graphs.length == 0) { cancelMacroSafe(this, NO_SKELETONS); return; } warnMultipleGraphs(graphs); final Graph largestGraph = Arrays.stream(graphs).max(Comparator .comparingInt(a -> a.getVertices().size())).orElse(new Graph()); statusService.showStatus("Intertrabecular angles: pruning graph"); statusService.showProgress(1, PROGRESS_STEPS); final ValuePair<Graph, double[]> pruningResult = GraphPruning .pruneShortEdges(largestGraph, minimumTrabecularLength, iteratePruning, useClusters, coefficients); final Graph cleanGraph = pruningResult.a; statusService.showStatus( "Intertrabecular angles: valence sorting trabeculae"); statusService.showProgress(3, PROGRESS_STEPS); final Map<Integer, List<Vertex>> valenceMap = VertexUtils.groupByValence( cleanGraph.getVertices(), minimumValence, maximumValence); if (valenceMap.isEmpty()) { cancelMacroSafe(this, NO_RESULTS_MSG); } statusService.showStatus("Intertrabecular angles: calculating angles"); statusService.showProgress(4, PROGRESS_STEPS); final Map<Integer, DoubleStream> radianMap = createRadianMap(valenceMap); addResults(radianMap); printEdgeCentroids(cleanGraph.getEdges()); printCulledEdgePercentages(pruningResult.b); reportUsage(); }  @Override void run(); static final String NO_RESULTS_MSG; }
@Test public void testAngleResults() throws Exception { final Predicate<Double> nonEmpty = Objects::nonNull; final URL resource = getClass().getClassLoader().getResource( "test-skelly.zip"); assert resource != null; final ImagePlus skelly = IJ.openImage(resource.getFile()); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", skelly, "minimumValence", 3, "maximumValence", 50, "minimumTrabecularLength", 2, "marginCutOff", 0, "useClusters", true, "iteratePruning", false).get(); @SuppressWarnings("unchecked") final List<DefaultColumn<Double>> table = (List<DefaultColumn<Double>>) module.getOutput("resultsTable"); assertNotNull(table); assertEquals(2, table.size()); final DefaultColumn<Double> threeColumn = table.get(0); assertEquals("3", threeColumn.getHeader()); assertEquals(10, threeColumn.size()); assertEquals(3, threeColumn.stream().filter(nonEmpty).count()); assertEquals(2, threeColumn.stream().filter(nonEmpty).distinct().count()); final DefaultColumn<Double> fiveColumn = table.get(1); assertEquals("5", fiveColumn.getHeader()); assertEquals(10, fiveColumn.size()); assertEquals(10, fiveColumn.stream().filter(nonEmpty).count()); assertEquals(6, fiveColumn.stream().filter(nonEmpty).distinct().count()); assertEquals(1, fiveColumn.stream().filter(nonEmpty) .filter(d -> d == Math.PI).count()); assertEquals(2, fiveColumn.stream().filter(nonEmpty) .filter(d -> d == Math.PI / 2).count()); }
@Test public void testCompositeImageCancelsPlugin() throws Exception { final String expectedMessage = CommonMessages.HAS_CHANNEL_DIMENSIONS + ". Please split the channels."; final ImagePlus imagePlus = IJ.createHyperStack("test", 3, 3, 3, 3, 1, 8); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", imagePlus).get(); assertTrue("A composite image should have cancelled the plugin", module .isCanceled()); assertEquals("Cancel reason is incorrect", expectedMessage, module .getCancelReason()); verify(MOCK_UI, timeout(1000)).dialogPrompt(anyString(), anyString(), any(), any()); }
@Test public void testMargins() throws Exception { final URL resource = getClass().getClassLoader().getResource( "test-skelly.zip"); assert resource != null; final ImagePlus skelly = IJ.openImage(resource.getFile()); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", skelly, "minimumValence", 3, "maximumValence", 50, "minimumTrabecularLength", 2, "marginCutOff", 10, "useClusters", true, "iteratePruning", false).get(); @SuppressWarnings("unchecked") final List<DefaultColumn<Double>> table = (List<DefaultColumn<Double>>) module.getOutput("resultsTable"); assertNotNull(table); assertEquals(1, table.size()); final DefaultColumn<Double> fiveColumn = table.get(0); assertEquals("5", fiveColumn.getHeader()); assertEquals(10, fiveColumn.size()); assertEquals(10, fiveColumn.stream().filter(Objects::nonNull).count()); }
@Test public void testMultipleGraphsShowsWarningDialog() throws Exception { final SwingDialogPrompt mockPrompt = mock(SwingDialogPrompt.class); when(MOCK_UI.dialogPrompt(startsWith("Image has multiple skeletons"), anyString(), eq(WARNING_MESSAGE), any())).thenReturn(mockPrompt); final ImagePlus pixels = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); pixels.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); pixels.getStack().getProcessor(1).set(3, 3, (byte) 0xFF); command().run(IntertrabecularAngleWrapper.class, true, "inputImage", pixels).get(); verify(MOCK_UI, timeout(1000)).dialogPrompt(startsWith( "Image has multiple skeletons"), anyString(), eq(WARNING_MESSAGE), any()); }
@Test public void testNoImageShownWhenSkeletonProducesEmptyGraph() throws Exception { doNothing().when(MOCK_UI).show(any(ImagePlus.class)); final ImagePlus pixel = NewImage.createByteImage("Test", 3, 3, 1, FILL_BLACK); pixel.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); command().run(IntertrabecularAngleWrapper.class, true, "inputImage", pixel, "minimumValence", 3).get(); verify(MOCK_UI, after(250).never()).show(any(ImagePlus.class)); }
@Test public void testNoResultsCancelsPlugin() throws Exception { final ImagePlus pixel = NewImage.createByteImage("Test", 3, 3, 1, FILL_BLACK); pixel.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", pixel).get(); assertTrue("Plugin should have cancelled", module.isCanceled()); assertEquals("Cancel reason is incorrect", NO_RESULTS_MSG, module .getCancelReason()); verify(MOCK_UI, timeout(1000)).dialogPrompt(anyString(), anyString(), any(), any()); }
@Test public void testNoSkeletonsCancelsPlugin() throws Exception { final ImagePlus imagePlus = IJ.createImage("test", 3, 3, 3, 8); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", imagePlus).get(); assertTrue("Plugin should have cancelled", module.isCanceled()); assertEquals("Cancel reason is incorrect", NO_SKELETONS, module .getCancelReason()); verify(MOCK_UI, timeout(1000)).dialogPrompt(anyString(), anyString(), any(), any()); }
@Test public void testPrintCentroids() throws Exception { doNothing().when(MOCK_UI).show(any(ImagePlus.class)); final ImagePlus line = NewImage.createByteImage("Test", 5, 3, 1, FILL_BLACK); line.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); line.getStack().getProcessor(1).set(2, 1, (byte) 0xFF); line.getStack().getProcessor(1).set(3, 1, (byte) 0xFF); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", line, "minimumTrabecularLength", 0, "printCentroids", true).get(); final DefaultResultsTable centroids = (DefaultResultsTable) module .getOutput("centroidTable"); assertEquals(6, centroids.getColumnCount()); assertEquals(1, centroids.getRowCount()); final Iterator<String> expectedColumnHeaders = Stream.of("V1x", "V1y", "V1z", "V2x", "V2y", "V2z").iterator(); centroids.forEach(c -> assertEquals(expectedColumnHeaders.next(), c .getHeader())); assertEquals(1.0, centroids.get(0, 0), 1e-12); assertEquals(1.0, centroids.get(1, 0), 1e-12); assertEquals(0.0, centroids.get(2, 0), 1e-12); assertEquals(3.0, centroids.get(3, 0), 1e-12); assertEquals(1.0, centroids.get(4, 0), 1e-12); assertEquals(0.0, centroids.get(5, 0), 1e-12); }
@Test public void testSkeletonImageWhenSkeletonised() throws Exception { doNothing().when(MOCK_UI).show(any(ImagePlus.class)); final ImagePlus square = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); square.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); square.getStack().getProcessor(1).set(1, 2, (byte) 0xFF); square.getStack().getProcessor(1).set(2, 1, (byte) 0xFF); square.getStack().getProcessor(1).set(2, 2, (byte) 0xFF); command().run(IntertrabecularAngleWrapper.class, true, "inputImage", square).get(); verify(MOCK_UI, timeout(1000)).show(any(ImagePlus.class)); }
@Test public void testTimeDimensionCancelsPlugin() throws Exception { final String expectedMessage = CommonMessages.HAS_TIME_DIMENSIONS + ". Please split the hyperstack."; final ImagePlus imagePlus = IJ.createHyperStack("test", 3, 3, 1, 3, 3, 8); final CommandModule module = command().run( IntertrabecularAngleWrapper.class, true, "inputImage", imagePlus).get(); assertTrue("An image with time dimension should have cancelled the plugin", module.isCanceled()); assertEquals("Cancel reason is incorrect", expectedMessage, module .getCancelReason()); verify(MOCK_UI, timeout(1000)).dialogPrompt(anyString(), anyString(), any(), any()); }