Last active
February 5, 2026 09:02
-
-
Save romainGuiet/81bf5e601e3ddf414246ccd2765a4f0d to your computer and use it in GitHub Desktop.
a QuPath GUI to load points from csv file
This file contains hidden or 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
| import javafx.application.Platform | |
| import javafx.scene.Scene | |
| import javafx.scene.control.* | |
| import javafx.scene.layout.* | |
| import javafx.stage.Stage | |
| import javafx.stage.DirectoryChooser | |
| import javafx.geometry.Insets | |
| import javafx.geometry.Pos | |
| import javafx.collections.FXCollections | |
| import javafx.collections.ObservableList | |
| import javafx.scene.control.cell.PropertyValueFactory | |
| import javafx.scene.paint.Color | |
| import java.io.File | |
| // QuPath imports for creating detections | |
| import qupath.lib.objects.PathObjects | |
| import qupath.lib.roi.ROIs | |
| import qupath.lib.regions.ImagePlane | |
| Platform.runLater { | |
| // -------------------- UI SETUP -------------------- | |
| def stage = new Stage() | |
| stage.setTitle("QuPath Custom GUI - CSV Loader") | |
| def tabPane = new TabPane() | |
| // ---------- shared state ---------- | |
| def topGenesFromTab1 = [] | |
| def allGenesFromTab1 = [] // all unique genes | |
| def currentCsvData = [] // List<List<String>> | |
| def currentHeaders = [] // List<String> | |
| def selectedFolder = [null] as File[] | |
| def geneColumnIndex = -1 | |
| def xColumnIndex = -1 | |
| def yColumnIndex = -1 | |
| def zColumnIndex = -1 | |
| def dimensionUnit = "micron" | |
| // ---------- Tab‑2 helpers ---------- | |
| def geneDropdowns = [] | |
| def colorPickers = [] | |
| def progressBar = null | |
| // ===================== TAB 1 ===================== | |
| def tab1 = new Tab("CSV Loader") | |
| tab1.setClosable(false) | |
| def vbox1 = new VBox(10) | |
| vbox1.setPadding(new Insets(15)) | |
| // Title | |
| def label1 = new Label("CSV File Loader") | |
| label1.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;") | |
| // ------ Folder chooser with extension & delimiter ------ | |
| def folderHBox = new HBox(10) | |
| folderHBox.setAlignment(Pos.CENTER_LEFT) | |
| def folderLabel = new Label("Folder:") | |
| def folderField = new TextField() | |
| folderField.setPromptText("No folder selected") | |
| folderField.setPrefWidth(200) | |
| folderField.setEditable(false) | |
| def browseButton = new Button("Browse...") | |
| def extensionLabel = new Label("Ext:") | |
| def extensionField = new TextField(".part") | |
| extensionField.setPrefWidth(60) | |
| def delimiterLabel = new Label("Delim:") | |
| def delimiterField = new TextField(",") | |
| delimiterField.setPrefWidth(40) | |
| browseButton.setOnAction({ e -> | |
| def chooser = new DirectoryChooser() | |
| chooser.setTitle("Select Folder Containing CSV Files") | |
| def folder = chooser.showDialog(stage) | |
| if (folder) { | |
| selectedFolder[0] = folder | |
| folderField.setText(folder.absolutePath) | |
| } | |
| }) | |
| folderHBox.getChildren().addAll(folderLabel, folderField, browseButton, | |
| extensionLabel, extensionField, | |
| delimiterLabel, delimiterField) | |
| // ------ Load button ------ | |
| def loadButton = new Button("Load CSV Files") | |
| loadButton.setPrefWidth(120) | |
| // ------ Table preview ------ | |
| def csvTable = new TableView<ObservableList<String>>() | |
| csvTable.setPrefHeight(150) | |
| csvTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY) | |
| // ------ Column‑mapping comboboxes ------ | |
| def coordHBox = new HBox(10) | |
| coordHBox.setAlignment(Pos.CENTER_LEFT) | |
| def xCombo = new ComboBox<String>(); xCombo.setPrefWidth(100) | |
| def yCombo = new ComboBox<String>(); yCombo.setPrefWidth(100) | |
| def zCombo = new ComboBox<String>(); zCombo.setPrefWidth(100) | |
| def geneCombo = new ComboBox<String>(); geneCombo.setPrefWidth(100) | |
| def dimensionCombo = new ComboBox<String>() | |
| dimensionCombo.getItems().addAll("micron", "pixel") | |
| dimensionCombo.setValue("micron") | |
| dimensionCombo.setPrefWidth(80) | |
| coordHBox.getChildren().addAll( | |
| new Label("X:"), xCombo, | |
| new Label("Y:"), yCombo, | |
| new Label("Z:"), zCombo, | |
| new Label("Gene:"), geneCombo, | |
| new Label("Dimension:"), dimensionCombo | |
| ) | |
| // ------ Analyse abundance button ------ | |
| def analyzeButton = new Button("Analyse Abundancy") | |
| analyzeButton.setPrefWidth(150) | |
| // placeholders for abundance results (will be created later) | |
| def abundanceLabel = null | |
| def abundanceTable = null | |
| // ----------------- LOAD BUTTON ACTION (NEW BLOCK) ----------------- | |
| loadButton.setOnAction({ e -> | |
| if (selectedFolder[0] == null) { | |
| showAlert("Error", "Please select a folder first.") | |
| return | |
| } | |
| def extension = extensionField.getText() | |
| def delimiter = delimiterField.getText() | |
| if (!extension || !delimiter) { | |
| showAlert("Error", "Please specify both extension and delimiter.") | |
| return | |
| } | |
| try { | |
| // ---- 1️⃣ Find every matching CSV file -------------------- | |
| def csvFiles = selectedFolder[0].listFiles().findAll { | |
| it.isFile() && it.name.endsWith(extension) | |
| } | |
| if (csvFiles.isEmpty()) { | |
| showAlert("Warning", "No files found with extension: $extension") | |
| return | |
| } | |
| // ---- 2️⃣ Read all files & concatenate rows ---------------- | |
| def allRows = [] | |
| def headerFound = false | |
| def headerList = null | |
| csvFiles.each { file -> | |
| def lines = file.readLines() | |
| if (lines.isEmpty()) return // skip empty file | |
| def thisHeader = lines[0].split(delimiter) | |
| .collect { it.trim().replace('"','') } | |
| if (!headerFound) { | |
| headerList = thisHeader | |
| headerFound = true | |
| } else { | |
| if (!thisHeader.equals(headerList)) { | |
| showAlert( | |
| "Error", | |
| "Header mismatch in file '${file.name}'.\nAll CSV files must have identical column headers." | |
| ) | |
| throw new IllegalStateException("Header mismatch") | |
| } | |
| } | |
| // collect data rows (skip header) | |
| def dataRows = lines.drop(1).collect { line -> | |
| line.split(delimiter).collect { it.trim().replace('"','') } | |
| } | |
| allRows.addAll(dataRows) | |
| } | |
| // ---- 3️⃣ Store globally --------------------------------- | |
| currentHeaders = headerList | |
| currentCsvData = allRows | |
| // ---- 4️⃣ Populate preview table (first 5 rows) ---------- | |
| csvTable.getColumns().clear() | |
| currentHeaders.eachWithIndex { header, idx -> | |
| def col = new TableColumn<ObservableList<String>, String>(header) | |
| col.setCellValueFactory({ param -> | |
| new javafx.beans.property.SimpleStringProperty( | |
| param.getValue().size() > idx ? param.getValue().get(idx) : "" | |
| ) | |
| }) | |
| csvTable.getColumns().add(col) | |
| } | |
| def preview = FXCollections.observableArrayList() | |
| def maxRows = Math.min(5, currentCsvData.size()) | |
| for (int i = 0; i < maxRows; i++) { | |
| preview.add(FXCollections.observableArrayList(currentCsvData[i])) | |
| } | |
| csvTable.setItems(preview) | |
| // ---- 5️⃣ Fill column‑mapping combos -------------------- | |
| xCombo.getItems().setAll(currentHeaders) | |
| yCombo.getItems().setAll(currentHeaders) | |
| zCombo.getItems().setAll(currentHeaders) | |
| zCombo.getItems().add("Current annotation Z") | |
| geneCombo.getItems().setAll(currentHeaders) | |
| // optional defaults (same as before) | |
| if (currentHeaders.contains("x_location")) xCombo.setValue("x_location") | |
| if (currentHeaders.contains("y_location")) yCombo.setValue("y_location") | |
| if (currentHeaders.contains("z_location")) zCombo.setValue("z_location") | |
| else zCombo.setValue("Current annotation Z") | |
| if (currentHeaders.contains("feature_name")) geneCombo.setValue("feature_name") | |
| // ---- 6️⃣ Console feedback ----------------------------- | |
| println "Loaded ${csvFiles.size()} CSV file(s) from '${selectedFolder[0]}'" | |
| println "Combined rows: ${currentCsvData.size()}" | |
| println "Headers: ${currentHeaders}" | |
| println "First 5 rows shown in the preview table." | |
| } catch (Exception ex) { | |
| showAlert("Error", "Failed to load CSV files: ${ex.message}") | |
| ex.printStackTrace() | |
| } | |
| }) | |
| // ----------------------------------------------------------------- | |
| // ----------------- ANALYSE ABUNDANCY ACTION (unchanged) ----------------- | |
| analyzeButton.setOnAction({ e -> | |
| if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) { | |
| showAlert("Error", "Please load a CSV file first.") | |
| return | |
| } | |
| def geneColumn = geneCombo.getValue() | |
| if (!geneColumn) { | |
| showAlert("Error", "Please select a gene column.") | |
| return | |
| } | |
| def geneIndex = currentHeaders.indexOf(geneColumn) | |
| if (geneIndex == -1) { | |
| showAlert("Error", "Selected gene column not found.") | |
| return | |
| } | |
| // store indices for Tab‑2 | |
| geneColumnIndex = geneIndex | |
| xColumnIndex = currentHeaders.indexOf(xCombo.getValue()) | |
| yColumnIndex = currentHeaders.indexOf(yCombo.getValue()) | |
| zColumnIndex = zCombo.getValue() == "Current annotation Z" ? -1 | |
| : currentHeaders.indexOf(zCombo.getValue()) | |
| dimensionUnit = dimensionCombo.getValue() | |
| try { | |
| // ---- count gene frequencies --------------------------------- | |
| def geneCounts = [:] as Map<String,Integer> | |
| def allUniqueGenes = [] as Set<String> | |
| currentCsvData.each { row -> | |
| if (row.size() > geneIndex) { | |
| def gene = row[geneIndex] | |
| if (gene?.trim()) { | |
| geneCounts[gene] = (geneCounts[gene] ?: 0) + 1 | |
| allUniqueGenes << gene | |
| } | |
| } | |
| } | |
| // ---- top‑10 ----------------------------------------------------------------- | |
| def topGenes = geneCounts.entrySet() | |
| .sort{ -it.value } | |
| .take(10) | |
| topGenesFromTab1 = topGenes.collect { [gene: it.key, count: it.value] } | |
| allGenesFromTab1 = allUniqueGenes.sort() | |
| // ---- update Tab‑2 dropdowns ------------------------------------------------- | |
| geneDropdowns.each { dd -> | |
| dd.getItems().clear() | |
| dd.getItems().add("none") | |
| dd.getItems().addAll(allGenesFromTab1) | |
| } | |
| // ---- pre‑fill the first 10 dropdowns with top genes ---------------------------- | |
| topGenesFromTab1.eachWithIndex { geneData, idx -> | |
| if (idx < geneDropdowns.size()) { | |
| geneDropdowns[idx].setValue(geneData.gene) | |
| // give each a different colour (you can change this) | |
| def colours = [ | |
| Color.RED, Color.BLUE, Color.GREEN, Color.ORANGE, | |
| Color.PURPLE, Color.CYAN, Color.MAGENTA, | |
| Color.YELLOW, Color.PINK, Color.BROWN | |
| ] | |
| colorPickers[idx].setValue(colours[idx % colours.size()]) | |
| } | |
| } | |
| // ---- build the top‑10 abundance table ----------------------------------------- | |
| // remove previous if they exist | |
| if (abundanceLabel) vbox1.getChildren().remove(abundanceLabel) | |
| if (abundanceTable) vbox1.getChildren().remove(abundanceTable) | |
| abundanceLabel = new Label("Top 10 Gene Abundance Results:") | |
| abundanceLabel.setStyle("-fx-font-weight: bold;") | |
| abundanceTable = new TableView() | |
| abundanceTable.setPrefHeight(50) | |
| abundanceTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY) | |
| // compact rows | |
| abundanceTable.setRowFactory({ tv -> | |
| def row = new TableRow() | |
| row.setPrefHeight(25) | |
| row | |
| }) | |
| // data array (10 columns, 1 row) | |
| def rowData = new String[10] | |
| for (int i = 0; i < topGenes.size(); i++) { | |
| rowData[i] = "${topGenes[i].value}" | |
| } | |
| for (int i = topGenes.size(); i < 10; i++) { | |
| rowData[i] = "" | |
| } | |
| // create columns with gene names as headers | |
| for (int i = 0; i < 10; i++) { | |
| def col = new TableColumn() | |
| col.setPrefWidth(100) | |
| col.setText(i < topGenes.size() ? topGenes[i].key : "") | |
| def idx = i | |
| col.setCellValueFactory({ cd -> | |
| new javafx.beans.property.SimpleStringProperty( | |
| cd.getValue()[idx] ?: "" | |
| ) | |
| }) | |
| abundanceTable.getColumns().add(col) | |
| } | |
| def tblData = FXCollections.observableArrayList() | |
| tblData.add(rowData) | |
| abundanceTable.setItems(tblData) | |
| abundanceTable.setTableMenuButtonVisible(false) | |
| abundanceTable.setEditable(false) | |
| abundanceTable.setStyle("-fx-table-cell-border-color: transparent;") | |
| vbox1.getChildren().addAll(abundanceLabel, abundanceTable) | |
| println "Gene abundance analysis completed – ${allUniqueGenes.size()} unique genes." | |
| topGenes.eachWithIndex { gene,i -> println "${i+1}. ${gene.key}: ${gene.value}" } | |
| } catch (Exception ex) { | |
| showAlert("Error", "Failed to analyse gene abundance: ${ex.message}") | |
| ex.printStackTrace() | |
| } | |
| }) | |
| // ----------------------------------------------------------------- | |
| // ---- helper alert ------------------------------------------------ | |
| def showAlert = { title, msg -> | |
| Platform.runLater { | |
| def a = new Alert(Alert.AlertType.INFORMATION) | |
| a.title = title | |
| a.headerText = null | |
| a.contentText = msg | |
| a.showAndWait() | |
| } | |
| } | |
| // ---- helper for Z‑plane (unchanged) ------------------------------- | |
| def getCurrentAnnotationZPlane = { | |
| def img = getCurrentImageData() | |
| if (!img) return ImagePlane.getDefaultPlane() | |
| def sel = getSelectedObject() | |
| if (sel && sel.getROI()) return sel.getROI().getImagePlane() | |
| ImagePlane.getDefaultPlane() | |
| } | |
| // ------------------- Assemble Tab 1 ------------------------------- | |
| vbox1.getChildren().addAll( | |
| label1, | |
| new Separator(), | |
| folderHBox, | |
| loadButton, | |
| new Label("CSV Preview (first 5 data rows):"), | |
| csvTable, | |
| new Label("Column Mapping:"), | |
| coordHBox, | |
| analyzeButton | |
| ) | |
| tab1.setContent(new ScrollPane(vbox1)) | |
| // ===================== TAB 2 ===================== | |
| def tab2 = new Tab("Point Loading") | |
| tab2.setClosable(false) | |
| def vbox2 = new VBox(10) | |
| vbox2.setPadding(new Insets(15)) | |
| def label2 = new Label("Point Loading & Gene Selection") | |
| label2.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;") | |
| // ---- Load Points (first N) line --------------------------------- | |
| def firstLineHBox = new HBox(10) | |
| firstLineHBox.setAlignment(Pos.CENTER_LEFT) | |
| def loadPointsButton = new Button("Load Points") | |
| loadPointsButton.setPrefWidth(120) | |
| def pointsLabel = new Label("Points number:") | |
| def pointsField = new TextField("10000") | |
| pointsField.setPrefWidth(80) | |
| firstLineHBox.getChildren().addAll(loadPointsButton, pointsLabel, pointsField) | |
| // ---- 10 gene‑dropdown + colour pickers ------------------------- | |
| def geneSelectionVBox = new VBox(5) | |
| geneSelectionVBox.setPadding(new Insets(10,0,10,0)) | |
| for (int i = 0; i < 10; i++) { | |
| def line = new HBox(10) | |
| line.setAlignment(Pos.CENTER_LEFT) | |
| def dd = new ComboBox<String>() | |
| dd.setPrefWidth(150) | |
| dd.getItems().add("none") | |
| geneDropdowns << dd | |
| def cp = new ColorPicker(Color.RED) | |
| colorPickers << cp | |
| def lbl = new Label("Gene ${i+1}:") | |
| lbl.setPrefWidth(60) | |
| line.getChildren().addAll(lbl, dd, cp) | |
| geneSelectionVBox.getChildren().add(line) | |
| } | |
| // ---- Load selected genes button -------------------------------- | |
| def loadSelectedGenesButton = new Button("Load Selected Genes") | |
| loadSelectedGenesButton.setPrefWidth(180) | |
| // ---- Progress UI ------------------------------------------------ | |
| progressBar = new ProgressBar(0) | |
| progressBar.setPrefWidth(300) | |
| progressBar.setVisible(false) | |
| def progressLabel = new Label("") | |
| progressLabel.setVisible(false) | |
| // ----------------------------------------------------------------- | |
| // ---- LOAD POINTS (first N) – unchanged except it now works on the combined data | |
| // ----------------------------------------------------------------- | |
| loadPointsButton.setOnAction({ e -> | |
| if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) { | |
| showAlert("Error", "Please load CSV data in Tab 1 first.") | |
| return | |
| } | |
| if (geneColumnIndex == -1 || xColumnIndex == -1 || yColumnIndex == -1) { | |
| showAlert("Error", "Please configure column mappings in Tab 1 first.") | |
| return | |
| } | |
| try { | |
| def pointsNumber = Integer.parseInt(pointsField.text) | |
| // pixel size (microns per pixel) | |
| def img = getCurrentImageData() | |
| if (!img) { showAlert("Error","No image open."); return } | |
| def pixX = img.getServer().getPixelCalibration().getAveragedPixelSizeMicrons() | |
| def pixY = img.getServer().getPixelCalibration().getPixelHeight() | |
| // plane – we reuse your helper (or use getSelectedROI()) | |
| def plane = ImagePlane.getPlane(getSelectedROI()) | |
| progressBar.visible = true | |
| progressLabel.visible = true | |
| progressLabel.text = "Loading first ${pointsNumber} points..." | |
| progressBar.progress = 0.1 | |
| def pointsByGene = [:] as Map<String, List<Map>> | |
| def count = 0 | |
| currentCsvData.each { row -> | |
| if (count >= pointsNumber) return | |
| if (row.size() > Math.max(geneColumnIndex, Math.max(xColumnIndex, yColumnIndex))) { | |
| try { | |
| def gene = row[geneColumnIndex].trim() | |
| def xRaw = Double.parseDouble(row[xColumnIndex].trim()) | |
| def yRaw = Double.parseDouble(row[yColumnIndex].trim()) | |
| def x = (dimensionUnit == "micron") ? xRaw / pixX : xRaw | |
| def y = (dimensionUnit == "micron") ? yRaw / pixY : yRaw | |
| pointsByGene.computeIfAbsent(gene){[]}.add([x:x, y:y]) | |
| count++ | |
| } catch (NumberFormatException ignored) {} | |
| } | |
| } | |
| progressBar.progress = 0.5 | |
| if (!pointsByGene) { | |
| progressLabel.text = "No valid points found" | |
| showAlert("Warning","No valid coordinate data found in CSV.") | |
| return | |
| } | |
| // create detections per gene | |
| def totalCreated = 0 | |
| def processed = 0 | |
| pointsByGene.each { gene, pts -> | |
| def xs = pts.collect{it.x} as double[] | |
| def ys = pts.collect{it.y} as double[] | |
| def roi = ROIs.createPointsROI(xs, ys, plane) | |
| def pc = getPathClass(gene) ?: createPathClass(gene) | |
| def det = PathObjects.createDetectionObject(roi, pc) | |
| det.setName("${gene} (${pts.size()} points)") | |
| addObject(det) | |
| totalCreated += pts.size() | |
| processed++ | |
| progressBar.progress = 0.5 + (processed / pointsByGene.size()) * 0.5 | |
| } | |
| progressBar.progress = 1.0 | |
| progressLabel.text = "Completed: ${totalCreated} points loaded across ${pointsByGene.size()} genes" | |
| fireHierarchyUpdate() | |
| // hide progress after a short pause | |
| Platform.runLater { | |
| Thread.sleep(2000) | |
| progressBar.visible = false | |
| progressLabel.visible = false | |
| } | |
| } catch (NumberFormatException nfe) { | |
| showAlert("Error","Invalid points number.") | |
| progressBar.visible = false | |
| progressLabel.visible = false | |
| } catch (Exception ex) { | |
| showAlert("Error","Failed to load points: ${ex.message}") | |
| ex.printStackTrace() | |
| progressBar.visible = false | |
| progressLabel.visible = false | |
| } | |
| }) | |
| // ----------------------------------------------------------------- | |
| // ---- LOAD SELECTED GENES (all points) – unchanged except uses combined data | |
| // ----------------------------------------------------------------- | |
| loadSelectedGenesButton.setOnAction({ e -> | |
| if (currentCsvData.isEmpty() || currentHeaders.isEmpty()) { | |
| showAlert("Error","Please load CSV data in Tab 1 first."); return | |
| } | |
| if (geneColumnIndex == -1 || xColumnIndex == -1 || yColumnIndex == -1) { | |
| showAlert("Error","Please configure column mappings in Tab 1 first."); return | |
| } | |
| // collect which genes the user actually chose | |
| def selected = [] | |
| geneDropdowns.eachWithIndex { dd, i -> | |
| def val = dd.value | |
| if (val && val != "none") { | |
| selected << [gene:val, color:colorPickers[i].value, idx:i] | |
| } | |
| } | |
| if (!selected) { | |
| showAlert("Warning","No genes selected."); return | |
| } | |
| try { | |
| def img = getCurrentImageData() | |
| if (!img) { showAlert("Error","No image open."); return } | |
| def pixX = img.getServer().getPixelCalibration().getAveragedPixelSizeMicrons() | |
| def pixY = img.getServer().getPixelCalibration().getPixelHeight() | |
| def plane = ImagePlane.getPlane(getSelectedROI()) | |
| progressBar.visible = true | |
| progressLabel.visible = true | |
| progressBar.progress = 0 | |
| selected.eachWithIndex { sel, idx -> | |
| progressBar.progress = idx / selected.size() | |
| progressLabel.text = "Processing ${sel.gene} (${idx+1}/${selected.size()})" | |
| // collect **all** points for this gene | |
| def pts = [] | |
| currentCsvData.each { row -> | |
| if (row.size() > Math.max(geneColumnIndex, Math.max(xColumnIndex, yColumnIndex))) { | |
| if (row[geneColumnIndex].trim() == sel.gene) { | |
| try { | |
| def xRaw = Double.parseDouble(row[xColumnIndex].trim()) | |
| def yRaw = Double.parseDouble(row[yColumnIndex].trim()) | |
| def x = (dimensionUnit == "micron") ? xRaw / pixX : xRaw | |
| def y = (dimensionUnit == "micron") ? yRaw / pixY : yRaw | |
| pts << [x:x, y:y] | |
| } catch (NumberFormatException ignored) {} | |
| } | |
| } | |
| } | |
| if (!pts) { | |
| println "No points found for gene ${sel.gene}" | |
| return | |
| } | |
| def xs = pts.collect{it.x} as double[] | |
| def ys = pts.collect{it.y} as double[] | |
| def roi = ROIs.createPointsROI(xs, ys, plane) | |
| def pc = getPathClass(sel.gene) ?: createPathClass(sel.gene) | |
| // set colour from the colour picker | |
| def r = (int)(sel.color.red * 255) | |
| def g = (int)(sel.color.green * 255) | |
| def b = (int)(sel.color.blue * 255) | |
| pc.setColor(r,g,b) | |
| def det = PathObjects.createDetectionObject(roi, pc) | |
| det.setName("${sel.gene} (${pts.size()} points)") | |
| addObject(det) | |
| println "Created detection for ${sel.gene}: ${pts.size()} points (colour RGB(${r},${g},${b}))" | |
| } | |
| progressBar.progress = 1.0 | |
| progressLabel.text = "Completed: ${selected.size()} genes processed" | |
| fireHierarchyUpdate() | |
| Platform.runLater { | |
| Thread.sleep(2000) | |
| progressBar.visible = false | |
| progressLabel.visible = false | |
| } | |
| } catch (Exception ex) { | |
| showAlert("Error","Failed to load gene detections: ${ex.message}") | |
| ex.printStackTrace() | |
| progressBar.visible = false | |
| progressLabel.visible = false | |
| } | |
| }) | |
| // ------------------- Assemble Tab 2 ---------------------------- | |
| vbox2.getChildren().addAll( | |
| label2, | |
| new Separator(), | |
| firstLineHBox, | |
| new Label("Gene Selection & Colors:"), | |
| geneSelectionVBox, | |
| loadSelectedGenesButton, | |
| new Separator(), | |
| progressLabel, | |
| progressBar | |
| ) | |
| tab2.setContent(new ScrollPane(vbox2)) | |
| // ------------------- Add tabs & show ------------------------- | |
| tabPane.getTabs().addAll(tab1, tab2) | |
| def scene = new Scene(tabPane, 700, 750) | |
| stage.scene = scene | |
| stage.show() | |
| stage.centerOnScreen() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment