Fahimeh Orvati Nia commited on
Commit
c170961
·
1 Parent(s): 7d932a4
__pycache__/wrapper.cpython-312.pyc ADDED
Binary file (3.49 kB). View file
 
app.py CHANGED
@@ -6,108 +6,61 @@ import numpy as np
6
  from PIL import Image
7
  from itertools import product
8
 
9
- def show_preview(image):
10
- """Render uploaded image faithfully, including 16-bit/single-channel inputs.
11
-
12
- - RGB/RGBA: show as-is (strip alpha)
13
- - 16-bit or single-channel: min-max (or 1-99%ile) normalize to 8-bit for display
14
- """
15
- if image is None:
16
- return None
17
- try:
18
- arr = np.array(image)
19
- # RGBA → RGB
20
- if arr.ndim == 3 and arr.shape[2] == 4:
21
- image = image.convert("RGB")
22
- arr = np.array(image)
23
- # RGB
24
- if arr.ndim == 3 and arr.shape[2] == 3:
25
- # If high bit-depth or non-uint8, normalize per-channel for visualization
26
- if arr.dtype != np.uint8 or np.max(arr) > 255:
27
- a = np.nan_to_num(arr.astype(np.float64), nan=0.0, posinf=0.0, neginf=0.0)
28
- vis = np.empty_like(a, dtype=np.float64)
29
- for c in range(3):
30
- vmin = np.percentile(a[..., c], 1.0)
31
- vmax = np.percentile(a[..., c], 99.0)
32
- if not np.isfinite(vmin) or not np.isfinite(vmax) or vmax <= vmin:
33
- vmin, vmax = float(np.min(a[..., c])), float(np.max(a[..., c]))
34
- denom = max(vmax - vmin, 1e-6)
35
- vis[..., c] = np.clip((a[..., c] - vmin) / denom, 0.0, 1.0) * 255.0
36
- return Image.fromarray(vis.astype(np.uint8), mode='RGB')
37
- return image
38
- # Single-channel or higher bit-depth
39
- if arr.ndim == 2 or (arr.ndim == 3 and arr.shape[2] == 1):
40
- if arr.ndim == 3:
41
- arr = arr[..., 0]
42
- a = np.nan_to_num(arr.astype(np.float64), nan=0.0, posinf=0.0, neginf=0.0)
43
- # Robust contrast stretch
44
- vmin = np.percentile(a, 1.0)
45
- vmax = np.percentile(a, 99.0)
46
- if not np.isfinite(vmin) or not np.isfinite(vmax) or vmax <= vmin:
47
- vmin, vmax = float(np.min(a)), float(np.max(a))
48
- denom = max(vmax - vmin, 1e-6)
49
- vis = np.clip((a - vmin) / denom, 0.0, 1.0) * 255.0
50
- vis8 = vis.astype(np.uint8)
51
- return Image.fromarray(vis8, mode='L')
52
- # Fallback
53
- return image.convert("RGB")
54
- except Exception:
55
- return image
56
-
57
  def process(image):
58
  if image is None:
59
- return None, None, [], ""
60
  with tempfile.TemporaryDirectory() as tmpdir:
61
- # Save PIL image preserving original format
62
  ext = image.format.lower() if image.format else 'png'
63
  img_path = Path(tmpdir) / f"input.{ext}"
64
  image.save(img_path)
65
  outputs = run_pipeline_on_image(str(img_path), tmpdir, save_artifacts=True)
66
 
67
- # Assemble displays
68
  def load_pil(path_str):
69
  try:
70
  if not path_str:
71
  return None
72
  im = Image.open(path_str)
73
  im = im.convert('RGB')
74
- # Copy to memory so it survives after tmpdir is removed
75
  copied = im.copy()
76
  im.close()
77
  return copied
78
  except Exception:
79
  return None
80
 
 
81
  overlay = load_pil(outputs.get('Overlay'))
82
  mask = load_pil(outputs.get('Mask'))
83
- composite = load_pil(outputs.get('Composite'))
84
- order = ['NDVI', 'ARI', 'GNDVI']
 
 
 
