{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 3: Enhancing Before Detection\n", "\n", "Detection operates on `detect_mat` -- a grayscale representation of your plate that the\n", "detector reads to separate colonies from background. If you improve `detect_mat` *before*\n", "running detection, you get cleaner, more complete results.\n", "\n", "In this tutorial you will:\n", "\n", "1. See what `detect_mat` looks like out of the box\n", "2. Apply **GaussianBlur** to smooth noise\n", "3. Apply **CLAHE** to boost local contrast\n", "4. Compare detection results before and after enhancement\n", "\n", "By the end, you will understand the key insight behind PhenoTypic's enhancement layer:\n", "enhancers modify `detect_mat`, while leaving your original `rgb` and `gray` data untouched." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from phenotypic.data import load_yeast_plate\n", "from phenotypic.enhance import GaussianBlur, CLAHE\n", "from phenotypic.detect import OtsuDetector" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load the plate\n", "\n", "We will work with the bundled Rhodotorula yeast plate -- a 96-well grid of round colonies\n", "that ships with every PhenoTypic install." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plate = load_yeast_plate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Baseline detection (no enhancement)\n", "\n", "Before enhancing anything, let's run detection on the plate as-is. This gives us a\n", "baseline to compare against later.\n", "\n", "We will use `OtsuDetector`, which finds a global intensity threshold to separate\n", "colonies from background. We work on a **copy** of the plate so that the original\n", "stays pristine for enhancement." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "baseline = plate.copy()\n", "detector = OtsuDetector()\n", "baseline = detector.apply(baseline)\n", "baseline.detect_mat.dash(overlay=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(f\"Baseline detection: {baseline.num_objects} colonies found\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Take note of that number -- we will see how enhancement changes it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inspect `detect_mat` before enhancement\n", "\n", "`detect_mat` is the grayscale matrix that every detector reads from. Right now it is\n", "a straight copy of the grayscale channel -- no preprocessing applied yet.\n", "\n", "Let's look at what the detector currently sees." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plate.detect_mat.dash()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may notice noise, subtle texture on the agar, and faint colonies that barely stand\n", "out from the background. Let's improve this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Smooth noise with GaussianBlur\n", "\n", "`GaussianBlur` applies Gaussian smoothing to `detect_mat`. This suppresses\n", "high-frequency noise -- scanner artifacts, agar granularity, condensation speckle --\n", "so that downstream thresholding is driven by colony signal rather than noise.\n", "\n", "The key parameter is **sigma** (blur strength). Typical values range from 0.5 to 5.0.\n", "Keep sigma below the width of your smallest colony to avoid merging neighbors." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "blur = GaussianBlur(sigma=2.0)\n", "plate = blur.apply(plate)\n", "plate.detect_mat.dash()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The image looks subtly smoother. Fine-grained noise is gone, but colony shapes are\n", "preserved. Importantly, only `detect_mat` changed -- your original RGB data is\n", "completely untouched." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Boost local contrast with CLAHE\n", "\n", "CLAHE (Contrast Limited Adaptive Histogram Equalization) enhances contrast\n", "*locally*, tile by tile. This is especially helpful when illumination is uneven\n", "or when faint, translucent colonies blend into the agar background.\n", "\n", "The key parameter is **clip_limit**, which controls how much local contrast\n", "amplification is allowed. Lower values are gentler; higher values make faint\n", "colonies pop more but can also amplify artifacts." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "clahe = CLAHE(clip_limit=0.01)\n", "plate = clahe.apply(plate)\n", "plate.detect_mat.dash()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice how the colony-to-background separation is now much clearer. Faint colonies\n", "that were barely visible before now stand out. The detector will have a much easier\n", "time finding the right threshold.\n", "\n", "Again, `rgb` and `gray` remain unchanged -- only `detect_mat` was modified." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Detect on the enhanced plate\n", "\n", "Now let's run the same `OtsuDetector` on our enhanced plate and see how the\n", "results compare." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plate = detector.apply(plate)\n", "plate.detect_mat.dash(overlay=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(f\"Enhanced detection: {plate.num_objects} colonies found\")\n", "print(f\"Baseline detection: {baseline.num_objects} colonies found\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With enhancement, detection typically finds more colonies with cleaner boundaries.\n", "The GaussianBlur removed noise that could confuse the threshold, and CLAHE\n", "boosted the contrast of faint colonies that were previously missed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The key insight\n", "\n", "Here is the mental model to keep in mind:\n", "\n", "- **Enhancers** read from and write to `detect_mat`. They never touch `rgb` or `gray`.\n", "- **Detectors** read from `detect_mat` and write to `objmask` / `objmap`.\n", "\n", "This separation is what makes the enhancement-then-detection workflow so powerful.\n", "You can stack as many enhancers as you need to clean up `detect_mat`, and the\n", "detector will always see the improved version -- while your original image data\n", "stays safe for visualization and measurement." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "Well done! You have learned how to:\n", "\n", "- Inspect `detect_mat` to see what the detector sees\n", "- Apply **GaussianBlur** to smooth noise (controlled by `sigma`)\n", "- Apply **CLAHE** to boost local contrast (controlled by `clip_limit`)\n", "- Compare detection results before and after enhancement\n", "- Understand that enhancers modify `detect_mat` while leaving `rgb` and `gray` intact\n", "\n", "Preprocessing with enhancers is one of the most effective ways to improve detection\n", "quality. GaussianBlur handles noise; CLAHE handles contrast. Together they give the\n", "detector a much cleaner signal to work with.\n", "\n", "In the next tutorial, you will learn how to chain enhancers and detectors into a\n", "reusable **pipeline** -- so you can apply the same processing steps to hundreds of\n", "plates with a single command.\n", "\n", "[Tutorial 4: Building a Pipeline](04_building_a_pipeline.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbformat_minor": 4, "pygments_lexer": "ipython3", "version": "3.12.0" } }, "nbformat": 4, "nbformat_minor": 4 }