Search in sources :

Example 1 with ChannelDisplayInfo

use of qupath.lib.display.ChannelDisplayInfo in project qupath by qupath.

the class ProjectImportImagesCommand method getThumbnailRGB.

// /**
// * Add a single ImageServer to a project, without considering sub-images.
// * <p>
// * This includes an optional attempt to request a thumbnail; if this fails, the image will not be added.
// *
// * @param project the project to which the entry should be added
// * @param server the server to add
// * @param type the ImageType that should be set for each entry being added
// * @return
// */
// public static ProjectImageEntry<BufferedImage> addSingleImageToProject(Project<BufferedImage> project, ImageServer<BufferedImage> server, ImageType type) {
// ProjectImageEntry<BufferedImage> entry = null;
// try {
// var img = getThumbnailRGB(server, null);
// entry = project.addImage(server);
// if (entry != null) {
// // Write a thumbnail if we can
// entry.setThumbnail(img);
// // Initialize an ImageData object with a type, if required
// if (type != null) {
// var imageData = new ImageData<>(server, type);
// entry.saveImageData(imageData);
// }
// }
// } catch (IOException e) {
// logger.warn("Error attempting to add " + server, e);
// }
// return entry;
// }
public static BufferedImage getThumbnailRGB(ImageServer<BufferedImage> server, ImageDisplay imageDisplay) throws IOException {
    var img2 = server.getDefaultThumbnail(server.nZSlices() / 2, 0);
    // Try to write RGB images directly
    boolean success = false;
    if (imageDisplay == null && (server.isRGB() || img2.getType() == BufferedImage.TYPE_BYTE_GRAY)) {
        return resizeForThumbnail(img2);
    }
    if (!success) {
        // Try with display transforms
        if (imageDisplay == null) {
            // By wrapping the thumbnail, we avoid slow z-stack/time series requests & determine brightness & contrast just from one plane
            var wrappedServer = new WrappedBufferedImageServer("Dummy", img2, server.getMetadata().getChannels());
            imageDisplay = new ImageDisplay(new ImageData<>(wrappedServer));
        // imageDisplay = new ImageDisplay(new ImageData<>(server));
        }
        for (ChannelDisplayInfo info : imageDisplay.selectedChannels()) {
            imageDisplay.autoSetDisplayRange(info);
        }
        img2 = imageDisplay.applyTransforms(img2, null);
        return resizeForThumbnail(img2);
    }
    return img2;
}
Also used : WrappedBufferedImageServer(qupath.lib.images.servers.WrappedBufferedImageServer) ImageData(qupath.lib.images.ImageData) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) ImageDisplay(qupath.lib.display.ImageDisplay)

Example 2 with ChannelDisplayInfo

use of qupath.lib.display.ChannelDisplayInfo in project qupath by qupath.

the class MiniViewers method createDialog.

static Stage createDialog(QuPathViewer viewer, boolean channelViewer) {
    final Stage dialog = new Stage();
    dialog.initOwner(viewer.getView().getScene().getWindow());
    ObservableList<ChannelDisplayInfo> channels = viewer.getImageDisplay().availableChannels();
    MiniViewerManager manager = new MiniViewerManager(viewer, channelViewer ? channels.size() : 0);
    manager.getPane().styleProperty().bind(style);
    if (channelViewer) {
        dialog.setTitle("Channel viewer");
        Scene scene = new Scene(manager.getPane(), 400, 400);
        ListChangeListener<ChannelDisplayInfo> listChangeListener = new ListChangeListener<ChannelDisplayInfo>() {

            @Override
            public void onChanged(Change<? extends ChannelDisplayInfo> c) {
                if (c.getList().size() != manager.nChannels()) {
                    manager.setChannels(channels.size());
                    scene.setRoot(manager.getPane());
                }
            }
        };
        channels.addListener(listChangeListener);
        dialog.setOnHiding(e -> {
            channels.removeListener(listChangeListener);
            manager.close();
            manager.getPane().styleProperty().unbind();
        });
        dialog.setScene(scene);
    } else {
        dialog.setTitle("Mini viewer");
        Scene scene = new Scene(manager.getPane(), 400, 400);
        dialog.setScene(scene);
        dialog.setOnHiding(e -> {
            manager.close();
            manager.getPane().styleProperty().unbind();
        });
    }
    createPopup(manager);
    return dialog;
}
Also used : Stage(javafx.stage.Stage) Scene(javafx.scene.Scene) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) ListChangeListener(javafx.collections.ListChangeListener)

Example 3 with ChannelDisplayInfo

use of qupath.lib.display.ChannelDisplayInfo in project qupath by qupath.

the class BrightnessContrastCommand method createDialog.

