diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0300dec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+**/.venv
diff --git a/case1-stardist/.gitignore b/case1-stardist/.gitignore
new file mode 100644
index 0000000..a802a57
--- /dev/null
+++ b/case1-stardist/.gitignore
@@ -0,0 +1,2 @@
+stardist*
+chatty-frog/
diff --git a/case1-stardist/QuPath_Export_Labels.groovy b/case1-stardist/QuPath_Export_Labels.groovy
new file mode 100644
index 0000000..165a9ce
--- /dev/null
+++ b/case1-stardist/QuPath_Export_Labels.groovy
@@ -0,0 +1,25 @@
+/**
+ * QuPath script to export labeled images corresponding to nucleus annotations to train StarDist.
+ * Written for QuPath v0.6.0.
+ */
+
+// Create a labeled image that assigns integer labels to all unclassified annotations.
+// Note that the 'parent' annotation created before running StarDist should have a classification
+// to make sure that it is not included here.
+def imageData = getCurrentImageData()
+def labelServer = new LabeledImageServer.Builder(imageData)
+ .useInstanceLabels()
+ .useFilter(a -> a.isAnnotation() && !a.classification)
+ .build()
+
+
+// Specify the folder where to save the results
+def outputDir = buildPathInProject("export")
+mkdirs(outputDir)
+
+// Write the image
+// For very large images, .ome.tif or .ome.zarr may be needed
+def name = getCurrentImageNameWithoutExtension()
+def path = buildFilePath(outputDir, name + ".tif")
+writeImage(labelServer, path)
+println "Image written successfully to " + path
diff --git a/case1-stardist/QuPath_Run_StarDist.groovy b/case1-stardist/QuPath_Run_StarDist.groovy
new file mode 100644
index 0000000..32ef8c3
--- /dev/null
+++ b/case1-stardist/QuPath_Run_StarDist.groovy
@@ -0,0 +1,39 @@
+/**
+ * Groovy script to apply Stardist using a model from bioimage.io in QuPath v0.6.
+ *
+ * It requires installing the StarDist extension in QuPath.
+ * For more information, see https://qupath.readthedocs.io/en/0.6/docs/deep/stardist.html
+ */
+
+
+import static qupath.lib.gui.scripting.QPEx.*
+import qupath.ext.stardist.*
+
+// Specify the path to the model
+var pathModel = buildPathInProject('chatty-frog')
+
+// Customize how StarDist will operate
+var stardist = StarDistBioimageIo.builder(pathModel)
+ .createAnnotations() // Create annotations (which are editable); remove to create detections
+ .threshold(0.4) // Specify probability threshold (lower to detect more objects)
+ .build()
+
+// Get the current image
+var imageData = getCurrentImageData()
+
+// If the image is small, and we have no objects, create a rectangle around everything
+if (getAllObjectsWithoutRoot().isEmpty() && Math.max(imageData.server.width, imageData.server.height) <= 2048) {
+ println "Creating a full image annotation"
+ createFullImageAnnotation(true)
+ // Assign a classification so we can distinguish our region from detected nuclei
+ getSelectedObject().classification = "Region*"
+}
+
+// Run detection for the selected objects
+var pathObjects = getSelectedObjects().findAll {it.isAnnotation()}
+if (pathObjects.isEmpty()) {
+ println "At least one annotation needs to be selected!"
+}
+stardist.detectObjects(imageData, pathObjects)
+stardist.close()
+println 'Done!'
diff --git a/case1-stardist/README.md b/case1-stardist/README.md
index 082ba81..3b30811 100644
--- a/case1-stardist/README.md
+++ b/case1-stardist/README.md
@@ -3,23 +3,28 @@
In this use-case, we apply the [Stardist H&E](https://bioimage.io/#/?tags=stardist&id=10.5281%2Fzenodo.6338614) model from the BioImage Model Zoo which was pretrained on [MoNuSeg](https://monuseg.grand-challenge.org/Data/) and [TNBC](https://zenodo.org/record/1175282#.X6mwG9so-CN) datasets.
This model is applied to the [Lizard dataset](https://www.kaggle.com/datasets/aadimator/lizard-dataset) in [deepImageJ](https://deepimagej.github.io), in [QuPath](https://qupath.github.io), in [ZeroCostDL4Mic](https://github.com/HenriquesLab/ZeroCostDL4Mic/wiki) and with a Python notebook.
-## Apply stardist model in QuPath and correct segmentation
+## Apply StarDist model in QuPath and correct segmentation
-We apply the pretrained model in QuPath, that has advanced image annotation capabilities. This allows correction of the stardist segmentation, for example for a more correct analysis of the result, or for training of a better model.
+We apply the pretrained model in QuPath, that has advanced image annotation capabilities.
+This allows correction of the StarDist segmentation, for example for a more correct analysis of the result, or for training of a better model.
Step-by-step:
-- Download the stardist model with `download_stardist_model.py`
-- Run `prepare_data_for_qupath.py` to select the data for QuPath
+- Download the StarDist H&E model from: https://bioimage.io/#/artifacts/chatty-frog
+ Be sure to extract the downloaded zip, and also extract the TF_SavedModel folder inside the extracted folder.
+- Download the images from https://www.kaggle.com/datasets/aadimator/lizard-dataset
+ You can download the entire dataset (~800MB) or just a few images.
- Prepare QuPath for running StarDist:
- - install the StarDist extension: https://qupath.readthedocs.io/en/stable/docs/advanced/stardist.html#getting-the-stardist-extension
- - install the tensorflow extension: https://qupath.readthedocs.io/en/stable/docs/advanced/stardist.html#use-tensorflow
-- Apply stardist to the lizard images with the `apply_stardist_qupath.groovy` script using the [QuPath scripting functionality](https://qupath.readthedocs.io/en/stable/docs/scripting/workflows_to_scripts.html#running-a-script-for-a-single-image).
- - To run it adapt the path to the model in the script here: https://github.com/bioimage-io/use-cases/blob/main/case2/apply_stardist_qupath.groovy#L27
-- Correct the predictions using the qupath annotation functionality (check out [these tweets](https://twitter.com/petebankhead/status/1295965136646176768) for a short overview of this functionality)
-- Export the label image using the `export_labels_qupath.groovy` script.
- - Important: Remove the rectangular annotation that the stardist plugin creates around the whole image before exporting the labels, otherwise the export script will not work correctly.
+ - install the StarDist extension: https://qupath.readthedocs.io/en/0.6/docs/intro/extensions.html#extensions
+ - install TensorFlow (and possibly CUDA): https://qupath.readthedocs.io/en/0.6/docs/deep/djl.html
+- Create a project by dragging an empty folder onto QuPath: https://qupath.readthedocs.io/en/0.6/docs/tutorials/projects.html
+- Add the lizard images to the project by drag & drop: https://qupath.readthedocs.io/en/0.6/docs/tutorials/projects.html#add-images
+- Apply StarDist to the lizard images with the `QuPath_Run_StarDist.groovy` script using [QuPath's scripting functionality](https://qupath.readthedocs.io/en/0.6/docs/scripting/workflows_to_scripts.html#running-a-script-for-multiple-images).
+ - Either copy the `chatty-frog` folder to be inside the QuPath project, or update the model path defined in the script: https://github.com/bioimage-io/use-cases/blob/main/case1-stardist/QuPath_Run_StarDist.groovy
+- Correct the predictions using QuPath's annotation tools (check out [these tweets](https://twitter.com/petebankhead/status/1295965136646176768) for a short overview)
+- Export the label image using the `QuPath_Export_Labels.groovy` script.
-See a short video demonstrating the label correction in qu-path:
+
+See a short video demonstrating the label correction in QuPath:
https://user-images.githubusercontent.com/4263537/160414686-10ae46ae-5903-4a67-a35b-1f043b68711d.mp4
@@ -30,7 +35,7 @@ And images of application in QuPath:
### Dependencies
-- qupath 0.3.2
+- QuPath 0.6.0
## Apply the model in deepImageJ
@@ -40,7 +45,7 @@ And images of application in QuPath:
- Select the model "StarDist H&E Nuclei Segmentation" from the drop down menu and click on Install.
- Adjust the Probability Threshold to a 0,40. You can try different thresholds depending on the number of objects that you want to detect.
- Apply the model by clicking `Run`.
- - This will result in the stardist predictions. You can change the lookup table for a better visualization of the segmented nuclei.
+ - This will result in the StarDist predictions. You can change the lookup table for a better visualization of the segmented nuclei.
See a screenshot of the deepImageJ StarDist interface:
@@ -54,7 +59,7 @@ The example was run using:
- DeepImageJ 3.1.0
-## Apply the model in stardist python
+## Apply the model in StarDist python
- `run_stardist_python.py`
diff --git a/case1-stardist/README.md.old b/case1-stardist/README.md.old
deleted file mode 100644
index eae84c4..0000000
--- a/case1-stardist/README.md.old
+++ /dev/null
@@ -1,42 +0,0 @@
-# Use-case 2: Stardist H&E nucleus segmentation
-
-In this use-casel, we use the stardist H&E model pretrained on [MoNuSeg](https://monuseg.grand-challenge.org/Data/) and [TNBC](https://zenodo.org/record/1175282#.X6mwG9so-CN): https://bioimage.io/#/?tags=stardist&id=10.5281%2Fzenodo.6338614.
-We apply it to the [Lizard dataset](https://warwick.ac.uk/fac/cross_fac/tia/data/lizard/), see the pretrained model applied to one of the lizard images below, colored instance masks are stardist predictions, red instance outlines mark the expected nuclei.
-
-
-
-Note that the images are upscaled by a factor of 2 to match the resolution of images that the stardist model was trained on.
-
-## Apply stardist model in QuPath and correct segmentation
-
-Apply the pretrained H&E model to some images from Lizard and correct the predictions:
-- Download the stardist model with `download_stardist_model.py`
-- Run `prepare_data_for_qupath.py` to select the data for QuPath
-- Prepare QuPath for running StarDist:
- - install the StarDist extension: https://qupath.readthedocs.io/en/stable/docs/advanced/stardist.html#getting-the-stardist-extension
- - install the tensorflow extension: https://qupath.readthedocs.io/en/stable/docs/advanced/stardist.html#use-tensorflow
-- Apply stardist to the lizard images with the `apply_stardist_qupath.groovy` script using the [QuPath scripting functionality](https://qupath.readthedocs.io/en/stable/docs/scripting/workflows_to_scripts.html#running-a-script-for-a-single-image).
- - To run it adapt the path to the model in the script here: https://github.com/bioimage-io/use-cases/blob/main/case2/apply_stardist_qupath.groovy#L27
-- Correct the predictions using the qupath annotation functionality (check out [these tweets](https://twitter.com/petebankhead/status/1295965136646176768) for a short overview of this functionality)
-- Export the label image using the `export_labels_qupath.groovy` script.
- - Important: Remove the rectangular annotation that the stardist plugin creates around the whole image before exporting the labels, otherwise the export script will not work correctly.
-
-See a short video demonstrating the label correction in qu-path:
-
-https://user-images.githubusercontent.com/4263537/160414686-10ae46ae-5903-4a67-a35b-1f043b68711d.mp4
-
-
-## Retrain with corrected segmentation in zero-cost
-
-TODO:
-- use stardist modelzoo library in zero-cost notebook
-
-## Apply retrained model in deepimagej
-
-TODO:
-- figure out how exactly to run stardist post-processing in deepIJ
-
-## Evaluate retrained model in python
-
-TODO:
-- quantitatively compare performance of the retrained model with the pretrained model on the test images using the available ground-truth labels
diff --git a/case1-stardist/apply_stardist_qupath.groovy b/case1-stardist/apply_stardist_qupath.groovy
deleted file mode 100644
index 2a48f07..0000000
--- a/case1-stardist/apply_stardist_qupath.groovy
+++ /dev/null
@@ -1,105 +0,0 @@
-/**
- * Groovy script to apply Stardist using a model from bioimage.io.
- * For more information about StarDist + QuPath in general, see https://qupath.readthedocs.io/en/stable/docs/advanced/stardist.html
- *
- * Note that this is written for compatibility with QuPath v0.3.2... and is therefore rather awkward.
- * It requires separately installing two extensions:
- * - https://github.com/qupath/qupath-extension-stardist
- * - https://github.com/qupath/qupath-extension-tensorflow
- * It also only supports percentile normalization, with values that should be specified in the script
- * (since the YAML is not parsed automatically).
- *
- * Future QuPath versions will reduce the complexity a bit by including more useful built-in methods.
- *
- * @author Pete Bankhead
- */
-
-
-import static qupath.lib.gui.scripting.QPEx.*
-import org.bytedeco.opencv.opencv_core.Mat
-import qupath.lib.gui.dialogs.Dialogs
-import qupath.ext.stardist.StarDist2D
-import qupath.opencv.tools.OpenCVTools
-
-setPixelSizeMicrons(0.5, 0.5)
-
-// specify the path to the model
-var pathModel = '/home/pape/Work/bioimageio/use-cases/case2/he-model-pretrained/bioimageio'
-// Define nput normalization percentiles etc
-double minPercentile = 1.0 // Input normalization min percentile
-double maxPercentile = 99.8 // Input normalization max percentile
-boolean jointChannelNormalize = true // Optionally normalize channels together (rather than independently)
-double predictionThreshold = 0.5 // Prediction threshold
-double pixelSizeMicrons = 0.25 // Specify if input resolution should be set (rather than using full resolution)
-boolean createAnnotations = true // Create cells as annotations, which can then be edited (default is to create detection objects)
-
-// Customize how StarDist will operate
-var stardistBuilder = StarDist2D.builder(pathModel)
- .threshold(predictionThreshold)
-if (pixelSizeMicrons && Double.isFinite(pixelSizeMicrons))
- stardistBuilder.pixelSize(pixelSizeMicrons)
-
-if (createAnnotations)
- stardistBuilder.createAnnotations()
-
-if (jointChannelNormalize)
- stardistBuilder.preprocess(
- new NormalizePercentileJointOp(minPercentile, maxPercentile)
- )
-else
- stardistBuilder.normalizePercentiles(minPercentile, maxPercentile)
-
-// If necessary, customize tile size
-//stardistBuilder.tileSize(1024, 1024)
-
-var stardist = stardistBuilder.build()
-
-
-// Run detection for the selected objects
-var imageData = getCurrentImageData()
-var pathObjects = getSelectedObjects()
-if (pathObjects.isEmpty()) {
- // If there are no annotations at all, and the image isn't too big, create an annotation for the full image
- if (getAnnotationObjects().isEmpty() && imageData.getServer().getWidth() <= 2048 && imageData.getServer().getHeight() <= 2048) {
- println "No objects are selected - I'll select the whole image then"
- createSelectAllObject(true)
- } else {
- Dialogs.showErrorMessage("StarDist", "Please select one or more parent objects!")
- return
- }
-}
-stardist.detectObjects(imageData, pathObjects)
-println 'Done!'
-
-
-
-/**
- * For QuPath v0.3.2, we need a custom ImageOp to perform joint channel normalization
- */
-class NormalizePercentileJointOp implements qupath.opencv.ops.ImageOp {
-
- private double[] percentiles
-
- NormalizePercentileJointOp(double percentileMin, double percentileMax) {
- this.percentiles = [percentileMin, percentileMax] as double[]
- if (percentileMin == percentileMax)
- throw new IllegalArgumentException("Percentile min and max values cannot be identical!")
- }
-
- @Override
- public Mat apply(Mat mat) {
- // Need to reshape for percentiles to be correct in QuPath v0.3.2
- var matTemp = mat.reshape(1, mat.rows()*mat.cols())
- var range = OpenCVTools.percentiles(matTemp, percentiles)
- double scale
- if (range[1] == range[0]) {
- println("Normalization percentiles give the same value ({}), scale will be Infinity", range[0])
- scale = Double.POSITIVE_INFINITY
- } else
- scale = 1.0/(range[1] - range[0])
- double offset = -range[0]
- mat.convertTo(mat, mat.type(), scale, offset*scale)
- return mat;
- }
-
-}
diff --git a/case1-stardist/bkp/evaluate_predictions.py b/case1-stardist/bkp/evaluate_predictions.py
deleted file mode 100644
index 2586ae1..0000000
--- a/case1-stardist/bkp/evaluate_predictions.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import argparse
-import json
-import os
-from glob import glob
-
-import imageio
-import numpy as np
-import scipy.io as sio
-from stardist.matching import matching
-from tqdm import tqdm
-
-
-def evaluate_prediction(pred_path, label_path):
- seg = imageio.imread(pred_path)
- gt = sio.loadmat(label_path)["inst_map"]
- assert seg.shape == gt.shape, f"{seg.shape}, {gt.shape}"
- score = matching(gt, seg)
- return score.mean_matched_score
-
-
-def compare(pred_folders, pred_scores, image_folder):
- import napari
-
- name1, name2 = os.path.basename(pred_folders[0].rstrip("/")), os.path.basename(pred_folders[1].rstrip("/"))
- paths1, paths2 = glob(os.path.join(pred_folders[0], "*.tif")), glob(os.path.join(pred_folders[1], "*.tif"))
- paths1.sort()
- paths2.sort()
-
- diffs = []
- for ii, (p1, p2) in enumerate(zip(paths1, paths2)):
- s1, s2 = pred_scores[name1][ii], pred_scores[name2][ii]
- diff = np.abs(s1 - s2)
- diffs.append(diff)
-
- max_diff_dis = np.argsort(diffs)[::-1]
- for diff_id in max_diff_dis:
- p1, p2 = paths1[diff_id], paths2[diff_id]
- diff = s1 - s2
- im_name = os.path.basename(p1).replace(".tif", ".png")
- im_path = os.path.join(image_folder, im_name)
- assert os.path.exists(im_path), im_path
-
- im = imageio.imread(im_path)
- seg1 = imageio.imread(p1)
- seg2 = imageio.imread(p2)
-
- msg = f"{name1} is better than {name2}" if s1 > s2 else f"{name2} is better than {name1}"
- title = f"Im={im_name}, diff={np.abs(diff)}, {msg}"
- v = napari.Viewer()
- v.add_image(im)
- v.add_labels(seg1, name=name1)
- v.add_labels(seg2, name=name2)
- v.title = title
- napari.run()
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--prediction_folder", "-p", required=True, nargs="+")
- parser.add_argument("--label_folder", "-l", required=True)
- parser.add_argument("-c", "--compare", default=0, type=int)
- args = parser.parse_args()
-
- pred_scores = {}
- for pred_folder in args.prediction_folder:
- method_name = os.path.basename(pred_folder.rstrip("/"))
- predictions = glob(os.path.join(pred_folder, "*.tif"))
- predictions.sort()
- scores = []
- for pred in tqdm(predictions):
- name = os.path.basename(pred)
- lab = os.path.join(args.label_folder, name.replace(".tif", ".mat"))
- assert os.path.exists(lab), lab
- scores.append(evaluate_prediction(pred, lab))
-
- save_path = os.path.join(pred_folder, "scores.json")
- with open(save_path, "w") as f:
- json.dump([float(score) for score in scores], f)
-
- score = np.mean(scores)
- pred_scores[method_name] = scores
- print("Evaluation for", method_name)
- print("Mean IoU-50:", score)
-
- if bool(args.compare):
- assert len(pred_scores) == 2, "Can only compare two predictions"
- # TODO don't hard-code
- image_folder = "/home/pape/Work/data/lizard/train_images"
- compare(args.prediction_folder, pred_scores, image_folder)
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/bkp/export_labels_qupath.groovy b/case1-stardist/bkp/export_labels_qupath.groovy
deleted file mode 100644
index b4424bb..0000000
--- a/case1-stardist/bkp/export_labels_qupath.groovy
+++ /dev/null
@@ -1,17 +0,0 @@
-def imageData = getCurrentImageData()
-
-def labelServer = new LabeledImageServer.Builder(imageData)
- .backgroundLabel(0, 0) // Specify background label (usually 0 or 255)
- .useInstanceLabels()
- .useAnnotations()
- .multichannelOutput(false)
- .build()
-
-
-// specify the folder where to save the results
-def outputDir = '/home/pape/Work/bioimageio/use-cases/case2/images_for_qupath/labels'
-mkdirs(outputDir)
-def name = GeneralTools.getNameWithoutExtension(imageData.getServer().getMetadata().getName())
-def path = buildFilePath(outputDir, name + ".tif")
-
-writeImage(labelServer, path)
\ No newline at end of file
diff --git a/case1-stardist/bkp/finetuning/.gitignore b/case1-stardist/bkp/finetuning/.gitignore
deleted file mode 100644
index fa8ff75..0000000
--- a/case1-stardist/bkp/finetuning/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-models/
-predictions/
-training_data/
diff --git a/case1-stardist/bkp/finetuning/README.md b/case1-stardist/bkp/finetuning/README.md
deleted file mode 100644
index 68708a4..0000000
--- a/case1-stardist/bkp/finetuning/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Scores
-
-baseline: 0.7866029100142583
-
-| Name | from-scratch | finetuned |
-| --------- | ------------- | --------- |
-| gt-full | 0.7812 | - |
-| qupath-v3 | - | 0.7577 |
-| gt-v3 | 0.7310 | 0.7497 |
-| zcost | - | 0.7303 |
diff --git a/case1-stardist/bkp/finetuning/evaluate_finetuned.py b/case1-stardist/bkp/finetuning/evaluate_finetuned.py
deleted file mode 100644
index 0e3bd06..0000000
--- a/case1-stardist/bkp/finetuning/evaluate_finetuned.py
+++ /dev/null
@@ -1,152 +0,0 @@
-import argparse
-import os
-import json
-from glob import glob
-from concurrent import futures
-from pathlib import Path
-
-# don't parallelize internally
-n_threads = 1
-os.environ["OMP_NUM_THREADS"] = str(n_threads)
-os.environ["OPENBLAS_NUM_THREADS"] = str(n_threads)
-os.environ["MKL_NUM_THREADS"] = str(n_threads)
-os.environ["VECLIB_NUM_THREADS"] = str(n_threads)
-os.environ["NUMEXPR_NUM_THREADS"] = str(n_threads)
-
-import imageio
-import numpy as np
-import scipy.io as sio
-from csbdeep.utils import normalize
-try:
- from stardist.models import StarDist2D
-except ImportError:
- StarDist2D = None
- print("Could not import stardist!!!!")
-from stardist.matching import matching
-from tqdm import tqdm
-
-
-PRETRAINED_SCORE = 0.7866029100142583
-
-
-def apply_model(model, image_path, save_path, scale):
- input_ = imageio.imread(image_path)
- input_ = normalize(input_.astype("float32"), 1.0, 99.8)
- nuclei, _ = model.predict_instances(input_, scale=scale)
- assert nuclei.shape == input_.shape[:-1]
- imageio.imsave(save_path, nuclei)
-
-
-def prediction(model_name, scale):
- input_folder = "/g/kreshuk/data/lizard/train_images"
- images = glob(os.path.join(input_folder, "*png"))
- print("Applying stardist model to", len(images), "images")
-
- output_folder = f"./predictions/{os.path.basename(model_name)}"
- os.makedirs(output_folder, exist_ok=True)
- model_root = Path(f"./{model_name}")
-
- def _predict(im):
- name = os.path.basename(im).replace(".png", ".tif")
- save_path = os.path.join(output_folder, name)
- if os.path.exists(save_path):
- return
- model_folder = Path(model_root)
- model = StarDist2D(None, model_folder.name, model_folder.parent)
- apply_model(model, im, save_path, scale)
-
- n_threads = 12
- with futures.ThreadPoolExecutor(n_threads) as tp:
- list(tqdm(tp.map(_predict, images), total=len(images), desc="Run prediction"))
- return output_folder
-
-
-def evaluate_prediction(pred_path, label_path):
- seg = imageio.imread(pred_path)
- gt = sio.loadmat(label_path)["inst_map"]
- assert seg.shape == gt.shape, f"{seg.shape}, {gt.shape}"
- score = matching(gt, seg)
- return score.mean_matched_score
-
-
-def evaluation(pred_folder):
- label_folder = "/g/kreshuk/data/lizard/train_labels/Labels"
- scores = []
- predictions = glob(os.path.join(pred_folder, "*.tif"))
- predictions.sort()
-
- def _eval(pred):
- name = os.path.basename(pred)
- label_path = os.path.join(label_folder, name.replace(".tif", ".mat"))
- score = evaluate_prediction(pred, label_path)
- return score
-
- n_threads = 8
- with futures.ThreadPoolExecutor(n_threads) as tp:
- scores = list(tqdm(tp.map(_eval, predictions), total=len(predictions), desc="Run evaluation"))
- print("Fine-tuned score:", np.mean(scores))
- print("Baseline:", PRETRAINED_SCORE)
- return scores
-
-
-def compare(pred_folder, new_scores):
- import napari
-
- image_folder = "/home/pape/Work/data/lizard/train_images"
- baseline_folder = "/home/pape/Work/data/lizard/predictions/train/he-model-pretrained"
- pred_folders = [baseline_folder, pred_folder]
-
- name1, name2 = os.path.basename(pred_folders[0].rstrip("/")), os.path.basename(pred_folders[1].rstrip("/"))
- paths1, paths2 = glob(os.path.join(pred_folders[0], "*.tif")), glob(os.path.join(pred_folders[1], "*.tif"))
- paths1.sort()
- paths2.sort()
-
- baseline_scores = "/home/pape/Work/data/lizard/predictions/train/he-model-pretrained/scores.json"
- with open(baseline_scores, "r") as f:
- baseline_scores = json.load(f)
- pred_scores = {
- name1: baseline_scores, name2: new_scores
- }
-
- diffs = []
- for ii, (p1, p2) in enumerate(zip(paths1, paths2)):
- s1, s2 = pred_scores[name1][ii], pred_scores[name2][ii]
- diff = np.abs(s1 - s2)
- diffs.append(diff)
-
- max_diff_dis = np.argsort(diffs)[::-1]
- for diff_id in max_diff_dis:
- p1, p2 = paths1[diff_id], paths2[diff_id]
- diff = s1 - s2
- im_name = os.path.basename(p1).replace(".tif", ".png")
- im_path = os.path.join(image_folder, im_name)
- assert os.path.exists(im_path), im_path
-
- im = imageio.imread(im_path)
- seg1 = imageio.imread(p1)
- seg2 = imageio.imread(p2)
-
- msg = f"{name1} is better than {name2}" if s1 > s2 else f"{name2} is better than {name1}"
- title = f"Im={im_name}, diff={np.abs(diff)}, {msg}"
- v = napari.Viewer()
- v.add_image(im)
- v.add_labels(seg1, name=name1)
- v.add_labels(seg2, name=name2)
- v.title = title
- napari.run()
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-m", "--model", required=True)
- parser.add_argument("-c", "--compare", type=int, default=0)
- parser.add_argument("-s", "--scale", default=1, type=int)
- args = parser.parse_args()
- pred_folder = prediction(args.model, args.scale)
- scores = evaluation(pred_folder)
- if bool(args.compare):
- compare(pred_folder, scores)
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/bkp/finetuning/finetune.py b/case1-stardist/bkp/finetuning/finetune.py
deleted file mode 100644
index 25e524a..0000000
--- a/case1-stardist/bkp/finetuning/finetune.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import argparse
-import os
-from glob import glob
-from pathlib import Path
-from shutil import copytree, rmtree
-
-import imageio
-import numpy as np
-from stardist.models import StarDist2D
-from csbdeep.utils import normalize
-
-
-def random_fliprot(img, mask):
- assert img.ndim >= mask.ndim
- axes = tuple(range(mask.ndim))
- perm = tuple(np.random.permutation(axes))
- img = img.transpose(perm + tuple(range(mask.ndim, img.ndim)))
- mask = mask.transpose(perm)
- for ax in axes:
- if np.random.rand() > 0.5:
- img = np.flip(img, axis=ax)
- mask = np.flip(mask, axis=ax)
- return img, mask
-
-
-def random_intensity_change(img):
- img = img*np.random.uniform(0.6, 2) + np.random.uniform(-0.2, 0.2)
- return img
-
-
-def augmenter(x, y):
- """Augmentation of a single input/label image pair.
- x is an input image
- y is the corresponding ground-truth label image
- """
- x, y = random_fliprot(x, y)
- x = random_intensity_change(x)
- # add some gaussian noise
- sig = 0.02*np.random.uniform(0, 1)
- x = x + sig*np.random.normal(0, 1, x.shape)
- return x, y
-
-
-def finetune():
- parser = argparse.ArgumentParser()
- parser.add_argument("-n", "--name", required=True)
- parser.add_argument("-r", "--root", required=True)
- parser.add_argument("--n_epochs", default=5, type=int)
- args = parser.parse_args()
-
- im_paths = glob(os.path.join(args.root, "images/*.tif"))
- im_paths.sort()
- label_paths = glob(os.path.join(args.root, "labels/*.tif"))
- label_paths.sort()
-
- val_names = ["consep_5", "consep_11", "crag_60"]
- val_ids = tuple(ii for ii, p in enumerate(im_paths) if any(nn in p for nn in val_names))
- train_ids = tuple(ii for ii, p in enumerate(im_paths) if not any(nn in p for nn in val_names))
- print("Train ids:", train_ids)
- print("Val ids:", val_ids)
- assert len(val_ids) == len(val_names)
- assert len(train_ids) == len(im_paths) - len(val_names)
-
- x = list(map(imageio.imread, im_paths))
- y = list(map(imageio.imread, label_paths))
-
- axis_norm = (0, 1)
- x = [normalize(xx, 1, 99.8, axis=axis_norm) for xx in x]
-
- x_train, y_train = [x[tid] for tid in train_ids], [y[tid] for tid in train_ids]
- x_val, y_val = [x[vid] for vid in val_ids], [y[vid] for vid in val_ids]
-
- model_folder = f"./models/finetuned-{args.name}"
- if os.path.exists(model_folder):
- rmtree(model_folder)
- copytree("../he-model-pretrained", model_folder)
- model_folder = Path(model_folder)
- model = StarDist2D(None, model_folder.name, model_folder.parent)
- model.config.train_patch_size = [384, 384]
-
- print("Start training ....")
- model.train(x_train, y_train, validation_data=(x_val, y_val), augmenter=augmenter, epochs=args.n_epochs)
-
- print("Optimize thresholds ...")
- model.optimize_thresholds(x_val, y_val)
-
-
-if __name__ == "__main__":
- finetune()
diff --git a/case1-stardist/bkp/finetuning/from_scratch.py b/case1-stardist/bkp/finetuning/from_scratch.py
deleted file mode 100644
index f0c7acc..0000000
--- a/case1-stardist/bkp/finetuning/from_scratch.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import argparse
-import os
-from glob import glob
-from pathlib import Path
-
-import imageio
-import numpy as np
-from stardist.models import StarDist2D, Config2D
-from csbdeep.utils import normalize
-
-
-def random_fliprot(img, mask):
- assert img.ndim >= mask.ndim
- axes = tuple(range(mask.ndim))
- perm = tuple(np.random.permutation(axes))
- img = img.transpose(perm + tuple(range(mask.ndim, img.ndim)))
- mask = mask.transpose(perm)
- for ax in axes:
- if np.random.rand() > 0.5:
- img = np.flip(img, axis=ax)
- mask = np.flip(mask, axis=ax)
- return img, mask
-
-
-def random_intensity_change(img):
- img = img*np.random.uniform(0.6, 2) + np.random.uniform(-0.2, 0.2)
- return img
-
-
-def augmenter(x, y):
- """Augmentation of a single input/label image pair.
- x is an input image
- y is the corresponding ground-truth label image
- """
- x, y = random_fliprot(x, y)
- x = random_intensity_change(x)
- # add some gaussian noise
- sig = 0.02*np.random.uniform(0, 1)
- x = x + sig*np.random.normal(0, 1, x.shape)
- return x, y
-
-
-def from_scratch():
- parser = argparse.ArgumentParser()
- parser.add_argument("-n", "--name", required=True)
- parser.add_argument("-r", "--root", required=True)
- parser.add_argument("--n_epochs", default=200, type=int)
- parser.add_argument("--use_gpu", "-g", default=0, type=int)
- args = parser.parse_args()
-
- im_paths = glob(os.path.join(args.root, "images/*.tif"))
- im_paths.sort()
- label_paths = glob(os.path.join(args.root, "labels/*.tif"))
- label_paths.sort()
-
- val_names = ["consep_5", "consep_11", "crag_60"]
- val_ids = tuple(ii for ii, p in enumerate(im_paths) if any(nn in p for nn in val_names))
- train_ids = tuple(ii for ii, p in enumerate(im_paths) if not any(nn in p for nn in val_names))
- print("Train ids:", train_ids)
- print("Val ids:", val_ids)
- assert len(val_ids) == len(val_names)
- assert len(train_ids) == len(im_paths) - len(val_names)
-
- x = list(map(imageio.imread, im_paths))
- y = list(map(imageio.imread, label_paths))
-
- axis_norm = (0, 1)
- x = [normalize(xx, 1, 99.8, axis=axis_norm) for xx in x]
-
- x_train, y_train = [x[tid] for tid in train_ids], [y[tid] for tid in train_ids]
- x_val, y_val = [x[vid] for vid in val_ids], [y[vid] for vid in val_ids]
-
- conf = Config2D(
- n_rays=32,
- grid=(2, 2),
- use_gpu=bool(args.use_gpu),
- n_channel_in=3
- )
-
- model_folder = f"./models/scratch-{args.name}"
- model_folder = Path(model_folder)
- model = StarDist2D(conf, model_folder.name, model_folder.parent)
- model.config.train_patch_size = [384, 384]
-
- print("Start training ....")
- model.train(x_train, y_train, validation_data=(x_val, y_val), augmenter=augmenter, epochs=args.n_epochs)
-
- print("Optimize thresholds ...")
- model.optimize_thresholds(x_val, y_val)
-
-
-if __name__ == "__main__":
- from_scratch()
diff --git a/case1-stardist/bkp/finetuning/make_gt_train_set.py b/case1-stardist/bkp/finetuning/make_gt_train_set.py
deleted file mode 100644
index f14c00f..0000000
--- a/case1-stardist/bkp/finetuning/make_gt_train_set.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import argparse
-import os
-from glob import glob
-
-import imageio
-import scipy.io as sio
-
-
-# create gt training set equivalent to the current corrected qupath set
-
-
-def make_gt_train_set():
- image_folder = "/g/kreshuk/data/lizard/train_images"
- label_folder = "/g/kreshuk/data/lizard/train_labels/Labels"
-
- refs = glob("./training_data/qupath/v*")
- refs.sort()
- ref_folder = refs[-1]
- version = os.path.basename(ref_folder)
- ref_folder = os.path.join(ref_folder, "images")
-
- im_out = f"./training_data/gt/{version}/images"
- label_out = f"./training_data/gt/{version}/labels"
- os.makedirs(im_out, exist_ok=True)
- os.makedirs(label_out, exist_ok=True)
-
- ref = glob(os.path.join(ref_folder, "*.tif"))
- for re in ref:
- name = os.path.splitext(os.path.basename(re))[0]
- im_path = os.path.join(image_folder, f"{name}.png")
- im = imageio.imread(im_path)
-
- label_path = os.path.join(label_folder, f"{name}.mat")
- labels = sio.loadmat(label_path)["inst_map"]
-
- assert labels.shape == im.shape[:-1], f"{labels.shape}, {im.shape}"
- imageio.imwrite(os.path.join(im_out, f"{name}.tif"), im)
- imageio.imwrite(os.path.join(label_out, f"{name}.tif"), labels)
-
-
-def make_full_gt():
- image_folder = "/g/kreshuk/data/lizard/train_images"
- label_folder = "/g/kreshuk/data/lizard/train_labels/Labels"
-
- im_out = "./training_data/gt/full/images"
- label_out = "./training_data/gt/full/labels"
- os.makedirs(im_out, exist_ok=True)
- os.makedirs(label_out, exist_ok=True)
-
- images = glob(os.path.join(image_folder, "*.png"))
- for im in images:
- name = os.path.splitext(os.path.basename(im))[0]
- im = imageio.imread(im)
-
- label_path = os.path.join(label_folder, f"{name}.mat")
- labels = sio.loadmat(label_path)["inst_map"]
-
- assert labels.shape == im.shape[:-1], f"{labels.shape}, {im.shape}"
- imageio.imwrite(os.path.join(im_out, f"{name}.tif"), im)
- imageio.imwrite(os.path.join(label_out, f"{name}.tif"), labels)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-f", "--full", type=int, default=0)
- args = parser.parse_args()
-
- if bool(args.full):
- make_full_gt()
- else:
- make_gt_train_set()
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/bkp/finetuning/make_qupath_train_set.py b/case1-stardist/bkp/finetuning/make_qupath_train_set.py
deleted file mode 100644
index b0ceaeb..0000000
--- a/case1-stardist/bkp/finetuning/make_qupath_train_set.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import argparse
-import os
-from glob import glob
-import imageio
-
-
-def make_qupath_train_set(version):
-
- input_images = glob("../images_for_qupath/*.png")
- input_labels = glob("../images_for_qupath/labels/*.tif")
- assert len(input_images) == len(input_labels)
- input_images.sort()
- input_labels.sort()
-
- # TODO implement getting the latest version numberw
- if version is None:
- assert False
-
- im_out = f"./training_data/qupath/{version}/images"
- label_out = f"./training_data/qupath/{version}/labels"
- os.makedirs(im_out, exist_ok=True)
- os.makedirs(label_out, exist_ok=True)
-
- for im, label in zip(input_images, input_labels):
- name = os.path.basename(im).replace(".png", ".tif")
- im = imageio.imread(im)
- imp = os.path.join(im_out, name)
- imageio.imwrite(imp, im)
-
- name = os.path.basename(label)
- label = imageio.imread(label)
- labelp = os.path.join(label_out, name)
- imageio.imwrite(labelp, label)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-v", "--version")
- args = parser.parse_args()
- make_qupath_train_set(args.version)
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/bkp/prepare_data_for_qupath.py b/case1-stardist/bkp/prepare_data_for_qupath.py
deleted file mode 100644
index 8e2e07c..0000000
--- a/case1-stardist/bkp/prepare_data_for_qupath.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import argparse
-import os
-import imageio
-from skimage.transform import rescale
-
-
-def prepare_for_qupath(data_folder, output_folder, image_names, rescale_image):
- os.makedirs(output_folder, exist_ok=True)
- scale_factors = (1, 2, 2)
- for name in image_names:
- path = os.path.join(data_folder, name)
- assert os.path.exists(path), path
- im = imageio.imread(path)
- if rescale_image:
- im = rescale(im, scale_factors, preserve_range=True).astype(im.dtype)
- out_path = os.path.join(output_folder, name)
- imageio.imsave(out_path, im)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-i", "--input_folder", required=True)
- parser.add_argument("-r", "--rescale", default=0, type=int)
- args = parser.parse_args()
- # select the images for corrections in qupath
- image_names = ["consep_1.png", "consep_2.png", "consep_4.png", "consep_5.png"]
- prepare_for_qupath(
- args.input_folder, "./images_for_qupath", image_names, rescale_image=bool(args.rescale)
- )
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/bkp/run_stardist_batch.py b/case1-stardist/bkp/run_stardist_batch.py
deleted file mode 100644
index 1f0821c..0000000
--- a/case1-stardist/bkp/run_stardist_batch.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import argparse
-import os
-from glob import glob
-from concurrent import futures
-from pathlib import Path
-
-# don't parallelize internally
-n_threads = 1
-os.environ["OMP_NUM_THREADS"] = str(n_threads)
-os.environ["OPENBLAS_NUM_THREADS"] = str(n_threads)
-os.environ["MKL_NUM_THREADS"] = str(n_threads)
-os.environ["VECLIB_NUM_THREADS"] = str(n_threads)
-os.environ["NUMEXPR_NUM_THREADS"] = str(n_threads)
-
-import imageio
-from csbdeep.utils import normalize
-from stardist.models import StarDist2D
-from tqdm import tqdm
-
-
-def apply_model(model, image_path, save_path, scale):
- if os.path.exists(save_path):
- return
- input_ = imageio.imread(image_path)
- input_ = normalize(input_.astype("float32"), 1.0, 99.8)
- nuclei, _ = model.predict_instances(input_, scale=scale)
- assert nuclei.shape == input_.shape[:-1]
- imageio.imsave(save_path, nuclei)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--input_folder", "-i", required=True)
- parser.add_argument("--output_root", "-o", required=True)
- parser.add_argument("--model_folder", "-m", required=True)
- parser.add_argument("--n_threads", "-n", default=16, type=int)
- parser.add_argument("--scale", "-s", default=1, type=int)
- args = parser.parse_args()
-
- images = glob(os.path.join(args.input_folder, "*png"))
- print("Applying stardist model to", len(images), "images")
-
- output_folder = os.path.join(args.output_root, os.path.basename(args.model_folder))
- os.makedirs(output_folder, exist_ok=True)
-
- def _predict(im):
- model_folder = Path(args.model_folder)
- model = StarDist2D(None, model_folder.name, model_folder.parent)
- name = os.path.basename(im).replace(".png", ".tif")
- save_path = os.path.join(output_folder, name)
- apply_model(model, im, save_path, scale=args.scale)
-
- with futures.ThreadPoolExecutor(args.n_threads) as tp:
- list(tqdm(tp.map(_predict, images), total=len(images)))
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/download_stardist_model.py b/case1-stardist/download_stardist_model.py
deleted file mode 100644
index e45a837..0000000
--- a/case1-stardist/download_stardist_model.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import zipfile
-import stardist
-
-
-def download_stardist(model_name, doi):
- out_folder = f"he-model-{model_name}"
- bioimageio_folder = os.path.join(out_folder, "bioimageio")
- if os.path.exists(out_folder):
- assert os.path.exists(os.path.join(bioimageio_folder, "rdf.yaml"))
- print("The", model_name, "H&E model has been downloaded already.")
- return
- stardist.import_bioimageio(doi, out_folder)
- # extract the zipped tensorflow weights for running it in qupath
- tf_path = os.path.join(bioimageio_folder, "TF_SavedModel.zip")
- assert os.path.exists(tf_path)
- with zipfile.ZipFile(tf_path) as f:
- f.extractall(bioimageio_folder)
-
-
-def main():
- doi = "10.5281/zenodo.6338614",
- download_stardist("pretrained", doi)
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/images/consep_1.png b/case1-stardist/images/consep_1.png
new file mode 100644
index 0000000..e764039
Binary files /dev/null and b/case1-stardist/images/consep_1.png differ
diff --git a/case1-stardist/images/stardist-qupath1.png b/case1-stardist/images/stardist-qupath1.png
index 2225a12..b9904fc 100644
Binary files a/case1-stardist/images/stardist-qupath1.png and b/case1-stardist/images/stardist-qupath1.png differ
diff --git a/case1-stardist/images/stardist-qupath2.png b/case1-stardist/images/stardist-qupath2.png
index 87f3b50..125f032 100644
Binary files a/case1-stardist/images/stardist-qupath2.png and b/case1-stardist/images/stardist-qupath2.png differ
diff --git a/case1-stardist/prepare_data_for_qupath.py b/case1-stardist/prepare_data_for_qupath.py
deleted file mode 100644
index 8e2e07c..0000000
--- a/case1-stardist/prepare_data_for_qupath.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import argparse
-import os
-import imageio
-from skimage.transform import rescale
-
-
-def prepare_for_qupath(data_folder, output_folder, image_names, rescale_image):
- os.makedirs(output_folder, exist_ok=True)
- scale_factors = (1, 2, 2)
- for name in image_names:
- path = os.path.join(data_folder, name)
- assert os.path.exists(path), path
- im = imageio.imread(path)
- if rescale_image:
- im = rescale(im, scale_factors, preserve_range=True).astype(im.dtype)
- out_path = os.path.join(output_folder, name)
- imageio.imsave(out_path, im)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("-i", "--input_folder", required=True)
- parser.add_argument("-r", "--rescale", default=0, type=int)
- args = parser.parse_args()
- # select the images for corrections in qupath
- image_names = ["consep_1.png", "consep_2.png", "consep_4.png", "consep_5.png"]
- prepare_for_qupath(
- args.input_folder, "./images_for_qupath", image_names, rescale_image=bool(args.rescale)
- )
-
-
-if __name__ == "__main__":
- main()
diff --git a/case1-stardist/run_stardist_python.py b/case1-stardist/run_stardist_python.py
index 807a8ee..500d75e 100644
--- a/case1-stardist/run_stardist_python.py
+++ b/case1-stardist/run_stardist_python.py
@@ -24,7 +24,7 @@ def run_stardist(image, scale=2):
def main():
- input_path = "/home/pape/Work/data/lizard/train_images/consep_13.png"
+ input_path = "consep_13.png"
image = imageio.imread(input_path)
nuclei = run_stardist(image)