Created
October 3, 2024 05:39
-
-
Save petebankhead/48c27f0f2c6a13945a812330f80240c5 to your computer and use it in GitHub Desktop.
QuPath script to copy simple, single-file images into a project to make the project self-contained.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* QuPath script to copy images into a project. | |
* This is used to help make the project directory self-contained. | |
* | |
* By default, local files are copied into an 'images' subdirectory of the project. | |
* | |
* Limitation: This only handles local files that are currently outside the project directory, | |
* and excludes images that are known to have 'multipart' files (e.g. dicom, mrxs, vsi & zarr). | |
* | |
* Warning: This has not been very extensively tested! | |
* It shouldn't delete or overwrite files (only copy them), but still... please check the code & use with caution. | |
* | |
* Originally written for QuPath v0.6.0. | |
*/ | |
import qupath.lib.common.GeneralTools | |
import java.nio.file.Files | |
import java.nio.file.Path | |
import java.nio.file.StandardCopyOption | |
import static qupath.lib.scripting.QP.* | |
Set multipartFileExtensions = Set.of(".dicom", ".mrxs", ".vsi", ".zarr") | |
String imagesSubDirName = "images" | |
// Check we can get a base directory for the project | |
var project = getProject() | |
var projectPath = project.getPath() | |
if (projectPath == null || !Files.exists(projectPath)) { | |
throw new UnsupportedOperationException("Project path not found!"); | |
} | |
var baseDir = Files.isDirectory(projectPath) ? projectPath.toRealPath() : projectPath.getParent().toRealPath() | |
// Get all the unique URIs | |
var uris = project.getImageList() | |
.stream() | |
.flatMap(e -> e.getURIs().stream()) | |
.distinct() | |
.toList() | |
// Check if we have anything to do at all | |
if (!uris.stream().anyMatch(u -> isExternalLocalFile(u, baseDir))) { | |
println "No paths to update!" | |
return | |
} | |
// Create an images directory within the project | |
var imagesDir = baseDir.resolve(imagesSubDirName) | |
if (!Files.exists(imagesDir)) { | |
println "Creating 'images' subdirectory in project" | |
Files.createDirectories(imagesDir); | |
} | |
// Gather all the URIs for files that are not already contained within the base directory | |
Map<URI, URI> replacements = new HashMap<>() | |
for (var uri : uris) { | |
var path = GeneralTools.toPath(uri) | |
if (isExternalLocalFile(path, baseDir)) { | |
String baseName = path.getFileName().toString() | |
if (GeneralTools.checkExtensions(baseName, multipartFileExtensions.toArray(String[]::new))) { | |
println "WARN: Skipping potential multi-part file " + path | |
continue | |
} | |
String targetName = baseName | |
int count = 0 | |
while (Files.exists(imagesDir.resolve(targetName))) { | |
// TODO: Consider checking file contents, so we don't end up with duplicates of the same image | |
com.google.common.io.Files.getNameWithoutExtension() | |
count++ | |
targetName = baseName + "-" + count | |
} | |
var targetPath = imagesDir.resolve(targetName) | |
println "Copying " + path + " -> " + targetPath | |
Files.copy(path, targetPath, StandardCopyOption.COPY_ATTRIBUTES) | |
replacements.put(uri, targetPath.toUri()) | |
} | |
} | |
// Update the URIs, if required | |
if (replacements.isEmpty()) { | |
println "No replacements made!" | |
} else { | |
if (replacements.size() == 1) | |
println "Replaced 1 URI" | |
else | |
println "Replaced " + replacements.size() + " URIs" | |
project.getImageList().stream().forEach(e -> e.updateURIs(replacements)) | |
} | |
/** | |
* Check if a URI is a local file outside a specified base directory. | |
* @param uri the URI to check | |
* @param baseDir the base directory | |
* @return true if the URI refers to a file, and it is not located within the baseDir; false otherwise | |
*/ | |
private static boolean isExternalLocalFile(URI uri, Path baseDir) { | |
var path = GeneralTools.toPath(uri) | |
return isExternalLocalFile(path, baseDir) | |
} | |
/** | |
* Check if a path refers to a local file outside a specified base directory. | |
* @param path the path to check | |
* @param baseDir the base directory | |
* @return true if the path refers to a file, and it is not located within the baseDir; false otherwise | |
*/ | |
private static boolean isExternalLocalFile(Path path, Path baseDir) { | |
if (path == null || !Files.exists(path)) | |
return null; | |
path = path.toRealPath() | |
return !path.toString().startsWith(baseDir.toString()) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment