Source code for iaa_od.utils.collapse_categories

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))