All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mianalysis.mia.module.objects.process.CreateSkeleton Maven / Gradle / Ivy

Go to download

ModularImageAnalysis (MIA) is an ImageJ plugin which provides a modular framework for assembling image and object analysis workflows. Detected objects can be transformed, filtered, measured and related. Analysis workflows are batch-enabled by default, allowing easy processing of high-content datasets.

There is a newer version: 1.6.12
Show newest version
package io.github.mianalysis.mia.module.objects.process;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.scijava.Priority;
import org.scijava.plugin.Plugin;

import ij.Prefs;
import io.github.mianalysis.mia.MIA;
import io.github.mianalysis.mia.module.Categories;
import io.github.mianalysis.mia.module.Category;
import io.github.mianalysis.mia.module.Module;
import io.github.mianalysis.mia.module.Modules;
import io.github.mianalysis.mia.module.core.InputControl;
import io.github.mianalysis.mia.module.images.process.binary.Skeletonise;
import io.github.mianalysis.mia.module.objects.detect.IdentifyObjects;
import io.github.mianalysis.mia.module.objects.filter.FilterOnImageEdge;
import io.github.mianalysis.mia.object.Measurement;
import io.github.mianalysis.mia.object.Obj;
import io.github.mianalysis.mia.object.Objs;
import io.github.mianalysis.mia.object.VolumeTypesInterface;
import io.github.mianalysis.mia.object.Workspace;
import io.github.mianalysis.mia.object.coordinates.volume.CoordinateSet;
import io.github.mianalysis.mia.object.coordinates.volume.PointOutOfRangeException;
import io.github.mianalysis.mia.object.coordinates.volume.VolumeType;
import io.github.mianalysis.mia.object.image.Image;
import io.github.mianalysis.mia.object.parameters.BooleanP;
import io.github.mianalysis.mia.object.parameters.ChoiceP;
import io.github.mianalysis.mia.object.parameters.InputImageP;
import io.github.mianalysis.mia.object.parameters.InputObjectsP;
import io.github.mianalysis.mia.object.parameters.Parameters;
import io.github.mianalysis.mia.object.parameters.SeparatorP;
import io.github.mianalysis.mia.object.parameters.choiceinterfaces.BinaryLogicInterface;
import io.github.mianalysis.mia.object.parameters.objects.OutputObjectsP;
import io.github.mianalysis.mia.object.parameters.objects.OutputSkeletonObjectsP;
import io.github.mianalysis.mia.object.parameters.text.DoubleP;
import io.github.mianalysis.mia.object.refs.ObjMeasurementRef;
import io.github.mianalysis.mia.object.refs.collections.ImageMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.MetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ParentChildRefs;
import io.github.mianalysis.mia.object.refs.collections.PartnerRefs;
import io.github.mianalysis.mia.object.system.Status;
import sc.fiji.analyzeSkeleton.AnalyzeSkeleton_;
import sc.fiji.analyzeSkeleton.Edge;
import sc.fiji.analyzeSkeleton.Graph;
import sc.fiji.analyzeSkeleton.Point;
import sc.fiji.analyzeSkeleton.SkeletonResult;
import sc.fiji.analyzeSkeleton.Vertex;

