From d394efe13dbd438e2182339c2fff69615ee6c1bf Mon Sep 17 00:00:00 2001 From: Rusfort Date: Tue, 6 May 2025 22:59:08 +0200 Subject: [PATCH] Is gloss source logic removal - metadata handling update --- .../01_User_Guide/09_Output_Structure.md | 2 +- .../05_Processing_Pipeline.md | 6 +- .../LLM_Integration_Progress.md | 55 ------------------- gui/llm_prediction_handler.py | 1 - gui/prediction_handler.py | 34 +++++------- rule_structure.py | 1 - 6 files changed, 18 insertions(+), 81 deletions(-) delete mode 100644 Documentation/Developer_Guide/LLM_Integration_Progress.md diff --git a/Documentation/01_User_Guide/09_Output_Structure.md b/Documentation/01_User_Guide/09_Output_Structure.md index 6bead4b..587ce03 100644 --- a/Documentation/01_User_Guide/09_Output_Structure.md +++ b/Documentation/01_User_Guide/09_Output_Structure.md @@ -60,7 +60,7 @@ Each asset directory contains the following: * Processed texture maps (e.g., `WoodFloor_Albedo_4k.png`, `MetalPanel_Normal_2k.exr`). The exact filenames depend on the `OUTPUT_FILENAME_PATTERN`. These are the resized, format-converted, and bit-depth adjusted texture files. * Merged texture maps (e.g., `WoodFloor_Combined_4k.png`). The exact filenames depend on the `OUTPUT_FILENAME_PATTERN`. These are maps created by combining channels from different source maps based on the configured merge rules. * Model files (if present in the source asset). -* `metadata.json`: A JSON file containing detailed information about the asset and the processing that was performed. This includes details about the maps, resolutions, formats, bit depths, merged map details, calculated image statistics, aspect ratio change information, asset category and archetype, the source preset used, and a list of ignored source files. This file is intended for use by downstream tools or scripts (like the Blender integration scripts). +* `metadata.json`: A JSON file containing detailed information about the asset and the processing that was performed. This includes details about the maps (resolutions, formats, bit depths, and for roughness maps, a `derived_from_gloss_filename: true` flag if it was inverted from an original gloss map), merged map details, calculated image statistics, aspect ratio change information, asset category and archetype, the source preset used, and a list of ignored source files. This file is intended for use by downstream tools or scripts (like the Blender integration scripts). * `EXTRA/` (subdirectory): Contains source files not classified as maps or models but marked as "EXTRA" by preset rules (e.g., previews, documentation). These files are placed in an `EXTRA` folder *within* the directory generated by `OUTPUT_DIRECTORY_PATTERN`. * `Unrecognised/` (subdirectory): Contains source files that were not classified as maps, models, or explicitly marked as extra, and were not ignored. * `Ignored/` (subdirectory): Contains source files that were explicitly ignored during processing (e.g., an 8-bit Normal map when a 16-bit variant exists and is prioritized). \ No newline at end of file diff --git a/Documentation/02_Developer_Guide/05_Processing_Pipeline.md b/Documentation/02_Developer_Guide/05_Processing_Pipeline.md index 8372415..14d801c 100644 --- a/Documentation/02_Developer_Guide/05_Processing_Pipeline.md +++ b/Documentation/02_Developer_Guide/05_Processing_Pipeline.md @@ -34,10 +34,10 @@ The pipeline steps are: * Iterates through files classified as maps in the `SourceRule`. * Loads images (`cv2.imread`). * **Glossiness-to-Roughness Inversion**: - * The system identifies a map as a gloss map if its input filename contains "MAP_GLOSS" (case-insensitive). - * If such a map is intended to become a roughness map (e.g., its `item_type` or `item_type_override` in the `SourceRule` effectively designates it as roughness), its colors are inverted. + * The system identifies a map as a gloss map if its input filename contains "MAP_GLOSS" (case-insensitive) and is intended to become a roughness map (e.g., its `item_type` or `item_type_override` in the `SourceRule` effectively designates it as roughness). + * If these conditions are met, its colors are inverted. * After inversion, the map is treated as a "MAP_ROUGH" type for subsequent processing steps. - * This filename-driven approach is the primary mechanism for triggering gloss-to-roughness inversion, replacing reliance on older contextual flags (like `file_rule.is_gloss_source`) or general `gloss_map_identifiers` from the configuration for this specific transformation within the processing engine. + * The fact that a map was derived from a gloss source and inverted is recorded in the output `metadata.json` for that map type using the `derived_from_gloss_filename: true` flag. This replaces the previous reliance on an internal `is_gloss_source` flag within the `FileRule` structure. * Resizes images based on `Configuration`. * Determines output bit depth and format based on `Configuration` and `SourceRule`. * Converts data types and saves images (`cv2.imwrite`). diff --git a/Documentation/Developer_Guide/LLM_Integration_Progress.md b/Documentation/Developer_Guide/LLM_Integration_Progress.md deleted file mode 100644 index 0242954..0000000 --- a/Documentation/Developer_Guide/LLM_Integration_Progress.md +++ /dev/null @@ -1,55 +0,0 @@ -# Developer Guide: LLM Integration Progress - -This document summarizes the goals, approach, and current progress on integrating Large Language Model (LLM) capabilities into the Asset Processor Tool for handling irregularly named asset inputs. - -## 1. Initial Goal - -The primary goal is to enhance the Asset Processor Tool's ability to process asset sources with irregular or non-standard naming conventions that cannot be reliably handled by the existing regex and keyword-based preset system. This involves leveraging an LLM to interpret lists of filenames and determine asset metadata and file classifications. - -## 2. Agreed Approach - -After initial discussion and exploring several options, the agreed approach for developing this feature is as follows: - -* **Dedicated LLM Preset:** The LLM classification logic will be triggered by selecting a specific preset type (or flag) in the main tool, indicating that standard rule-based processing should be bypassed in favor of the LLM. -* **Standalone Prototype:** The core LLM interaction and classification logic is being developed as a standalone Python prototype within the `llm_prototype/` directory. This allows for focused development, testing, and refinement in isolation before integration into the main application. -* **Configurable LLM Endpoint:** The prototype is designed to allow users to configure the LLM API endpoint, supporting various providers including local LLMs (e.g., via LM Studio) and commercial APIs. API keys are handled via environment variables for security. -* **Multi-Asset Handling:** The prototype is being built to handle input sources that contain multiple distinct assets within a single directory or archive. The LLM is expected to identify these separate assets and return a JSON **list**, where each item in the list represents one asset. -* **Chain of Thought (CoT) Prompting:** To improve the LLM's ability to handle the complex task of identifying multiple assets and classifying files, the prompt includes instructions for the LLM to output its reasoning process within tags before generating the final JSON list. -* **Unified Asset Category:** The asset classification uses a single `asset_category` field with defined valid values: `Model`, `Surface`, `Decal`, `ATLAS`, `Imperfection`. -* **Robust JSON Extraction & Validation:** The prototype includes logic to extract the JSON list from the LLM's response (handling potential extra text) and validate its structure and content against expected schemas and values. - -## 3. Prototype Development Progress - -The initial structure for the standalone prototype has been created in the `llm_prototype/` directory: - -* `llm_prototype/PLAN.md`: This document outlines the detailed plan. -* `llm_prototype/config_llm.py`: Configuration file for LLM settings, expected values, and placeholders. -* `llm_prototype/llm_classifier.py`: Main script containing the core logic (loading config/input/prompt, formatting prompt, calling LLM API, extracting/validating JSON). -* `llm_prototype/requirements_llm.txt`: Lists the `requests` library dependency. -* `llm_prototype/prompt_template.txt`: Contains the Chain of Thought prompt template with placeholders and few-shot examples. -* `llm_prototype/README.md`: Provides setup and running instructions for the prototype. -* `llm_prototype/test_inputs/`: Contains example input JSON files (`dinesen_example.json`, `imperfections_example.json`) representing file lists from asset sources. - -Code has been added to `llm_classifier.py` for loading inputs/config/prompt, formatting the prompt, calling the API, and extracting/validating the JSON response. The JSON extraction logic has been made more robust to handle potential variations in LLM output format. - -## 4. Current Status and Challenges - -Initial testing of the prototype revealed the following: - -* Successful communication with the configured LLM API endpoint. -* The LLM is attempting to follow the Chain of Thought structure and generate the list-based JSON output. -* **Challenge:** The LLM is currently failing to consistently produce complete and valid JSON output, leading to JSON decoding errors in the prototype script. -* **Challenge:** The LLM is not strictly adhering to the specified classification values (e.g., returning "Map" instead of "PBRMap"), despite the prompt explicitly listing the allowed values and including few-shot examples. - -To address these challenges, few-shot examples demonstrating the expected JSON structure and exact classification values were added to the `prompt_template.txt`. The JSON extraction logic in `llm_classifier.py` was also updated to be more resilient. - -## 5. Next Steps - -The immediate next steps are focused on debugging and improving the LLM's output reliability: - -1. Continue testing the prototype with the updated `prompt_template.txt` (including examples) using the example input files. -2. Analyze the terminal output to determine if the few-shot examples and improved extraction logic have resolved the JSON completeness and classification value issues. -3. Based on the results, iterate on the prompt template (e.g., further emphasizing strict adherence to output format and values) and/or the JSON extraction/validation logic in `llm_classifier.py` as needed. -4. Repeat testing and iteration until the prototype reliably produces valid JSON output with correct classifications for the test cases. - -Once the prototype demonstrates reliable classification, we can proceed to evaluate its performance and plan the integration into the main Asset Processor Tool. \ No newline at end of file diff --git a/gui/llm_prediction_handler.py b/gui/llm_prediction_handler.py index 78ede21..f995927 100644 --- a/gui/llm_prediction_handler.py +++ b/gui/llm_prediction_handler.py @@ -383,7 +383,6 @@ class LLMPredictionHandler(BasePredictionHandler): item_type_override=file_type, # Initial override based on LLM target_asset_name_override=group_name, output_format_override=None, - is_gloss_source=False, resolution_override=None, channel_merge_instructions={} ) diff --git a/gui/prediction_handler.py b/gui/prediction_handler.py index 1b59db8..9cdd641 100644 --- a/gui/prediction_handler.py +++ b/gui/prediction_handler.py @@ -53,9 +53,9 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis Example: { 'AssetName1': [ - {'file_path': '/path/to/AssetName1_DISP16.png', 'item_type': 'DISP', 'asset_name': 'AssetName1', 'is_gloss_source': False}, - {'file_path': '/path/to/AssetName1_DISP.png', 'item_type': 'EXTRA', 'asset_name': 'AssetName1', 'is_gloss_source': False}, - {'file_path': '/path/to/AssetName1_Color.png', 'item_type': 'COL', 'asset_name': 'AssetName1', 'is_gloss_source': False} + {'file_path': '/path/to/AssetName1_DISP16.png', 'item_type': 'DISP', 'asset_name': 'AssetName1'}, + {'file_path': '/path/to/AssetName1_DISP.png', 'item_type': 'EXTRA', 'asset_name': 'AssetName1'}, + {'file_path': '/path/to/AssetName1_Color.png', 'item_type': 'COL', 'asset_name': 'AssetName1'} ], # ... other assets } @@ -133,7 +133,6 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis if match: log.debug(f"PASS 1: File '{filename}' matched PRIORITIZED bit depth variant for type '{target_type}'.") matched_item_type = target_type - is_gloss_flag = False if (asset_name, matched_item_type) in primary_assignments: log.warning(f"PASS 1: Primary assignment ({asset_name}, {matched_item_type}) already exists. File '{filename}' will be handled in Pass 2.") @@ -145,8 +144,7 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis temp_grouped_files[asset_name].append({ 'file_path': file_path_str, 'item_type': matched_item_type, - 'asset_name': asset_name, - 'is_gloss_source': is_gloss_flag + 'asset_name': asset_name }) processed_in_pass1.add(file_path_str) processed = True @@ -183,12 +181,11 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis for compiled_regex, original_keyword, rule_index in patterns_list: match = compiled_regex.search(filename) if match: - is_gloss_flag = False try: - map_type_mapping_list = config.map_type_mapping - matched_rule_details = map_type_mapping_list[rule_index] - is_gloss_flag = matched_rule_details.get('is_gloss_source', False) - log.debug(f" PASS 2: Match found! Rule Index: {rule_index}, Keyword: '{original_keyword}', Target: '{target_type}', Gloss: {is_gloss_flag}") + # map_type_mapping_list = config.map_type_mapping # Old gloss logic source + # matched_rule_details = map_type_mapping_list[rule_index] # Old gloss logic source + # is_gloss_flag = matched_rule_details.get('is_gloss_source', False) # Old gloss logic + log.debug(f" PASS 2: Match found! Rule Index: {rule_index}, Keyword: '{original_keyword}', Target: '{target_type}'") # Removed Gloss from log except Exception as e: log.exception(f" PASS 2: Error accessing rule details for index {rule_index}: {e}") @@ -196,7 +193,7 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis if (asset_name, target_type) in primary_assignments: log.debug(f"PASS 2: File '{filename}' matched '{original_keyword}' for type '{target_type}', but primary already assigned via Pass 1. Classifying as EXTRA.") matched_item_type = "EXTRA" - is_gloss_flag = False + # is_gloss_flag = False # Old gloss logic else: log.debug(f"PASS 2: File '{filename}' matched '{original_keyword}' for item_type '{target_type}'.") matched_item_type = target_type @@ -204,8 +201,7 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis temp_grouped_files[asset_name].append({ 'file_path': file_path_str, 'item_type': matched_item_type, - 'asset_name': asset_name, - 'is_gloss_source': is_gloss_flag + 'asset_name': asset_name }) is_map = True break @@ -218,8 +214,7 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis temp_grouped_files[asset_name].append({ 'file_path': file_path_str, 'item_type': "FILE_IGNORE", - 'asset_name': asset_name, - 'is_gloss_source': False + 'asset_name': asset_name }) log.debug("--- Finished Pass 2 ---") @@ -264,8 +259,7 @@ def classify_files(file_list: List[str], config: Configuration) -> Dict[str, Lis temp_grouped_files[final_primary_asset_name].append({ 'file_path': file_path_str, 'item_type': "EXTRA", - 'asset_name': final_primary_asset_name, - 'is_gloss_source': False + 'asset_name': final_primary_asset_name }) else: log.debug(f"Skipping duplicate association of extra file: {filename}") @@ -477,7 +471,7 @@ class RuleBasedPredictionHandler(BasePredictionHandler): final_item_type = "FILE_IGNORE" - is_gloss_source_value = file_info.get('is_gloss_source', False) + # is_gloss_source_value = file_info.get('is_gloss_source', False) # Removed file_rule = FileRule( file_path=file_info['file_path'], @@ -485,7 +479,7 @@ class RuleBasedPredictionHandler(BasePredictionHandler): item_type_override=final_item_type, target_asset_name_override=target_asset_name_override, output_format_override=None, - is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False, + # is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False, # Removed resolution_override=None, channel_merge_instructions={}, ) diff --git a/rule_structure.py b/rule_structure.py index 5d68b30..14b2313 100644 --- a/rule_structure.py +++ b/rule_structure.py @@ -10,7 +10,6 @@ class FileRule: resolution_override: Tuple[int, int] = None channel_merge_instructions: Dict[str, Any] = dataclasses.field(default_factory=dict) output_format_override: str = None - is_gloss_source: bool = False def to_json(self) -> str: return json.dumps(dataclasses.asdict(self), indent=4)