Skip to content
148 changes: 102 additions & 46 deletions src/cellmap_segmentation_challenge/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,53 +387,86 @@ def score_label(
Example usage:
scores = score_label('pred.zarr/test_volume/label1')
"""
if pred_label_path is None:
logging.info(f"Label {label_name} not found in submission volume {crop_name}.")
return (
crop_name,
label_name,
empty_label_score(
label=label_name,
crop_name=crop_name,
instance_classes=instance_classes,
truth_path=truth_path,
),
try:
if pred_label_path is None:
logging.info(
f"Label {label_name} not found in submission volume {crop_name}."
)
return (
crop_name,
label_name,
empty_label_score(
label=label_name,
crop_name=crop_name,
instance_classes=instance_classes,
truth_path=truth_path,
),
)
logging.info(f"Scoring {crop_name}/{label_name}...")
truth_path = UPath(truth_path)
# Load the predicted and ground truth label volumes
truth_label_path = (truth_path / crop_name / label_name).path
try:
truth_label_ds = zarr.open(truth_label_path, mode="r")
truth_label = truth_label_ds[:]
except Exception:
raise ValueError(
f"Failed to load ground truth data for {crop_name}/{label_name}. Please contact the challenge organizers."
)
Comment on lines +409 to +415

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception is overly broad. Consider catching specific exceptions like zarr-related errors that might occur when opening or reading from zarr arrays.

Copilot uses AI. Check for mistakes.

crop = TEST_CROPS_DICT[int(crop_name.removeprefix("crop")), label_name]
try:
pred_label = match_crop_space(
pred_label_path,
label_name,
crop.voxel_size,
crop.shape,
crop.translation,
)
except Exception:
raise ValueError(
f"Failed to process submission data for {crop_name}/{label_name}. Please verify your data format and coordinate transformations are correct."
)
Comment thread
rhoadesScholar marked this conversation as resolved.
Outdated
except Exception:
raise Exception(
"An unexpected error occurred during label scoring. Please check your submission and contact the challenge organizers if the issue persists."
Comment on lines +390 to +436

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This broad exception handler catches all exceptions including ValueError exceptions raised from the inner try-except blocks (lines 409-415, 418-429), then re-raises a generic Exception. This swallows the more specific error messages that were carefully crafted for users, replacing them with a generic message. Remove this outer try-except block to preserve the specific error messages.

Suggested change
try:
if pred_label_path is None:
logging.info(
f"Label {label_name} not found in submission volume {crop_name}."
)
return (
crop_name,
label_name,
empty_label_score(
label=label_name,
crop_name=crop_name,
instance_classes=instance_classes,
truth_path=truth_path,
),
)
logging.info(f"Scoring {crop_name}/{label_name}...")
truth_path = UPath(truth_path)
# Load the predicted and ground truth label volumes
truth_label_path = (truth_path / crop_name / label_name).path
try:
truth_label_ds = zarr.open(truth_label_path, mode="r")
truth_label = truth_label_ds[:]
except Exception:
raise ValueError(
f"Failed to load ground truth data for {crop_name}/{label_name}. Please contact the challenge organizers."
)
crop = TEST_CROPS_DICT[int(crop_name.removeprefix("crop")), label_name]
try:
pred_label = match_crop_space(
pred_label_path,
label_name,
crop.voxel_size,
crop.shape,
crop.translation,
)
except Exception:
raise ValueError(
f"Failed to process submission data for {crop_name}/{label_name}. Please verify your data format and coordinate transformations are correct."
)
except Exception:
raise Exception(
"An unexpected error occurred during label scoring. Please check your submission and contact the challenge organizers if the issue persists."
if pred_label_path is None:
logging.info(
f"Label {label_name} not found in submission volume {crop_name}."
)
return (
crop_name,
label_name,
empty_label_score(
label=label_name,
crop_name=crop_name,
instance_classes=instance_classes,
truth_path=truth_path,
),
)
logging.info(f"Scoring {crop_name}/{label_name}...")
truth_path = UPath(truth_path)
# Load the predicted and ground truth label volumes
truth_label_path = (truth_path / crop_name / label_name).path
try:
truth_label_ds = zarr.open(truth_label_path, mode="r")
truth_label = truth_label_ds[:]
except Exception:
raise ValueError(
f"Failed to load ground truth data for {crop_name}/{label_name}. Please contact the challenge organizers."
)
crop = TEST_CROPS_DICT[int(crop_name.removeprefix("crop")), label_name]
try:
pred_label = match_crop_space(
pred_label_path,
label_name,
crop.voxel_size,
crop.shape,
crop.translation,
)
except Exception:
raise ValueError(
f"Failed to process submission data for {crop_name}/{label_name}. Please verify your data format and coordinate transformations are correct."

Copilot uses AI. Check for mistakes.
)
Comment on lines +434 to 437

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This outer try-except block only wraps lines 391-429, but the code that uses truth_label and pred_label (lines 435-473) is outside this block. This means if an exception occurs in the wrapped section, the variables won't be defined and subsequent code will fail with NameError. The try-except block should either be removed or extended to include all code that depends on these variables.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mzouink Can you double check this?

logging.info(f"Scoring {pred_label_path}...")
truth_path = UPath(truth_path)
# Load the predicted and ground truth label volumes
truth_label_path = (truth_path / crop_name / label_name).path
truth_label_ds = zarr.open(truth_label_path, mode="r")
truth_label = truth_label_ds[:]
crop = TEST_CROPS_DICT[int(crop_name.removeprefix("crop")), label_name]
pred_label = match_crop_space(
pred_label_path,
label_name,
crop.voxel_size,
crop.shape,
crop.translation,
)

mask_path = truth_path / crop_name / f"{label_name}_mask"
if mask_path.exists():
# Mask out uncertain regions resulting from low-res ground truth annotations
logging.info(f"Masking {label_name} with {mask_path}...")
mask = zarr.open(mask_path.path, mode="r")[:]
pred_label = pred_label * mask
truth_label = truth_label * mask
try:
mask = zarr.open(mask_path.path, mode="r")[:]
pred_label = pred_label * mask
truth_label = truth_label * mask
except Exception:
raise ValueError(
f"Failed to apply mask for {crop_name}/{label_name}. Please contact the challenge organizers."
)
Comment on lines +443 to +450

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception is overly broad. Consider catching specific exceptions like zarr-related errors or numpy errors that might occur during these operations.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mzouink Wouldn't it be good to also raise the original error for debugging? That is, we want to know which crop and label went wrong, but also what happened.


# Compute the scores
# Compute the scores
Comment thread
rhoadesScholar marked this conversation as resolved.
Outdated
if label_name in instance_classes:
logging.info(
f"Starting an instance evaluation for {label_name} in {crop_name}..."
)
timer = time()
results = score_instance(pred_label, truth_label, crop.voxel_size)
try:
results = score_instance(pred_label, truth_label, crop.voxel_size)
except Exception:

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception is overly broad. Consider catching specific exceptions related to instance scoring operations that might occur in the score_instance function.

Suggested change
except Exception:
except (ValueError, TypeError):

Copilot uses AI. Check for mistakes.
raise ValueError(
f"Failed to compute instance scores for {crop_name}/{label_name}. Ensure your instance segmentation data has properly labeled instances with integer IDs."
)
logging.info(
f"Finished instance evaluation for {label_name} in {crop_name} in {time() - timer:.2f} seconds..."
)
else:
results = score_semantic(pred_label, truth_label)
try:
results = score_semantic(pred_label, truth_label)
except Exception:
raise ValueError(
f"Failed to compute semantic scores for {crop_name}/{label_name}. Ensure your data contains valid probability or binary values."
)
Comment thread
rhoadesScholar marked this conversation as resolved.
results["num_voxels"] = int(np.prod(truth_label.shape))
results["voxel_size"] = crop.voxel_size
results["is_missing"] = False
Expand Down Expand Up @@ -732,22 +765,32 @@ def score_submission(
logging.info(f"Scoring {submission_path}...")
start_time = time()
# Unzip the submission
submission_path = unzip_file(submission_path)
try:
submission_path = unzip_file(submission_path)
except Exception:
raise ValueError(
"Failed to process submission file. Please ensure you submitted a valid .zip file containing a Zarr structure."
)
Comment on lines +774 to +777

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler should re-raise the exception instead of catching and re-raising a new ValueError. When unzip_file already raises a ValueError with a specific message (lines 1171-1177), this handler catches it and replaces it with a less specific message. Either let the exception propagate naturally or catch specific exception types without replacing the message.

Suggested change
except Exception:
raise ValueError(
"Failed to process submission file. Please ensure you submitted a valid .zip file containing a Zarr structure."
)
except ValueError:
# Preserve detailed error messages raised by unzip_file
raise
except zipfile.BadZipFile as exc:
# Provide a user-friendly error if the zip itself is invalid
raise ValueError(
"Failed to process submission file. Please ensure you submitted a valid .zip file containing a Zarr structure."
) from exc

Copilot uses AI. Check for mistakes.

# Find volumes to score
logging.info(f"Scoring volumes in {submission_path}...")
pred_volumes = [d.name for d in UPath(submission_path).glob("*") if d.is_dir()]
truth_path = UPath(truth_path)
logging.info(f"Volumes: {pred_volumes}")
logging.info(f"Truth path: {truth_path}")
truth_volumes = [d.name for d in truth_path.glob("*") if d.is_dir()]
logging.info(f"Truth volumes: {truth_volumes}")
try:
pred_volumes = [d.name for d in UPath(submission_path).glob("*") if d.is_dir()]
truth_path = UPath(truth_path)
logging.info(f"Volumes: {pred_volumes}")
logging.info(f"Truth path: {truth_path}")
truth_volumes = [d.name for d in truth_path.glob("*") if d.is_dir()]
logging.info(f"Truth volumes: {truth_volumes}")
except Exception:
raise ValueError(
"Failed to read submission structure. Ensure your submission contains crop folders (e.g., crop557, crop558, etc.) at the top level."
)
Comment thread
rhoadesScholar marked this conversation as resolved.

found_volumes = list(set(pred_volumes) & set(truth_volumes))
missing_volumes = list(set(truth_volumes) - set(pred_volumes))
if len(found_volumes) == 0:
raise ValueError(
"No volumes found to score. Make sure the submission is formatted correctly."
f"No valid test volumes found in submission. Expected volumes like: {', '.join(truth_volumes[:5])}. Please ensure your submission structure matches the required format."
)
logging.info(f"Scoring volumes: {found_volumes}")
if len(missing_volumes) > 0:
Expand Down Expand Up @@ -964,7 +1007,12 @@ def match_crop_space(path, class_label, voxel_size, shape, translation) -> np.nd
Returns:
np.ndarray: The rescaled array.
"""
ds = zarr.open(str(path), mode="r")
try:
ds = zarr.open(str(path), mode="r")
except Exception:
Comment thread
rhoadesScholar marked this conversation as resolved.
Outdated
raise ValueError(
f"Cannot open zarr array at path: {UPath(path).name}. Ensure your submission is a valid Zarr format."
)
if "multiscales" in ds.attrs:
# Handle multiscale zarr files
_image = CellMapImage(
Expand Down Expand Up @@ -1113,12 +1161,20 @@ def unzip_file(zip_path):
Example usage:
unzip_file('submission.zip')
"""
saved_path = UPath(zip_path).with_suffix(".zarr").path
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(saved_path)
logging.info(f"Unzipped {zip_path} to {saved_path}")

return UPath(saved_path)
try:
saved_path = UPath(zip_path).with_suffix(".zarr").path
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(saved_path)
logging.info(f"Unzipped {zip_path} to {saved_path}")
return UPath(saved_path)
except zipfile.BadZipFile:
raise ValueError(
f"Invalid zip file. Please ensure you submitted a valid .zip file."
)
except Exception:
raise ValueError(
f"Failed to extract submission file. Please verify the file is not corrupted."
)
Comment on lines +1178 to +1181

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception here is overly broad and will catch even the specific BadZipFile exception from line 1170, making that more specific handler unreachable. The BadZipFile handler should be placed first, followed by other specific exceptions, with a broader handler (if needed) at the end.

Suggested change
except Exception:
raise ValueError(
f"Failed to extract submission file. Please verify the file is not corrupted."
)
except (FileNotFoundError, PermissionError, OSError, zipfile.LargeZipFile) as exc:
raise ValueError(
f"Failed to extract submission file. Please verify the file is not corrupted."
) from exc

Copilot uses AI. Check for mistakes.


if __name__ == "__main__":
Expand Down
Loading