85
  gallery_items = [load_pil(outputs[k]) for k in order if k in outputs]
86
  stats_text = outputs.get('StatsText', '')
87
- return composite, overlay, mask, gallery_items, stats_text
88
 
89
  with gr.Blocks() as demo:
90
  gr.Markdown("# 🌿 Sorghum Plant Analysis Demo")
91
- gr.Markdown("Upload a sorghum plant image to analyze vegetation indices, segmentation overlay, and stats.")
92
 
93
  with gr.Row():
94
  with gr.Column():
95
  inp = gr.Image(type="pil", label="Upload Image")
96
  run = gr.Button("Run Pipeline", variant="primary")
97
- with gr.Column():
98
- preview = gr.Image(type="pil", label="Uploaded Image Preview", interactive=False)
99
 
100
  with gr.Row():
 
101
  composite_img = gr.Image(type="pil", label="Composite (Segmentation Input)", interactive=False)
102
- overlay_img = gr.Image(type="pil", label="Segmentation Overlay", interactive=False)
103
  mask_img = gr.Image(type="pil", label="Mask", interactive=False)
 
 
 
 
104
 
105
  gallery = gr.Gallery(label="Vegetation Indices", columns=3, height="auto")
106
  stats = gr.Textbox(label="Statistics", lines=4)
107
 
108
- # Update preview when image is uploaded
109
- inp.change(fn=show_preview, inputs=inp, outputs=preview)
110
- run.click(process, inputs=inp, outputs=[composite_img, overlay_img, mask_img, gallery, stats])
111
 
112
  if __name__ == "__main__":
113
  demo.launch()
 
6
  from PIL import Image
7
  from itertools import product
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def process(image):
10
  if image is None:
11
+ return None, None, None, None, [], ""
12
  with tempfile.TemporaryDirectory() as tmpdir:
 
13
  ext = image.format.lower() if image.format else 'png'
14
  img_path = Path(tmpdir) / f"input.{ext}"
15
  image.save(img_path)
16
  outputs = run_pipeline_on_image(str(img_path), tmpdir, save_artifacts=True)
17
 
 
18
  def load_pil(path_str):
19
  try:
20
  if not path_str:
21
  return None
22
  im = Image.open(path_str)
23
  im = im.convert('RGB')
 
24
  copied = im.copy()
25
  im.close()
26
  return copied
27
  except Exception:
28
  return None
29
 
30
+ composite = load_pil(outputs.get('Composite'))
31
  overlay = load_pil(outputs.get('Overlay'))
32
  mask = load_pil(outputs.get('Mask'))
33
+ size_img = load_pil(str(Path(tmpdir) / 'results/size.size_analysis.png'))
34
+ # Texture LBP green path
35
+ lbp_path = Path(tmpdir) / 'texture_output/lbp_green.png'
36
+ texture_img = load_pil(str(lbp_path)) if lbp_path.exists() else None
37
+ order = ['NDVI', 'GNDVI', 'SAVI']
38
  gallery_items = [load_pil(outputs[k]) for k in order if k in outputs]
39
  stats_text = outputs.get('StatsText', '')
40
+ return size_img, composite, mask, overlay, texture_img, gallery_items, stats_text
41
 
42
  with gr.Blocks() as demo:
43
  gr.Markdown("# 🌿 Sorghum Plant Analysis Demo")
44
+ gr.Markdown("Upload a sorghum plant image to compute and visualize composite, mask, overlay, texture (LBP), vegetation indices, and statistics.")
45
 
46
  with gr.Row():
47
  with gr.Column():
48
  inp = gr.Image(type="pil", label="Upload Image")
49
  run = gr.Button("Run Pipeline", variant="primary")
 
 
50
 
51
  with gr.Row():
52
+ size_img = gr.Image(type="pil", label="Morphology Size", interactive=False)
53
  composite_img = gr.Image(type="pil", label="Composite (Segmentation Input)", interactive=False)
 
54
  mask_img = gr.Image(type="pil", label="Mask", interactive=False)
55
+ overlay_img = gr.Image(type="pil", label="Segmentation Overlay", interactive=False)
56
+
57
+ with gr.Row():
58
+ texture_img = gr.Image(type="pil", label="Texture LBP (Green Band)", interactive=False)
59
 
