import re
[docs]
def collapse_categories_interactive(gts) -> dict[str, list[int]]:
"""
An interactive function which allows the user to select which categories to collapse
into single macro-categories for the computation of the scale complexity metric.
Returns:
dict[str, list[int]]: A dictionary mapping macro-category names to lists of category IDs
"""
# Import the categories dictionary
categories_dict = gts[0].categories_dict
# Initialise the collapsed categories dictionary
collapsed_categories = {}
# Set to keep track of used category IDs
used_category_ids = set()
# Dictionary of filtered categories
filtered_categories_dict = categories_dict.copy()
# Print the first help menu
print("Welcome to the Collapse Categories Interactive Function!")
_print_help_menu()
# Main interactive loop
while True:
user_input = input("Enter command (type 'help' for options): ").strip().lower()
# Exits the loop
if user_input == 'exit':
confirm_exit = input("Are you sure you want to exit without saving? (Yes to confirm) ").strip().lower()
if confirm_exit == 'yes':
print("Exiting without saving. Thank you!")
break
else:
print("Continuing interactive editor.")
continue
if user_input == 'done':
if len(filtered_categories_dict) > 0:
print("There are still available categories left to collapse.")
print("If you want to exit without saving, type 'exit'.")
else:
print("Exiting interactive editor. Thank you!")
break
# Prints the help menu
elif user_input == 'help':
_print_help_menu()
# Prints the current collapsed categories
elif user_input == 'view':
print("Current collapsed categories:")
for macro_cat, cat_ids in collapsed_categories.items():
cat_names = [categories_dict[cat_id] for cat_id in cat_ids]
print(f"- {macro_cat}: {cat_names} (IDs: {cat_ids})")
# Prints the available categories
elif user_input == 'categories':
if len(filtered_categories_dict) == 0:
print("No available categories left to collapse.")
continue
print("Available categories:")
for cat_id, cat_name in filtered_categories_dict.items():
print(f"- ID {cat_id}: {cat_name}")
# Is this what you wanted?
elif user_input == 'cat':
cat = """
/\\_/\\ ___
= o_o =_______ \\ \\ -Julie Rhodes-
__^ __( \\.__) )
(@)<_____>__(_____)____/
Is this what you wanted?
"""
print(cat)
# Add a new macro-category
elif user_input.startswith('new'):
parts = user_input.split()
# Check that the command is well-formed
if len(parts) < 2:
print("Please provide a macro-category name and at least one category ID.")
continue
if not _is_valid_macro_category_name(parts[1]):
print("Macro-category name must be a non-empty string of alphanumeric characters, optionally separated by a single space or underscore.")
continue
macro_category_name = parts[1]
# Abort if macro-category already exists
if macro_category_name in collapsed_categories:
print(f"Macro-category '{macro_category_name}' already exists. Use 'edit' to modify it.")
continue
# Check that at least one category ID is provided
if len(parts) < 3:
print("Please provide at least one category ID to collapse.")
continue
# Validate that all provided IDs are integers
if not all(part.isdigit() for part in parts[2:]):
print("Category IDs must be integers.")
continue
category_ids = list(map(int, parts[2:]))
# Check if any of the category IDs have already been used
if any(cat_id in used_category_ids for cat_id in category_ids):
print("One or more category IDs have already been used in another macro-category. Please choose different IDs.")
continue
# Update used set and filtered list of categories
used_category_ids.update(category_ids)
filtered_categories_dict = {k: v for k, v in categories_dict.items() if k not in used_category_ids}
# Add the new macro-category
collapsed_categories[macro_category_name] = category_ids
print(f"Added new macro-category '{macro_category_name}' with IDs {category_ids}.")
# Edit an existing macro-category
elif user_input.startswith('edit'):
parts = user_input.split()
# Check that the command is well-formed
if len(parts) < 2:
print("Please provide a macro-category name and at least one new category ID.")
continue
if not _is_valid_macro_category_name(parts[1]):
print("Macro-category name must be a non-empty string of alphanumeric characters, optionally separated by a single space, an underscore, a dash, a slash, or a comma followed by a space.")
continue
macro_category_name = parts[1]
# Abort if macro-category does not exist
if macro_category_name not in collapsed_categories:
print(f"Macro-category '{macro_category_name}' does not exist. Use 'new' to create it first.")
continue
# Check that at least one category ID is provided
if len(parts) < 3:
print("Please provide at least one category ID to collapse.")
continue
# Validate that all provided IDs are integers
if not all(part.isdigit() for part in parts[2:]):
print("Category IDs must be integers.")
continue
category_ids = list(map(int, parts[2:]))
# Check if any of the category IDs have already been used (excluding the current macro-category)
previous_ids = set(collapsed_categories[macro_category_name])
temp_used_category_ids = used_category_ids.copy()
temp_used_category_ids.difference_update(previous_ids)
if any(cat_id in temp_used_category_ids for cat_id in category_ids):
print("One or more category IDs have already been used in another macro-category. Please choose different IDs.")
continue
# Update used set and filtered list of categories
used_category_ids.update(category_ids)
used_category_ids.difference_update(previous_ids)
filtered_categories_dict = {k: v for k, v in categories_dict.items() if k not in used_category_ids}
# Update the macro-category
collapsed_categories[macro_category_name] = category_ids
print(f"Updated macro-category '{macro_category_name}' with new IDs {category_ids}.")
# Default case for invalid commands
else:
print("Invalid command. Type 'help' for options.")
return collapsed_categories
[docs]
def validate_macrocats(gts, macro_categories: dict[str, list[int]]) -> dict[str, list[int]]:
"""
Validates the provided macro-categories dictionary against the categories in the ground truth objects.
Parameters:
gts: The ground truth segmentation objects
macro_categories (dict[str, list[int]]): A dictionary mapping macro-category names to lists of category IDs
Returns:
dict[str, list[int]]: A dictionary mapping macro-category names to lists of category IDs
"""
collapsed_categories: dict[str, list[int]] = {}
used_category_ids = set()
categories_dict = gts[0].categories_dict
for macro_category_name, category_ids in macro_categories.items():
# Macro-category name validity check
if not _is_valid_macro_category_name(macro_category_name):
raise ValueError("Macro-category name must be a non-empty string of alphanumeric characters, optionally separated by a single space, an underscore, a dash, a slash, or a comma followed by a space.")
if macro_category_name in collapsed_categories:
raise ValueError(f"Macro-category '{macro_category_name}' already exists.")
# Category IDs validity check
if not all(isinstance(cat_id, int) for cat_id in category_ids):
raise ValueError("Category IDs must be integers.")
if len(category_ids) < 1:
raise ValueError("At least one category ID must be provided for each macro-category.")
if any(cat_id not in categories_dict for cat_id in category_ids):
raise ValueError("One or more category IDs do not exist in the categories dictionary.")
if any(cat_id in used_category_ids for cat_id in category_ids):
raise ValueError("One or more category IDs have already been used in another macro-category.")
# Add the macro-category and update used IDs
collapsed_categories[macro_category_name] = category_ids
used_category_ids.update(category_ids)
# Final check: is the collapsed_categories dictionary made up of partitions of the original categories?
if len(used_category_ids) != len(categories_dict):
raise ValueError("The provided macro-categories do not cover all original categories without overlap.")
if len(used_category_ids) == 0:
raise ValueError("No categories have been collapsed. Please provide at least one macro-category.")
return collapsed_categories
def _print_help_menu() -> None:
"""
Print the help menu for the collapse categories interactive function.
"""
help_menu = """
Help Menu:
- To create a new macro-category, enter 'new' followed by the macro-category name and the category IDs separated by spaces.
- To change the category IDs for an existing macro-category, enter 'edit', the macro-category name and the new category IDs separated by spaces.
- To view the current collapsed categories, enter 'view'.
- To view the list of available categories, enter 'categories'.
- To finish and return the collapsed categories, enter 'done'.
- To exit without saving, enter 'exit'.
- To view this help menu again, enter 'help'.
"""
print(help_menu)
def _is_valid_macro_category_name(name: str) -> bool:
"""
Check if the provided macro-category name is valid according to the specified regular expression.
Parameters:
name (str): The macro-category name to validate.
Returns:
bool: True if the name is valid, False otherwise.
"""
pattern_string: str = "^([a-zA-Z0-9]+( |_|,|, |-|/)?[a-zA-Z0-9]*)+$"
pattern = re.compile(pattern_string)
return bool(pattern.match(name))