kuko6 commited on
Commit
7bcce0f
Β·
1 Parent(s): 24c963e

Removed qc from the directory cleaning

Browse files
Files changed (2) hide show
  1. README.md +62 -1
  2. app.py +5 -14
README.md CHANGED
@@ -39,13 +39,74 @@ both be downloaded; the uploaded source image remains unchanged.
39
 
40
  For batch processing, open the **Image directory** tab and select a folder.
41
  The app processes all uploaded images, including `.tif` and `.tiff` files, and
42
- returns a ZIP archive containing `masks/` and `qc_cleaned/` folders.
43
 
44
  Project layout:
45
 
46
  - `main.py` β€” primary image-cleaning script
47
  - `cleaning.py` β€” reusable cleaning operations
 
 
48
  - `app.py` β€” optional Gradio interface
49
  - `data/` β€” source and example images
50
  - `out/` β€” generated cleaned images and masks
51
  - `test.ipynb` β€” image-cleaning experiments
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  For batch processing, open the **Image directory** tab and select a folder.
41
  The app processes all uploaded images, including `.tif` and `.tiff` files, and
42
+ returns a ZIP archive containing only the cleaned images.
43
 
44
  Project layout:
45
 
46
  - `main.py` β€” primary image-cleaning script
47
  - `cleaning.py` β€” reusable cleaning operations
48
+ - `ihc_quantification_simplified.py` β€” per-spheroid DAB quantification
49
+ - `ihc_qc.py` β€” reusable segmentation, debris, and DAB QC drawing
50
  - `app.py` β€” optional Gradio interface
51
  - `data/` β€” source and example images
52
  - `out/` β€” generated cleaned images and masks
53
  - `test.ipynb` β€” image-cleaning experiments
54
+
55
+ ## DAB quantification
56
+
57
+ `ihc_quantification_simplified.py` segments individual spheroids, excludes the
58
+ debris mask, separates H-DAB stains from the original image values, and writes
59
+ one CSV row per spheroid. Image metadata such as treatment, image number, and
60
+ an optional image note is extracted from the filename.
61
+
62
+ ```bash
63
+ uv run python ihc_quantification_simplified.py data/dab_quantification_test
64
+ ```
65
+
66
+ The input can be one TIFF image or a directory. Directories are searched
67
+ recursively. Results from all discovered images are combined in
68
+ `out/quantification/ihc_quantification.csv` by default. Each spheroid is one
69
+ row, so the table can be filtered or grouped by treatment in Excel or pandas.
70
+ One two-panel QC image per source image is saved in
71
+ `out/quantification/qc/`, showing the segmented spheroids, excluded debris,
72
+ and measured DAB signal. The DAB heatmap uses the same fixed `0-0.3` scale for
73
+ every image so its colors can be compared directly across the dataset.
74
+
75
+ Use `--output` to choose another CSV location:
76
+
77
+ ```bash
78
+ uv run python ihc_quantification_simplified.py data/treatment_images \
79
+ --output out/my_results.csv
80
+ ```
81
+
82
+ With a custom output path, QC images are placed in a `qc/` directory beside
83
+ the CSV.
84
+
85
+ Experiment-sensitive boundary and watershed settings can be changed without
86
+ editing the script:
87
+
88
+ ```bash
89
+ uv run python ihc_quantification_simplified.py data/treatment_images \
90
+ --max-boundary-contact-ratio 0.3 \
91
+ --watershed-marker-core-fraction 0.55 \
92
+ --watershed-minimum-relative-region-area 0.3
93
+ ```
94
+
95
+ A larger boundary-contact ratio tolerates more image-edge contact. Lowering
96
+ the minimum relative watershed-region area permits more uneven splits, such as
97
+ a complete spheroid touching a partially visible spheroid. The marker-core
98
+ fraction controls which distance-transform cores seed the watershed.
99
+
100
+ The QC image can also be created directly from a notebook:
101
+
102
+ ```python
103
+ from ihc_qc import create_qc_image
104
+
105
+ qc = create_qc_image(preview, labels, debris_mask, dab)
106
+ ```
107
+
108
+ Pass `dab_vmax` to use another fixed upper limit for a particular analysis:
109
+
110
+ ```python
111
+ qc = create_qc_image(preview, labels, debris_mask, dab, dab_vmax=1.0)
112
+ ```
app.py CHANGED
@@ -8,6 +8,7 @@ import gradio as gr
8
  import numpy as np
