hm_qc/checks/image_resolution_check.py
2025-09-30 10:37:12 -05:00

203 lines
No EOL
8.1 KiB
Python
Executable file

import os
import json
from PIL import Image
def run_check(config):
"""
Check that each image in linkingrecord.json matches the specified resolution
based on (viewtype, imagetype) conditions and an overall MEC-or-BAU pack
designation. However, if the pack is MEC, certain items will still use the
BAU resolution map under these conditions:
1) viewtype in ("exterior", "interior") AND no experienceCondition
2) imagetype == "powertrain"
MEC detection (pack-level): If ANY item has "experienceCondition": "2d-background",
the entire pack is MEC. Otherwise, the entire pack is BAU.
Expected config:
- working_dir (str): Directory where linkingrecord.json and images reside.
- linkingrecord_filename (str): Name of the linking record file. Default: linkingrecord.json
Returns:
- "passed" if all checked images match their expected resolution
- "failed" if any present images don't match, listing each that failed
- "error" if linkingrecord is missing or invalid
"""
working_dir = config.get("working_dir", "working")
linkingrecord_filename = config.get("linkingrecord_filename", "linkingrecord.json")
linkingrecord_path = os.path.join(working_dir, linkingrecord_filename)
if not os.path.exists(linkingrecord_path):
return {
"status": "error",
"error_message": f"Linking record '{linkingrecord_filename}' not found in {working_dir}."
}
# Load linkingrecord.json
with open(linkingrecord_path, 'r', encoding='utf-8') as f:
linkingrecord = json.load(f)
if "items" not in linkingrecord or not isinstance(linkingrecord["items"], list):
return {
"status": "error",
"error_message": "Invalid linkingrecord.json structure: 'items' missing or not a list."
}
# -------------------------------------------------------------------------
# 1) Determine if ANY item has experienceCondition = "2d-background" => MEC
# -------------------------------------------------------------------------
any_mec = any(
item.get("conditions", {}).get("experienceCondition") == "2d-background"
for item in linkingrecord["items"]
)
pack_experience_type = "MEC" if any_mec else "BAU"
# -------------------------------------------------------------------------
# Define BAU and MEC resolution maps
# -------------------------------------------------------------------------
bau_map = {
("exterior", None): (1600, 900),
("interior", None): (1600, 900),
("exterior", "layeroptionext"): (1600, 900),
("interior", "layeroptionint"): (1600, 900),
("exterior", "showroom"): (768, 432),
("carousel", "extra"): (678, 381),
("carousel", "powertrain"): (678, 381),
("carousel", "colour"): (148, 83),
("carousel", "bodystyle"): (678, 381),
("carousel", "series"): (678, 381),
("carousel", "trim"): (678, 381),
}
mec_map = {
("exterior", None): (1600, 1600),
("interior", None): (1600, 1600),
("exterior", "layeroptionext"): (1600, 1600),
("interior", "layeroptionint"): (1600, 1600),
("exterior", "showroom"): (768, 432), # Same as BAU
("carousel", "extra"): (1280, 720),
("carousel", "powertrain"): (1600, 1600),
("carousel", "colour"): (148, 83), # Not changed
("carousel", "bodystyle"): (678, 381),
("carousel", "series"): (678, 381),
("carousel", "trim"): (678, 381),
}
# -------------------------------------------------------------------------
# Helper to decide which resolution map to use for a given item.
# Even if the entire pack is MEC, we might override to BAU for certain items.
# -------------------------------------------------------------------------
def get_resolution_map_for_item(viewtype, imagetype, item_experience_condition, pack_experience_type):
"""
Returns (resolution_map, used_experience_type_string).
If the pack is MEC, we override to BAU when:
- viewtype in ["exterior", "interior"] AND no experienceCondition
OR
- imagetype == "powertrain"
Otherwise, use the pack_experience_type's map (MEC or BAU).
"""
if pack_experience_type == "MEC":
# Condition #1: viewtype is exterior/interior & no experienceCondition
# Condition #2: imagetype is powertrain
if ((item_experience_condition is None and viewtype in ("exterior", "interior"))
or imagetype == "powertrain"):
return bau_map, "BAU"
else:
return mec_map, "MEC"
else:
# If pack is BAU, always BAU
return bau_map, "BAU"
def get_required_resolution(conditions, pack_experience_type):
"""
Determine the required resolution for the item based on the *actual*
resolution map used, which might be MEC or BAU due to overrides.
Returns ( (width, height), used_experience_type ).
"""
viewtype = conditions.get("viewtype")
imagetype = conditions.get("imagetype", None)
item_experience_condition = conditions.get("experienceCondition")
resolution_map, used_experience_type = get_resolution_map_for_item(
viewtype,
imagetype,
item_experience_condition,
pack_experience_type
)
key = (viewtype, imagetype)
# If the exact key isn't in the map, try a fallback without imagetype
if key not in resolution_map:
fallback_key = (viewtype, None)
if fallback_key in resolution_map:
return resolution_map[fallback_key], used_experience_type
else:
return None, used_experience_type
return resolution_map[key], used_experience_type
# -------------------------------------------------------------------------
# Main check logic
# -------------------------------------------------------------------------
failed_images = []
for item in linkingrecord["items"]:
conditions = item.get("conditions", {})
required_res, used_experience_type = get_required_resolution(conditions, pack_experience_type)
if required_res is None:
# If we don't know what resolution to apply for this item => error
return {
"status": "error",
"error_message": f"No known resolution for conditions: {conditions}"
}
expected_width, expected_height = required_res
records = item.get("records", [])
for record in records:
assets = record.get("assets", [])
for asset in assets:
filename = asset.get("filename")
if not filename:
continue
image_path = os.path.join(working_dir, filename)
if not os.path.exists(image_path):
# Ignore missing files for this check
continue
# Open image and check resolution
try:
with Image.open(image_path) as img:
width, height = img.size
except Exception:
# Can't open or read the file => skip
continue
if width != expected_width or height != expected_height:
failed_images.append({
"filename": filename,
"viewtype": conditions.get("viewtype"),
"imagetype": conditions.get("imagetype"),
"used_experience_type": used_experience_type,
"expected_resolution": f"{expected_width}x{expected_height}",
"actual_resolution": f"{width}x{height}"
})
if failed_images:
return {
"status": "failed",
"details": {
"failed_images": failed_images
}
}
return {
"status": "passed",
"details": {
"message": "All present images match their required resolution."
}
}