60
  gallery = gr.Gallery(label="Vegetation Indices", columns=3, height="auto")
61
  stats = gr.Textbox(label="Statistics", lines=4)
62
 
63
+ run.click(process, inputs=inp, outputs=[size_img, composite_img, mask_img, overlay_img, texture_img, gallery, stats])
 
 
64
 
65
  if __name__ == "__main__":
66
  demo.launch()
sorghum_pipeline/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/__pycache__/__init__.cpython-312.pyc and b/sorghum_pipeline/__pycache__/__init__.cpython-312.pyc differ
 
sorghum_pipeline/__pycache__/config.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/__pycache__/config.cpython-312.pyc and b/sorghum_pipeline/__pycache__/config.cpython-312.pyc differ
 
sorghum_pipeline/__pycache__/pipeline.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/__pycache__/pipeline.cpython-312.pyc and b/sorghum_pipeline/__pycache__/pipeline.cpython-312.pyc differ
 
sorghum_pipeline/data/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/data/__pycache__/__init__.cpython-312.pyc and b/sorghum_pipeline/data/__pycache__/__init__.cpython-312.pyc differ
 
sorghum_pipeline/data/__pycache__/mask_handler.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/data/__pycache__/mask_handler.cpython-312.pyc and b/sorghum_pipeline/data/__pycache__/mask_handler.cpython-312.pyc differ
 
sorghum_pipeline/data/__pycache__/preprocessor.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/data/__pycache__/preprocessor.cpython-312.pyc and b/sorghum_pipeline/data/__pycache__/preprocessor.cpython-312.pyc differ
 
sorghum_pipeline/data/preprocessor.py CHANGED
@@ -1,9 +1,14 @@
1
- """Minimal image preprocessing following the requested composite/spectral logic."""
 
 
 
 
2
 
3
  import numpy as np
4
  from PIL import Image
5
  from typing import Dict, Tuple, Any
6
  from itertools import product
 
7
 
8
 
9
  class ImagePreprocessor:
@@ -22,25 +27,9 @@ class ImagePreprocessor:
22
  return norm.astype(np.uint8)
23
 
24
  def process_raw_image(self, pil_img: Image.Image) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
25
- """
26
- Split a 4-band RAW mosaic image into tiles and build:
27
- - composite: 8-bit BGR array arranged as (green, red_edge, red)
28
- - spectral stack: dict with keys green, red, red_edge, nir (each HxWx1)
29
- """
30
- d = pil_img.size[0] // 2
31
- boxes = [(j, i, j + d, i + d)
32
- for i, j in product(range(0, pil_img.height, d),
33
- range(0, pil_img.width, d))]
34
- # Ensure each quadrant is single-channel (grayscale) so bands are 2D
35
- stack = np.stack([np.array(pil_img.crop(b).convert('L'), float) for b in boxes], axis=-1)
36
- green, red, red_edge, nir = np.split(stack, 4, axis=-1)
37
-
38
- # Build BGR composite so that displayed RGB = (red, red_edge, green)
39
- comp = np.concatenate([green, red_edge, red], axis=-1)
40
- comp_uint8 = self.convert_to_uint8(comp)
41
-
42
- spectral_bands = {"green": green, "red": red, "red_edge": red_edge, "nir": nir}
43
- return comp_uint8, spectral_bands
44
 