/**
 * Creates and measures the skeletonised form of specified input objects. This
 * module uses the
 * AnalyzeSkeleton plugin by
 * Ignacio Arganda-Carreras.
*
* The optional, output skeleton object acts solely as a linking object for the * edge, junction and loop objects. It doesn't itself hold any coordinate data. */ @Plugin(type = Module.class, priority = Priority.LOW, visible = true) public class CreateSkeleton extends Module { /** * */ public static final String INPUT_SEPARATOR = "Image/object input"; /** * Input image from the workspace to be skeletonised. */ public static final String INPUT_MODE = "Input mode"; /** * Input image from the workspace to be skeletonised. */ public static final String INPUT_IMAGE = "Input image"; /** * Controls whether objects are considered to be white (255 intensity) on a * black (0 intensity) background, or black on a white background. */ public static final String BINARY_LOGIC = "Binary logic"; /** * Input objects from the workspace to be skeletonised. These can be either 2D * or 3D objects. Skeleton measurements will be added to this object. */ public static final String INPUT_OBJECTS = "Input objects"; /** * */ public static final String OUTPUT_SEPARATOR = "Object output"; /** * When selected, the coordinates for the various skeleton components (edges, * junctions and loops) will be stored as new objects. These objects will all be * children of a parent "Skeleton" object, which itself will be a child of the * corresponding input object. */ public static final String ADD_SKELETONS_TO_WORKSPACE = "Add skeletons to workspace"; /** * If "Add skeletons to workspace" is selected, a single "Skeleton" object will * be created per input object. This skeleton object will act as a linking * object (parent) for the edges, junctions and loops that comprise that * skeleton. As such, the skeleton object itself doesn't store any coordinate * information. */ public static final String OUTPUT_SKELETON_OBJECTS = "Output skeleton objects"; /** * If "Add skeletons to workspace" is selected, the edges of each skeleton will * be stored in these objects. An "Edge" is comprised of a continuous run of * points each with one (end points) or two neighbours. These edge objects are * children of a "Skeleton" object (specified by the "Output skeleton objects" * parameter), which itself is the child of the corresponding input object. Each * edge object has a partner relationship with its adjacent "Junction" and * (optionally) "Loop" objects (specified by the "Output junction objects" and * "Output loop objects" parameters, respectively). */ public static final String OUTPUT_EDGE_OBJECTS = "Output edge objects"; /** * If "Add skeletons to workspace" is selected, the junctions of each skeleton * will be stored in these objects. A "Junction" is comprised of a contiguous * regions of points each with three or neighbours. These junction objects are * children of a "Skeleton" object (specified by the "Output skeleton objects" * parameter), which itself is the child of the corresponding input object. Each * junction object has a partner relationship with its adjacent "Edge" and * (optionally) "Loop" objects (specified by the "Output edge objects" and * "Output loop objects" parameters, respectively). */ public static final String OUTPUT_JUNCTION_OBJECTS = "Output junction objects"; /** * When selected (and if "Add skeletons to workspace" is also selected), the * loops of each skeleton will be stored in the workspace as new objects. The * name for the output loop objects is determined by the "Output loop objects" * parameter. */ public static final String EXPORT_LOOP_OBJECTS = "Export loop objects"; /** * If both "Add skeletons to workspace" and "Export loop objects" are selected, * the loops of each skeleton will be stored in these objects. A "Loop" is * comprised of a continuous region of points bounded on all sides by either * "Edge" or "Junction" points. These loop objects are children of a "Skeleton" * object (specified by the "Output skeleton objects" parameter), which itself * is the child of the corresponding input object. Each loop object has a * partner relationship with its adjacent "Edge" and "Junction" objects * (specified by the "Output edge objects" and "Output junction objects" * parameters, respectively). */ public static final String OUTPUT_LOOP_OBJECTS = "Output loop objects"; /** * When selected, the largest shortest path between any two points in the * skeleton will be stored in the workspace as a new object. For each input * object, the shortest path between all point pairs within the skeleton is * calculated and the largest of all these paths stored as a new object. The * name for the output largest shortest path object associated with each input * object is determined by the "Output largest shortest path" parameter. * Analyse Skeleton * calculates the largest shortest path using Floyd-Warshall * algorithm. Note: These objects are not the same as the * longest possible * path. */ public static final String EXPORT_LARGEST_SHORTEST_PATH = "Export largest shortest path"; /** * If "Export largest shortest path"is selected, the largest shortest path for * each skeleton will be stored in the workspace. For each skeleton, the * shortest path between all point pairs is calculated; the largest shortest * path is the longest of all these paths. The largest shortest path objects are * children of the corresponding input object. */ public static final String OUTPUT_LARGEST_SHORTEST_PATH = "Output largest shortest path"; /** * */ public static final String SKELETONISATION_SEPARATOR = "Skeletonisation settings"; /** * The minimum length of a branch (edge terminating in point with just one * neighbour) for it to be included in skeleton measurements and (optionally) * exported as an object. */ public static final String MINIMUM_BRANCH_LENGTH = "Minimum branch length"; /** * When selected, spatial values are assumed to be specified in calibrated units * (as defined by the "Input control" parameter "Spatial unit"). Otherwise, * pixel units are assumed. */ public static final String CALIBRATED_UNITS = "Calibrated units"; /** * */ public static final String EXECUTION_SEPARATOR = "Execution controls"; /** * Break the image down into strips, each one processed on a separate CPU * thread. The overhead required to do this means it's best for large multi-core * CPUs, but should be left disabled for small images or on CPUs with few cores. */ public static final String ENABLE_MULTITHREADING = "Enable multithreading"; public interface InputModes { String IMAGE = "Image"; String OBJECTS = "Objects"; String[] ALL = new String[] { IMAGE, OBJECTS }; } public interface BinaryLogic extends BinaryLogicInterface { } public interface Measurements { String SUM_LENGTH_PX = "SKELETON // SUM_LENGTH_(PX)"; String SUM_LENGTH_CAL = "SKELETON // SUM_LENGTH_(${SCAL})"; String EDGE_LENGTH_PX = "SKELETON // LENGTH_(PX)"; String EDGE_LENGTH_CAL = "SKELETON // LENGTH_(${SCAL})"; } public CreateSkeleton(Modules modules) { super("Create skeleton", modules); } public static Image getSkeletonImage(Obj inputObject) { // Getting tight image of object Image skeletonImage = inputObject.getAsTightImage("Skeleton"); // Running 3D skeletonisation Skeletonise.process(skeletonImage, true); return skeletonImage; } public static Object[] initialiseAnalyzer(Obj inputObject, double minLengthFinal, boolean exportLargestShortestPathFinal) { Image skeletonImage = getSkeletonImage(inputObject); try { AnalyzeSkeleton_ analyzeSkeleton = new AnalyzeSkeleton_(); analyzeSkeleton.setup("", skeletonImage.getImagePlus()); SkeletonResult skeletonResult = analyzeSkeleton.run(AnalyzeSkeleton_.NONE, minLengthFinal, exportLargestShortestPathFinal, skeletonImage.getImagePlus(), true, false); return new Object[] { analyzeSkeleton, skeletonResult }; } catch (Exception e) { MIA.log.writeError(e); return null; } } public static Obj createEdgeJunctionObjects(Obj inputObject, SkeletonResult result, Objs skeletonObjects, Objs edgeObjects, Objs junctionObjects) { return createEdgeJunctionObjects(inputObject, result, skeletonObjects, edgeObjects, junctionObjects, true); } public static Obj createEdgeJunctionObjects(Obj inputObject, SkeletonResult result, Objs skeletonObjects, Objs edgeObjects, Objs junctionObjects, boolean addRelationship) { double[][] extents = inputObject.getExtents(true, false); int xOffs = (int) Math.round(extents[0][0]); int yOffs = (int) Math.round(extents[1][0]); int zOffs = (int) Math.round(extents[2][0]); // The Skeleton object links branches, junctions and loops. Obj skeletonObject = skeletonObjects.createAndAddNewObject(VolumeType.POINTLIST); skeletonObject.setT(inputObject.getT()); if (addRelationship) { inputObject.addChild(skeletonObject); skeletonObject.addParent(inputObject); } // For the purpose of linking edges and junctions, these are stored in a // HashMap. HashMap edgeObjs = new HashMap<>(); HashMap junctionObjs = new HashMap<>(); // Creating objects double dppXY = inputObject.getDppXY(); for (Graph graph : result.getGraph()) { for (Edge edge : graph.getEdges()) { Obj edgeObj = createEdgeObject(skeletonObject, edgeObjects, edge, xOffs, yOffs, zOffs); edgeObjs.put(edge, edgeObj); // Adding edge length measurements double calLength = edge.getLength(); Measurement lengthPx = new Measurement(Measurements.EDGE_LENGTH_PX, calLength / dppXY); edgeObj.addMeasurement(lengthPx); Measurement lengthCal = new Measurement(Measurements.EDGE_LENGTH_CAL, calLength); edgeObj.addMeasurement(lengthCal); } for (Vertex junction : graph.getVertices()) { Obj junctionObj = createJunctionObject(skeletonObject, junctionObjects, junction, xOffs, yOffs, zOffs); junctionObjs.put(junction, junctionObj); } } // Applying partnerships between edges and junctions applyEdgeJunctionPartnerships(edgeObjs, junctionObjs); // Returning skeleton (linking) object return skeletonObject; } public static void createLoopObjects(Objs loopObjects, String edgeObjectsName, String junctionObjectsName, String loopObjectsName, Obj skeletonObject) { // Creating an object for the entire skeleton Objs tempCollection = new Objs("Skeleton", loopObjects); Obj tempObject = tempCollection.createAndAddNewObject(VolumeType.POINTLIST); CoordinateSet coords = tempObject.getCoordinateSet(); // Adding all points from edges and junctions for (Obj edgeObject : skeletonObject.getChildren(edgeObjectsName).values()) coords.addAll(edgeObject.getCoordinateSet()); for (Obj junctionObject : skeletonObject.getChildren(junctionObjectsName).values()) coords.addAll(junctionObject.getCoordinateSet()); // Creating a binary image of all the points with a 1px border, so we can remove // objects on the image edge still int[][] borders = new int[][] { { 1, 1 }, { 1, 1 }, { 0, 0 } }; Image binaryImage = tempObject.getAsTightImage("outputName", borders); // Converting binary image to loop objects Objs tempLoopObjects = IdentifyObjects.process(binaryImage, loopObjectsName, false, false, IdentifyObjects.DetectionModes.THREE_D, 6, VolumeTypesInterface.QUADTREE, false, 0, false); // Removing any objects on the image edge, as these aren't loops FilterOnImageEdge.process(tempLoopObjects, 0, null, false, true, null); // Shifting objects back to the correct positions double[][] extents = tempObject.getExtents(true, false); int xOffs = (int) Math.round(extents[0][0]) - 1; int yOffs = (int) Math.round(extents[1][0]) - 1; int zOffs = (int) Math.round(extents[2][0]); tempLoopObjects.setSpatialCalibration(loopObjects.getSpatialCalibration(), true); for (Obj tempLoopObject : tempLoopObjects.values()) tempLoopObject.translateCoords(xOffs, yOffs, zOffs); for (Obj tempLoopObject : tempLoopObjects.values()) { tempLoopObject.setID(loopObjects.getAndIncrementID()); tempLoopObject.addParent(skeletonObject); skeletonObject.addChild(tempLoopObject); loopObjects.add(tempLoopObject); } } public static Obj createEdgeObject(Obj skeletonObject, Objs edgeObjects, Edge edge, int xOffs, int yOffs, int zOffs) { Obj edgeObject = edgeObjects.createAndAddNewObject(VolumeType.POINTLIST); edgeObject.setT(skeletonObject.getT()); skeletonObject.addChild(edgeObject); edgeObject.addParent(skeletonObject); // Adding coordinates for (Point point : edge.getSlabs()) { try { skeletonObject.add(point.x + xOffs, point.y + yOffs, point.z + zOffs); edgeObject.add(point.x + xOffs, point.y + yOffs, point.z + zOffs); } catch (PointOutOfRangeException e) { } } return edgeObject; } public static Obj createJunctionObject(Obj skeletonObject, Objs junctionObjects, Vertex junction, int xOffs, int yOffs, int zOffs) { Obj junctionObject = junctionObjects.createAndAddNewObject(VolumeType.POINTLIST); junctionObject.setT(skeletonObject.getT()); skeletonObject.addChild(junctionObject); junctionObject.addParent(skeletonObject); // Adding coordinates for (Point point : junction.getPoints()) { try { skeletonObject.add(point.x + xOffs, point.y + yOffs, point.z + zOffs); junctionObject.add(point.x + xOffs, point.y + yOffs, point.z + zOffs); } catch (PointOutOfRangeException e) { } } return junctionObject; } public static ArrayList> getLargestShortestPath( Obj inputObject) { Object[] result = initialiseAnalyzer(inputObject, 0, true); AnalyzeSkeleton_ analyzeSkeleton = (AnalyzeSkeleton_) result[0]; SkeletonResult skeletonResult = (SkeletonResult) result[1]; return getLargestShortestPath(inputObject, analyzeSkeleton, skeletonResult); } public static ArrayList> getLargestShortestPath( Obj inputObject, AnalyzeSkeleton_ analyzeSkeleton, SkeletonResult skeletonResult) { ArrayList> points2 = new ArrayList<>(); double[][] extents = inputObject.getExtents(true, false); int xOffs = (int) Math.round(extents[0][0]); int yOffs = (int) Math.round(extents[1][0]); int zOffs = (int) Math.round(extents[2][0]); ArrayList shortestPaths = skeletonResult.getShortestPathList(); if (shortestPaths.size() == 0) return points2; int longestPathIdx = -1; double longestPathLength = -1; for (int i = 0; i < shortestPaths.size(); i++) { if (shortestPaths.get(i) > longestPathLength) { longestPathLength = shortestPaths.get(i); longestPathIdx = i; } } ArrayList points1 = analyzeSkeleton.getShortestPathPoints()[longestPathIdx]; for (Point point : points1) points2.add(new io.github.mianalysis.mia.object.coordinates.Point(point.x + xOffs, point.y + yOffs, point.z + zOffs)); return points2; } static void createLargestShortestPath(Obj inputObject, Objs largestShortestPathObjects, AnalyzeSkeleton_ analyzeSkeleton, SkeletonResult skeletonResult, boolean addRelationship) { ArrayList> points = getLargestShortestPath( inputObject, analyzeSkeleton, skeletonResult); Obj largestShortestPath = largestShortestPathObjects.createAndAddNewObject(VolumeType.POINTLIST); largestShortestPath.getCoordinateSet().addAll(points); largestShortestPath.setT(inputObject.getT()); if (addRelationship) { largestShortestPath.addParent(inputObject); inputObject.addChild(largestShortestPath); } } static void applyEdgeJunctionPartnerships(HashMap edgeObjs, HashMap junctionObjs) { // Iterating over each edge, adding the two vertices at either end as partners for (Edge edge : edgeObjs.keySet()) { Obj edgeObject = edgeObjs.get(edge); Obj junction1 = junctionObjs.get(edge.getV1()); Obj junction2 = junctionObjs.get(edge.getV2()); edgeObject.addPartner(junction1); junction1.addPartner(edgeObject); edgeObject.addPartner(junction2); junction2.addPartner(edgeObject); } } static void applyLoopPartnerships(Objs loopObjects, Objs edgeObjects, Objs junctionObjects) { // Linking junctions and loops with surfaces separated by 1px or less for (Obj loopObject : loopObjects.values()) { for (Obj junctionObject : junctionObjects.values()) { if (loopObject.getSurfaceSeparation(junctionObject, true,false,false) <= 1) { loopObject.addPartner(junctionObject); junctionObject.addPartner(loopObject); } } } // Linking edges with both junctions linked to the loop for (Obj loopObject : loopObjects.values()) { for (Obj edgeObject : edgeObjects.values()) { Objs junctionPartners = edgeObject.getPartners(junctionObjects.getName()); boolean matchFound = true; for (Obj junctionPartnerObject : junctionPartners.values()) { Objs loopPartners = junctionPartnerObject.getPartners(loopObjects.getName()); if (loopPartners == null) { matchFound = false; continue; } if (!loopPartners.values().contains(loopObject)) { matchFound = false; } } if (matchFound) { loopObject.addPartner(edgeObject); edgeObject.addPartner(loopObject); } } } } static void addMeasurements(Obj inputObject, SkeletonResult result) { double length = 0; // If the skeleton has no voxels (edge, end or junction), the graphs will be // null if (result.getGraph() != null) { for (Graph graph : result.getGraph()) { for (Edge edge : graph.getEdges()) length = length + edge.getLength(); } } double dppXY = inputObject.getDppXY(); inputObject.addMeasurement(new Measurement(Measurements.SUM_LENGTH_PX, length / dppXY)); inputObject.addMeasurement(new Measurement(Measurements.SUM_LENGTH_CAL, length)); } @Override public Category getCategory() { return Categories.OBJECTS_PROCESS; } @Override protected Status process(Workspace workspace) { String inputMode = parameters.getValue(INPUT_MODE, workspace); String inputImageName = parameters.getValue(INPUT_IMAGE, workspace); String binaryLogic = parameters.getValue(BINARY_LOGIC, workspace); String inputObjectsName = parameters.getValue(INPUT_OBJECTS, workspace); boolean addToWorkspace = (boolean) parameters.getValue(ADD_SKELETONS_TO_WORKSPACE, workspace) || inputMode.equals(InputModes.IMAGE); String skeletonObjectsName = parameters.getValue(OUTPUT_SKELETON_OBJECTS, workspace); String edgeObjectsName = parameters.getValue(OUTPUT_EDGE_OBJECTS, workspace); String junctionObjectsName = parameters.getValue(OUTPUT_JUNCTION_OBJECTS, workspace); boolean exportLoops = parameters.getValue(EXPORT_LOOP_OBJECTS, workspace); String loopObjectsName = parameters.getValue(OUTPUT_LOOP_OBJECTS, workspace); boolean exportLargestShortestPath = parameters.getValue(EXPORT_LARGEST_SHORTEST_PATH, workspace); String largestShortestPathName = parameters.getValue(OUTPUT_LARGEST_SHORTEST_PATH, workspace); double minLength = parameters.getValue(MINIMUM_BRANCH_LENGTH, workspace); boolean calibratedUnits = parameters.getValue(CALIBRATED_UNITS, workspace); boolean multithread = parameters.getValue(ENABLE_MULTITHREADING, workspace); // If processing an image, create a temporary object set Objs inputObjects; switch (inputMode) { case InputModes.IMAGE: Image inputImage = workspace.getImage(inputImageName); boolean blackBackground = binaryLogic.equals(BinaryLogic.BLACK_BACKGROUND); String detectionMode = IdentifyObjects.DetectionModes.THREE_D; String volumeType = IdentifyObjects.VolumeTypes.QUADTREE; inputObjects = IdentifyObjects.process(inputImage, "TempObjects", blackBackground, false, detectionMode, 26, volumeType, multithread, 60, false); break; case InputModes.OBJECTS: default: inputObjects = workspace.getObjects(inputObjectsName); break; } if (inputObjects == null || inputObjects.size() == 0) return Status.PASS; // If necessary, converting to calibrated units (Skeletonise takes calibrated // measurements, so unlike most modules, we want to convert to calibrated units) if (!calibratedUnits) minLength = minLength * inputObjects.getDppXY(); // Creating empty output object collections final Objs skeletonObjects = addToWorkspace ? new Objs(skeletonObjectsName, inputObjects) : null; final Objs edgeObjects = addToWorkspace ? new Objs(edgeObjectsName, inputObjects) : null; final Objs junctionObjects = addToWorkspace ? new Objs(junctionObjectsName, inputObjects) : null; final Objs loopObjects = addToWorkspace & exportLoops ? new Objs(loopObjectsName, inputObjects) : null; final Objs largestShortestPathObjects = exportLargestShortestPath ? new Objs(largestShortestPathName, inputObjects) : null; if (addToWorkspace) { workspace.addObjects(skeletonObjects); workspace.addObjects(edgeObjects); workspace.addObjects(junctionObjects); if (exportLoops) workspace.addObjects(loopObjects); } // These can be exported independently of the main skeleton if (exportLargestShortestPath) { workspace.addObjects(largestShortestPathObjects); // Largest shortest path requires calibrated units. If none present, export // empty collection if (Double.isNaN(inputObjects.getFirst().getDppXY()) || Double.isNaN(inputObjects.getFirst().getDppXY())) { MIA.log.writeWarning( "Spatial calibration required for largest shortest path in Measure Skeleton. No largest shortest paths output."); exportLargestShortestPath = false; } } // Configuring multithreading int nThreads = multithread ? Prefs.getThreads() : 1; ThreadPoolExecutor pool = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); int total = inputObjects.size(); AtomicInteger count = new AtomicInteger(); final double minLengthFinal = minLength; final boolean exportLargestShortestPathFinal = exportLargestShortestPath; for (Obj inputObject : inputObjects.values()) { Runnable task = () -> { try { Object[] result = initialiseAnalyzer(inputObject, minLengthFinal, exportLargestShortestPathFinal); // Adding the skeleton to the input object if (addToWorkspace) { Obj skeletonObject = createEdgeJunctionObjects(inputObject, (SkeletonResult) result[1], skeletonObjects, edgeObjects, junctionObjects, inputMode.equals(InputModes.OBJECTS)); // Creating loop objects if (exportLoops) { createLoopObjects(loopObjects, edgeObjectsName, junctionObjectsName, loopObjectsName, skeletonObject); workspace.addObjects(loopObjects); applyLoopPartnerships(loopObjects, edgeObjects, junctionObjects); } } if (exportLargestShortestPathFinal) createLargestShortestPath(inputObject, largestShortestPathObjects, (AnalyzeSkeleton_) result[0], (SkeletonResult) result[1], inputMode.equals(InputModes.OBJECTS)); if (((SkeletonResult) result[1]) != null && inputMode.equals(InputModes.OBJECTS)) addMeasurements(inputObject, (SkeletonResult) result[1]); } catch (Throwable t) { MIA.log.writeError(t); } writeProgressStatus(count.incrementAndGet(), total, "objects"); }; pool.submit(task); } pool.shutdown(); try { pool.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS); // i.e. never terminate early } catch (Throwable t) { MIA.log.writeError(t); return Status.FAIL; } if (showOutput) { switch (inputMode) { case InputModes.IMAGE: edgeObjects.showMeasurements(this, modules); skeletonObjects.convertToImageIDColours().show(false); break; case InputModes.OBJECTS: inputObjects.showMeasurements(this, modules); if (addToWorkspace) { edgeObjects.showMeasurements(this, modules); skeletonObjects.convertToImageIDColours().show(false); } break; } } return Status.PASS; } @Override protected void initialiseParameters() { parameters.add(new SeparatorP(INPUT_SEPARATOR, this)); parameters.add(new ChoiceP(INPUT_MODE, this, InputModes.OBJECTS, InputModes.ALL)); parameters.add(new InputImageP(INPUT_IMAGE, this)); parameters.add(new ChoiceP(BINARY_LOGIC, this, BinaryLogic.BLACK_BACKGROUND, BinaryLogic.ALL)); parameters.add(new InputObjectsP(INPUT_OBJECTS, this)); parameters.add(new SeparatorP(OUTPUT_SEPARATOR, this)); parameters.add(new BooleanP(ADD_SKELETONS_TO_WORKSPACE, this, false)); parameters.add(new OutputSkeletonObjectsP(OUTPUT_SKELETON_OBJECTS, this)); parameters.add(new OutputSkeletonObjectsP(OUTPUT_EDGE_OBJECTS, this)); parameters.add(new OutputSkeletonObjectsP(OUTPUT_JUNCTION_OBJECTS, this)); parameters.add(new BooleanP(EXPORT_LOOP_OBJECTS, this, false)); parameters.add(new OutputSkeletonObjectsP(OUTPUT_LOOP_OBJECTS, this)); parameters.add(new BooleanP(EXPORT_LARGEST_SHORTEST_PATH, this, false)); parameters.add(new OutputObjectsP(OUTPUT_LARGEST_SHORTEST_PATH, this)); parameters.add(new SeparatorP(SKELETONISATION_SEPARATOR, this)); parameters.add(new DoubleP(MINIMUM_BRANCH_LENGTH, this, 0d)); parameters.add(new BooleanP(CALIBRATED_UNITS, this, false)); parameters.add(new SeparatorP(EXECUTION_SEPARATOR, this)); parameters.add(new BooleanP(ENABLE_MULTITHREADING, this, true)); addParameterDescriptions(); } @Override public Parameters updateAndGetParameters() { Workspace workspace = null; Parameters returnedParameters = new Parameters(); returnedParameters.add(parameters.getParameter(INPUT_SEPARATOR)); returnedParameters.add(parameters.getParameter(INPUT_MODE)); switch ((String) parameters.getValue(INPUT_MODE, workspace)) { case InputModes.IMAGE: returnedParameters.add(parameters.getParameter(INPUT_IMAGE)); returnedParameters.add(parameters.getParameter(BINARY_LOGIC)); break; case InputModes.OBJECTS: returnedParameters.add(parameters.getParameter(INPUT_OBJECTS)); break; } returnedParameters.add(parameters.getParameter(OUTPUT_SEPARATOR)); if (((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.OBJECTS)) returnedParameters.add(parameters.getParameter(ADD_SKELETONS_TO_WORKSPACE)); if ((boolean) parameters.getValue(ADD_SKELETONS_TO_WORKSPACE, workspace) || ((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.IMAGE)) { returnedParameters.add(parameters.getParameter(OUTPUT_SKELETON_OBJECTS)); returnedParameters.add(parameters.getParameter(OUTPUT_EDGE_OBJECTS)); returnedParameters.add(parameters.getParameter(OUTPUT_JUNCTION_OBJECTS)); returnedParameters.add(parameters.getParameter(EXPORT_LOOP_OBJECTS)); if ((boolean) parameters.getValue(EXPORT_LOOP_OBJECTS, workspace)) returnedParameters.add(parameters.getParameter(OUTPUT_LOOP_OBJECTS)); } returnedParameters.add(parameters.getParameter(EXPORT_LARGEST_SHORTEST_PATH)); if ((boolean) parameters.getValue(EXPORT_LARGEST_SHORTEST_PATH, workspace)) returnedParameters.add(parameters.getParameter(OUTPUT_LARGEST_SHORTEST_PATH)); returnedParameters.add(parameters.getParameter(SKELETONISATION_SEPARATOR)); returnedParameters.add(parameters.getParameter(MINIMUM_BRANCH_LENGTH)); returnedParameters.add(parameters.getParameter(CALIBRATED_UNITS)); returnedParameters.add(parameters.getParameter(EXECUTION_SEPARATOR)); returnedParameters.add(parameters.getParameter(ENABLE_MULTITHREADING)); return returnedParameters; } @Override public ImageMeasurementRefs updateAndGetImageMeasurementRefs() { return null; } @Override public ObjMeasurementRefs updateAndGetObjectMeasurementRefs() { Workspace workspace = null; ObjMeasurementRefs returnedRefs = new ObjMeasurementRefs(); if (((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.OBJECTS)) { String inputObjectsName = parameters.getValue(INPUT_OBJECTS, workspace); ObjMeasurementRef ref = objectMeasurementRefs.getOrPut(Measurements.SUM_LENGTH_PX); ref.setObjectsName(inputObjectsName); returnedRefs.add(ref); ref = objectMeasurementRefs.getOrPut(Measurements.SUM_LENGTH_CAL); ref.setObjectsName(inputObjectsName); returnedRefs.add(ref); } if ((boolean) parameters.getValue(ADD_SKELETONS_TO_WORKSPACE, workspace) || ((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.IMAGE)) { String edgeObjectsName = parameters.getValue(OUTPUT_EDGE_OBJECTS, workspace); ObjMeasurementRef ref = objectMeasurementRefs.getOrPut(Measurements.EDGE_LENGTH_PX); ref.setObjectsName(edgeObjectsName); returnedRefs.add(ref); ref = objectMeasurementRefs.getOrPut(Measurements.EDGE_LENGTH_CAL); ref.setObjectsName(edgeObjectsName); returnedRefs.add(ref); } return returnedRefs; } @Override public ObjMetadataRefs updateAndGetObjectMetadataRefs() { return null; } @Override public MetadataRefs updateAndGetMetadataReferences() { return null; } @Override public ParentChildRefs updateAndGetParentChildRefs() { Workspace workspace = null; ParentChildRefs returnedRefs = new ParentChildRefs(); String inputObjectsName = parameters.getValue(INPUT_OBJECTS, workspace); if ((boolean) parameters.getValue(ADD_SKELETONS_TO_WORKSPACE, workspace) || ((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.IMAGE)) { String skeletonObjectsName = parameters.getValue(OUTPUT_SKELETON_OBJECTS, workspace); String edgeObjectsName = parameters.getValue(OUTPUT_EDGE_OBJECTS, workspace); String junctionObjectsName = parameters.getValue(OUTPUT_JUNCTION_OBJECTS, workspace); String loopObjectsName = parameters.getValue(OUTPUT_LOOP_OBJECTS, workspace); if (((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.OBJECTS)) returnedRefs.add(parentChildRefs.getOrPut(inputObjectsName, skeletonObjectsName)); returnedRefs.add(parentChildRefs.getOrPut(skeletonObjectsName, edgeObjectsName)); returnedRefs.add(parentChildRefs.getOrPut(skeletonObjectsName, junctionObjectsName)); if ((boolean) parameters.getValue(EXPORT_LOOP_OBJECTS, workspace)) returnedRefs.add(parentChildRefs.getOrPut(skeletonObjectsName, loopObjectsName)); } if ((boolean) parameters.getValue(EXPORT_LARGEST_SHORTEST_PATH, workspace) && ((String) parameters.getValue(INPUT_MODE, workspace)).equals(InputModes.OBJECTS)) { String largestShortestPathName = parameters.getValue(OUTPUT_LARGEST_SHORTEST_PATH, workspace); returnedRefs.add(parentChildRefs.getOrPut(inputObjectsName, largestShortestPathName)); } return returnedRefs; } @Override public PartnerRefs updateAndGetPartnerRefs() { Workspace workspace = null; PartnerRefs returnedRefs = new PartnerRefs(); if ((boolean) parameters.getValue(ADD_SKELETONS_TO_WORKSPACE, workspace)) { String edgeObjectsName = parameters.getValue(OUTPUT_EDGE_OBJECTS, workspace); String junctionObjectsName = parameters.getValue(OUTPUT_JUNCTION_OBJECTS, workspace); String loopObjectsName = parameters.getValue(OUTPUT_LOOP_OBJECTS, workspace); returnedRefs.add(partnerRefs.getOrPut(edgeObjectsName, junctionObjectsName)); if ((boolean) parameters.getValue(EXPORT_LOOP_OBJECTS, workspace)) { returnedRefs.add(partnerRefs.getOrPut(edgeObjectsName, loopObjectsName)); returnedRefs.add(partnerRefs.getOrPut(junctionObjectsName, loopObjectsName)); } } return returnedRefs; } @Override public String getVersionNumber() { return "1.1.0"; } @Override public String getDescription() { return "Creates and measures the skeletonised form of specified input objects. This module uses the AnalyzeSkeleton plugin by Ignacio Arganda-Carreras." + "

The optional, output skeleton object acts solely as a linking object for the edge, junction and loop objects. It doesn't itself hold any coordinate data."; } @Override public boolean verify() { return true; } void addParameterDescriptions() { parameters.get(INPUT_IMAGE).setDescription( "Controls whether the skeleton will be created from existing objects in the workspace or taken from a binary image."); parameters.get(INPUT_IMAGE).setDescription( "Input image from the workspace to be skeletonised."); parameters.get(INPUT_OBJECTS).setDescription( "Input objects from the workspace to be skeletonised. These can be either 2D or 3D objects. Skeleton measurements will be added to this object."); parameters.get(ADD_SKELETONS_TO_WORKSPACE).setDescription( "When selected, the coordinates for the various skeleton components (edges, junctions and loops) will be stored as new objects. These objects will all be children of a parent \"Skeleton\" object, which itself will be a child of the corresponding input object."); parameters.get(OUTPUT_SKELETON_OBJECTS).setDescription("If \"" + ADD_SKELETONS_TO_WORKSPACE + "\" is selected, a single \"Skeleton\" object will be created per input object. This skeleton object will act as a linking object (parent) for the edges, junctions and loops that comprise that skeleton. As such, the skeleton object itself doesn't store any coordinate information."); parameters.get(OUTPUT_EDGE_OBJECTS).setDescription("If \"" + ADD_SKELETONS_TO_WORKSPACE + "\" is selected, the edges of each skeleton will be stored in these objects. An \"Edge\" is comprised of a continuous run of points each with one (end points) or two neighbours. These edge objects are children of a \"Skeleton\" object (specified by the \"" + OUTPUT_SKELETON_OBJECTS + "\" parameter), which itself is the child of the corresponding input object. Each edge object has a partner relationship with its adjacent \"Junction\" and (optionally) \"Loop\" objects (specified by the \"" + OUTPUT_JUNCTION_OBJECTS + "\" and \"" + OUTPUT_LOOP_OBJECTS + "\" parameters, respectively)."); parameters.get(OUTPUT_JUNCTION_OBJECTS).setDescription("If \"" + ADD_SKELETONS_TO_WORKSPACE + "\" is selected, the junctions of each skeleton will be stored in these objects. A \"Junction\" is comprised of a contiguous regions of points each with three or neighbours. These junction objects are children of a \"Skeleton\" object (specified by the \"" + OUTPUT_SKELETON_OBJECTS + "\" parameter), which itself is the child of the corresponding input object. Each junction object has a partner relationship with its adjacent \"Edge\" and (optionally) \"Loop\" objects (specified by the \"" + OUTPUT_EDGE_OBJECTS + "\" and \"" + OUTPUT_LOOP_OBJECTS + "\" parameters, respectively)."); parameters.get(EXPORT_LOOP_OBJECTS).setDescription("When selected (and if \"" + ADD_SKELETONS_TO_WORKSPACE + "\" is also selected), the loops of each skeleton will be stored in the workspace as new objects. The name for the output loop objects is determined by the \"" + OUTPUT_LOOP_OBJECTS + "\" parameter."); parameters.get(OUTPUT_LOOP_OBJECTS).setDescription("If both \"" + ADD_SKELETONS_TO_WORKSPACE + "\" and \"" + EXPORT_LOOP_OBJECTS + "\" are selected, the loops of each skeleton will be stored in these objects. A \"Loop\" is comprised of a continuous region of points bounded on all sides by either \"Edge\" or \"Junction\" points. These loop objects are children of a \"Skeleton\" object (specified by the \"" + OUTPUT_SKELETON_OBJECTS + "\" parameter), which itself is the child of the corresponding input object. Each loop object has a partner relationship with its adjacent \"Edge\" and \"Junction\" objects (specified by the \"" + OUTPUT_EDGE_OBJECTS + "\" and \"" + OUTPUT_JUNCTION_OBJECTS + "\" parameters, respectively)."); parameters.get(EXPORT_LARGEST_SHORTEST_PATH).setDescription( "When selected, the largest shortest path between any two points in the skeleton will be stored in the workspace as a new object. For each input object, the shortest path between all point pairs within the skeleton is calculated and the largest of all these paths stored as a new object. The name for the output largest shortest path object associated with each input object is determined by the \"" + OUTPUT_LARGEST_SHORTEST_PATH + "\" parameter. Analyse Skeleton calculates the largest shortest path using Floyd-Warshall algorithm. Note: These objects are not the same as the longest possible path."); parameters.get(OUTPUT_LARGEST_SHORTEST_PATH).setDescription("If \"" + EXPORT_LARGEST_SHORTEST_PATH + "\"is selected, the largest shortest path for each skeleton will be stored in the workspace. For each skeleton, the shortest path between all point pairs is calculated; the largest shortest path is the longest of all these paths. The largest shortest path objects are children of the corresponding input object."); parameters.get(MINIMUM_BRANCH_LENGTH).setDescription( "The minimum length of a branch (edge terminating in point with just one neighbour) for it to be included in skeleton measurements and (optionally) exported as an object."); parameters.get(CALIBRATED_UNITS).setDescription( "When selected, spatial values are assumed to be specified in calibrated units (as defined by the \"" + new InputControl(null).getName() + "\" parameter \"" + InputControl.SPATIAL_UNIT + "\"). Otherwise, pixel units are assumed."); parameters.get(ENABLE_MULTITHREADING).setDescription( "Break the image down into strips, each one processed on a separate CPU thread. The overhead required to do this means it's best for large multi-core CPUs, but should be left disabled for small images or on CPUs with few cores."); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy