AnalyseSkeletonWrapper extends BoneJCommand { @Override public void run() { if (isIntensityNeeded()) { openIntensityImage(); if (intensityImage == null) { return; } } statusService.showStatus("Analyse skeleton: skeletonising"); final ImagePlus skeleton = skeletonise(inputImage); final int pruneIndex = mapPruneCycleMethod(pruneCycleMethod); final AnalyzeSkeleton_ analyzeSkeleton_ = new AnalyzeSkeleton_(); final Roi roi = excludeRoi ? inputImage.getRoi() : null; analyzeSkeleton_.setup("", skeleton); statusService.showStatus("Analyse skeleton: analysing skeletons"); final SkeletonResult results = analyzeSkeleton_.run(pruneIndex, pruneEnds, calculateShortestPaths, intensityImage, true, verbose, roi); if (hasNoSkeletons(analyzeSkeleton_)) { cancelMacroSafe(this, NO_SKELETONS); return; } showResults(results); showAdditionalResults(results); if (displaySkeletons) { final ImageStack labelledStack = analyzeSkeleton_.getResultImage(false); labelledSkeleton = new ImagePlus(inputImage.getTitle() + "-labelled-skeletons", labelledStack); labelledSkeleton.setCalibration(inputImage.getCalibration()); if (calculateShortestPaths) { final ImageStack stack = analyzeSkeleton_.getResultImage(true); final String title = inputImage.getShortTitle() + "-shortest-paths"; shortestPaths = new ImagePlus(title, stack); shortestPaths.setCalibration(inputImage.getCalibration()); } } reportUsage(); }  @Override void run();  }
@Test public void testAdditionalResultsTable() throws Exception { final ImagePlus line = NewImage.createByteImage("Test", 3, 3, 1, FILL_BLACK); line.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); line.getStack().getProcessor(1).set(2, 2, (byte) 0xFF); final String length = String.valueOf(Math.sqrt(2.0) / 2); Calibration cal = line.getCalibration(); cal.pixelWidth = 0.5; cal.pixelHeight = 0.5; cal.pixelDepth = 0.5; line.setCalibration(cal); final String[] expectedHeaders = { "# Skeleton", "# Branch", "Branch length", "V1 x", "V1 y", "V1 z", "V2 x", "V2 y", "V2 z", "Euclidean distance", "running average length", "average intensity (inner 3rd)", "average intensity" }; final String[] expectedValues = { "1", "1", length, "0.5", "0.5", "0.0", "1.0", "1.0", "0.0", length, length, "255.0", "255.0" }; final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", line, "pruneCycleMethod", "None", "verbose", true, "pruneEnds", false).get(); final DefaultGenericTable table = (DefaultGenericTable) module.getOutput( "verboseTable"); assertNotNull(table); assertEquals("Results table has wrong number of columns", expectedHeaders.length, table.size()); for (int i = 0; i < table.size(); i++) { final PrimitiveColumn<?, ?> column = (PrimitiveColumn<?, ?>) table.get(i); assertEquals("Column has incorrect header", expectedHeaders[i], column .getHeader()); assertEquals("Column has wrong number of rows", 1, column.size()); assertEquals("Column "+i+" has an incorrect value", expectedValues[i], String .valueOf(column.get(0))); } }
@Test public void testAdditionalResultsTableNullWhenVerboseFalse() throws Exception { final ImagePlus pixel = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); pixel.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None", "verbose", false).get(); assertNull(module.getOutput("verboseTable")); }
@Test public void testBadFormatIntensityImageCancelsPlugin() throws Exception { final ImagePlus imagePlus = IJ.createImage("test", 3, 3, 3, 8); final File exceptionFile = mock(File.class); when(exceptionFile.getAbsolutePath()).thenReturn("file.foo"); when(MOCK_UI.chooseFile(any(File.class), anyString())).thenReturn( exceptionFile); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", imagePlus, "pruneCycleMethod", "Lowest intensity voxel").get(); assertTrue("Plugin should have cancelled", module.isCanceled()); assertEquals("Cancel reason is incorrect", "Image format is not recognized", module.getCancelReason()); }
@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( AnalyseSkeletonWrapper.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 testIntensityImageChannelsCancelPlugin() throws Exception { final ImagePlus inputImage = IJ.createImage("test", 3, 3, 3, 8); final String intensityPath = "8bit-signed&pixelType=int8&planarDims=2&lengths=5,5,3&axes=X,Y,Channel.fake"; mockIntensityFileOpening(intensityPath); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", inputImage, "pruneCycleMethod", "Lowest intensity voxel").get(); assertTrue(module.isCanceled()); assertEquals("The intensity image can't have a channel dimension", module .getCancelReason()); }
@Test public void testIntensityImageFramesCancelPlugin() throws Exception { final ImagePlus inputImage = IJ.createImage("test", 3, 3, 3, 8); final String intensityPath = "8bit-signed&pixelType=int8&planarDims=2&lengths=5,5,3&axes=X,Y,Time.fake"; mockIntensityFileOpening(intensityPath); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", inputImage, "pruneCycleMethod", "Lowest intensity voxel").get(); assertTrue(module.isCanceled()); assertEquals("The intensity image can't have a time dimension", module .getCancelReason()); }
@Test public void testIntensityImageMismatchingDimensionsCancelPlugin() throws Exception { final ImagePlus inputImage = IJ.createImage("test", 3, 3, 1, 8); final String intensityPath = "8bit-signed&pixelType=int8&planarDims=2&lengths=5,5,3&axes=X,Y,Z.fake"; mockIntensityFileOpening(intensityPath); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", inputImage, "pruneCycleMethod", "Lowest intensity voxel").get(); assertTrue(module.isCanceled()); assertEquals( "The intensity image should match the dimensionality of the input image", module.getCancelReason()); }
@Test public void testNoImageWhenNoSkeletonisation() 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( AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None").get(); assertFalse( "Sanity check failed: plugin cancelled before image could have been shown", module.isCanceled()); verify(MOCK_UI, after(250).never()).show(any(ImagePlus.class)); }
@Test public void testNoSkeletonsCancelsPlugin() throws Exception { final ImagePlus imagePlus = IJ.createImage("test", 3, 3, 3, 8); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", imagePlus, "pruneCycleMethod", "None").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 testNot8BitIntensityImageCancelsPlugin() throws Exception { final ImagePlus inputImage = IJ.createImage("test", 3, 3, 3, 8); final String intensityPath = "16bit-unsigned&pixelType=uint16&planarDims=2&lengths=10,11,3&axes=X,Y,Z.fake"; mockIntensityFileOpening(intensityPath); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", inputImage, "pruneCycleMethod", "Lowest intensity voxel").get(); assertTrue(module.isCanceled()); assertEquals("The intensity image needs to be 8-bit greyscale", module .getCancelReason()); }
@Test public void testResultImages() throws Exception { final ImagePlus pixel = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); pixel.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); CommandModule module = command().run(AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None", "displaySkeletons", false, "calculateShortestPaths", true).get(); assertNull(module.getOutput("labelledSkeleton")); assertNull(module.getOutput("shortestPaths")); module = command().run(AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None", "displaySkeletons", true, "calculateShortestPaths", false).get(); assertNotNull(module.getOutput("labelledSkeleton")); assertNull(module.getOutput("shortestPaths")); module = command().run(AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None", "displaySkeletons", true, "calculateShortestPaths", true).get(); final ImagePlus labelledSkeleton = (ImagePlus) module.getOutput( "labelledSkeleton"); assertNotNull(labelledSkeleton); final ImagePlus shortestPaths = (ImagePlus) module.getOutput( "shortestPaths"); assertNotNull(shortestPaths); assertNotSame("Input image should not have been overwritten", pixel, labelledSkeleton); assertNotSame("Input image should not have been overwritten", pixel, shortestPaths); }
@Test public void testResultsTable() throws Exception { 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); final String[] expectedHeaders = { "# Skeleton", "# Branches", "# Junctions", "# End-point voxels", "# Junction voxels", "# Slab voxels", "Average Branch Length", "# Triple points", "# Quadruple points", "Maximum Branch Length", "Longest Shortest Path", "spx", "spy", "spz" }; final double[][] expectedValues = { { 1, 2 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0.0, 0.0 }, { 0, 0 }, { 0, 0 }, { 0.0, 0.0 }, { 0.0, 0.0 }, { 1.0, 3.0 }, { 1.0, 3.0 }, { 0.0, 0.0 } }; final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", pixels, "pruneCycleMethod", "None", "calculateShortestPaths", true).get(); @SuppressWarnings("unchecked") final List<DefaultColumn<Double>> table = (List<DefaultColumn<Double>>) module.getOutput("resultsTable"); assertNotNull(table); assertEquals("Results table has wrong number of columns", expectedHeaders.length, table.size()); for (int i = 0; i < table.size(); i++) { final DefaultColumn<Double> column = table.get(i); assertEquals("Column has incorrect header", expectedHeaders[i], column .getHeader()); assertEquals("Column has wrong number of rows", 2, column.size()); for (int j = 0; j < 2; j++) { assertEquals("Column has an incorrect value", expectedValues[i][j], column.get(j), 1e-12); } } }
@Test public void testResultsTableShortestPathFalse() throws Exception { final ImagePlus pixel = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); pixel.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", pixel, "pruneCycleMethod", "None", "calculateShortestPaths", false).get(); @SuppressWarnings("unchecked") final Collection<DefaultColumn<Double>> table = (Collection<DefaultColumn<Double>>) module.getOutput("resultsTable"); assertNotNull(table); assertEquals("Results table has wrong number of columns", 10, table.size()); }
@Test public void testSkeletonImageWhenSkeletonised() throws Exception { 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); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", square, "pruneCycleMethod", "None").get(); assertFalse( "Sanity check failed: plugin cancelled before image could have been shown", module.isCanceled()); 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( AnalyseSkeletonWrapper.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()); }
@Test public void testCancelledRunDoesNotReport() throws ExecutionException, InterruptedException { final ImagePlus blank = NewImage.createByteImage("Blank", 4, 4, 1, FILL_BLACK); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", blank).get(); assertTrue("Sanity check failed: method didn't cancel", module .isCanceled()); verify(MOCK_REPORTER, timeout(1000).times(0)).reportEvent(anyString()); }
@Test public void testSuccessfulRunReports() throws ExecutionException, InterruptedException { final ImagePlus image = NewImage.createByteImage("Test", 4, 4, 1, FILL_BLACK); image.getStack().getProcessor(1).set(1, 1, (byte) 0xFF); final CommandModule module = command().run( AnalyseSkeletonWrapper.class, true, "inputImage", image).get(); assertFalse("Sanity check failed: method cancelled", module.isCanceled()); verify(MOCK_REPORTER, timeout(1000)).reportEvent(anyString()); }