Skip to content

Commit 3d07489

Browse files
committed
Restructure detection of reconstruction files...
This should solve conflicts between Neurolucida XML and BDV/BVV XML files
1 parent e25906f commit 3d07489

10 files changed

Lines changed: 234 additions & 80 deletions

File tree

‎src/main/java/sc/fiji/snt/PathAndFillManager.java‎

Lines changed: 23 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,6 @@ public class PathAndFillManager extends DefaultHandler implements
9191

9292
static { net.imagej.patcher.LegacyInjector.preinit(); } // required for _every_ class that imports ij. classes
9393

94-
protected static final int TRACES_FILE_TYPE_COMPRESSED_XML = 1;
95-
protected static final int TRACES_FILE_TYPE_UNCOMPRESSED_XML = 2;
96-
protected static final int TRACES_FILE_TYPE_SWC = 3;
97-
protected static final int TRACES_FILE_TYPE_ML_JSON = 4;
98-
protected static final int TRACES_FILE_TYPE_NDF = 5;
99-
protected static final int TRACES_FILE_TYPE_NEUROLUCIDA = 6;
10094

10195
private static final DecimalFormat fileIndexFormatter = new DecimalFormat("000");
10296

@@ -2906,53 +2900,16 @@ public boolean importSWC(final String filePath,
29062900

29072901
}
29082902