9
 
10
  from cleaning import (
 
11
  clean_image_and_mask,
12
  cleaned_image_name,
13
  read_image_rgb,
@@ -99,36 +100,26 @@ def clean_directory(image_paths: list[str] | None) -> tuple[str, str]:
99
 
100
  batch_dir = Path(_BATCH_OUTPUTS.name) / uuid4().hex
101
  cleaned_dir = batch_dir / "cleaned"
102
- masks_dir = batch_dir / "masks"
103
  cleaned_dir.mkdir(parents=True)
104
- masks_dir.mkdir(parents=True)
105
 
106
  used_names: set[str] = set()
107
 
108
  for image_path_string in image_paths:
109
  image_path = Path(image_path_string)
110
- cleaned_rgb, debris_mask = clean_image_and_mask(
111
- read_image_rgb(image_path)
112
- )
113
 
114
  output_name = cleaned_image_name(image_path, used_names)
115
  write_image_rgb(cleaned_dir / output_name, cleaned_rgb)
116
- mask_name = f"{Path(output_name).stem}_debris_mask.png"
117
- mask_path = masks_dir / mask_name
118
- if not cv2.imwrite(str(mask_path), debris_mask):
119
- raise OSError(f"Could not write debris mask: {mask_path}")
120
 
121
  archive_path = batch_dir / "cleaned_images.zip"
122
  with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as archive:
123
  for output_path in sorted(cleaned_dir.iterdir()):
124
- archive.write(output_path, arcname=f"qc_cleaned/{output_path.name}")
125
- for output_path in sorted(masks_dir.iterdir()):
126
- archive.write(output_path, arcname=f"masks/{output_path.name}")
127
 
128
  count = len(used_names)
129
  return (
130
  str(archive_path),
131
- f"Created QC previews and debris masks for {count} "
132
  f"image{'s' if count != 1 else ''}.",
133
  )
134
 
@@ -236,7 +227,7 @@ def build_app() -> gr.Blocks:
236
  )
237
  batch_status = gr.Textbox(label="Status", interactive=False)
238
  batch_download = gr.File(
239
- label="QC images and debris masks",
240
  interactive=False,
241
  )
242
 
 
8
  import numpy as np
9
 
10
  from cleaning import (
11
+ clean_image,
12
  clean_image_and_mask,
13
  cleaned_image_name,
14
  read_image_rgb,
 
100
 
101
  batch_dir = Path(_BATCH_OUTPUTS.name) / uuid4().hex
102
  cleaned_dir = batch_dir / "cleaned"
 
103
  cleaned_dir.mkdir(parents=True)
 
104
 
105
  used_names: set[str] = set()
106
 
107
  for image_path_string in image_paths:
108
  image_path = Path(image_path_string)
109
+ cleaned_rgb = clean_image(read_image_rgb(image_path))
 
 
110
 
111
  output_name = cleaned_image_name(image_path, used_names)
112
  write_image_rgb(cleaned_dir / output_name, cleaned_rgb)
 
 
 
 
113
 
114
  archive_path = batch_dir / "cleaned_images.zip"
115
  with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as archive:
116
  for output_path in sorted(cleaned_dir.iterdir()):
117
+ archive.write(output_path, arcname=output_path.name)
 
 
118
 
119
  count = len(used_names)
120
  return (
121
  str(archive_path),
122
+ f"Created cleaned images for {count} "
123
  f"image{'s' if count != 1 else ''}.",
124
  )
125
 
 
227
  )
228
  batch_status = gr.Textbox(label="Status", interactive=False)
229
  batch_download = gr.File(
230
+ label="Cleaned images",
231
  interactive=False,
232
  )
233