protected Stage createDialog() {
    if (!isInitialized())
        initializeSliders();
    initializePopup();
    changed(null, null, qupath.getImageData());
    BorderPane pane = new BorderPane();
    GridPane box = new GridPane();
    String blank = "      ";
    Label labelMin = new Label("Min display");
    // labelMin.setTooltip(new Tooltip("Set minimum lookup table value - double-click to edit manually"));
    Label labelMinValue = new Label(blank);
    labelMinValue.setTooltip(new Tooltip("Set minimum lookup table value - double-click to edit manually"));
    labelMinValue.textProperty().bind(Bindings.createStringBinding(() -> {
        // If value < 10, return 2 dp, otherwise 1 dp. Should be more useful for 16-/32-bit images.
        return sliderMin.getValue() < 10 ? String.format("%.2f", sliderMin.getValue()) : String.format("%.1f", sliderMin.getValue());
    }, table.getSelectionModel().selectedItemProperty(), sliderMin.valueProperty()));
    box.add(labelMin, 0, 0);
    box.add(sliderMin, 1, 0);
    box.add(labelMinValue, 2, 0);
    Label labelMax = new Label("Max display");
    labelMax.setTooltip(new Tooltip("Set maximum lookup table value - double-click to edit manually"));
    Label labelMaxValue = new Label(blank);
    labelMaxValue.setTooltip(new Tooltip("Set maximum lookup table value - double-click to edit manually"));
    labelMaxValue.textProperty().bind(Bindings.createStringBinding(() -> {
        // If value < 10, return 2 dp, otherwise 1 dp. Should be more useful for 16-/32-bit images.
        return sliderMax.getValue() < 10 ? String.format("%.2f", sliderMax.getValue()) : String.format("%.1f", sliderMax.getValue());
    }, table.getSelectionModel().selectedItemProperty(), sliderMax.valueProperty()));
    box.add(labelMax, 0, 1);
    box.add(sliderMax, 1, 1);
    box.add(labelMaxValue, 2, 1);
    box.setVgap(5);
    GridPane.setFillWidth(sliderMin, Boolean.TRUE);
    GridPane.setFillWidth(sliderMax, Boolean.TRUE);
    box.prefWidthProperty().bind(pane.widthProperty());
    box.setPadding(new Insets(5, 0, 5, 0));
    GridPane.setHgrow(sliderMin, Priority.ALWAYS);
    GridPane.setHgrow(sliderMax, Priority.ALWAYS);
    // In the absence of a better way, make it possible to enter display range values
    // manually by double-clicking on the corresponding label
    labelMinValue.setOnMouseClicked(e -> {
        if (e.getClickCount() == 2) {
            ChannelDisplayInfo infoVisible = getCurrentInfo();
            if (infoVisible == null)
                return;
            Double value = Dialogs.showInputDialog("Display range", "Set display range minimum", (double) infoVisible.getMinDisplay());
            if (value != null && !Double.isNaN(value)) {
                sliderMin.setValue(value);
                // Update display directly if out of slider range
                if (value < sliderMin.getMin() || value > sliderMin.getMax()) {
                    imageDisplay.setMinMaxDisplay(infoVisible, (float) value.floatValue(), (float) infoVisible.getMaxDisplay());
                    // infoVisible.setMinDisplay(value.floatValue());
                    // viewer.updateThumbnail();
                    updateSliders();
                    viewer.repaintEntireImage();
                }
            }
        }
    });
    labelMaxValue.setOnMouseClicked(e -> {
        if (e.getClickCount() == 2) {
            ChannelDisplayInfo infoVisible = getCurrentInfo();
            if (infoVisible == null)
                return;
            Double value = Dialogs.showInputDialog("Display range", "Set display range maximum", (double) infoVisible.getMaxDisplay());
            if (value != null && !Double.isNaN(value)) {
                sliderMax.setValue(value);
                // Update display directly if out of slider range
                if (value < sliderMax.getMin() || value > sliderMax.getMax()) {
                    imageDisplay.setMinMaxDisplay(infoVisible, (float) infoVisible.getMinDisplay(), (float) value.floatValue());
                    // infoVisible.setMaxDisplay(value.floatValue());
                    // viewer.updateThumbnail();
                    updateSliders();
                    viewer.repaintEntireImage();
                }
            }
        }
    });
    Button btnAuto = new Button("Auto");
    btnAuto.setOnAction(e -> {
        if (imageDisplay == null)
            return;
        // if (histogram == null)
        // return;
        // //				setSliders((float)histogram.getEdgeMin(), (float)histogram.getEdgeMax());
        ChannelDisplayInfo info = getCurrentInfo();
        double saturation = PathPrefs.autoBrightnessContrastSaturationPercentProperty().get() / 100.0;
        imageDisplay.autoSetDisplayRange(info, saturation);
        for (ChannelDisplayInfo info2 : table.getSelectionModel().getSelectedItems()) {
            imageDisplay.autoSetDisplayRange(info2, saturation);
        }
        updateSliders();
        handleSliderChange();
    });
    Button btnReset = new Button("Reset");
    btnReset.setOnAction(e -> {
        for (ChannelDisplayInfo info : table.getSelectionModel().getSelectedItems()) {
            imageDisplay.setMinMaxDisplay(info, info.getMinAllowed(), info.getMaxAllowed());
        }
        sliderMin.setValue(sliderMin.getMin());
        sliderMax.setValue(sliderMax.getMax());
    });
    Stage dialog = new Stage();
    dialog.initOwner(qupath.getStage());
    dialog.setTitle("Brightness & contrast");
    // Create color/channel display table
    table = new TableView<>(imageDisplay == null ? FXCollections.observableArrayList() : imageDisplay.availableChannels());
    table.setPlaceholder(new Text("No channels available"));
    table.addEventHandler(KeyEvent.KEY_PRESSED, new CopyTableListener());
    table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    table.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> {
        updateHistogram();
        updateSliders();
    });
    TableColumn<ChannelDisplayInfo, ChannelDisplayInfo> col1 = new TableColumn<>("Color");
    col1.setCellValueFactory(new Callback<CellDataFeatures<ChannelDisplayInfo, ChannelDisplayInfo>, ObservableValue<ChannelDisplayInfo>>() {

        @Override
        public ObservableValue<ChannelDisplayInfo> call(CellDataFeatures<ChannelDisplayInfo, ChannelDisplayInfo> p) {
            return new SimpleObjectProperty<ChannelDisplayInfo>(p.getValue());
        }
    });
    col1.setCellFactory(column -> {
        return new TableCell<ChannelDisplayInfo, ChannelDisplayInfo>() {

            @Override
            protected void updateItem(ChannelDisplayInfo item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty) {
                    setText(null);
                    setGraphic(null);
                    return;
                }
                setText(item.getName());
                Rectangle square = new Rectangle(0, 0, 10, 10);
                Integer rgb = item.getColor();
                if (rgb == null)
                    square.setFill(Color.TRANSPARENT);
                else
                    square.setFill(ColorToolsFX.getCachedColor(rgb));
                setGraphic(square);
            }
        };
    });
    col1.setSortable(false);
    TableColumn<ChannelDisplayInfo, Boolean> col2 = new TableColumn<>("Selected");
    col2.setCellValueFactory(new Callback<CellDataFeatures<ChannelDisplayInfo, Boolean>, ObservableValue<Boolean>>() {

        @Override
        public ObservableValue<Boolean> call(CellDataFeatures<ChannelDisplayInfo, Boolean> item) {
            SimpleBooleanProperty property = new SimpleBooleanProperty(imageDisplay.selectedChannels().contains(item.getValue()));
            // Remove repaint code here - now handled by table selection changes
            property.addListener((v, o, n) -> {
                imageDisplay.setChannelSelected(item.getValue(), n);
                table.refresh();
                Platform.runLater(() -> viewer.repaintEntireImage());
            });
            return property;
        }
    });
    col2.setCellFactory(column -> {
        CheckBoxTableCell<ChannelDisplayInfo, Boolean> cell = new CheckBoxTableCell<>();
        // Select cells when clicked - means a click anywhere within the row forces selection.
        // Previously, clicking within the checkbox didn't select the row.
        cell.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
            if (e.isPopupTrigger())
                return;
            int ind = cell.getIndex();
            var tableView = cell.getTableView();
            if (ind < tableView.getItems().size()) {
                if (e.isShiftDown())
                    tableView.getSelectionModel().select(ind);
                else
                    tableView.getSelectionModel().clearAndSelect(ind);
                var channel = cell.getTableRow().getItem();
                // Handle clicks within the cell but outside the checkbox
                if (e.getTarget() == cell && channel != null && imageDisplay != null) {
                    updateDisplay(channel, !imageDisplay.selectedChannels().contains(channel));
                }
                e.consume();
            }
        });
        return cell;
    });
    col2.setSortable(false);
    col2.setEditable(true);
    col2.setResizable(false);
    // Handle color change requests when an appropriate row is double-clicked
    table.setRowFactory(tableView -> {
        TableRow<ChannelDisplayInfo> row = new TableRow<>();
        row.setOnMouseClicked(e -> {
            if (e.getClickCount() == 2) {
                ChannelDisplayInfo info = row.getItem();
                var imageData = viewer.getImageData();
                if (info instanceof DirectServerChannelInfo && imageData != null) {
                    DirectServerChannelInfo multiInfo = (DirectServerChannelInfo) info;
                    int c = multiInfo.getChannel();
                    var channel = imageData.getServer().getMetadata().getChannel(c);
                    Color color = ColorToolsFX.getCachedColor(multiInfo.getColor());
                    picker.setValue(color);
                    Dialog<ButtonType> colorDialog = new Dialog<>();
                    colorDialog.setTitle("Channel properties");
                    colorDialog.getDialogPane().getButtonTypes().setAll(ButtonType.APPLY, ButtonType.CANCEL);
                    var paneColor = new GridPane();
                    int r = 0;
                    var labelName = new Label("Channel name");
                    var tfName = new TextField(channel.getName());
                    labelName.setLabelFor(tfName);
                    PaneTools.addGridRow(paneColor, r++, 0, "Enter a name for the current channel", labelName, tfName);
                    var labelColor = new Label("Channel color");
                    labelColor.setLabelFor(picker);
                    PaneTools.addGridRow(paneColor, r++, 0, "Choose the color for the current channel", labelColor, picker);
                    paneColor.setVgap(5.0);
                    paneColor.setHgap(5.0);
                    colorDialog.getDialogPane().setContent(paneColor);
                    Optional<ButtonType> result = colorDialog.showAndWait();
                    if (result.orElse(ButtonType.CANCEL) == ButtonType.APPLY) {
                        // if (!DisplayHelpers.showMessageDialog("Choose channel color", picker))
                        // return;
                        String name = tfName.getText().trim();
                        if (name.isEmpty()) {
                            Dialogs.showErrorMessage("Set channel name", "The channel name must not be empty!");
                            return;
                        }
                        Color color2 = picker.getValue();
                        if (color == color2 && name.equals(channel.getName()))
                            return;
                        // Update the server metadata
                        int colorUpdated = ColorToolsFX.getRGB(color2);
                        if (imageData != null) {
                            var server = imageData.getServer();
                            var metadata = server.getMetadata();
                            var channels = new ArrayList<>(metadata.getChannels());
                            channels.set(c, ImageChannel.getInstance(name, colorUpdated));
                            var metadata2 = new ImageServerMetadata.Builder(metadata).channels(channels).build();
                            imageData.updateServerMetadata(metadata2);
                        }
                        // Update the display
                        multiInfo.setLUTColor((int) (color2.getRed() * 255), (int) (color2.getGreen() * 255), (int) (color2.getBlue() * 255));
                        // Add color property
                        imageDisplay.saveChannelColorProperties();
                        viewer.repaintEntireImage();
                        updateHistogram();
                        table.refresh();
                    }
                }
            }
        });
        return row;
    });
    table.getColumns().add(col1);
    table.getColumns().add(col2);
    table.setEditable(true);
    table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
    // Hack... space for a scrollbar
    col1.prefWidthProperty().bind(table.widthProperty().subtract(col2.widthProperty()).subtract(25));
    // col2.setResizable(false);
    // col2.setMaxWidth(100);
    // col2.setPrefWidth(50);
    // col2.setResizable(false);
    // col1.prefWidthProperty().bind(table.widthProperty().multiply(0.7));
    // col2.prefWidthProperty().bind(table.widthProperty().multiply(0.2));
    // table.getColumnModel().getColumn(0).setCellRenderer(new ChannelCellRenderer());
    // table.getColumnModel().getColumn(1).setMaxWidth(table.getColumnModel().getColumn(1).getPreferredWidth());
    BorderPane panelColor = new BorderPane();
    // panelColor.setBorder(BorderFactory.createTitledBorder("Color display"));
    BorderPane paneTableAndFilter = new BorderPane(table);
    TextField tfFilter = new TextField("");
    tfFilter.textProperty().bindBidirectional(filterText);
    tfFilter.setTooltip(new Tooltip("Enter text to find specific channels by name"));
    tfFilter.setPromptText("Filter channels by name");
    paneTableAndFilter.setBottom(tfFilter);
    predicate.addListener((v, o, n) -> updatePredicate());
    panelColor.setCenter(paneTableAndFilter);
    CheckBox cbShowGrayscale = new CheckBox("Show grayscale");
    cbShowGrayscale.selectedProperty().bindBidirectional(showGrayscale);
    cbShowGrayscale.setTooltip(new Tooltip("Show single channel with grayscale lookup table"));
    if (imageDisplay != null)
        cbShowGrayscale.setSelected(!imageDisplay.useColorLUTs());
    showGrayscale.addListener(o -> {
        if (imageDisplay == null)
            return;
        Platform.runLater(() -> viewer.repaintEntireImage());
        table.refresh();
    });
    CheckBox cbKeepDisplaySettings = new CheckBox("Keep settings");
    cbKeepDisplaySettings.selectedProperty().bindBidirectional(PathPrefs.keepDisplaySettingsProperty());
    cbKeepDisplaySettings.setTooltip(new Tooltip("Retain same display settings where possible when opening similar images"));
    FlowPane paneCheck = new FlowPane();
    paneCheck.getChildren().add(cbShowGrayscale);
    paneCheck.getChildren().add(cbKeepDisplaySettings);
    paneCheck.setHgap(10);
    paneCheck.setPadding(new Insets(5, 0, 0, 0));
    panelColor.setBottom(paneCheck);
    pane.setCenter(panelColor);
    // Create brightness/contrast panel
    BorderPane panelSliders = new BorderPane();
    panelSliders.setTop(box);
    GridPane panelButtons = PaneTools.createColumnGridControls(btnAuto, btnReset);
    panelSliders.setBottom(panelButtons);
    panelSliders.setPadding(new Insets(5, 0, 5, 0));
    BorderPane panelMinMax = new BorderPane();
    // panelMinMax.setBorder(BorderFactory.createTitledBorder("Brightness/Contrast"));
    panelMinMax.setTop(panelSliders);
    histogramPanel.setShowTickLabels(false);
    histogramPanel.getChart().setAnimated(false);
    // histogramPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    // panelMinMax.setCenter(histogramPanel.getChart());
    panelMinMax.setCenter(chartWrapper.getPane());
    chartWrapper.getPane().setPrefSize(200, 200);
    // histogramPanel.getChart().setPrefSize(200, 200);
    // histogramPanel.setPreferredSize(new Dimension(200, 120));
    pane.setBottom(panelMinMax);
    pane.setPadding(new Insets(10, 10, 10, 10));
    Scene scene = new Scene(pane, 350, 500);
    dialog.setScene(scene);
    dialog.setMinWidth(300);
    dialog.setMinHeight(400);
    dialog.setMaxWidth(600);
    dialog.setMaxHeight(800);
    updateTable();
    if (!table.getItems().isEmpty())
        table.getSelectionModel().select(0);
    updateDisplay(getCurrentInfo(), true);
    updateHistogram();
    updateSliders();
    // Update sliders when receiving focus - in case the display has been updated elsewhere
    dialog.focusedProperty().addListener((v, o, n) -> {
        if (n)
            updateSliders();
    });
    return dialog;
}
Also used : EventHandler(javafx.event.EventHandler) Button(javafx.scene.control.Button) ImageServer(qupath.lib.images.servers.ImageServer) LoggerFactory(org.slf4j.LoggerFactory) KeyCombination(javafx.scene.input.KeyCombination) ContextMenu(javafx.scene.control.ContextMenu) DirectServerChannelInfo(qupath.lib.display.DirectServerChannelInfo) TableView(javafx.scene.control.TableView) QuPathGUI(qupath.lib.gui.QuPathGUI) HistogramData(qupath.lib.gui.charts.HistogramPanelFX.HistogramData) TextField(javafx.scene.control.TextField) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Predicate(java.util.function.Predicate) ObjectBinding(javafx.beans.binding.ObjectBinding) FilteredList(javafx.collections.transformation.FilteredList) Set(java.util.Set) Rectangle(javafx.scene.shape.Rectangle) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) KeyEvent(javafx.scene.input.KeyEvent) Collectors(java.util.stream.Collectors) CellDataFeatures(javafx.scene.control.TableColumn.CellDataFeatures) Platform(javafx.application.Platform) Text(javafx.scene.text.Text) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) Priority(javafx.scene.layout.Priority) List(java.util.List) BooleanProperty(javafx.beans.property.BooleanProperty) PropertyChangeListener(java.beans.PropertyChangeListener) Clipboard(javafx.scene.input.Clipboard) FlowPane(javafx.scene.layout.FlowPane) CheckBoxTableCell(javafx.scene.control.cell.CheckBoxTableCell) ColorToolsFX(qupath.lib.gui.tools.ColorToolsFX) Optional(java.util.Optional) NumberAxis(javafx.scene.chart.NumberAxis) ClipboardContent(javafx.scene.input.ClipboardContent) BorderPane(javafx.scene.layout.BorderPane) StringProperty(javafx.beans.property.StringProperty) Scene(javafx.scene.Scene) TextArea(javafx.scene.control.TextArea) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) ButtonType(javafx.scene.control.ButtonType) MouseEvent(javafx.scene.input.MouseEvent) FXCollections(javafx.collections.FXCollections) ImageChannel(qupath.lib.images.servers.ImageChannel) Bindings(javafx.beans.binding.Bindings) ImageDisplay(qupath.lib.display.ImageDisplay) ArrayList(java.util.ArrayList) TableColumn(javafx.scene.control.TableColumn) HashSet(java.util.HashSet) Dialogs(qupath.lib.gui.dialogs.Dialogs) TableCell(javafx.scene.control.TableCell) Insets(javafx.geometry.Insets) Slider(javafx.scene.control.Slider) HistogramPanelFX(qupath.lib.gui.charts.HistogramPanelFX) Callback(javafx.util.Callback) Tooltip(javafx.scene.control.Tooltip) PropertyChangeEvent(java.beans.PropertyChangeEvent) LinkedHashSet(java.util.LinkedHashSet) GridPane(javafx.scene.layout.GridPane) ColorPicker(javafx.scene.control.ColorPicker) ImageData(qupath.lib.images.ImageData) KeyCode(javafx.scene.input.KeyCode) Color(javafx.scene.paint.Color) Logger(org.slf4j.Logger) Dialog(javafx.scene.control.Dialog) Label(javafx.scene.control.Label) TableRow(javafx.scene.control.TableRow) DecimalFormat(java.text.DecimalFormat) CheckBox(javafx.scene.control.CheckBox) ThresholdedChartWrapper(qupath.lib.gui.charts.HistogramPanelFX.ThresholdedChartWrapper) KeyCodeCombination(javafx.scene.input.KeyCodeCombination) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) SelectionMode(javafx.scene.control.SelectionMode) Stage(javafx.stage.Stage) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) ObservableValue(javafx.beans.value.ObservableValue) Histogram(qupath.lib.analysis.stats.Histogram) ChangeListener(javafx.beans.value.ChangeListener) PathPrefs(qupath.lib.gui.prefs.PathPrefs) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) PaneTools(qupath.lib.gui.tools.PaneTools) CellDataFeatures(javafx.scene.control.TableColumn.CellDataFeatures) Label(javafx.scene.control.Label) ObservableValue(javafx.beans.value.ObservableValue) Rectangle(javafx.scene.shape.Rectangle) ArrayList(java.util.ArrayList) DirectServerChannelInfo(qupath.lib.display.DirectServerChannelInfo) Button(javafx.scene.control.Button) Dialog(javafx.scene.control.Dialog) Stage(javafx.stage.Stage) TextField(javafx.scene.control.TextField) FlowPane(javafx.scene.layout.FlowPane) ButtonType(javafx.scene.control.ButtonType) GridPane(javafx.scene.layout.GridPane) Tooltip(javafx.scene.control.Tooltip) Color(javafx.scene.paint.Color) Scene(javafx.scene.Scene) TableColumn(javafx.scene.control.TableColumn) CheckBox(javafx.scene.control.CheckBox) TableRow(javafx.scene.control.TableRow) BorderPane(javafx.scene.layout.BorderPane) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) Insets(javafx.geometry.Insets) CheckBoxTableCell(javafx.scene.control.cell.CheckBoxTableCell) TableCell(javafx.scene.control.TableCell) CheckBoxTableCell(javafx.scene.control.cell.CheckBoxTableCell) Text(javafx.scene.text.Text) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo)

