use of qupath.lib.images.ImageData in project qupath by qupath.
the class ProjectBrowser method getPopup.
ContextMenu getPopup() {
Action actionOpenImage = new Action("Open image", e -> qupath.openImageEntry(getSelectedEntry()));
Action actionRemoveImage = new Action("Remove image(s)", e -> {
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
Collection<ProjectImageEntry<BufferedImage>> entries = ProjectTreeRow.getEntries(imageRows);
if (entries.isEmpty())
return;
// Don't allow us to remove any entries that are currently open (in any viewer)
for (var viewer : qupath.getViewers()) {
var imageData = viewer.getImageData();
var entry = imageData == null ? null : getProject().getEntry(imageData);
if (entry != null && entries.contains(entry)) {
Dialogs.showErrorMessage("Remove project entries", "Please close all images you want to remove!");
return;
}
}
if (entries.size() == 1) {
if (!Dialogs.showConfirmDialog("Remove project entry", "Remove " + entries.iterator().next().getImageName() + " from project?"))
return;
} else if (!Dialogs.showYesNoDialog("Remove project entries", String.format("Remove %d entries?", entries.size())))
return;
var result = Dialogs.showYesNoCancelDialog("Remove project entries", "Delete all associated data?");
if (result == DialogButton.CANCEL)
return;
project.removeAllImages(entries, result == DialogButton.YES);
refreshTree(null);
syncProject(project);
if (tree != null) {
boolean isExpanded = tree.getRoot() != null && tree.getRoot().isExpanded();
tree.setRoot(model.getRoot());
tree.getRoot().setExpanded(isExpanded);
}
});
Action actionDuplicateImages = new Action("Duplicate image(s)", e -> {
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
if (imageRows.isEmpty()) {
logger.debug("Nothing to duplicate - no entries selected");
return;
}
boolean singleImage = false;
String name = "";
String title = "Duplicate images";
String namePrompt = "Append to image name";
String nameHelp = "Specify text to append to the image name to distinguish duplicated images";
if (imageRows.size() == 1) {
title = "Duplicate image";
namePrompt = "Duplicate image name";
nameHelp = "Specify name for the duplicated image";
singleImage = true;
name = imageRows.iterator().next().getDisplayableString();
name = GeneralTools.generateDistinctName(name, project.getImageList().stream().map(p -> p.getImageName()).collect(Collectors.toSet()));
}
var params = new ParameterList().addStringParameter("name", namePrompt, name, nameHelp).addBooleanParameter("copyData", "Also duplicate data files", true, "Duplicate any associated data files along with the image");
if (!Dialogs.showParameterDialog(title, params))
return;
boolean copyData = params.getBooleanParameterValue("copyData");
name = params.getStringParameterValue("name");
// Ensure we have a single space and then the text to append, with extra whitespace removed
if (!singleImage && !name.isBlank())
name = " " + name.strip();
for (var imageRow : imageRows) {
try {
var newEntry = project.addDuplicate(ProjectTreeRow.getEntry(imageRow), copyData);
if (newEntry != null && !name.isBlank()) {
if (singleImage)
newEntry.setImageName(name);
else
newEntry.setImageName(newEntry.getImageName() + name);
}
} catch (Exception ex) {
Dialogs.showErrorNotification("Duplicating image", "Error duplicating " + ProjectTreeRow.getEntry(imageRow).getImageName());
logger.error(ex.getLocalizedMessage(), ex);
}
}
try {
project.syncChanges();
} catch (Exception ex) {
logger.error("Error synchronizing project changes: " + ex.getLocalizedMessage(), ex);
}
refreshProject();
if (imageRows.size() == 1)
logger.debug("Duplicated 1 image entry");
else
logger.debug("Duplicated {} image entries");
});
Action actionSetImageName = new Action("Rename image", e -> {
TreeItem<ProjectTreeRow> path = tree.getSelectionModel().getSelectedItem();
if (path == null)
return;
if (path.getValue().getType() == ProjectTreeRow.Type.IMAGE) {
if (setProjectEntryImageName(ProjectTreeRow.getEntry(path.getValue())) && project != null)
syncProject(project);
}
});
// Add a metadata value
Action actionAddMetadataValue = new Action("Add metadata", e -> {
Project<BufferedImage> project = getProject();
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
if (project != null && !imageRows.isEmpty()) {
TextField tfMetadataKey = new TextField();
var suggestions = project.getImageList().stream().map(p -> p.getMetadataKeys()).flatMap(Collection::stream).distinct().sorted().collect(Collectors.toList());
TextFields.bindAutoCompletion(tfMetadataKey, suggestions);
TextField tfMetadataValue = new TextField();
Label labKey = new Label("New key");
Label labValue = new Label("New value");
labKey.setLabelFor(tfMetadataKey);
labValue.setLabelFor(tfMetadataValue);
tfMetadataKey.setTooltip(new Tooltip("Enter the name for the metadata entry"));
tfMetadataValue.setTooltip(new Tooltip("Enter the value for the metadata entry"));
ProjectImageEntry<BufferedImage> entry = imageRows.size() == 1 ? ProjectTreeRow.getEntry(imageRows.iterator().next()) : null;
int nMetadataValues = entry == null ? 0 : entry.getMetadataKeys().size();
GridPane pane = new GridPane();
pane.setVgap(5);
pane.setHgap(5);
pane.add(labKey, 0, 0);
pane.add(tfMetadataKey, 1, 0);
pane.add(labValue, 0, 1);
pane.add(tfMetadataValue, 1, 1);
String name = imageRows.size() + " images";
if (entry != null) {
name = entry.getImageName();
if (nMetadataValues > 0) {
Label labelCurrent = new Label("Current metadata");
TextArea textAreaCurrent = new TextArea();
textAreaCurrent.setEditable(false);
String keyString = entry.getMetadataSummaryString();
if (keyString.isEmpty())
textAreaCurrent.setText("No metadata entries yet");
else
textAreaCurrent.setText(keyString);
textAreaCurrent.setPrefRowCount(3);
labelCurrent.setLabelFor(textAreaCurrent);
pane.add(labelCurrent, 0, 2);
pane.add(textAreaCurrent, 1, 2);
}
}
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Metadata");
dialog.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setHeaderText("Set metadata for " + name);
dialog.getDialogPane().setContent(pane);
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
String key = tfMetadataKey.getText().trim();
String value = tfMetadataValue.getText();
if (key.isEmpty()) {
logger.warn("Attempted to set metadata value for {}, but key was empty!", name);
} else {
// Set metadata for all entries
for (var temp : imageRows) ProjectTreeRow.getEntry(temp).putMetadataValue(key, value);
syncProject(project);
tree.refresh();
}
}
} else {
Dialogs.showErrorMessage("Edit image description", "No entry is selected!");
}
});
// Edit the description for the image
Action actionEditDescription = new Action("Edit description", e -> {
Project<?> project = getProject();
ProjectImageEntry<?> entry = getSelectedEntry();
if (project != null && entry != null) {
if (showDescriptionEditor(entry)) {
descriptionText.set(entry.getDescription());
syncProject(project);
}
} else {
Dialogs.showErrorMessage("Edit image description", "No entry is selected!");
}
});
// Mask the name of the images and shuffle the entry
Action actionMaskImageNames = ActionTools.createSelectableAction(PathPrefs.maskImageNamesProperty(), "Mask image names");
// Refresh thumbnail according to current display settings
Action actionRefreshThumbnail = new Action("Refresh thumbnail", e -> {
TreeItem<ProjectTreeRow> path = tree.getSelectionModel().getSelectedItem();
if (path == null)
return;
if (path.getValue().getType() == ProjectTreeRow.Type.IMAGE) {
ProjectImageEntry<BufferedImage> entry = ProjectTreeRow.getEntry(path.getValue());
if (!isCurrentImage(entry)) {
logger.warn("Cannot refresh entry for image that is not open!");
return;
}
BufferedImage imgThumbnail = qupath.getViewer().getRGBThumbnail();
imgThumbnail = resizeForThumbnail(imgThumbnail);
try {
entry.setThumbnail(imgThumbnail);
} catch (IOException e1) {
logger.error("Error writing thumbnail", e1);
}
tree.refresh();
}
});
// Open the project directory using Explorer/Finder etc.
Action actionOpenProjectDirectory = createBrowsePathAction("Project...", () -> getProjectPath());
Action actionOpenProjectEntryDirectory = createBrowsePathAction("Project entry...", () -> getProjectEntryPath());
Action actionOpenImageServerDirectory = createBrowsePathAction("Image server...", () -> getImageServerPath());
Menu menuSort = new Menu("Sort by...");
ContextMenu menu = new ContextMenu();
var hasProjectBinding = qupath.projectProperty().isNotNull();
var menuOpenDirectories = MenuTools.createMenu("Open directory...", actionOpenProjectDirectory, actionOpenProjectEntryDirectory, actionOpenImageServerDirectory);
menuOpenDirectories.visibleProperty().bind(hasProjectBinding);
// MenuItem miOpenProjectDirectory = ActionUtils.createMenuItem(actionOpenProjectDirectory);
MenuItem miOpenImage = ActionUtils.createMenuItem(actionOpenImage);
MenuItem miRemoveImage = ActionUtils.createMenuItem(actionRemoveImage);
MenuItem miDuplicateImage = ActionUtils.createMenuItem(actionDuplicateImages);
MenuItem miSetImageName = ActionUtils.createMenuItem(actionSetImageName);
MenuItem miRefreshThumbnail = ActionUtils.createMenuItem(actionRefreshThumbnail);
MenuItem miEditDescription = ActionUtils.createMenuItem(actionEditDescription);
MenuItem miAddMetadata = ActionUtils.createMenuItem(actionAddMetadataValue);
MenuItem miMaskImages = ActionUtils.createCheckMenuItem(actionMaskImageNames);
// Set visibility as menu being displayed
menu.setOnShowing(e -> {
TreeItem<ProjectTreeRow> selected = tree.getSelectionModel().getSelectedItem();
ProjectImageEntry<BufferedImage> selectedEntry = selected == null ? null : ProjectTreeRow.getEntry(selected.getValue());
var entries = getSelectedImageRowsRecursive();
boolean isImageEntry = selectedEntry != null;
// miOpenProjectDirectory.setVisible(project != null && project.getBaseDirectory().exists());
miOpenImage.setVisible(isImageEntry);
miDuplicateImage.setVisible(isImageEntry);
miSetImageName.setVisible(isImageEntry);
miAddMetadata.setVisible(!entries.isEmpty());
miEditDescription.setVisible(isImageEntry);
miRefreshThumbnail.setVisible(isImageEntry && isCurrentImage(selectedEntry));
miRemoveImage.setVisible(selected != null && project != null && !project.getImageList().isEmpty());
if (project == null) {
menuSort.setVisible(false);
return;
}
Map<String, MenuItem> newItems = new TreeMap<>();
for (ProjectImageEntry<?> entry : project.getImageList()) {
// Add all entry metadata keys
for (String key : entry.getMetadataKeys()) {
if (!newItems.containsKey(key))
newItems.put(key, ActionUtils.createMenuItem(createSortByKeyAction(key, key)));
}
// Add all additional keys
for (String key : baseMetadataKeys) {
if (!newItems.containsKey(key))
newItems.put(key, ActionUtils.createMenuItem(createSortByKeyAction(key, key)));
}
}
menuSort.getItems().setAll(newItems.values());
menuSort.getItems().add(0, ActionUtils.createMenuItem(createSortByKeyAction("None", null)));
menuSort.getItems().add(1, new SeparatorMenuItem());
menuSort.setVisible(true);
if (menu.getItems().isEmpty())
e.consume();
});
SeparatorMenuItem separator = new SeparatorMenuItem();
separator.visibleProperty().bind(menuSort.visibleProperty());
menu.getItems().addAll(miOpenImage, miRemoveImage, miDuplicateImage, new SeparatorMenuItem(), miSetImageName, miAddMetadata, miEditDescription, miMaskImages, miRefreshThumbnail, separator, menuSort);
separator = new SeparatorMenuItem();
separator.visibleProperty().bind(menuOpenDirectories.visibleProperty());
if (Desktop.isDesktopSupported()) {
menu.getItems().addAll(separator, menuOpenDirectories);
}
return menu;
}
use of qupath.lib.images.ImageData in project qupath by qupath.
the class DefaultScriptEditor method createInsertAction.
Action createInsertAction(final String name) {
Action action = new Action(name, e -> {
var control = getCurrentTextComponent();
String join = "," + System.lineSeparator() + " ";
String listFormat = "[" + System.lineSeparator() + " %s" + System.lineSeparator() + "]";
if (name.toLowerCase().equals("pixel classifiers")) {
try {
String classifiers = qupath.getProject().getPixelClassifiers().getNames().stream().map(classifierName -> "\"" + classifierName + "\"").collect(Collectors.joining(join));
String s = classifiers.isEmpty() ? "[]" : String.format(listFormat, classifiers);
control.paste(s);
} catch (IOException ex) {
logger.error("Could not fetch classifiers", ex.getLocalizedMessage());
}
} else if (name.toLowerCase().equals("object classifiers")) {
try {
String classifiers = qupath.getProject().getObjectClassifiers().getNames().stream().map(classifierName -> "\"" + classifierName + "\"").collect(Collectors.joining(join));
String s = classifiers.isEmpty() ? "[]" : String.format(listFormat, classifiers);
control.paste(s);
} catch (IOException ex) {
logger.error("Could not fetch classifiers", ex.getLocalizedMessage());
}
} else if (name.toLowerCase().equals("detection")) {
var imageData = qupath.getImageData();
String measurements = "";
if (imageData != null) {
measurements = imageData.getHierarchy().getDetectionObjects().stream().flatMap(d -> d.getMeasurementList().getMeasurementNames().stream()).distinct().map(m -> "\"" + m + "\"").collect(Collectors.joining(join));
}
String s = measurements.isEmpty() ? "[]" : String.format(listFormat, measurements);
control.paste(s);
} else if (name.toLowerCase().equals(GeneralTools.SYMBOL_MU + ""))
control.paste(GeneralTools.SYMBOL_MU + "");
else {
// TODO: fix
// // Imports (end with a new line)
// if (name.toLowerCase().equals("qpex"))
// control.insertText(0, "import static qupath.lib.gui.scripting.QPEx.*");
// else if (name.toLowerCase().equals("qp"))
// control.insertText(0, "import static qupath.lib.gui.scripting.QP.*");
// else if (name.toLowerCase().equals("all default"))
// control.insertText(0, QPEx.getDefaultImports(false));
// currentLanguage.get().getSyntax().handleNewLine(control, smartEditing.get());
}
e.consume();
});
if (name.equals(GeneralTools.SYMBOL_MU + ""))
action.setAccelerator(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
else if (name.toLowerCase().equals("pixel classifiers") || name.toLowerCase().equals("object classifiers"))
action.disabledProperty().bind(qupath.projectProperty().isNull());
else if (name.toLowerCase().equals("detection"))
action.disabledProperty().bind(qupath.imageDataProperty().isNull());
return action;
}
use of qupath.lib.images.ImageData in project qupath by qupath.
the class DensityMapDataOp method apply.
@Override
public Mat apply(ImageData<BufferedImage> imageData, RegionRequest request) throws IOException {
ensureInitialized();
logger.trace("Applying density map op for {}", request);
// Calculate how much padding we need
var padding = op.getPadding();
if (!padding.isEmpty()) {
// Add padding to the request
double downsample = request.getDownsample();
var padding2 = Padding.getPadding((int) Math.round(padding.getX1() * downsample), (int) Math.round(padding.getX2() * downsample), (int) Math.round(padding.getY1() * downsample), (int) Math.round(padding.getY2() * downsample));
request = request.pad2D(padding2);
}
// Get all objects within the padded region
var allPathObjects = imageData.getHierarchy().getObjectsForRegion(null, request, null).stream().filter(allObjects).collect(Collectors.toList());
if (allPathObjects.size() == 1)
logger.trace("Generating counts tile for 1 object");
else
logger.trace("Generating counts tile for {} objects", allPathObjects.size());
// Create an output mat
int nChannels = getChannelCount();
int width = (int) Math.round(request.getWidth() / request.getDownsample());
int height = (int) Math.round(request.getHeight() / request.getDownsample());
var mat = new Mat(height, width, opencv_core.CV_64FC(nChannels), Scalar.ZERO);
DoubleIndexer idx = mat.createIndexer();
// Get points representing all the centroids of each subpopulation of object
// Use these to increment pixel values in a counts image
int c = 0;
for (var entry : primaryObjects.entrySet()) {
var predicate = entry.getValue();
var primaryROIs = allPathObjects.stream().filter(predicate).map(p -> PathObjectTools.getROI(p, true)).collect(Collectors.toList());
var points = objectsToPoints(primaryROIs);
incrementCounts(idx, points, request, width, height, c);
c++;
}
// Get points for all objects, if we need them
if (c < getChannelCount()) {
var allObjectROIs = allPathObjects.stream().map(p -> PathObjectTools.getROI(p, true)).collect(Collectors.toList());
var points = objectsToPoints(allObjectROIs);
incrementCounts(idx, points, request, width, height, c);
c++;
}
idx.close();
// Now apply the op
var output = this.op.apply(mat);
return output;
}
use of qupath.lib.images.ImageData in project qupath by qupath.
the class OMEPyramidWriterCommand method run.
@Override
public void run() {
if (currentTask != null && !currentTask.isDone()) {
if (!Dialogs.showConfirmDialog("OME Pyramid writer", "Do you want to stop the current export?"))
// TODO: Delete exporting file?
return;
else {
currentTask.cancel(true);
}
}
QuPathViewer viewer = qupath.getViewer();
int zPos = viewer.getZPosition();
int tPos = viewer.getTPosition();
ImageData<BufferedImage> imageData = viewer.getImageData();
if (imageData == null) {
Dialogs.showNoImageError("OME Pyramid writer");
return;
}
ImageServer<BufferedImage> server = imageData.getServer();
// Region
PathObject selected = imageData.getHierarchy().getSelectionModel().getSelectedObject();
ImageRegion region = null;
int width, height;
if (selected == null || !selected.hasROI() || !selected.getROI().isArea()) {
width = server.getWidth();
height = server.getHeight();
} else {
region = ImageRegion.createInstance(selected.getROI());
width = region.getWidth();
height = region.getHeight();
}
// Set compression - with a sanity check for validity, defaulting to another comparable method if necessary
CompressionType compression = getDefaultPyramidCompression();
List<String> compatibleCompression = Arrays.stream(CompressionType.values()).filter(c -> c.supportsImage(server)).map(c -> c.toFriendlyString()).collect(Collectors.toList());
if (!compatibleCompression.contains(compression.toFriendlyString()))
compression = CompressionType.DEFAULT;
var params = new ParameterList().addChoiceParameter("compression", "Compression type", compression.toFriendlyString(), compatibleCompression).addIntParameter("scaledDownsample", "Pyramidal downsample", scaledDownsample.get(), "", 1, 8, "Amount to downsample each consecutive pyramidal level; use 1 to indicate the image should not be pyramidal").addIntParameter("tileSize", "Tile size", getDefaultTileSize(), "px", "Tile size for export (should be between 128 and 8192)").addBooleanParameter("parallelize", "Parallelize export", parallelizeTiling.get(), "Export image tiles in parallel - " + "this should be faster, best keep it on unless you encounter export problems").addBooleanParameter("allZ", "All z-slices", allZ.get(), "Include all z-slices in the stack").addBooleanParameter("allT", "All timepoints", allT.get(), "Include all timepoints in the time-series");
boolean singleTile = server.getTileRequestManager().getTileRequests(RegionRequest.createInstance(server)).size() == 1;
params.setHiddenParameters(server.nZSlices() == 1, "allZ");
params.setHiddenParameters(server.nTimepoints() == 1, "allT");
params.setHiddenParameters(singleTile, "tileSize", "parallelize");
if (!Dialogs.showParameterDialog("Export OME-TIFF", params))
return;
compression = CompressionType.fromFriendlyString((String) params.getChoiceParameterValue("compression"));
defaultPyramidCompression.set(compression);
int downsampleScale = params.getIntParameterValue("scaledDownsample");
scaledDownsample.set(downsampleScale);
int tileSize = params.getIntParameterValue("tileSize");
boolean parallelize = params.getBooleanParameterValue("parallelize");
if (!singleTile) {
tileSize = GeneralTools.clipValue(tileSize, 128, 8192);
defaultTileSize.set(tileSize);
parallelizeTiling.set(parallelize);
}
boolean doAllZ = false;
boolean doAllT = false;
if (server.nZSlices() > 1) {
doAllZ = params.getBooleanParameterValue("allZ");
allZ.set(doAllZ);
}
if (server.nTimepoints() > 1) {
doAllT = params.getBooleanParameterValue("allT");
allT.set(doAllT);
}
OMEPyramidWriter.Builder builder = new OMEPyramidWriter.Builder(server);
if (region != null) {
builder = builder.region(region);
} else {
if (server.nZSlices() > 1 && !doAllZ)
builder.zSlice(zPos);
if (server.nTimepoints() > 1 && !doAllT)
builder.timePoint(tPos);
}
builder.compression(compression);
if (downsampleScale <= 1 || Math.max(width, height) / server.getDownsampleForResolution(0) < minSizeForTiling.get())
builder.downsamples(server.getDownsampleForResolution(0));
else
builder.scaledDownsampling(server.getDownsampleForResolution(0), downsampleScale);
// Set tile size; if we just have one tile, use the image width & height
if (singleTile)
builder = builder.tileSize(width, height);
else
builder = builder.tileSize(tileSize).parallelize(parallelize);
if (server.nZSlices() > 1 && doAllZ)
builder.allZSlices();
if (server.nTimepoints() > 1 && doAllT)
builder.allTimePoints();
// Prompt for file
File fileOutput = Dialogs.promptToSaveFile("Write pyramid", null, null, "OME TIFF pyramid", ".ome.tif");
if (fileOutput == null)
return;
String name = fileOutput.getName();
// We can have trouble with only the '.tif' part of the name being included
if (name.endsWith(".tif") && !name.endsWith(".ome.tif"))
fileOutput = new File(fileOutput.getParentFile(), name.substring(0, name.length() - 4) + ".ome.tif");
OMEPyramidSeries writer = builder.build();
if (pool == null) {
pool = Executors.newSingleThreadExecutor(ThreadTools.createThreadFactory("ome-pyramid-export", false));
}
currentTask = pool.submit(new WriterTask(OMEPyramidWriter.createWriter(writer), fileOutput.getAbsolutePath()));
}
use of qupath.lib.images.ImageData in project qupath by qupath.
the class PathClassPane method selectObjectsByClassification.
/**
* Select objects by classification, logging the step (if performed) in the history workflow.
* @param imageData the {@link ImageData} containing objects to be selected
* @param pathClasses classifications that will result in an object being selected
* @return true if a selection command was run, false otherwise (e.g. if no pathClasses were specified)
*/
public static boolean selectObjectsByClassification(ImageData<?> imageData, PathClass... pathClasses) {
var hierarchy = imageData.getHierarchy();
if (pathClasses.length == 0) {
logger.warn("Cannot select objects by classification - no classifications selected!");
return false;
}
QP.selectObjectsByPathClass(hierarchy, pathClasses);
var s = Arrays.stream(pathClasses).map(p -> p == null || p == PathClassFactory.getPathClassUnclassified() ? "null" : "\"" + p.toString() + "\"").collect(Collectors.joining(", "));
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Select objects by classification", "selectObjectsByClassification(" + s + ");"));
return true;
}
Aggregations