Fahimeh Orvati Nia
commited on
Commit
·
668a993
1
Parent(s):
a28653b
update the latest
Browse files- SSL_greenhouse_tip_detection.pt +0 -3
- app.py +55 -114
- sorghum_pipeline/data/__pycache__/__init__.cpython-312.pyc +0 -0
- sorghum_pipeline/data/__pycache__/loader.cpython-312.pyc +0 -0
- sorghum_pipeline/data/__pycache__/mask_handler.cpython-312.pyc +0 -0
- sorghum_pipeline/data/__pycache__/preprocessor.cpython-312.pyc +0 -0
- sorghum_pipeline/data/preprocessor.py +6 -0
- sorghum_pipeline/features/__pycache__/__init__.cpython-312.pyc +0 -0
- sorghum_pipeline/features/__pycache__/morphology.cpython-312.pyc +0 -0
- sorghum_pipeline/features/__pycache__/spectral.cpython-312.pyc +0 -0
- sorghum_pipeline/features/__pycache__/texture.cpython-312.pyc +0 -0
- sorghum_pipeline/features/__pycache__/vegetation.cpython-312.pyc +0 -0
- sorghum_pipeline/features/morphology.py +9 -26
- sorghum_pipeline/models/__pycache__/__init__.cpython-312.pyc +0 -0
- sorghum_pipeline/models/__pycache__/dbc_lacunarity.cpython-312.pyc +0 -0
- sorghum_pipeline/output/__pycache__/__init__.cpython-312.pyc +0 -0
- sorghum_pipeline/output/__pycache__/manager.cpython-312.pyc +0 -0
- sorghum_pipeline/output/manager.py +1 -20
- sorghum_pipeline/pipeline.py +15 -3
- sorghum_pipeline/segmentation/__pycache__/__init__.cpython-312.pyc +0 -0
- sorghum_pipeline/segmentation/__pycache__/advanced_occlusion_handler.cpython-312.pyc +0 -0
- sorghum_pipeline/segmentation/__pycache__/leaf_occlusion_handler.cpython-312.pyc +0 -0
- sorghum_pipeline/segmentation/__pycache__/manager.cpython-312.pyc +0 -0
- sorghum_pipeline/segmentation/__pycache__/occlusion_handler.cpython-312.pyc +0 -0
- sorghum_pipeline/segmentation/manager.py +2 -1
SSL_greenhouse_tip_detection.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ada9393d2ee2ad99651f0b26f1cef8ac43aa2ce2b470eacd80af9d8f585d2207
|
| 3 |
-
size 42273811
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -4,13 +4,28 @@ from pathlib import Path
|
|
| 4 |
from wrapper import run_pipeline_on_image
|
| 5 |
from PIL import Image
|
| 6 |
|
| 7 |
-
#
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
-
##add the process function
|
| 11 |
-
def process(file_path):
|
| 12 |
if not file_path:
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 16 |
src = Path(file_path)
|
|
@@ -25,7 +40,28 @@ def process(file_path):
|
|
| 25 |
# Fallback: save via PIL if direct copy fails
|
| 26 |
Image.open(src).save(img_path)
|
| 27 |
|
| 28 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
outputs = run_pipeline_on_image(str(img_path), tmpdir, save_artifacts=True)
|
| 30 |
|
| 31 |
def load_pil(path_str):
|
|
@@ -42,9 +78,8 @@ def process(file_path):
|
|
| 42 |
composite = load_pil(outputs.get('Composite'))
|
| 43 |
overlay = load_pil(outputs.get('Overlay'))
|
| 44 |
mask = load_pil(outputs.get('Mask'))
|
| 45 |
-
input_img = load_pil(outputs.get('InputImage'))
|
| 46 |
size_img = load_pil(str(Path(tmpdir) / 'results/size.size_analysis.png'))
|
| 47 |
-
yolo_img = load_pil(str(Path(tmpdir) / 'results/yolo_tips.png'))
|
| 48 |
|
| 49 |
# Texture images (green band)
|
| 50 |
lbp_path = Path(tmpdir) / 'texture_output/lbp_green.png'
|
|
@@ -60,14 +95,8 @@ def process(file_path):
|
|
| 60 |
|
| 61 |
stats_text = outputs.get('StatsText', '')
|
| 62 |
|
| 63 |
-
#
|
| 64 |
-
|
| 65 |
-
# Row 2: Composite, Mask, Overlay
|
| 66 |
-
# Row 3: Texture images (LBP, HOG, Lac1)
|
| 67 |
-
# Row 4: Vegetation indices (gallery)
|
| 68 |
-
# Row 5: Morphology Size and YOLO Tips
|
| 69 |
-
# Final: Stats table
|
| 70 |
-
return (
|
| 71 |
input_img,
|
| 72 |
composite,
|
| 73 |
mask,
|
|
@@ -77,102 +106,13 @@ def process(file_path):
|
|
| 77 |
lac1_img,
|
| 78 |
gallery_items,
|
| 79 |
size_img,
|
| 80 |
-
yolo_img,
|
| 81 |
stats_text,
|
| 82 |
)
|
| 83 |
|
| 84 |
-
def _load_pil(path: Path):
|
| 85 |
-
try:
|
| 86 |
-
if not path or not path.exists():
|
| 87 |
-
return None
|
| 88 |
-
im = Image.open(path)
|
| 89 |
-
out = im.copy()
|
| 90 |
-
im.close()
|
| 91 |
-
return out
|
| 92 |
-
except Exception:
|
| 93 |
-
return None
|
| 94 |
-
|
| 95 |
-
def _first_existing(paths):
|
| 96 |
-
for p in paths:
|
| 97 |
-
if p and p.exists():
|
| 98 |
-
return p
|
| 99 |
-
return None
|
| 100 |
-
|
| 101 |
-
def load_precomputed():
|
| 102 |
-
base = PRECOMPUTED_DIR
|
| 103 |
-
if not base.exists():
|
| 104 |
-
return None, None, None, None, None, None, None, [], None, None, ""
|
| 105 |
-
|
| 106 |
-
# Common subdirs
|
| 107 |
-
results = base / "results"
|
| 108 |
-
veg = base / "Vegetation_indices_images"
|
| 109 |
-
tex = base / "texture_output"
|
| 110 |
-
|
| 111 |
-
input_img = _load_pil(_first_existing([
|
| 112 |
-
base / "input_image.png",
|
| 113 |
-
base / "input_image.tif",
|
| 114 |
-
base / "input.png",
|
| 115 |
-
]))
|
| 116 |
-
composite = _load_pil(_first_existing([
|
| 117 |
-
results / "composite.png",
|
| 118 |
-
]))
|
| 119 |
-
mask = _load_pil(results / "mask.png")
|
| 120 |
-
overlay = _load_pil(results / "overlay.png")
|
| 121 |
-
|
| 122 |
-
texture_img = _load_pil(_first_existing([
|
| 123 |
-
tex / "lbp_green.png",
|
| 124 |
-
tex / "lbp.png",
|
| 125 |
-
]))
|
| 126 |
-
hog_img = _load_pil(_first_existing([
|
| 127 |
-
tex / "hog_green.png",
|
| 128 |
-
tex / "hog.png",
|
| 129 |
-
]))
|
| 130 |
-
lac1_img = _load_pil(_first_existing([
|
| 131 |
-
tex / "lac1_green.png",
|
| 132 |
-
tex / "lacunarity.png",
|
| 133 |
-
]))
|
| 134 |
-
|
| 135 |
-
# Vegetation gallery in order
|
| 136 |
-
veg_order = ["ndvi.png", "gndvi.png", "savi.png"]
|
| 137 |
-
gallery_items = []
|
| 138 |
-
for fname in veg_order:
|
| 139 |
-
p = veg / fname
|
| 140 |
-
img = _load_pil(p)
|
| 141 |
-
if img is not None:
|
| 142 |
-
gallery_items.append(img)
|
| 143 |
-
|
| 144 |
-
size_img = _load_pil(results / "size.size_analysis.png")
|
| 145 |
-
yolo_img = _load_pil(results / "yolo_tips.png")
|
| 146 |
-
|
| 147 |
-
stats_txt_path = _first_existing([
|
| 148 |
-
base / "stats.txt",
|
| 149 |
-
results / "stats.txt",
|
| 150 |
-
])
|
| 151 |
-
stats_text = ""
|
| 152 |
-
if stats_txt_path and stats_txt_path.exists():
|
| 153 |
-
try:
|
| 154 |
-
stats_text = stats_txt_path.read_text()
|
| 155 |
-
except Exception:
|
| 156 |
-
stats_text = ""
|
| 157 |
-
|
| 158 |
-
return (
|
| 159 |
-
input_img,
|
| 160 |
-
composite,
|
| 161 |
-
mask,
|
| 162 |
-
overlay,
|
| 163 |
-
texture_img,
|
| 164 |
-
hog_img,
|
| 165 |
-
lac1_img,
|
| 166 |
-
gallery_items,
|
| 167 |
-
size_img,
|
| 168 |
-
yolo_img,
|
| 169 |
-
stats_text,
|
| 170 |
-
)
|
| 171 |
-
|
| 172 |
|
| 173 |
with gr.Blocks() as demo:
|
| 174 |
gr.Markdown("# 🌿 Automated Plant Analysis Demo")
|
| 175 |
-
gr.Markdown("Upload a
|
| 176 |
|
| 177 |
with gr.Row():
|
| 178 |
with gr.Column():
|
|
@@ -182,11 +122,17 @@ with gr.Blocks() as demo:
|
|
| 182 |
file_types=[".tif", ".tiff", ".png", ".jpg"],
|
| 183 |
label="Upload Image"
|
| 184 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
run = gr.Button("Run Pipeline", variant="primary")
|
| 186 |
|
| 187 |
-
# Row 1: input image
|
| 188 |
with gr.Row():
|
| 189 |
input_img = gr.Image(type="pil", label="Input Image", interactive=False, height=380)
|
|
|
|
| 190 |
# Row 2: composite, mask, overlay
|
| 191 |
with gr.Row():
|
| 192 |
composite_img = gr.Image(type="pil", label="Composite (Segmentation Input)", interactive=False)
|
|
@@ -200,20 +146,18 @@ with gr.Blocks() as demo:
|
|
| 200 |
lac1_img = gr.Image(type="pil", label="Texture Lac1 (Green Band)", interactive=False)
|
| 201 |
|
| 202 |
# Row 4: vegetation indices
|
| 203 |
-
|
| 204 |
gallery = gr.Gallery(label="Vegetation Indices", columns=3, height="auto")
|
| 205 |
|
| 206 |
-
# Row 5: morphology
|
| 207 |
with gr.Row():
|
| 208 |
size_img = gr.Image(type="pil", label="Morphology Size", interactive=False)
|
| 209 |
-
yolo_img = gr.Image(type="pil", label="YOLO Tips", interactive=False)
|
| 210 |
|
| 211 |
# Final: statistics table
|
| 212 |
stats = gr.Textbox(label="Statistics", lines=4)
|
| 213 |
|
| 214 |
run.click(
|
| 215 |
process,
|
| 216 |
-
inputs=inp,
|
| 217 |
outputs=[
|
| 218 |
input_img,
|
| 219 |
composite_img,
|
|
@@ -224,12 +168,9 @@ with gr.Blocks() as demo:
|
|
| 224 |
lac1_img,
|
| 225 |
gallery,
|
| 226 |
size_img,
|
| 227 |
-
yolo_img,
|
| 228 |
stats,
|
| 229 |
]
|
| 230 |
)
|
| 231 |
|
| 232 |
-
# No preloading: start with empty UI until the user runs the pipeline
|
| 233 |
-
|
| 234 |
if __name__ == "__main__":
|
| 235 |
demo.launch()
|
|
|
|
| 4 |
from wrapper import run_pipeline_on_image
|
| 5 |
from PIL import Image
|
| 6 |
|
| 7 |
+
# Base directory for preset images
|
| 8 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 9 |
+
|
| 10 |
+
# Preset images available for selection
|
| 11 |
+
PRESET_IMAGES = {
|
| 12 |
+
"Sorghum": str(BASE_DIR / "Sorghum.tif"),
|
| 13 |
+
"Corn": str(BASE_DIR / "Corn.tif"),
|
| 14 |
+
"Cotton": str(BASE_DIR / "Cotton.tif"),
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def process(file_path, preset_choice):
|
| 19 |
+
"""Process image and yield results progressively for immediate display."""
|
| 20 |
+
# If a preset is chosen, override the uploaded file path
|
| 21 |
+
if preset_choice:
|
| 22 |
+
chosen = PRESET_IMAGES.get(preset_choice)
|
| 23 |
+
if chosen:
|
| 24 |
+
file_path = chosen
|
| 25 |
|
|
|
|
|
|
|
| 26 |
if not file_path:
|
| 27 |
+
# Return 10 outputs (removed YOLO tips)
|
| 28 |
+
return None, None, None, None, None, None, None, [], None, ""
|
| 29 |
|
| 30 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 31 |
src = Path(file_path)
|
|
|
|
| 40 |
# Fallback: save via PIL if direct copy fails
|
| 41 |
Image.open(src).save(img_path)
|
| 42 |
|
| 43 |
+
# Show input image immediately
|
| 44 |
+
try:
|
| 45 |
+
preview_im = Image.open(img_path)
|
| 46 |
+
input_preview = preview_im.copy()
|
| 47 |
+
preview_im.close()
|
| 48 |
+
except Exception:
|
| 49 |
+
input_preview = None
|
| 50 |
+
|
| 51 |
+
yield (
|
| 52 |
+
input_preview, # input image shown immediately
|
| 53 |
+
None, # composite
|
| 54 |
+
None, # mask
|
| 55 |
+
None, # overlay
|
| 56 |
+
None, # texture_img
|
| 57 |
+
None, # hog_img
|
| 58 |
+
None, # lac1_img
|
| 59 |
+
[], # gallery_items
|
| 60 |
+
None, # size_img
|
| 61 |
+
"", # stats
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Run the pipeline
|
| 65 |
outputs = run_pipeline_on_image(str(img_path), tmpdir, save_artifacts=True)
|
| 66 |
|
| 67 |
def load_pil(path_str):
|
|
|
|
| 78 |
composite = load_pil(outputs.get('Composite'))
|
| 79 |
overlay = load_pil(outputs.get('Overlay'))
|
| 80 |
mask = load_pil(outputs.get('Mask'))
|
| 81 |
+
input_img = load_pil(outputs.get('InputImage')) or input_preview
|
| 82 |
size_img = load_pil(str(Path(tmpdir) / 'results/size.size_analysis.png'))
|
|
|
|
| 83 |
|
| 84 |
# Texture images (green band)
|
| 85 |
lbp_path = Path(tmpdir) / 'texture_output/lbp_green.png'
|
|
|
|
| 95 |
|
| 96 |
stats_text = outputs.get('StatsText', '')
|
| 97 |
|
| 98 |
+
# Final yield with all results (10 outputs, no YOLO)
|
| 99 |
+
yield (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
input_img,
|
| 101 |
composite,
|
| 102 |
mask,
|
|
|
|
| 106 |
lac1_img,
|
| 107 |
gallery_items,
|
| 108 |
size_img,
|
|
|
|
| 109 |
stats_text,
|
| 110 |
)
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
with gr.Blocks() as demo:
|
| 114 |
gr.Markdown("# 🌿 Automated Plant Analysis Demo")
|
| 115 |
+
gr.Markdown("Upload a plant image (TIFF preferred) to compute and visualize composite, mask, overlay, texture, vegetation indices, and statistics.")
|
| 116 |
|
| 117 |
with gr.Row():
|
| 118 |
with gr.Column():
|
|
|
|
| 122 |
file_types=[".tif", ".tiff", ".png", ".jpg"],
|
| 123 |
label="Upload Image"
|
| 124 |
)
|
| 125 |
+
preset = gr.Radio(
|
| 126 |
+
choices=list(PRESET_IMAGES.keys()),
|
| 127 |
+
label="Or choose a preset image",
|
| 128 |
+
value=None
|
| 129 |
+
)
|
| 130 |
run = gr.Button("Run Pipeline", variant="primary")
|
| 131 |
|
| 132 |
+
# Row 1: input image
|
| 133 |
with gr.Row():
|
| 134 |
input_img = gr.Image(type="pil", label="Input Image", interactive=False, height=380)
|
| 135 |
+
|
| 136 |
# Row 2: composite, mask, overlay
|
| 137 |
with gr.Row():
|
| 138 |
composite_img = gr.Image(type="pil", label="Composite (Segmentation Input)", interactive=False)
|
|
|
|
| 146 |
lac1_img = gr.Image(type="pil", label="Texture Lac1 (Green Band)", interactive=False)
|
| 147 |
|
| 148 |
# Row 4: vegetation indices
|
|
|
|
| 149 |
gallery = gr.Gallery(label="Vegetation Indices", columns=3, height="auto")
|
| 150 |
|
| 151 |
+
# Row 5: morphology size (YOLO removed)
|
| 152 |
with gr.Row():
|
| 153 |
size_img = gr.Image(type="pil", label="Morphology Size", interactive=False)
|
|
|
|
| 154 |
|
| 155 |
# Final: statistics table
|
| 156 |
stats = gr.Textbox(label="Statistics", lines=4)
|
| 157 |
|
| 158 |
run.click(
|
| 159 |
process,
|
| 160 |
+
inputs=[inp, preset],
|
| 161 |
outputs=[
|
| 162 |
input_img,
|
| 163 |
composite_img,
|
|
|
|
| 168 |
lac1_img,
|
| 169 |
gallery,
|
| 170 |
size_img,
|
|
|
|
| 171 |
stats,
|
| 172 |
]
|
| 173 |
)
|
| 174 |
|
|
|
|
|
|
|
| 175 |
if __name__ == "__main__":
|
| 176 |
demo.launch()
|
sorghum_pipeline/data/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (369 Bytes)
|
|
|
sorghum_pipeline/data/__pycache__/loader.cpython-312.pyc
DELETED
|
Binary file (21.9 kB)
|
|
|
sorghum_pipeline/data/__pycache__/mask_handler.cpython-312.pyc
DELETED
|
Binary file (2.55 kB)
|
|
|
sorghum_pipeline/data/__pycache__/preprocessor.cpython-312.pyc
DELETED
|
Binary file (4 kB)
|
|
|
sorghum_pipeline/data/preprocessor.py
CHANGED
|
@@ -23,6 +23,12 @@ class ImagePreprocessor:
|
|
| 23 |
|
| 24 |
def process_raw_image(self, pil_img: Image.Image) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
|
| 25 |
"""Split the 4-band RAW into tiles and build composite + spectral stack."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
d = pil_img.size[0] // 2
|
| 27 |
boxes = [(j, i, j + d, i + d)
|
| 28 |
for i, j in product(range(0, pil_img.height, d),
|
|
|
|
| 23 |
|
| 24 |
def process_raw_image(self, pil_img: Image.Image) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
|
| 25 |
"""Split the 4-band RAW into tiles and build composite + spectral stack."""
|
| 26 |
+
# Downsample for 2x speed
|
| 27 |
+
w, h = pil_img.size
|
| 28 |
+
if w > 768 or h > 768:
|
| 29 |
+
scale = 768 / max(w, h)
|
| 30 |
+
pil_img = pil_img.resize((int(w * scale), int(h * scale)), Image.BILINEAR)
|
| 31 |
+
|
| 32 |
d = pil_img.size[0] // 2
|
| 33 |
boxes = [(j, i, j + d, i + d)
|
| 34 |
for i, j in product(range(0, pil_img.height, d),
|
sorghum_pipeline/features/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (435 Bytes)
|
|
|
sorghum_pipeline/features/__pycache__/morphology.cpython-312.pyc
DELETED
|
Binary file (16.6 kB)
|
|
|
sorghum_pipeline/features/__pycache__/spectral.cpython-312.pyc
DELETED
|
Binary file (18 kB)
|
|
|
sorghum_pipeline/features/__pycache__/texture.cpython-312.pyc
DELETED
|
Binary file (6.92 kB)
|
|
|
sorghum_pipeline/features/__pycache__/vegetation.cpython-312.pyc
DELETED
|
Binary file (4.29 kB)
|
|
|
sorghum_pipeline/features/morphology.py
CHANGED
|
@@ -39,11 +39,12 @@ class MorphologyExtractor:
|
|
| 39 |
pcv.params.dpi = 100
|
| 40 |
|
| 41 |
def extract_morphology_features(self, image: np.ndarray, mask: np.ndarray) -> Dict[str, Any]:
|
| 42 |
-
"""
|
| 43 |
features: Dict[str, Any] = {'traits': {}, 'images': {}, 'success': False}
|
| 44 |
|
| 45 |
try:
|
| 46 |
-
|
|
|
|
| 47 |
if clean_mask is None:
|
| 48 |
return features
|
| 49 |
|
|
@@ -51,25 +52,10 @@ class MorphologyExtractor:
|
|
| 51 |
if rgb is None:
|
| 52 |
return features
|
| 53 |
|
| 54 |
-
#
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
if PLANT_CV_AVAILABLE:
|
| 59 |
-
with contextlib.redirect_stdout(self._FilteredStream(sys.stdout)), \
|
| 60 |
-
contextlib.redirect_stderr(self._FilteredStream(sys.stderr)):
|
| 61 |
-
try:
|
| 62 |
-
labeled_mask, n_labels = pcv.create_labels(size_mask)
|
| 63 |
-
size_analysis = pcv.analyze.size(rgb, labeled_mask, n_labels, label="default")
|
| 64 |
-
features['images']['size_analysis'] = size_analysis
|
| 65 |
-
features['success'] = True
|
| 66 |
-
except Exception as e:
|
| 67 |
-
logger.warning(f"Size analysis failed: {e}")
|
| 68 |
-
else:
|
| 69 |
-
# Fallback: make a simple contour visualization
|
| 70 |
-
vis = self._simple_size_visual(rgb, size_mask)
|
| 71 |
-
features['images']['size_analysis'] = vis
|
| 72 |
-
features['success'] = True
|
| 73 |
|
| 74 |
# Compute simple plant height from mask (px → cm)
|
| 75 |
rows = np.where(clean_mask > 0)[0]
|
|
@@ -79,11 +65,8 @@ class MorphologyExtractor:
|
|
| 79 |
else:
|
| 80 |
features['traits']['plant_height_cm'] = 0.0
|
| 81 |
|
| 82 |
-
# YOLO
|
| 83 |
-
|
| 84 |
-
if yolo_img is not None:
|
| 85 |
-
features['images']['yolo_tips'] = yolo_img
|
| 86 |
-
features['traits']['num_yolo_tips'] = int(len(tips) if tips is not None else 0)
|
| 87 |
|
| 88 |
except Exception as e:
|
| 89 |
logger.error(f"Morphology extraction failed: {e}")
|
|
|
|
| 39 |
pcv.params.dpi = 100
|
| 40 |
|
| 41 |
def extract_morphology_features(self, image: np.ndarray, mask: np.ndarray) -> Dict[str, Any]:
|
| 42 |
+
"""Fast size visualization (YOLO disabled for speed). Simplified for performance."""
|
| 43 |
features: Dict[str, Any] = {'traits': {}, 'images': {}, 'success': False}
|
| 44 |
|
| 45 |
try:
|
| 46 |
+
# Use raw mask (no preprocessing for speed)
|
| 47 |
+
clean_mask = mask
|
| 48 |
if clean_mask is None:
|
| 49 |
return features
|
| 50 |
|
|
|
|
| 52 |
if rgb is None:
|
| 53 |
return features
|
| 54 |
|
| 55 |
+
# Simple size visualization without PlantCV for speed
|
| 56 |
+
vis = self._simple_size_visual(rgb, ((clean_mask > 0).astype(np.uint8) * 255))
|
| 57 |
+
features['images']['size_analysis'] = vis
|
| 58 |
+
features['success'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
# Compute simple plant height from mask (px → cm)
|
| 61 |
rows = np.where(clean_mask > 0)[0]
|
|
|
|
| 65 |
else:
|
| 66 |
features['traits']['plant_height_cm'] = 0.0
|
| 67 |
|
| 68 |
+
# YOLO disabled for speed
|
| 69 |
+
features['traits']['num_yolo_tips'] = 0
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
except Exception as e:
|
| 72 |
logger.error(f"Morphology extraction failed: {e}")
|
sorghum_pipeline/models/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (438 Bytes)
|
|
|
sorghum_pipeline/models/__pycache__/dbc_lacunarity.cpython-312.pyc
DELETED
|
Binary file (4.14 kB)
|
|
|
sorghum_pipeline/output/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (303 Bytes)
|
|
|
sorghum_pipeline/output/__pycache__/manager.cpython-312.pyc
DELETED
|
Binary file (26.5 kB)
|
|
|
sorghum_pipeline/output/manager.py
CHANGED
|
@@ -222,26 +222,7 @@ class OutputManager:
|
|
| 222 |
titled = self._add_title_banner(synthesized, 'Morphology Size')
|
| 223 |
cv2.imwrite(str(results_dir / 'size.size_analysis.png'), titled)
|
| 224 |
|
| 225 |
-
#
|
| 226 |
-
yolo_img = images.get('yolo_tips')
|
| 227 |
-
if isinstance(yolo_img, np.ndarray) and yolo_img.size > 0:
|
| 228 |
-
titled = self._add_title_banner(yolo_img, 'YOLO Tips')
|
| 229 |
-
cv2.imwrite(str(results_dir / 'yolo_tips.png'), titled)
|
| 230 |
-
else:
|
| 231 |
-
# Fallback YOLO visualization: draw nothing but preserve panel, or overlay mask centroids
|
| 232 |
-
try:
|
| 233 |
-
mask_for_tips = plant_data.get('mask')
|
| 234 |
-
base_img_for_tips = plant_data.get('composite')
|
| 235 |
-
if isinstance(base_img_for_tips, np.ndarray) and base_img_for_tips.size > 0:
|
| 236 |
-
base = base_img_for_tips
|
| 237 |
-
else:
|
| 238 |
-
# If no composite, synthesize white background overlay from mask
|
| 239 |
-
base = None
|
| 240 |
-
fallback = self._create_fallback_yolo_panel(mask_for_tips, base)
|
| 241 |
-
titled = self._add_title_banner(fallback, 'YOLO Tips (No Detections)')
|
| 242 |
-
cv2.imwrite(str(results_dir / 'yolo_tips.png'), titled)
|
| 243 |
-
except Exception as e:
|
| 244 |
-
logger.warning(f"Failed to create fallback YOLO tips image: {e}")
|
| 245 |
except Exception as e:
|
| 246 |
logger.error(f"Failed to save size analysis: {e}")
|
| 247 |
|
|
|
|
| 222 |
titled = self._add_title_banner(synthesized, 'Morphology Size')
|
| 223 |
cv2.imwrite(str(results_dir / 'size.size_analysis.png'), titled)
|
| 224 |
|
| 225 |
+
# YOLO disabled for speed - skip saving yolo_tips.png
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
except Exception as e:
|
| 227 |
logger.error(f"Failed to save size analysis: {e}")
|
| 228 |
|
sorghum_pipeline/pipeline.py
CHANGED
|
@@ -123,7 +123,7 @@ class SorghumPipeline:
|
|
| 123 |
composite = pdata['composite']
|
| 124 |
mask = pdata.get('mask')
|
| 125 |
|
| 126 |
-
# --- Texture: LBP/HOG/Lacunarity on green band ---
|
| 127 |
pdata['texture_features'] = {}
|
| 128 |
spectral = pdata.get('spectral_stack', {})
|
| 129 |
if 'green' in spectral:
|
|
@@ -131,8 +131,20 @@ class SorghumPipeline:
|
|
| 131 |
if green_band.ndim == 3 and green_band.shape[-1] == 1:
|
| 132 |
green_band = green_band[..., 0]
|
| 133 |
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
else:
|
| 137 |
valid = green_band
|
| 138 |
|
|
|
|
| 123 |
composite = pdata['composite']
|
| 124 |
mask = pdata.get('mask')
|
| 125 |
|
| 126 |
+
# --- Texture: LBP/HOG/Lacunarity on green band (downsample for speed) ---
|
| 127 |
pdata['texture_features'] = {}
|
| 128 |
spectral = pdata.get('spectral_stack', {})
|
| 129 |
if 'green' in spectral:
|
|
|
|
| 131 |
if green_band.ndim == 3 and green_band.shape[-1] == 1:
|
| 132 |
green_band = green_band[..., 0]
|
| 133 |
|
| 134 |
+
# Downsample to 256x256 max for faster texture computation
|
| 135 |
+
h, w = green_band.shape[:2]
|
| 136 |
+
if h > 256 or w > 256:
|
| 137 |
+
scale = 256 / max(h, w)
|
| 138 |
+
green_band = cv2.resize(green_band, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
|
| 139 |
+
if mask is not None:
|
| 140 |
+
mask_resized = cv2.resize(mask, (green_band.shape[1], green_band.shape[0]), interpolation=cv2.INTER_NEAREST)
|
| 141 |
+
else:
|
| 142 |
+
mask_resized = None
|
| 143 |
+
else:
|
| 144 |
+
mask_resized = mask
|
| 145 |
+
|
| 146 |
+
if mask_resized is not None:
|
| 147 |
+
valid = np.where(mask_resized > 0, green_band, np.nan)
|
| 148 |
else:
|
| 149 |
valid = green_band
|
| 150 |
|
sorghum_pipeline/segmentation/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (310 Bytes)
|
|
|
sorghum_pipeline/segmentation/__pycache__/advanced_occlusion_handler.cpython-312.pyc
DELETED
|
Binary file (26.3 kB)
|
|
|
sorghum_pipeline/segmentation/__pycache__/leaf_occlusion_handler.cpython-312.pyc
DELETED
|
Binary file (27 kB)
|
|
|
sorghum_pipeline/segmentation/__pycache__/manager.cpython-312.pyc
DELETED
|
Binary file (5.14 kB)
|
|
|
sorghum_pipeline/segmentation/__pycache__/occlusion_handler.cpython-312.pyc
DELETED
|
Binary file (20.2 kB)
|
|
|
sorghum_pipeline/segmentation/manager.py
CHANGED
|
@@ -38,8 +38,9 @@ class SegmentationManager:
|
|
| 38 |
token=hf_token,
|
| 39 |
).eval().to(self.device)
|
| 40 |
|
|
|
|
| 41 |
self.transform = transforms.Compose([
|
| 42 |
-
transforms.Resize((
|
| 43 |
transforms.ToTensor(),
|
| 44 |
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
| 45 |
])
|
|
|
|
| 38 |
token=hf_token,
|
| 39 |
).eval().to(self.device)
|
| 40 |
|
| 41 |
+
# Use 512x512 for 4x speed improvement
|
| 42 |
self.transform = transforms.Compose([
|
| 43 |
+
transforms.Resize((512, 512)),
|
| 44 |
transforms.ToTensor(),
|
| 45 |
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
| 46 |
])
|