Example 4 with ChannelDisplayInfo

use of qupath.lib.display.ChannelDisplayInfo in project qupath by qupath.

the class ExtractRegionCommand method run.

@Override
public void run() {
    QuPathViewer viewer = qupath.getViewer();
    ImageServer<BufferedImage> server = null;
    if (viewer != null)
        server = viewer.getServer();
    if (server == null)
        return;
    List<String> unitOptions = new ArrayList<>();
    unitOptions.add(PIXELS_UNIT);
    String unit = server.getPixelCalibration().getPixelWidthUnit();
    if (unit.equals(server.getPixelCalibration().getPixelHeightUnit()) && !unit.equals(PixelCalibration.PIXEL))
        unitOptions.add(unit);
    if (!unitOptions.contains(resolutionUnit))
        resolutionUnit = PIXELS_UNIT;
    ParameterList params = new ParameterList().addDoubleParameter("resolution", "Resolution", resolution, null, "Resolution at which the image will be exported, defined as the 'pixel size' in Resolution units").addChoiceParameter("resolutionUnit", "Resolution unit", resolutionUnit, unitOptions, "Units defining the export resolution; if 'pixels' then the resolution is the same as a downsample value").addBooleanParameter("includeROI", "Include ROI", includeROI, "Include the primary object defining the exported region as an active ROI in ImageJ").addBooleanParameter("includeOverlay", "Include overlay", includeOverlay, "Include any objects overlapping the exported region as ROIs on an ImageJ overlay").addBooleanParameter("doTransforms", "Apply color transforms", doTransforms, "Optionally apply any color transforms when sending the pixels to ImageJ").addBooleanParameter("doZ", "All z-slices", doZ, "Optionally include all slices of a z-stack").addBooleanParameter("doT", "All timepoints", doT, "Optionally include all timepoints of a time series");
    // params.setHiddenParameters(unitOptions.size() <= 1, "resolutionUnit");
    params.setHiddenParameters(server.nZSlices() == 1, "doZ");
    params.setHiddenParameters(server.nTimepoints() == 1, "doT");
    if (!Dialogs.showParameterDialog("Send region to ImageJ", params))
        return;
    // Parse values
    resolution = params.getDoubleParameterValue("resolution");
    resolutionUnit = (String) params.getChoiceParameterValue("resolutionUnit");
    includeROI = params.getBooleanParameterValue("includeROI");
    includeOverlay = params.getBooleanParameterValue("includeOverlay");
    doTransforms = params.getBooleanParameterValue("doTransforms");
    doZ = params.getBooleanParameterValue("doZ");
    doT = params.getBooleanParameterValue("doT");
    // Calculate downsample
    double downsample = resolution;
    if (!resolutionUnit.equals(PIXELS_UNIT))
        downsample = resolution / (server.getPixelCalibration().getPixelHeight().doubleValue() / 2.0 + server.getPixelCalibration().getPixelWidth().doubleValue() / 2.0);
    // Color transforms are (currently) only applied for brightfield images - for fluorescence we always provide everything as unchanged as possible
    List<ChannelDisplayInfo> selectedChannels = new ArrayList<>(viewer.getImageDisplay().selectedChannels());
    List<ChannelDisplayInfo> channels = doTransforms && !selectedChannels.isEmpty() ? selectedChannels : null;
    if (channels != null)
        server = ChannelDisplayTransformServer.createColorTransformServer(server, channels);
    // Loop through all selected objects
    Collection<PathObject> pathObjects = viewer.getHierarchy().getSelectionModel().getSelectedObjects();
    if (pathObjects.isEmpty())
        pathObjects = Collections.singletonList(viewer.getHierarchy().getRootObject());
    List<ImagePlus> imps = new ArrayList<>();
    for (PathObject pathObject : pathObjects) {
        if (Thread.currentThread().isInterrupted() || IJ.escapePressed())
            return;
        int width, height;
        if (pathObject == null || !pathObject.hasROI()) {
            width = server.getWidth();
            height = server.getHeight();
        } else {
            Rectangle bounds = AwtTools.getBounds(pathObject.getROI());
            width = bounds.width;
            height = bounds.height;
        }
        RegionRequest region;
        ROI roi = pathObject == null ? null : pathObject.getROI();
        if (roi == null || PathObjectTools.hasPointROI(pathObject)) {
            region = RegionRequest.createInstance(server.getPath(), downsample, 0, 0, server.getWidth(), server.getHeight(), viewer.getZPosition(), viewer.getTPosition());
        } else
            region = RegionRequest.createInstance(server.getPath(), downsample, roi);
        // region = RegionRequest.createInstance(server.getPath(), downsample, pathObject.getROI(), viewer.getZPosition(), viewer.getTPosition());
        // Minimum size has been removed (v0.2.0-m4); returned regions should be at least 1x1 pixels
        // if (region.getWidth() / downsample < 8 || region.getHeight() / downsample < 8) {
        // DisplayHelpers.showErrorMessage("Send region to ImageJ", "The width & height of the extracted image must both be >= 8 pixels");
        // continue;
        // }
        // Calculate required z-slices and time-points
        int zStart = doZ ? 0 : region.getZ();
        int zEnd = doZ ? server.nZSlices() : region.getZ() + 1;
        int tStart = doT ? 0 : region.getT();
        int tEnd = doT ? server.nTimepoints() : region.getT() + 1;
        long nZ = zEnd - zStart;
        long nT = tEnd - tStart;
        int bytesPerPixel = server.isRGB() ? 4 : server.getPixelType().getBytesPerPixel() * server.nChannels();
        double memory = ((long) width * height * nZ * nT * bytesPerPixel) / (downsample * downsample);
        // TODO: Perform calculation based on actual amount of available memory
        long availableMemory = GeneralTools.estimateAvailableMemory();
        if (memory >= availableMemory * 0.95) {
            logger.error("Cannot extract region {} - estimated size is too large (approx. {} MB)", pathObject, GeneralTools.formatNumber(memory / (1024.0 * 1024.0), 2));
            Dialogs.showErrorMessage("Send region to ImageJ error", "Selected region is too large to extract - please selected a smaller region or use a higher downsample factor");
            continue;
        }
        if (memory / 1024 / 1024 > 100) {
            if (pathObjects.size() == 1 && !Dialogs.showYesNoDialog("Send region to ImageJ", String.format("Attempting to extract this region is likely to require > %.2f MB - are you sure you want to continue?", memory / 1024 / 1024)))
                return;
        }
        // We should switch to the event dispatch thread when interacting with ImageJ
        try {
            ImagePlus imp;
            PathObjectHierarchy hierarchy = viewer.getHierarchy();
            OverlayOptions options = viewer.getOverlayOptions();
            if (zEnd - zStart > 1 || tEnd - tStart > 1) {
                // TODO: Handle overlays
                imp = IJTools.extractHyperstack(server, region, zStart, zEnd, tStart, tEnd);
                if (includeROI && roi != null) {
                    Roi roiIJ = IJTools.convertToIJRoi(roi, imp.getCalibration(), region.getDownsample());
                    imp.setRoi(roiIJ);
                }
                if (includeOverlay) {
                    Overlay overlay = new Overlay();
                    for (int t = tStart; t < tEnd; t++) {
                        for (int z = zStart; z < zEnd; z++) {
                            RegionRequest request2 = RegionRequest.createInstance(region.getPath(), region.getDownsample(), region.getX(), region.getY(), region.getWidth(), region.getHeight(), z, t);
                            var regionPredicate = PathObjectTools.createImageRegionPredicate(request2);
                            Overlay temp = IJExtension.extractOverlay(hierarchy, request2, options, p -> p != pathObject && regionPredicate.test(p));
                            if (overlay == null)
                                overlay = temp;
                            for (int i = 0; i < temp.size(); i++) {
                                Roi roiIJ = temp.get(i);
                                roiIJ.setPosition(-1, z + 1, t + 1);
                                overlay.add(roiIJ);
                            }
                        }
                    }
                    if (overlay != null && overlay.size() > 0)
                        imp.setOverlay(overlay);
                }
            } else if (includeOverlay)
                imp = IJExtension.extractROIWithOverlay(server, pathObject, hierarchy, region, includeROI, options).getImage();
            else
                imp = IJExtension.extractROIWithOverlay(server, pathObject, null, region, includeROI, options).getImage();
            // Set display ranges if we can
            if (viewer != null && imp instanceof CompositeImage) {
                var availableChannels = viewer.getImageDisplay().availableChannels().stream().filter(c -> c instanceof SingleChannelDisplayInfo).map(c -> (SingleChannelDisplayInfo) c).collect(Collectors.toList());
                CompositeImage impComp = (CompositeImage) imp;
                if (availableChannels.size() == imp.getNChannels()) {
                    for (int c = 0; c < availableChannels.size(); c++) {
                        var channel = availableChannels.get(c);
                        imp.setPosition(c + 1, 1, 1);
                        impComp.setDisplayRange(channel.getMinDisplay(), channel.getMaxDisplay());
                    }
                    imp.setPosition(1);
                }
            } else if (selectedChannels.size() == 1 && imp.getType() != ImagePlus.COLOR_RGB) {
                // Setting the display range for non-RGB images can give unexpected results (changing pixel values)
                var channel = selectedChannels.get(0);
                imp.setDisplayRange(channel.getMinDisplay(), channel.getMaxDisplay());
            }
            imps.add(imp);
        } catch (IOException e) {
            Dialogs.showErrorMessage("Send region to ImageJ", e);
            return;
        }
    }
    // Show all the images we've got
    if (!imps.isEmpty()) {
        SwingUtilities.invokeLater(() -> {
            boolean batchMode = Interpreter.batchMode;
            // Try to start an ImageJ instance, and return if this fails
            try {
                ImageJ ij = IJExtension.getImageJInstance();
                if (ij == null)
                    return;
                ij.setVisible(true);
                // Make sure we aren't in batch mode, so that image will display
                Interpreter.batchMode = false;
                for (ImagePlus imp : imps) {
                    imp.show();
                }
            } finally {
                Interpreter.batchMode = batchMode;
            }
        });
    }
}
Also used : CompositeImage(ij.CompositeImage) Rectangle(java.awt.Rectangle) ImageServer(qupath.lib.images.servers.ImageServer) IJTools(qupath.imagej.tools.IJTools) LoggerFactory(org.slf4j.LoggerFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ArrayList(java.util.ArrayList) Dialogs(qupath.lib.gui.dialogs.Dialogs) SwingUtilities(javax.swing.SwingUtilities) ParameterList(qupath.lib.plugins.parameters.ParameterList) Overlay(ij.gui.Overlay) QuPathGUI(qupath.lib.gui.QuPathGUI) Logger(org.slf4j.Logger) Interpreter(ij.macro.Interpreter) BufferedImage(java.awt.image.BufferedImage) GeneralTools(qupath.lib.common.GeneralTools) RegionRequest(qupath.lib.regions.RegionRequest) Collection(java.util.Collection) ChannelDisplayTransformServer(qupath.lib.gui.images.servers.ChannelDisplayTransformServer) AwtTools(qupath.lib.awt.common.AwtTools) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) IOException(java.io.IOException) Collectors(java.util.stream.Collectors) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) ImageJ(ij.ImageJ) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) ROI(qupath.lib.roi.interfaces.ROI) ImagePlus(ij.ImagePlus) List(java.util.List) IJ(ij.IJ) PixelCalibration(qupath.lib.images.servers.PixelCalibration) SingleChannelDisplayInfo(qupath.lib.display.SingleChannelDisplayInfo) Collections(java.util.Collections) Roi(ij.gui.Roi) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) SingleChannelDisplayInfo(qupath.lib.display.SingleChannelDisplayInfo) BufferedImage(java.awt.image.BufferedImage) ImageJ(ij.ImageJ) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) CompositeImage(ij.CompositeImage) Overlay(ij.gui.Overlay) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) IOException(java.io.IOException) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) SingleChannelDisplayInfo(qupath.lib.display.SingleChannelDisplayInfo) ImagePlus(ij.ImagePlus) ROI(qupath.lib.roi.interfaces.ROI) Roi(ij.gui.Roi) PathObject(qupath.lib.objects.PathObject) ParameterList(qupath.lib.plugins.parameters.ParameterList) RegionRequest(qupath.lib.regions.RegionRequest)

