Processing-Engine - Gloss > Rough conversion

This commit is contained in:
Rusfort 2025-05-06 20:58:01 +02:00
parent 9a27d23a4c
commit ddb5a43a21
2 changed files with 85 additions and 40 deletions

View File

@ -33,7 +33,11 @@ The pipeline steps are:
6. **Map Processing (`_process_maps`)**:
* Iterates through files classified as maps in the `SourceRule`.
* Loads images (`cv2.imread`).
* Handles Glossiness-to-Roughness inversion.
* **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.
* 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.
* Resizes images based on `Configuration`.
* Determines output bit depth and format based on `Configuration` and `SourceRule`.
* Converts data types and saves images (`cv2.imwrite`).

View File

@ -520,7 +520,7 @@ class ProcessingEngine:
def _load_and_transform_source(self, source_path_abs: Path, map_type: str, target_resolution_key: str, is_gloss_source: bool) -> Tuple[Optional[np.ndarray], Optional[np.dtype]]:
"""
Loads a source image file, performs initial prep (BGR->RGB, Gloss->Rough),
Loads a source image file, performs initial prep (BGR->RGB, Gloss->Rough if applicable),
resizes it to the target resolution, and caches the result.
Uses static configuration from self.config_obj.
@ -528,7 +528,7 @@ class ProcessingEngine:
source_path_abs: Absolute path to the source file in the workspace.
map_type: The item_type_override (e.g., "MAP_NRM", "MAP_ROUGH-1").
target_resolution_key: The key for the target resolution (e.g., "4K").
is_gloss_source: Boolean indicating if this source should be treated as gloss for inversion.
is_gloss_source: Boolean indicating if this source should be treated as gloss for inversion (if map_type is ROUGH).
Returns:
Tuple containing:
@ -608,11 +608,10 @@ class ProcessingEngine:
if img_prepared is None: raise ProcessingEngineError("Image data is None after MASK/Color prep.")
# Gloss -> Roughness Inversion
# map_type is item_type_override, e.g. "MAP_ROUGH-1"
# standard_type_for_checks is "ROUGH"
# Gloss -> Roughness Inversion (if map_type is ROUGH and is_gloss_source is True)
# This is triggered by the new filename logic in _process_individual_maps
if standard_type_for_checks == 'ROUGH' and is_gloss_source:
log.info(f"Performing Gloss->Roughness inversion for {source_path_abs.name} (map_type: {map_type})")
log.info(f"Performing filename-triggered Gloss->Roughness inversion for {source_path_abs.name} (map_type: {map_type})")
if len(img_prepared.shape) == 3:
log.debug("Gloss Inversion: Converting 3-channel image to grayscale before inversion.")
img_prepared = cv2.cvtColor(img_prepared, cv2.COLOR_RGB2GRAY) # Should be RGB at this point if 3-channel
@ -666,7 +665,9 @@ class ProcessingEngine:
# --- 4. Cache and Return ---
# Keep resized dtype unless it was gloss-inverted (which is float32)
final_data_to_cache = img_resized
if map_type.startswith('ROUGH') and is_gloss_source and final_data_to_cache.dtype != np.float32:
# Ensure gloss-inverted maps are float32
if standard_type_for_checks == 'ROUGH' and is_gloss_source and final_data_to_cache.dtype != np.float32:
log.debug(f"Ensuring gloss-inverted ROUGH map ({map_type}) is float32.")
final_data_to_cache = final_data_to_cache.astype(np.float32)
log.debug(f"CACHING result for {cache_key}. Shape: {final_data_to_cache.shape}, Dtype: {final_data_to_cache.dtype}")
@ -994,7 +995,7 @@ class ProcessingEngine:
source_path_abs,
first_map_rule_for_aspect.item_type_override,
first_res_key,
is_gloss_source=False, # Added: Not relevant for dimension check, but required by method
is_gloss_source=False # Not relevant for dimension check
# self.loaded_data_cache is used internally by the method
)
if temp_img_for_dims is not None:
@ -1033,15 +1034,49 @@ class ProcessingEngine:
# as individual maps should have been copied there by the caller (ProcessingTask)
# Correction: _process_individual_maps receives the *engine's* temp_dir as workspace_path
source_path_abs = workspace_path / source_path_rel
map_type = file_rule.item_type_override # Use the explicit map type from the rule
# Determine if the source is gloss based on the flag set during prediction
# is_gloss_source = map_type in gloss_identifiers # <<< INCORRECT: Re-calculates based on target type
is_gloss_source = getattr(file_rule, 'is_gloss_source', False) # <<< CORRECT: Use flag from FileRule object
log.debug(f"Using is_gloss_source={is_gloss_source} directly from FileRule for {file_rule.file_path}") # DEBUG ADDED
# Store original rule-based type and gloss flag
original_item_type_override = file_rule.item_type_override
# original_is_gloss_source_context removed as it's part of deprecated logic
# --- New gloss map filename logic ---
filename_str = source_path_rel.name
is_filename_gloss_map = "map_gloss" in filename_str.lower()
effective_map_type_for_processing = original_item_type_override
effective_is_gloss_source_for_load = False # Default to False, new filename logic will set to True if applicable
map_was_retagged_from_filename_gloss = False
if is_filename_gloss_map:
log.info(f"-- Asset '{asset_name}': Filename '{filename_str}' contains 'MAP_GLOSS'. Applying new gloss handling. Original type from rule: '{original_item_type_override}'.")
effective_is_gloss_source_for_load = True # Force inversion if type becomes ROUGH (handled by filename logic below)
map_was_retagged_from_filename_gloss = True
# Attempt to retag original_item_type_override from GLOSS to ROUGH, preserving MAP_ prefix case and suffix
if original_item_type_override and "gloss" in original_item_type_override.lower():
match = re.match(r"(MAP_)(GLOSS)((?:[-_]\w+)*)", original_item_type_override, re.IGNORECASE)
if match:
prefix = match.group(1) # e.g., "MAP_"
suffix = match.group(3) if match.group(3) else "" # e.g., "-variant1_detail" or ""
effective_map_type_for_processing = f"{prefix}ROUGH{suffix}"
log.debug(f"Retagged filename gloss: original FTD key '{original_item_type_override}' to '{effective_map_type_for_processing}' for processing.")
else:
log.warning(f"Filename gloss '{original_item_type_override}' matched 'gloss' but not the expected 'MAP_GLOSS<suffix>' pattern for precise retagging. Defaulting to 'MAP_ROUGH'.")
effective_map_type_for_processing = "MAP_ROUGH"
else:
# If original_item_type_override was None or didn't contain "gloss" (e.g., file was untyped but filename had MAP_GLOSS)
log.debug(f"Filename '{filename_str}' identified as gloss, but original type override ('{original_item_type_override}') was not GLOSS-specific. Setting type to 'MAP_ROUGH' for processing.")
effective_map_type_for_processing = "MAP_ROUGH"
# --- End of new gloss map filename logic ---
original_extension = source_path_rel.suffix.lower() # Get from path
log.info(f"-- Asset '{asset_name}': Processing Individual Map: {map_type} (Source: {source_path_rel.name}, IsGlossSource: {is_gloss_source}) --") # DEBUG: Added flag to log
current_map_details = {"derived_from_gloss": is_gloss_source}
log.info(f"-- Asset '{asset_name}': Processing Individual Map: {effective_map_type_for_processing} (Source: {source_path_rel.name}, EffectiveIsGlossSourceForLoad: {effective_is_gloss_source_for_load}, OriginalRuleItemType: {original_item_type_override}) --")
current_map_details = {} # Old "derived_from_gloss_context" removed
if map_was_retagged_from_filename_gloss:
current_map_details["derived_from_gloss_filename"] = True
current_map_details["original_item_type_override_before_gloss_filename_retag"] = original_item_type_override
current_map_details["effective_item_type_override_after_gloss_filename_retag"] = effective_map_type_for_processing
source_bit_depth_found = None # Track if we've found the bit depth for this map type
try:
@ -1053,29 +1088,29 @@ class ProcessingEngine:
# This now only runs for files that have an item_type_override
img_resized, source_dtype = self._load_and_transform_source(
source_path_abs=source_path_abs,
map_type=map_type, # Pass the specific map type (e.g., ROUGH-1)
map_type=effective_map_type_for_processing, # Use effective type
target_resolution_key=res_key,
is_gloss_source=is_gloss_source
is_gloss_source=effective_is_gloss_source_for_load # Pass the flag determined by filename logic
# self.loaded_data_cache is used internally
)
if img_resized is None:
# This warning now correctly indicates a failure for a map we *intended* to process
log.warning(f"Failed to load/transform source map {source_path_rel} for {res_key}. Skipping resolution.")
log.warning(f"Failed to load/transform source map {source_path_rel} (processed as {effective_map_type_for_processing}) for {res_key}. Skipping resolution.")
continue # Skip this resolution
# Store source bit depth once found
if source_dtype is not None and source_bit_depth_found is None:
source_bit_depth_found = 16 if source_dtype == np.uint16 else (8 if source_dtype == np.uint8 else 8) # Default non-uint to 8
current_map_details["source_bit_depth"] = source_bit_depth_found
log.debug(f"Stored source bit depth for {map_type}: {source_bit_depth_found}")
log.debug(f"Stored source bit depth for {effective_map_type_for_processing}: {source_bit_depth_found}")
# --- 2. Calculate Stats (if applicable) ---
if res_key == stats_res_key and stats_target_dim:
log.debug(f"Calculating stats for {map_type} using {res_key} image...")
log.debug(f"Calculating stats for {effective_map_type_for_processing} using {res_key} image...")
stats = _calculate_image_stats(img_resized)
if stats: image_stats_asset[map_type] = stats # Store locally first
else: log.warning(f"Stats calculation failed for {map_type} at {res_key}.")
if stats: image_stats_asset[effective_map_type_for_processing] = stats # Store locally first
else: log.warning(f"Stats calculation failed for {effective_map_type_for_processing} at {res_key}.")
# --- 3. Calculate Aspect Ratio Change String (once per asset) ---
if aspect_ratio_change_string_asset == "N/A" and orig_w_aspect is not None and orig_h_aspect is not None:
@ -1097,14 +1132,14 @@ class ProcessingEngine:
'involved_extensions': {original_extension} # Only self for individual maps
}
# Get bit depth rule solely from the static configuration using the correct method signature
bit_depth_rule = self.config_obj.get_bit_depth_rule(map_type) # Pass only map_type
bit_depth_rule = self.config_obj.get_bit_depth_rule(effective_map_type_for_processing) # Use effective type
# Determine the map_type to use for saving (use item_type_override)
save_map_type = file_rule.item_type_override
# If item_type_override is None, this file shouldn't be saved as an individual map.
# Determine the map_type to use for saving (use effective_map_type_for_processing)
save_map_type_for_filename = effective_map_type_for_processing
# If effective_map_type_for_processing is None, this file shouldn't be saved as an individual map.
# This case should ideally be caught by the skip logic earlier, but adding a check here for safety.
if save_map_type is None:
log.warning(f"Skipping save for {file_rule.file_path}: item_type_override is None.")
if save_map_type_for_filename is None:
log.warning(f"Skipping save for {file_rule.file_path}: effective_map_type_for_processing is None.")
continue # Skip saving this file
# Get supplier name from metadata (set in process method)
@ -1114,7 +1149,7 @@ class ProcessingEngine:
image_data=img_resized,
supplier_name=supplier_name,
asset_name=base_name,
current_map_identifier=save_map_type, # Pass the map type to be saved
current_map_identifier=save_map_type_for_filename, # Pass the effective map type to be saved
resolution_key=res_key,
source_info=source_info,
output_bit_depth_rule=bit_depth_rule
@ -1122,20 +1157,20 @@ class ProcessingEngine:
# --- 5. Store Result ---
if save_result:
processed_maps_details_asset.setdefault(map_type, {})[res_key] = save_result
processed_maps_details_asset.setdefault(effective_map_type_for_processing, {})[res_key] = save_result
# Update overall map detail (e.g., final format) if needed
current_map_details["output_format"] = save_result.get("format")
else:
log.error(f"Failed to save {map_type} at {res_key}.")
processed_maps_details_asset.setdefault(map_type, {})[f'error_{res_key}'] = "Save failed"
log.error(f"Failed to save {effective_map_type_for_processing} at {res_key}.")
processed_maps_details_asset.setdefault(effective_map_type_for_processing, {})[f'error_{res_key}'] = "Save failed"
except Exception as map_proc_err:
log.error(f"Failed processing map {map_type} from {source_path_rel.name}: {map_proc_err}", exc_info=True)
processed_maps_details_asset.setdefault(map_type, {})['error'] = str(map_proc_err)
log.error(f"Failed processing map {effective_map_type_for_processing} from {source_path_rel.name}: {map_proc_err}", exc_info=True)
processed_maps_details_asset.setdefault(effective_map_type_for_processing, {})['error'] = str(map_proc_err)
# Store collected details for this map type
map_details_asset[map_type] = current_map_details
# Store collected details for this map type (using effective_map_type_for_processing as the key)
map_details_asset[effective_map_type_for_processing] = current_map_details
# --- Final Metadata Updates ---
# Update the passed-in current_asset_metadata dictionary directly
@ -1278,16 +1313,22 @@ class ProcessingEngine:
source_path_rel_str = file_rule.file_path # Keep original string if needed
source_path_rel = Path(source_path_rel_str) # Convert to Path object
source_path_abs = workspace_path / source_path_rel
is_gloss = file_rule.item_type_override in getattr(self.config_obj, 'gloss_map_identifiers', [])
original_ext = source_path_rel.suffix.lower() # Now works on Path object
source_info_for_save['involved_extensions'].add(original_ext)
log.debug(f"Loading source '{source_path_rel}' for merge input '{map_type_needed}' at {current_res_key} (Gloss: {is_gloss})")
# Determine if this specific source for merge should be treated as gloss
# based on its filename, aligning with the new primary rule.
filename_str_for_merge_input = source_path_rel.name
is_gloss_for_merge_input = "map_gloss" in filename_str_for_merge_input.lower()
if is_gloss_for_merge_input:
log.debug(f"Merge input '{filename_str_for_merge_input}' for '{map_type_needed}' identified as gloss by filename. Will pass is_gloss_source=True.")
log.debug(f"Loading source '{source_path_rel}' for merge input '{map_type_needed}' at {current_res_key} (is_gloss_for_merge_input: {is_gloss_for_merge_input})")
img_resized, source_dtype = self._load_and_transform_source(
source_path_abs=source_path_abs,
map_type=file_rule.item_type_override, # Use the specific type override from rule (e.g., ROUGH-1)
target_resolution_key=current_res_key,
is_gloss_source=is_gloss
is_gloss_source=is_gloss_for_merge_input # Pass determined gloss state
# self.loaded_data_cache used internally
)