From d5a784542c13ca77747cfbc143c0abbedd2fd997 Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Wed, 22 Nov 2023 02:03:57 -0500 Subject: [PATCH] Implement SWC type tags from latest SWC specification ( SWC v1.0.0 ) In addition: - Fix non persistence of SWC-type tagging options - Fix #203 The 'new' SWC specification is at https://swc-specification.readthedocs.io/en/latest/swc.html --- src/main/java/sc/fiji/snt/Path.java | 78 +++++++------ src/main/java/sc/fiji/snt/Tree.java | 3 + .../sc/fiji/snt/analysis/RoiConverter.java | 8 +- .../fiji/snt/gui/cmds/SWCTypeOptionsCmd.java | 107 +++++++++--------- src/main/java/sc/fiji/snt/util/SWCPoint.java | 25 ++-- 5 files changed, 113 insertions(+), 108 deletions(-) diff --git a/src/main/java/sc/fiji/snt/Path.java b/src/main/java/sc/fiji/snt/Path.java index 3ce1e4465..fd6cb4f57 100644 --- a/src/main/java/sc/fiji/snt/Path.java +++ b/src/main/java/sc/fiji/snt/Path.java @@ -68,32 +68,33 @@ public class Path implements Comparable { // http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html - /** Flag specifying the SWC type 'undefined'. @see Path#SWC_UNDEFINED_LABEL */ + + /** SWC type flag specifying {@value #SWC_UNDEFINED_LABEL} */ public static final int SWC_UNDEFINED = 0; - /** Flag specifying the SWC type 'soma'. @see Path#SWC_SOMA_LABEL */ + /** SWC type flag specifying {@value #SWC_SOMA_LABEL} */ public static final int SWC_SOMA = 1; - /** Flag specifying the SWC type 'axon'. @see Path#SWC_AXON_LABEL */ + /** SWC type flag specifying {@value #SWC_AXON_LABEL} */ public static final int SWC_AXON = 2; - /** - * Flag specifying the SWC type '(basal) dendrite'. @see - * Path#SWC_DENDRITE_LABEL - */ + /** SWC type flag specifying {@value #SWC_DENDRITE_LABEL} */ public static final int SWC_DENDRITE = 3; - /** - * Flag specifying the SWC type 'apical dendrite'. @see - * Path#SWC_APICAL_DENDRITE_LABEL - */ + /** SWC type flag specifying {@value #SWC_APICAL_DENDRITE_LABEL} */ public static final int SWC_APICAL_DENDRITE = 4; - /** - * Flag specifying the SWC type 'fork point' @see Path#SWC_FORK_POINT_LABEL - */ + /** SWC type flag specifying {@value #SWC_CUSTOM_LABEL} */ + public static final int SWC_CUSTOM = 5; + /** SWC type flag specifying {@value #SWC_UNSPECIFIED_LABEL} */ + public static final int SWC_UNSPECIFIED = 6; + /** SWC type flag specifying {@value #SWC_GLIA_LABEL} */ + public static final int SWC_GLIA_PROCESS = 7; + /** SWC type flag specifying {@value #SWC_CUSTOM2_LABEL} */ + public static final int SWC_CUSTOM2 = 8; + + /** Deprecated. No longer part of the SWC specification */ @Deprecated - public static final int SWC_FORK_POINT = 5; // redundant + public static final int SWC_FORK_POINT = -5; + /** Deprecated. No longer part of the SWC specification */ @Deprecated - /** Flag specifying the SWC type 'end point'. @see Path#SWC_END_POINT_LABEL */ - public static final int SWC_END_POINT = 6; // redundant - /** Flag specifying the SWC type 'custom'. @see Path#SWC_CUSTOM_LABEL */ - public static final int SWC_CUSTOM = 7; + public static final int SWC_END_POINT = -6; + /** String representation of {@link Path#SWC_UNDEFINED} */ public static final String SWC_UNDEFINED_LABEL = "undefined"; /** String representation of {@link Path#SWC_SOMA} */ @@ -104,12 +105,14 @@ public class Path implements Comparable { public static final String SWC_DENDRITE_LABEL = "(basal) dendrite"; /** String representation of {@link Path#SWC_APICAL_DENDRITE} */ public static final String SWC_APICAL_DENDRITE_LABEL = "apical dendrite"; - /** String representation of {@link Path#SWC_FORK_POINT} */ - public static final String SWC_FORK_POINT_LABEL = "fork point"; - /** String representation of {@link Path#SWC_END_POINT} */ - public static final String SWC_END_POINT_LABEL = "end point"; /** String representation of {@link Path#SWC_CUSTOM} */ public static final String SWC_CUSTOM_LABEL = "custom"; + /** String representation of {@link Path#SWC_UNSPECIFIED} */ + public static final String SWC_UNSPECIFIED_LABEL = "unspecified neurite"; + /** String representation of {@link Path#SWC_GLIA_PROCESS} */ + public static final String SWC_GLIA_PROCESS_LABEL = "glia process"; + /** String representation of {@link Path#SWC_CUSTOM2} */ + public static final String SWC_CUSTOM2_LABEL = "custom (2)"; // FIXME: this should be based on distance between points in the path, not a static number: protected static final int noMoreThanOneEvery = 2; @@ -1637,18 +1640,20 @@ public static Color getSWCcolor(final int swcType) { switch (swcType) { case Path.SWC_SOMA: return Color.BLUE; + case Path.SWC_AXON: + return Color.RED; case Path.SWC_DENDRITE: return Color.GREEN; case Path.SWC_APICAL_DENDRITE: return Color.CYAN; - case Path.SWC_AXON: - return Color.RED; - case Path.SWC_FORK_POINT: - return Color.ORANGE; - case Path.SWC_END_POINT: - return Color.PINK; case Path.SWC_CUSTOM: return Color.YELLOW; + case Path.SWC_UNSPECIFIED: + return Color.ORANGE; + case Path.SWC_GLIA_PROCESS: + return Color.PINK; + case Path.SWC_CUSTOM2: + return Color.YELLOW.darker(); case Path.SWC_UNDEFINED: default: return SNT.DEFAULT_DESELECTED_COLOR; @@ -1852,15 +1857,18 @@ public static String getSWCtypeName(final int type, case SWC_APICAL_DENDRITE: typeName = SWC_APICAL_DENDRITE_LABEL; break; - case SWC_FORK_POINT: - typeName = SWC_FORK_POINT_LABEL; - break; - case SWC_END_POINT: - typeName = SWC_END_POINT_LABEL; - break; case SWC_CUSTOM: typeName = SWC_CUSTOM_LABEL; break; + case SWC_UNSPECIFIED: + typeName = SWC_UNSPECIFIED_LABEL; + break; + case SWC_GLIA_PROCESS: + typeName = SWC_GLIA_PROCESS_LABEL; + break; + case SWC_CUSTOM2: + typeName = SWC_CUSTOM2_LABEL; + break; case SWC_UNDEFINED: default: typeName = SWC_UNDEFINED_LABEL; diff --git a/src/main/java/sc/fiji/snt/Tree.java b/src/main/java/sc/fiji/snt/Tree.java index 31535d333..624632846 100644 --- a/src/main/java/sc/fiji/snt/Tree.java +++ b/src/main/java/sc/fiji/snt/Tree.java @@ -1492,6 +1492,9 @@ public static Map getSWCTypeMap() { map.put(Path.SWC_DENDRITE, Path.SWC_DENDRITE_LABEL); map.put(Path.SWC_APICAL_DENDRITE, Path.SWC_APICAL_DENDRITE_LABEL); map.put(Path.SWC_CUSTOM, Path.SWC_CUSTOM_LABEL); + map.put(Path.SWC_UNSPECIFIED, Path.SWC_UNSPECIFIED_LABEL); + map.put(Path.SWC_GLIA_PROCESS, Path.SWC_GLIA_PROCESS_LABEL); + map.put(Path.SWC_CUSTOM2, Path.SWC_CUSTOM2_LABEL); return map; } diff --git a/src/main/java/sc/fiji/snt/analysis/RoiConverter.java b/src/main/java/sc/fiji/snt/analysis/RoiConverter.java index cbe7bd065..a35a73543 100644 --- a/src/main/java/sc/fiji/snt/analysis/RoiConverter.java +++ b/src/main/java/sc/fiji/snt/analysis/RoiConverter.java @@ -205,11 +205,9 @@ public List getROIs(final Path path) { * @see TreeAnalyzer#getTips() * @param overlay the target overlay to hold converted point */ - @SuppressWarnings("deprecation") public void convertTips(Overlay overlay) { if (overlay == null) overlay = new Overlay(); - convertPoints(getTips(), overlay, Path.getSWCcolor(Path.SWC_END_POINT), - Path.SWC_END_POINT_LABEL); + convertPoints(getTips(), overlay, Color.PINK, "end point"); } /** @@ -279,11 +277,9 @@ private Overlay getImpOverlay() throws IllegalArgumentException { * @see TreeAnalyzer#getBranchPoints() * @param overlay the target overlay to hold converted point */ - @SuppressWarnings("deprecation") public void convertBranchPoints(Overlay overlay) { if (overlay == null) overlay = new Overlay(); - convertPoints(getBranchPoints(), overlay, Path.getSWCcolor( - Path.SWC_FORK_POINT), Path.SWC_FORK_POINT_LABEL); + convertPoints(getBranchPoints(), overlay, Color.ORANGE, "fork point"); } /** diff --git a/src/main/java/sc/fiji/snt/gui/cmds/SWCTypeOptionsCmd.java b/src/main/java/sc/fiji/snt/gui/cmds/SWCTypeOptionsCmd.java index e764e971a..31f95e877 100644 --- a/src/main/java/sc/fiji/snt/gui/cmds/SWCTypeOptionsCmd.java +++ b/src/main/java/sc/fiji/snt/gui/cmds/SWCTypeOptionsCmd.java @@ -53,9 +53,10 @@ initializer = "init") public class SWCTypeOptionsCmd extends ContextCommand { - private static final String HEADER = "
"; private static final String MAP_KEY = "colors"; private static final String ASSIGN_KEY = "assign"; + private static String HEADER = "
"; @Parameter private PrefService prefService; @@ -68,33 +69,40 @@ public class SWCTypeOptionsCmd extends ContextCommand { @Parameter(required = false, label = " Enable color pairing") private boolean enableColors; - @Parameter(required = true, persist = false, label = Path.SWC_DENDRITE_LABEL) + @Parameter(persist = false, label = Path.SWC_SOMA_LABEL) + private ColorRGB somaColor; + + @Parameter(persist = false, label = Path.SWC_AXON_LABEL) + private ColorRGB axonColor; + + @Parameter(persist = false, label = Path.SWC_DENDRITE_LABEL) private ColorRGB basalDendriteColor; - @Parameter(required = true, persist = false, - label = Path.SWC_APICAL_DENDRITE_LABEL) + @Parameter(persist = false, label = Path.SWC_APICAL_DENDRITE_LABEL) private ColorRGB apicalDendriteColor; - @Parameter(required = true, persist = false, label = Path.SWC_AXON_LABEL) - private ColorRGB axonColor; + @Parameter(persist = false, label = Path.SWC_UNDEFINED_LABEL) + private ColorRGB undefinedColor; - @Parameter(required = true, persist = false, label = Path.SWC_CUSTOM_LABEL) - private ColorRGB customColor; + @Parameter(persist = false, label = Path.SWC_UNSPECIFIED_LABEL) + private ColorRGB unspecifiedColor; - @Parameter(required = true, label = Path.SWC_SOMA_LABEL) - private ColorRGB somaColor; + @Parameter(persist = false, label = Path.SWC_GLIA_PROCESS_LABEL) + private ColorRGB gliaColor; - @Parameter(required = true, persist = false, label = Path.SWC_UNDEFINED_LABEL) - private ColorRGB undefinedColor; + @Parameter(persist = false, label = Path.SWC_CUSTOM_LABEL) + private ColorRGB customColor; - @Parameter(required = false, persist = false, label = "Reset Defaults", - callback = "reset") + @Parameter(persist = false, label = Path.SWC_CUSTOM2_LABEL) + private ColorRGB custom2Color; + + @Parameter(required = false, persist = false, label = "Reset Defaults", callback = "reset") private Button reset; @Parameter(visibility = ItemVisibility.MESSAGE) private final String msg = HEADER + "When color pairing is enabled, " + "assigning a SWC-type tag automaticaly colors the path " + - "with the respective listed color. Note that it is also possible " + + "with its associated color. Note that it is also possible " + "to assign ad-hoc colors using the Tag>Color> menu."; /* @@ -104,54 +112,47 @@ public class SWCTypeOptionsCmd extends ContextCommand { */ @Override public void run() { - final LinkedHashMap map = - new LinkedHashMap<>(); - map.put(String.valueOf(Path.SWC_DENDRITE), basalDendriteColor - .toHTMLColor()); - map.put(String.valueOf(Path.SWC_APICAL_DENDRITE), apicalDendriteColor - .toHTMLColor()); - map.put(String.valueOf(Path.SWC_AXON), axonColor.toHTMLColor()); - map.put(String.valueOf(Path.SWC_CUSTOM), customColor.toHTMLColor()); + final LinkedHashMap map = new LinkedHashMap<>(); map.put(String.valueOf(Path.SWC_SOMA), somaColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_AXON), axonColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_DENDRITE), basalDendriteColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_APICAL_DENDRITE), apicalDendriteColor.toHTMLColor()); map.put(String.valueOf(Path.SWC_UNDEFINED), undefinedColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_UNSPECIFIED), unspecifiedColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_GLIA_PROCESS), gliaColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_CUSTOM), customColor.toHTMLColor()); + map.put(String.valueOf(Path.SWC_CUSTOM2), custom2Color.toHTMLColor()); prefService.put(SWCTypeOptionsCmd.class, MAP_KEY, map); prefService.put(SWCTypeOptionsCmd.class, ASSIGN_KEY, enableColors); } private Map getDefaultMap() { - final LinkedHashMap map = - new LinkedHashMap<>(); - map.put(Path.SWC_DENDRITE, getDefaultSWCColorRGB(Path.SWC_DENDRITE)); - map.put(Path.SWC_APICAL_DENDRITE, getDefaultSWCColorRGB( - Path.SWC_APICAL_DENDRITE)); - map.put(Path.SWC_AXON, getDefaultSWCColorRGB(Path.SWC_AXON)); - map.put(Path.SWC_CUSTOM, getDefaultSWCColorRGB(Path.SWC_CUSTOM)); + final LinkedHashMap map = new LinkedHashMap<>(); map.put(Path.SWC_SOMA, getDefaultSWCColorRGB(Path.SWC_SOMA)); + map.put(Path.SWC_AXON, getDefaultSWCColorRGB(Path.SWC_AXON)); + map.put(Path.SWC_DENDRITE, getDefaultSWCColorRGB(Path.SWC_DENDRITE)); + map.put(Path.SWC_APICAL_DENDRITE, getDefaultSWCColorRGB(Path.SWC_APICAL_DENDRITE)); map.put(Path.SWC_UNDEFINED, getDefaultSWCColorRGB(Path.SWC_UNDEFINED)); + map.put(Path.SWC_UNSPECIFIED, getDefaultSWCColorRGB(Path.SWC_UNSPECIFIED)); + map.put(Path.SWC_GLIA_PROCESS, getDefaultSWCColorRGB(Path.SWC_GLIA_PROCESS)); + map.put(Path.SWC_CUSTOM, getDefaultSWCColorRGB(Path.SWC_CUSTOM)); + map.put(Path.SWC_CUSTOM2, getDefaultSWCColorRGB(Path.SWC_CUSTOM2)); return map; } private Map getSavedMap() { - final Map smap = prefService.getMap(SWCTypeOptionsCmd.class, - MAP_KEY); - if (smap == null || smap.isEmpty()) { + final Map smap = prefService.getMap(SWCTypeOptionsCmd.class, MAP_KEY); + if (smap == null || smap.isEmpty() || smap.size() < 9) { // only 6 colors before v4.3 return getDefaultMap(); } - final LinkedHashMap map = - new LinkedHashMap<>(); - for (final Map.Entry entry : smap.entrySet()) { - final String key = entry.getKey(); - final String value = entry.getValue(); - map.put(Integer.valueOf(key), ColorRGB.fromHTMLColor(value)); - } - // while at it, read other preferences - enableColors = prefService.getBoolean(SWCTypeOptionsCmd.class, ASSIGN_KEY, - true); + final LinkedHashMap map = new LinkedHashMap<>(); + smap.forEach((k, v) -> map.put(Integer.valueOf(k), ColorRGB.fromHTMLColor(v))); return map; } @SuppressWarnings("unused") private void init() { + enableColors = isColorPairingEnabled(); assignColors(getSavedMap()); } @@ -163,12 +164,15 @@ private void reset() { } private void assignColors(final Map map) { - apicalDendriteColor = map.get(Path.SWC_APICAL_DENDRITE); + somaColor = map.get(Path.SWC_SOMA); axonColor = map.get(Path.SWC_AXON); basalDendriteColor = map.get(Path.SWC_DENDRITE); - customColor = map.get(Path.SWC_CUSTOM); - somaColor = map.get(Path.SWC_SOMA); + apicalDendriteColor = map.get(Path.SWC_APICAL_DENDRITE); undefinedColor = map.get(Path.SWC_UNDEFINED); + unspecifiedColor = map.get(Path.SWC_UNSPECIFIED); + gliaColor = map.get(Path.SWC_GLIA_PROCESS); + customColor = map.get(Path.SWC_CUSTOM); + custom2Color = map.get(Path.SWC_CUSTOM2); } private static class SWCTypeComparator implements Comparator { @@ -183,18 +187,13 @@ public int compare(final Integer i1, final Integer i2) { public TreeMap getColorMap() { final Map maprgb = getSavedMap(); - final TreeMap map = new TreeMap<>( - new SWCTypeComparator()); // new SWCTypeComparator()); - for (final Map.Entry entry : maprgb.entrySet()) { - final int key = entry.getKey(); - final ColorRGB color = entry.getValue(); - map.put(key, getColorFromColorRGB(color)); - } + final TreeMap map = new TreeMap<>(new SWCTypeComparator()); + maprgb.forEach((k,v) -> map.put(k, getColorFromColorRGB(v))); return map; } public boolean isColorPairingEnabled() { - return enableColors; + return prefService.getBoolean(SWCTypeOptionsCmd.class, ASSIGN_KEY, true); } private Color getColorFromColorRGB(final ColorRGB c) { diff --git a/src/main/java/sc/fiji/snt/util/SWCPoint.java b/src/main/java/sc/fiji/snt/util/SWCPoint.java index 66292d773..06ba23e51 100644 --- a/src/main/java/sc/fiji/snt/util/SWCPoint.java +++ b/src/main/java/sc/fiji/snt/util/SWCPoint.java @@ -153,23 +153,22 @@ public int hashCode() { /** * Converts a collection of SWC points into a Reader. * - * @param points the collection of SWC points to be converted into a space/ - * tab separated String. Points should be sorted by sample number to - * ensure valid connectivity. + * @param points the collection of SWC points to be converted into a space + * separated String. Points should be sorted by sample number to + * ensure valid connectivity. * @return the Reader */ - public static StringReader collectionAsReader( - final Collection points) - { + public static StringReader collectionAsReader(final Collection points) { + final String SEP = " "; final StringBuilder sb = new StringBuilder(); for (final SWCPoint p : points) { - sb.append(p.id).append("\t") // - .append(p.type).append("\t") // see https://github.com/morphonets/SNT/issues/147 - .append(String.format(Locale.US, "%.6f", p.x)).append(" ") // - .append(String.format(Locale.US, "%.6f", p.y)).append(" ") // - .append(String.format(Locale.US, "%.6f", p.z)).append(" ") // - .append(String.format(Locale.US, "%.6f", p.radius)).append("\t") // - .append(p.parent).append(System.lineSeparator()); + sb.append(p.id).append(SEP) // + .append(p.type).append(SEP) // see https://github.com/morphonets/SNT/issues/147 + .append(String.format(Locale.US, "%.6f", p.x)).append(SEP) // + .append(String.format(Locale.US, "%.6f", p.y)).append(SEP) // + .append(String.format(Locale.US, "%.6f", p.z)).append(SEP) // + .append(String.format(Locale.US, "%.6f", p.radius)).append(SEP) // + .append(p.parent).append(System.lineSeparator()); } return new StringReader(sb.toString()); }