Example 5 with ChannelDisplayInfo

use of qupath.lib.display.ChannelDisplayInfo in project qupath by qupath.

the class QPEx method setChannelDisplayRange.

/**
 * Set the minimum and maximum display range for the specified {@link ImageData} for a channel identified by name.
 * @param imageData
 * @param channelName
 * @param minDisplay
 * @param maxDisplay
 */
public static void setChannelDisplayRange(ImageData<BufferedImage> imageData, String channelName, double minDisplay, double maxDisplay) {
    // Try to get an existing display if the image is currently open
    var viewer = getQuPath().getViewers().stream().filter(v -> v.getImageData() == imageData).findFirst().orElse(null);
    ImageDisplay display = viewer == null ? new ImageDisplay(imageData) : viewer.getImageDisplay();
    var available = display.availableChannels();
    ChannelDisplayInfo info = null;
    var serverChannels = imageData.getServer().getMetadata().getChannels();
    for (var c : available) {
        if (channelName.equals(c.getName())) {
            info = c;
            break;
        }
        // We also need to check the channel names, since the info might have adjusted them (e.g. by adding (C1) at the end)
        if (c instanceof DirectServerChannelInfo) {
            int channelNumber = ((DirectServerChannelInfo) c).getChannel();
            if (channelNumber >= 0 && channelNumber < serverChannels.size() && channelName.equals(serverChannels.get(channelNumber).getName())) {
                info = c;
                break;
            }
        }
    }
    if (info == null) {
        logger.warn("No channel found with name {} - cannot set display range", channelName);
        return;
    }
    display.setMinMaxDisplay(info, (float) minDisplay, (float) maxDisplay);
    // Update the viewer is necessary
    if (viewer != null)
        viewer.repaintEntireImage();
}
Also used : DirectServerChannelInfo(qupath.lib.display.DirectServerChannelInfo) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) ImageDisplay(qupath.lib.display.ImageDisplay)

Aggregations

ChannelDisplayInfo (qupath.lib.display.ChannelDisplayInfo)10 BufferedImage (java.awt.image.BufferedImage)3 ImageDisplay (qupath.lib.display.ImageDisplay)3 ArrayList (java.util.ArrayList)2 HashSet (java.util.HashSet)2 LinkedHashSet (java.util.LinkedHashSet)2 List (java.util.List)2 Collectors (java.util.stream.Collectors)2 Scene (javafx.scene.Scene)2 Color (javafx.scene.paint.Color)2 Stage (javafx.stage.Stage)2 Logger (org.slf4j.Logger)2 LoggerFactory (org.slf4j.LoggerFactory)2 QuPathGUI (qupath.lib.gui.QuPathGUI)2 Dialogs (qupath.lib.gui.dialogs.Dialogs)2 QuPathViewer (qupath.lib.gui.viewer.QuPathViewer)2 ImageData (qupath.lib.images.ImageData)2 ImageServer (qupath.lib.images.servers.ImageServer)2 CompositeImage (ij.CompositeImage)1 IJ (ij.IJ)1