2909-
protected int guessTracesFileType(final String filename) {
2903+
/** Detects the format of a file. Returns null (and reports an error) if the file could not be read */
2904+
protected IOUtils.ReconstructionFormat guessTracesFileType(final String filename) {
29102905
try {
2911-
return guessTracesFileType(Files.newInputStream(Paths.get(filename)), true);
2912-
} catch (IOException e) {
2906+
return IOUtils.detectFormat(new File(filename));
2907+
} catch (final IOException e) {
29132908
errorStatic("The file '" + filename + "' could not be parsed.");
2914-
return -1;
2909+
return null;
29152910
}
29162911
}
29172912

2918-
private int guessTracesFileType(InputStream is, final boolean closeStreamAfterGuess) throws IOException {
2919-
/*
2920-
* Look at the magic bytes at the start of the file:
2921-
*
2922-
* If this looks as if it's gzip compressed, assume it's a compressed traces
2923-
* file. If it begins "<?xml", check for Neurolucida's <mbf> root element;
2924-
* otherwise assume it's an uncompressed traces file. If it begins with '{"'
2925-
* assume it is a ML JSON file, otherwise assume it's an SWC file.
2926-
*/
2927-
if (!headless)
2928-
SNTUtils.log("Guessing file type...");
2929-
if (is.markSupported())
2930-
is.mark(4096);
2931-
final byte[] buf = new byte[4096];
2932-
final int bytesRead = is.read(buf, 0, buf.length);
2933-
if (closeStreamAfterGuess)
2934-
is.close();
2935-
else if (is.markSupported()) {
2936-
is.reset();
2937-
}
2938-
if (bytesRead < 2) return TRACES_FILE_TYPE_SWC;
2939-
if(buf[ 0 ] == (byte) 0x1f && buf[ 1 ] == (byte) 0x8b ) { //check if matches standard gzip magic number
2940-
return TRACES_FILE_TYPE_COMPRESSED_XML;
2941-
} else if (bytesRead >= 6 && buf[0] == '<' && buf[1] == '?' && buf[2] == 'x' && buf[3] == 'm'
2942-
&& buf[4] == 'l' && buf[5] == ' ') {
2943-
// XML file: distinguish SNT traces from Neurolucida by checking for <mbf> root
2944-
final String header = new String(buf, 0, bytesRead);
2945-
if (header.contains("<mbf"))
2946-
return TRACES_FILE_TYPE_NEUROLUCIDA;
2947-
return TRACES_FILE_TYPE_UNCOMPRESSED_XML;
2948-
} else if (((char) (buf[0] & 0xFF) == '{')) {
2949-
return TRACES_FILE_TYPE_ML_JSON;
2950-
} else if (((char) (buf[0] & 0xFF) == '/')) {
2951-
return TRACES_FILE_TYPE_NDF;
2952-
}
2953-
return TRACES_FILE_TYPE_SWC;
2954-
}
2955-
29562913
protected boolean loadCompressedXML(final String filename) {
29572914
try {
29582915
SNTUtils.log("Loading gzipped file...");
@@ -3052,25 +3009,29 @@ private boolean loadNeurolucida(final Object filenameOrInputStream) {
30523009
public boolean load(final String filePath, final int... swcTypes) {
30533010
final boolean existingEnableUiUpdates = enableUIupdates;
30543011
enableUIupdates = false;
3055-
final int guessedType = guessTracesFileType(filePath);
3012+
final IOUtils.ReconstructionFormat guessedType = guessTracesFileType(filePath);
3013+
if (guessedType == null) {
3014+
enableUIupdates = existingEnableUiUpdates;
3015+
return false;
3016+
}
30563017
boolean result;
30573018
switch (guessedType) {
3058-
case TRACES_FILE_TYPE_COMPRESSED_XML:
3019+
case COMPRESSED_XML:
30593020
result = loadCompressedXML(filePath);
30603021
break;
3061-
case TRACES_FILE_TYPE_UNCOMPRESSED_XML:
3022+
case UNCOMPRESSED_XML:
30623023
result = loadUncompressedXML(filePath);
30633024
break;
3064-
case TRACES_FILE_TYPE_ML_JSON:
3025+
case ML_JSON:
30653026
result = loadJSON(filePath, swcTypes);
30663027
break;
3067-
case TRACES_FILE_TYPE_NDF:
3028+
case NDF:
30683029
result = loadNDF(filePath);
30693030
break;
3070-
case TRACES_FILE_TYPE_NEUROLUCIDA:
3031+
case NEUROLUCIDA:
30713032
result = loadNeurolucida(filePath);
30723033
break;
3073-
case TRACES_FILE_TYPE_SWC:
3034+
case SWC:
30743035
result = importSWC(filePath, false, 0, 0, 0, 1, 1, 1, 1, true, swcTypes);
30753036
break;
30763037
default:
@@ -3099,32 +3060,32 @@ public boolean loadGuessingType(final String filePathOrURL) throws IOException,
30993060

31003061
public boolean loadGuessingType(final String optionalDescription, final InputStream is) throws IOException {
31013062
final BufferedInputStream bis = (is instanceof BufferedInputStream) ? ((BufferedInputStream)is) : new BufferedInputStream(is);
3102-
final int guessedType = guessTracesFileType(bis, false);
3063+
final IOUtils.ReconstructionFormat guessedType = IOUtils.detectFormat(bis, false);
31033064
final boolean existingEnableUiUpdates = enableUIupdates;
31043065
enableUIupdates = false;
31053066
boolean result;
31063067
switch (guessedType) {
3107-
case TRACES_FILE_TYPE_COMPRESSED_XML:
3068+
case COMPRESSED_XML:
31083069
SNTUtils.log("Loading gzipped file...");
31093070
result = load(new GZIPInputStream(bis));
31103071
break;
3111-
case TRACES_FILE_TYPE_UNCOMPRESSED_XML:
3072+
case UNCOMPRESSED_XML:
31123073
SNTUtils.log("Loading uncompressed file...");
31133074
result = load(bis);
31143075
break;
3115-
case TRACES_FILE_TYPE_ML_JSON:
3076+
case ML_JSON:
31163077
final Map<String, TreeSet<SWCPoint>> nMap = MouseLightLoader.extractNodes(bis, "all");
31173078
final Map<String, Tree> outMap = importNeurons(nMap, null, GuiUtils.micrometer());
31183079
result = outMap.values().stream().anyMatch(tree -> tree != null && !tree.isEmpty());
31193080
break;
3120-
case TRACES_FILE_TYPE_SWC:
3081+
case SWC:
31213082
final BufferedReader br = new BufferedReader(new InputStreamReader(bis, StandardCharsets.UTF_8));
31223083
result = importSWC(br, optionalDescription, false, 0, 0, 0, 1, 1, 1, 1, true);
31233084
break;
3124-
case TRACES_FILE_TYPE_NDF:
3085+
case NDF:
31253086
result = loadNDF(bis);
31263087
break;
3127-
case TRACES_FILE_TYPE_NEUROLUCIDA:
3088+
case NEUROLUCIDA:
31283089
result = loadNeurolucida(bis);
31293090
break;
31303091
default:

‎src/main/java/sc/fiji/snt/SNT.java‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,11 +2429,12 @@ protected synchronized boolean loadTracesFile(File file) {
24292429
guiUtils.error(file.getAbsolutePath() + " is no longer available");
24302430
return false;
24312431
}
2432-
final int guessedType = pathAndFillManager.guessTracesFileType(file.getAbsolutePath());
2432+
final IOUtils.ReconstructionFormat guessedType = pathAndFillManager.guessTracesFileType(file.getAbsolutePath());
2433+
if (guessedType == null) return false; // unreadable file (already reported)
24332434
return switch (guessedType) {
2434-
case PathAndFillManager.TRACES_FILE_TYPE_COMPRESSED_XML ->
2435+
case COMPRESSED_XML ->
24352436
pathAndFillManager.loadCompressedXML(file.getAbsolutePath());
2436-
case PathAndFillManager.TRACES_FILE_TYPE_UNCOMPRESSED_XML ->
2437+
case UNCOMPRESSED_XML ->
24372438
pathAndFillManager.loadUncompressedXML(file.getAbsolutePath());
24382439
default -> {
24392440
guiUtils.error(file.getAbsolutePath() + " is not a valid traces file.");
@@ -2449,8 +2450,8 @@ protected synchronized boolean loadSWCFile(File file) {
24492450
getUI().runCommand("SWC...", file.getAbsolutePath());
24502451
return false; // no way to know if file was actually imported via GUI
24512452
}
2452-
final int guessedType = pathAndFillManager.guessTracesFileType(file.getAbsolutePath());
2453-
if (guessedType == PathAndFillManager.TRACES_FILE_TYPE_SWC) {
2453+
final IOUtils.ReconstructionFormat guessedType = pathAndFillManager.guessTracesFileType(file.getAbsolutePath());
2454+
if (guessedType == IOUtils.ReconstructionFormat.SWC) {
24542455
return pathAndFillManager.importSWC(file.getAbsolutePath(), false, 0, 0, 0, 1, 1, 1, 1, false);
24552456
} else {
24562457
error(file.getAbsolutePath() + " does not seem to contain valid SWC data.");

‎src/main/java/sc/fiji/snt/SNTUI.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7002,7 +7002,7 @@ static int getImportActionType(final File file) {
70027002
final String filename = file.getName().toLowerCase();
70037003
if (filename.endsWith(".traces"))
70047004
return ImportAction.TRACES;
7005-
if (filename.endsWith("swc"))
7005+
if (IOUtils.isSWCFile(file))
70067006
return ImportAction.SWC;
70077007
if (filename.endsWith(".json"))
70087008
return ImportAction.JSON;

‎src/main/java/sc/fiji/snt/SNTUtils.java‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ else if (time < 90000)
649649
* valid, readable directory.
650650
*/
651651
public static File[] getReconstructionFiles(final File dir, final String pattern) {
652-
if (dir != null && dir.isFile() && dir.canRead() && isReconstructionFile(dir))
652+
if (dir != null && dir.isFile() && dir.canRead() && isReconstructionFile(dir, true))
653653
return new File[]{dir};
654654
if (dir == null || !dir.isDirectory() || !dir.exists() || !dir.canRead()) {
655655
return null;
@@ -659,14 +659,33 @@ public static File[] getReconstructionFiles(final File dir, final String pattern
659659
final String name = file.getName();
660660
if (!name.contains(validatedPattern))
661661
return false;
662-
return file.canRead() && isReconstructionFile(file);
662+
return file.canRead() && isReconstructionFile(file, true);
663663
};
664664
return dir.listFiles(filter);
665665
}
666666

667+
/**
668+
* Checks if a file is a reconstruction file based on its extension only.
669+
*
670+
* @param file the file to be tested
671+
* @return true if the extension is that of a supported reconstruction format
672+
* @see sc.fiji.snt.util.IOUtils#isReconstructionFile(File)
673+
*/
667674
public static boolean isReconstructionFile(final File file) {
668-
final String lName = file.getName().toLowerCase();
669-
return (lName.endsWith("swc") || lName.endsWith(".traces") || lName.endsWith(".json") || lName.endsWith(".ndf"));
675+
return sc.fiji.snt.util.IOUtils.isReconstructionFile(file);
676+
}
677+
678+
/**
679+
* Checks if a file is a reconstruction file, optionally inspecting the header of .xml files
680+
* (Neurolucida).
681+
*
682+
* @param file the file to be tested
683+
* @param sniffXml whether .xml files should be inspected
684+
* @return true if the file is a supported reconstruction file
685+
* @see sc.fiji.snt.util.IOUtils#isReconstructionFile(File, boolean)
686+
*/
687+
public static boolean isReconstructionFile(final File file, final boolean sniffXml) {
688+
return sc.fiji.snt.util.IOUtils.isReconstructionFile(file, sniffXml);
670689
}
671690
/**
672691
* Returns timestamped backup copies of traces files in the specified location.

‎src/main/java/sc/fiji/snt/gui/GuiUtils.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1507,8 +1507,15 @@ private File showClassicSaveDialog(final String title, final File file, final Fi
15071507
}
15081508

15091509
public File[] getReconstructionFiles(final File selectedFile) {
1510+
// NB: xml (Neurolucida) cannot be told apart by name alone. It is listed here, and validated
1511+
// by the importers once selected
1512+
final String[] extensions = java.util.stream.Stream.concat(
1513+
java.util.Arrays.stream(sc.fiji.snt.util.IOUtils.getReconstructionExtensions()),
1514+
java.util.stream.Stream.of("xml")).toArray(String[]::new);
1515+
final String label = java.util.Arrays.stream(extensions).map(e -> "." + e)
1516+
.collect(java.util.stream.Collectors.joining(", "));
15101517
final FileNameExtensionFilter filter = new FileNameExtensionFilter(
1511-
"Reconstruction files (.traces, .swc, .json, .ndf, .xml)", "traces", "swc", "json", "ndf", "xml");
1518+
"Reconstruction files (" + label + ")", extensions);
15121519
return (File[]) showOpenDialog("Choose Reconstruction File(s)", JFileChooser.FILES_ONLY, true,
15131520
selectedFile, false, filter);
15141521
}

‎src/main/java/sc/fiji/snt/gui/SWCImportDialog.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ private class PreviewArea extends JTextArea implements DocumentListener {
238238
void previewFileHeader(final File file) {
239239
if (isVisible() && lastPreviewedFile == file && getText().startsWith("#"))
240240
return;
241-
if (file == null || !file.getAbsolutePath().toLowerCase().endsWith("swc")) {
241+
if (!sc.fiji.snt.util.IOUtils.isSWCFile(file)) {
242242
setForeground(GuiUtils.Colors.errorColor());
243243
setText("File path does not contain a valid SWC extension...");
244244
return;

‎src/main/java/sc/fiji/snt/gui/cmds/MultiSWCImporterCmd.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,8 @@ private Map<String, String> getImportMap() {
133133
if (dir == null || !dir.isDirectory() || !dir.exists()) return null;
134134
final File[] files = dir.listFiles(file -> {
135135
if (file.isHidden()) return false;
136-
final String fName = file.getName().toLowerCase();
137-
if (!fName.endsWith("swc")) return false;
138-
return pattern == null || pattern.isEmpty() || fName.contains(pattern);
136+
if (!sc.fiji.snt.util.IOUtils.isSWCFile(file)) return false;
137+
return pattern == null || pattern.isEmpty() || file.getName().toLowerCase().contains(pattern);
139138
});
140139
if (files == null || files.length == 0) return null;
141140
final Map<String, String> map = new HashMap<>();

‎src/main/java/sc/fiji/snt/gui/cmds/ShortcutWindowCmd.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,7 @@ public void filesDropped(final File[] files) {
422422
}
423423

424424
private boolean supported(final File file) {
425-
final String filename = file.getName().toLowerCase();
426-
return (filename.endsWith(".traces")) || (filename.endsWith("swc")) || (filename.endsWith(".json") || (filename.endsWith(".ndf")));
425+
return sc.fiji.snt.util.IOUtils.isReconstructionFile(file, true);
427426
}
428427

429428
void importFile(final File file) {

0 commit comments

Comments
 (0)