45
  def create_composites(self, plants: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
46
  """
 
1
+ """Minimal image preprocessing following the requested composite/spectral logic.
2
+
3
+ This now delegates composite building to `src.composite.process_raw_image`
4
+ so results match the verified src pipeline exactly.
5
+ """
6
 
7
  import numpy as np
8
  from PIL import Image
9
  from typing import Dict, Tuple, Any
10
  from itertools import product
11
+ from src.composite import process_raw_image as src_process_raw_image
12
 
13
 
14
  class ImagePreprocessor:
 
27
  return norm.astype(np.uint8)
28
 
29
  def process_raw_image(self, pil_img: Image.Image) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
30
+ """Use src.composite.process_raw_image for parity with src flow."""
31
+ comp_uint8_bgr, spectral_bands = src_process_raw_image(pil_img)
32
+ return comp_uint8_bgr, spectral_bands
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  def create_composites(self, plants: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
35
  """
sorghum_pipeline/features/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/features/__pycache__/__init__.cpython-312.pyc and b/sorghum_pipeline/features/__pycache__/__init__.cpython-312.pyc differ
 
sorghum_pipeline/features/__pycache__/morphology.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/features/__pycache__/morphology.cpython-312.pyc and b/sorghum_pipeline/features/__pycache__/morphology.cpython-312.pyc differ
 
sorghum_pipeline/features/__pycache__/texture.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/features/__pycache__/texture.cpython-312.pyc and b/sorghum_pipeline/features/__pycache__/texture.cpython-312.pyc differ
 
sorghum_pipeline/features/__pycache__/vegetation.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/features/__pycache__/vegetation.cpython-312.pyc and b/sorghum_pipeline/features/__pycache__/vegetation.cpython-312.pyc differ
 
sorghum_pipeline/features/vegetation.py CHANGED
@@ -12,21 +12,21 @@ logger = logging.getLogger(__name__)
12
  class VegetationIndexExtractor:
13
  """Minimal vegetation index extraction."""
14
 
15
- def __init__(self, epsilon: float = 1e-10, soil_factor: float = 0.16):
16
  """Initialize with defaults."""
17
  self.epsilon = epsilon
18
  self.soil_factor = soil_factor
19
 
20
  self.index_formulas = {
21
  "NDVI": lambda nir, red: (nir - red) / (nir + red + self.epsilon),
22
- "ARI": lambda green, red_edge: (1.0 / (green + self.epsilon)) - (1.0 / (red_edge + self.epsilon)),
23
  "GNDVI": lambda nir, green: (nir - green) / (nir + green + self.epsilon),
 
24
  }
25
 
26
  self.index_bands = {
27
  "NDVI": ["nir", "red"],
28
- "ARI": ["green", "red_edge"],
29
  "GNDVI": ["nir", "green"],
 
30
  }
31
 
32
  def compute_vegetation_indices(self, spectral_stack: Dict[str, np.ndarray],
 
12
  class VegetationIndexExtractor:
13
  """Minimal vegetation index extraction."""
14
 
15
+ def __init__(self, epsilon: float = 1e-10, soil_factor: float = 0.5):
16
  """Initialize with defaults."""
17
  self.epsilon = epsilon
18
  self.soil_factor = soil_factor
19
 
20
  self.index_formulas = {
21
  "NDVI": lambda nir, red: (nir - red) / (nir + red + self.epsilon),
 
22
  "GNDVI": lambda nir, green: (nir - green) / (nir + green + self.epsilon),
23
+ "SAVI": lambda nir, red: ((nir - red) / (nir + red + self.soil_factor)) * (1.0 + self.soil_factor),
24
  }
25
 
26
  self.index_bands = {
27
  "NDVI": ["nir", "red"],
 
28
  "GNDVI": ["nir", "green"],
29
+ "SAVI": ["nir", "red"],
30
  }
31
 
32
  def compute_vegetation_indices(self, spectral_stack: Dict[str, np.ndarray],
sorghum_pipeline/output/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/output/__pycache__/__init__.cpython-312.pyc and b/sorghum_pipeline/output/__pycache__/__init__.cpython-312.pyc differ
 
sorghum_pipeline/output/__pycache__/manager.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/output/__pycache__/manager.cpython-312.pyc and b/sorghum_pipeline/output/__pycache__/manager.cpython-312.pyc differ
 
sorghum_pipeline/output/manager.py CHANGED
@@ -65,7 +65,9 @@ class OutputManager:
65
  mask = plant_data.get('mask')
66
  if isinstance(base_image, np.ndarray) and isinstance(mask, np.ndarray):
67
  overlay = self._create_overlay(base_image, mask)
68
- cv2.imwrite(str(results_dir / 'overlay.png'), overlay)
 
 
69
  except Exception as e:
70
  logger.error(f"Failed to save overlay: {e}")
71
 
@@ -76,51 +78,65 @@ class OutputManager:
76
  # Ensure uint8
77
  if base_image.dtype != np.uint8:
78
  base_image = self._normalize_to_uint8(base_image.astype(np.float64))
79
- cv2.imwrite(str(results_dir / 'composite.png'), base_image)
 
 
80
  except Exception as e:
81
  logger.error(f"Failed to save composite: {e}")
82
 
83
- # 3-5. Vegetation indices (NDVI, ARI, GNDVI)
84
  try:
85
  veg = plant_data.get('vegetation_indices', {})
86
- for name in ['NDVI', 'ARI', 'GNDVI']:
87
  data = veg.get(name, {})
88
  values = data.get('values') if isinstance(data, dict) else None
89
  if isinstance(values, np.ndarray) and values.size > 0:
90
  try:
91
- cmap = cm.RdYlGn if name in ['NDVI', 'GNDVI'] else cm.magma
92
- vmin, vmax = (-1, 1) if name in ['NDVI', 'GNDVI'] else (0, 1)
93
-
 
 
 
 
 
94
  masked = np.ma.masked_invalid(values.astype(np.float64))
95
  fig, ax = plt.subplots(figsize=(5, 5))
96
  ax.set_axis_off()
97
  ax.set_facecolor('white')
98
- ax.imshow(masked, cmap=cmap, vmin=vmin, vmax=vmax)
 
 
 
99
  plt.tight_layout()
100
- plt.savefig(veg_dir / f"{name.lower()}.png", dpi=100, bbox_inches='tight')
101
  plt.close(fig)
102
  except Exception as e:
103
  logger.error(f"Failed to save {name}: {e}")
104
  except Exception as e:
105
  logger.error(f"Failed to save vegetation indices: {e}")
106
 
107
- # 6-8. Texture features (LBP, HOG, Lacunarity)
108
  try:
109
  tex = plant_data.get('texture_features', {})
110
- color_band = tex.get('color', {})
111
- feats = color_band.get('features', {})
112
-
113
- if isinstance(feats.get('lbp'), np.ndarray) and feats['lbp'].size > 0:
114
- cv2.imwrite(str(tex_dir / 'lbp.png'), feats['lbp'].astype(np.uint8))
115
-
116
- if isinstance(feats.get('hog'), np.ndarray) and feats['hog'].size > 0:
117
- cv2.imwrite(str(tex_dir / 'hog.png'), feats['hog'].astype(np.uint8))
118
-
119
- lac = feats.get('lac2')
120
- if isinstance(lac, np.ndarray) and lac.size > 0:
121
- if lac.dtype != np.uint8:
122
- lac = self._normalize_to_uint8(lac.astype(np.float64))
123
- cv2.imwrite(str(tex_dir / 'lacunarity.png'), lac)
 
 
 
 
124
  except Exception as e:
125
  logger.error(f"Failed to save texture: {e}")
126
 
@@ -135,14 +151,21 @@ class OutputManager:
135
  logger.error(f"Failed to save size analysis: {e}")
136
 
137
  def _create_overlay(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray:
138
- """Create overlay (masked pixels only)."""
139
  if mask is None:
140
  return image
141
  if mask.shape[:2] != image.shape[:2]:
142
- mask = cv2.resize(mask.astype(np.uint8), (image.shape[1], image.shape[0]),
143
- interpolation=cv2.INTER_NEAREST)
144
  binary = (mask.astype(np.int32) > 0).astype(np.uint8) * 255
145
- return cv2.bitwise_and(image, image, mask=binary)
 
 
 
 
 
 
 
146
 
147
  def _normalize_to_uint8(self, arr: np.ndarray) -> np.ndarray:
148
  """Normalize to uint8."""
 
65
  mask = plant_data.get('mask')
66
  if isinstance(base_image, np.ndarray) and isinstance(mask, np.ndarray):
67
  overlay = self._create_overlay(base_image, mask)
68
+ # Convert BGR→RGB for correct viewing in standard image viewers
69
+ overlay_rgb = cv2.cvtColor(overlay, cv2.COLOR_BGR2RGB)
70
+ cv2.imwrite(str(results_dir / 'overlay.png'), overlay_rgb)
71
  except Exception as e:
72
  logger.error(f"Failed to save overlay: {e}")
73
 
 
78
  # Ensure uint8
79
  if base_image.dtype != np.uint8:
80
  base_image = self._normalize_to_uint8(base_image.astype(np.float64))
81
+ # Convert BGR→RGB for human viewing
82
+ comp_rgb = cv2.cvtColor(base_image, cv2.COLOR_BGR2RGB)
83
+ cv2.imwrite(str(results_dir / 'composite.png'), comp_rgb)
84
  except Exception as e:
85
  logger.error(f"Failed to save composite: {e}")
86
 
87
+ # 3-5. Vegetation indices (NDVI, GNDVI, SAVI)
88
  try:
89
  veg = plant_data.get('vegetation_indices', {})
90
+ for name in ['NDVI', 'GNDVI', 'SAVI']:
91
  data = veg.get(name, {})
92
  values = data.get('values') if isinstance(data, dict) else None
93
  if isinstance(values, np.ndarray) and values.size > 0:
94
  try:
95
+ # Colormap with colorbar similar to src: use matplotlib savefig
96
+ cmap = cm.RdYlGn
97
+ # Value ranges
98
+ if name in ['NDVI', 'GNDVI']:
99
+ vmin, vmax = (-1, 1)
100
+ else:
101
+ vmin, vmax = (0, 1)
102
+
103
  masked = np.ma.masked_invalid(values.astype(np.float64))
104
  fig, ax = plt.subplots(figsize=(5, 5))
105
  ax.set_axis_off()
106
  ax.set_facecolor('white')
107
+ im = ax.imshow(masked, cmap=cmap, vmin=vmin, vmax=vmax)
108
+ # add colorbar
109
+ cbar = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
110
+ cbar.ax.tick_params(labelsize=8)
111
  plt.tight_layout()
112
+ plt.savefig(veg_dir / f"{name.lower()}.png", dpi=120, bbox_inches='tight')
113
  plt.close(fig)
114
  except Exception as e:
115
  logger.error(f"Failed to save {name}: {e}")
116
  except Exception as e:
117
  logger.error(f"Failed to save vegetation indices: {e}")
118
 
119
+ # 6. Texture features: ONLY LBP on green band
120
  try:
121
  tex = plant_data.get('texture_features', {})
122
+ green_band = tex.get('green', {})
123
+ feats = green_band.get('features', {})
124
+
125
+ lbp = feats.get('lbp')
126
+ if isinstance(lbp, np.ndarray) and lbp.size > 0:
127
+ try:
128
+ img = lbp.astype(np.float64)
129
+ fig, ax = plt.subplots(figsize=(5, 5))
130
+ ax.set_axis_off()
131
+ ax.set_facecolor('white')
132
+ im = ax.imshow(img, cmap='gray', vmin=0, vmax=255)
133
+ cbar = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
134
+ cbar.ax.tick_params(labelsize=8)
135
+ plt.tight_layout()
136
+ plt.savefig(tex_dir / 'lbp_green.png', dpi=120, bbox_inches='tight')
137
+ plt.close(fig)
138
+ except Exception as e:
139
+ logger.error(f"Failed to save LBP with colorbar: {e}")
140
  except Exception as e:
141
  logger.error(f"Failed to save texture: {e}")
142
 
 
151
  logger.error(f"Failed to save size analysis: {e}")
152
 
153
  def _create_overlay(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray:
154
+ """Create green overlay on brightened composite, following src pipeline style."""
155
  if mask is None:
156
  return image
157
  if mask.shape[:2] != image.shape[:2]:
158
+ mask = cv2.resize(mask.astype(np.uint8), (image.shape[1], image.shape[0]),
159
+ interpolation=cv2.INTER_NEAREST)
160
  binary = (mask.astype(np.int32) > 0).astype(np.uint8) * 255
161
+ base = image
162
+ if base.dtype != np.uint8:
163
+ base = self._normalize_to_uint8(base.astype(np.float64))
164
+ bright = cv2.convertScaleAbs(base, alpha=1.2, beta=15)
165
+ green_overlay = bright.copy()
166
+ green_overlay[binary == 255] = (0, 255, 0)
167
+ blended = cv2.addWeighted(bright, 1.0, green_overlay, 0.5, 0)
168
+ return blended
169
 
170
  def _normalize_to_uint8(self, arr: np.ndarray) -> np.ndarray:
171
  """Normalize to uint8."""
sorghum_pipeline/pipeline.py CHANGED
@@ -13,6 +13,7 @@ from .data import ImagePreprocessor, MaskHandler
13
  from .features import TextureExtractor, VegetationIndexExtractor, MorphologyExtractor
14
  from .output import OutputManager
15
  from .segmentation import SegmentationManager
 
16
 
17
  logger = logging.getLogger(__name__)
18
 
@@ -92,32 +93,47 @@ class SorghumPipeline:
92
  for key, pdata in plants.items():
93
  composite = pdata['composite']
94
  mask = pdata.get('mask')
95
-
96
- # # Texture: LBP, HOG, Lacunarity from pseudo-color (COMMENTED OUT)
97
- # masked = self.mask_handler.apply_mask_to_image(composite, mask) if mask is not None else composite
98
- # gray = cv2.cvtColor(masked, cv2.COLOR_BGR2GRAY)
99
- # feats = self.texture_extractor.extract_all_texture_features(gray)
100
- # stats = self.texture_extractor.compute_texture_statistics(feats, mask)
101
- # pdata['texture_features'] = {'color': {'features': feats, 'statistics': stats}}
102
  pdata['texture_features'] = {}
103
-
104
- # Vegetation: NDVI, ARI, GNDVI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  spectral = pdata.get('spectral_stack', {})
106
  if spectral and mask is not None:
107
  pdata['vegetation_indices'] = self._compute_vegetation(spectral, mask)
108
  else:
109
  pdata['vegetation_indices'] = {}
110
 
111
- # # Morphology: PlantCV size analysis (COMMENTED OUT)
112
- # pdata['morphology_features'] = self.morphology_extractor.extract_morphology_features(composite, mask)
113
- pdata['morphology_features'] = {}
 
 
 
 
114
 
115
  return plants
116
 
117
  def _compute_vegetation(self, spectral: Dict[str, np.ndarray], mask: np.ndarray) -> Dict[str, Any]:
118
  """Compute NDVI, ARI, GNDVI only."""
119
  out = {}
120
- for name in ("NDVI", "ARI", "GNDVI"):
121
  bands = self.vegetation_extractor.index_bands.get(name, [])
122
  if not all(b in spectral for b in bands):
123
  continue
 
13
  from .features import TextureExtractor, VegetationIndexExtractor, MorphologyExtractor
14
  from .output import OutputManager
15
  from .segmentation import SegmentationManager
16
+ from .features.morphology import MorphologyExtractor
17
 
18
  logger = logging.getLogger(__name__)
19
 
 
93
  for key, pdata in plants.items():
94
  composite = pdata['composite']
95
  mask = pdata.get('mask')
96
+
97
+ # Texture: ONLY LBP on green band within mask
 
 
 
 
 
98
  pdata['texture_features'] = {}
99
+ green_band = None
100
+ spectral = pdata.get('spectral_stack', {})
101
+ if 'green' in spectral:
102
+ green_band = spectral['green'].squeeze(-1).astype(np.float64)
103
+ if mask is not None:
104
+ valid = np.where(mask > 0, green_band, np.nan)
105
+ else:
106
+ valid = green_band
107
+ # normalize to uint8 for LBP
108
+ v = valid.copy()
109
+ v = np.nan_to_num(v, nan=np.nanmin(v))
110
+ m, M = np.min(v), np.max(v)
111
+ denom = (M - m) if (M - m) > 1e-6 else 1.0
112
+ gray8 = ((v - m) / denom * 255.0).astype(np.uint8)
113
+ lbp_map = self.texture_extractor.extract_lbp(gray8)
114
+ pdata['texture_features'] = {'green': {'features': {'lbp': lbp_map}}}
115
+
116
+ # Vegetation: NDVI, GNDVI, SAVI
117
  spectral = pdata.get('spectral_stack', {})
118
  if spectral and mask is not None:
119
  pdata['vegetation_indices'] = self._compute_vegetation(spectral, mask)
120
  else:
121
  pdata['vegetation_indices'] = {}
122
 
123
+ # Morphology: compute size analysis image via internal extractor
124
+ try:
125
+ pdata['morphology_features'] = self.morphology_extractor.extract_morphology_features(
126
+ cv2.cvtColor(composite, cv2.COLOR_BGR2RGB), mask
127
+ )
128
+ except Exception:
129
+ pdata['morphology_features'] = {}
130
 
131
  return plants
132
 
133
  def _compute_vegetation(self, spectral: Dict[str, np.ndarray], mask: np.ndarray) -> Dict[str, Any]:
134
  """Compute NDVI, ARI, GNDVI only."""
135
  out = {}
136
+ for name in ("NDVI", "GNDVI", "SAVI"):
137
  bands = self.vegetation_extractor.index_bands.get(name, [])
138
  if not all(b in spectral for b in bands):
139
  continue
sorghum_pipeline/segmentation/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/segmentation/__pycache__/__init__.cpython-312.pyc and b/sorghum_pipeline/segmentation/__pycache__/__init__.cpython-312.pyc differ
 
sorghum_pipeline/segmentation/__pycache__/manager.cpython-312.pyc CHANGED
Binary files a/sorghum_pipeline/segmentation/__pycache__/manager.cpython-312.pyc and b/sorghum_pipeline/segmentation/__pycache__/manager.cpython-312.pyc differ
 
wrapper.py CHANGED
@@ -40,18 +40,14 @@ def run_pipeline_on_image(input_image_path: str, work_dir: str, save_artifacts:
40
  # Collect outputs
41
  outputs: Dict[str, str] = {}
42
 
43
- # Return only NDVI for now (others commented out for debugging)
44
  wanted = [
45
  work / 'Vegetation_indices_images/ndvi.png',
46
- work / 'Vegetation_indices_images/ari.png',
47
  work / 'Vegetation_indices_images/gndvi.png',
48
- # work / 'texture_output/lbp.png',
49
- # work / 'texture_output/hog.png',
50
- # work / 'texture_output/lacunarity.png',
51
- # work / 'results/size.size_analysis.png',
52
  ]
53
  labels = [
54
- 'NDVI', 'ARI', 'GNDVI', # 'LBP', 'HOG', 'Lacunarity', 'SizeAnalysis'
55
  ]
56
  for label, path in zip(labels, wanted):
57
  if path.exists():
@@ -75,7 +71,7 @@ def run_pipeline_on_image(input_image_path: str, work_dir: str, save_artifacts:
75
  _, pdata = next(iter(plants.items()))
76
  veg = pdata.get('vegetation_indices', {})
77
  stats_lines = []
78
- for name in ['NDVI', 'ARI', 'GNDVI']:
79
  entry = veg.get(name, {})
80
  st = entry.get('statistics', {}) if isinstance(entry, dict) else {}
81
  if st:
 
40
  # Collect outputs
41
  outputs: Dict[str, str] = {}
42
 
43
+ # Collect desired vegetation indices (replace ARI with SAVI)
44
  wanted = [
45
  work / 'Vegetation_indices_images/ndvi.png',
 
46
  work / 'Vegetation_indices_images/gndvi.png',
47
+ work / 'Vegetation_indices_images/savi.png',
 
 
 
48
  ]
49
  labels = [
50
+ 'NDVI', 'GNDVI', 'SAVI',
51
  ]
52
  for label, path in zip(labels, wanted):
53
  if path.exists():
 
71
  _, pdata = next(iter(plants.items()))
72
  veg = pdata.get('vegetation_indices', {})
73
  stats_lines = []
74
+ for name in ['NDVI', 'GNDVI', 'SAVI']:
75
  entry = veg.get(name, {})
76
  st = entry.get('statistics', {}) if isinstance(entry, dict) else {}
77
  if st: