@@ -192,7 +192,9 @@ def calculate_canopy_cover(pad: np.ndarray,
192192
193193 Returns:
194194 np.ndarray: 2D array (X, Y) of canopy cover values in [0, 1], with NaN where
195- PAD is entirely missing for the integration range.
195+ PAD is entirely missing for the integration range. If the requested
196+ integration range is empty (e.g., min_height >= available max height),
197+ returns a zeros array (no canopy above the threshold).
196198
197199 Raises:
198200 ValueError: If parameters are invalid (e.g., non-positive voxel_height, k < 0,
@@ -203,16 +205,18 @@ def calculate_canopy_cover(pad: np.ndarray,
203205 if k < 0 :
204206 raise ValueError (f"k must be >= 0 (got { k } )" )
205207
206- # Compute PAI integrated from min_height up to max_height/top
208+ # Determine effective max height and handle empty integration range
209+ effective_max_height = max_height if max_height is not None else pad .shape [2 ] * voxel_height
210+ if min_height >= effective_max_height :
211+ # No foliage above threshold: cover is zero everywhere
212+ return np .zeros ((pad .shape [0 ], pad .shape [1 ]), dtype = float )
213+
214+ # Compute PAI integrated from min_height up to effective_max_height/top
207215 pai_above = calculate_pai (pad , voxel_height , min_height = min_height , max_height = max_height )
208216
209217 # Identify columns that are entirely NaN within the integration range
210- if max_height is None :
211- max_height = pad .shape [2 ] * voxel_height
212- if min_height >= max_height :
213- raise ValueError ("Minimum height index must be less than maximum height index." )
214218 start_idx = int (np .ceil (min_height / voxel_height ))
215- end_idx = int (np .floor (max_height / voxel_height ))
219+ end_idx = int (np .floor (effective_max_height / voxel_height ))
216220 range_slice = pad [:, :, start_idx :end_idx ]
217221 all_nan_mask = np .all (np .isnan (range_slice ), axis = 2 )
218222
0 commit comments