Reinforcement Learning
Diffusers
Safetensors
English
image-quality-assessment
vision-language
image-editing
Instructions to use RobinY99/MR-IQA-2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use RobinY99/MR-IQA-2 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("RobinY99/MR-IQA-2", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
| #!/usr/bin/env python3 | |
| """Assess, edit, and evaluate one image with the released MR-IQA-2 models. | |
| Actor, Editor, and Judge run sequentially, so all three stages can reuse one | |
| physical GPU without sharing model memory. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import math | |
| import os | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import Any, Callable | |
| REPO_ID = "RobinY99/MR-IQA-2" | |
| SEED = 764952063587760 | |
| ROOT = Path(__file__).resolve().parents[1] | |
| if str(ROOT) not in sys.path: | |
| sys.path.insert(0, str(ROOT)) | |
| def parse_args(argv: list[str] | None = None) -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description="Run MR-IQA-2 Actor, Editor, and Judge on one GPU." | |
| ) | |
| parser.add_argument("image", help="Input image") | |
| parser.add_argument("--output-dir", default="outputs/quick_start") | |
| parser.add_argument("--gpu", default="0", help="Physical GPU index") | |
| parser.add_argument("--actor-env", default="mr_iqa_actor_judge") | |
| parser.add_argument("--editor-env", default="mr_iqa_editor") | |
| parser.add_argument("--actor-python", default="") | |
| parser.add_argument("--editor-python", default="") | |
| parser.add_argument("--actor-model", default=REPO_ID) | |
| parser.add_argument("--actor-subfolder", default="actor") | |
| parser.add_argument("--judge-model", default=REPO_ID) | |
| parser.add_argument("--judge-subfolder", default="judge") | |
| parser.add_argument( | |
| "--editor-model", | |
| default="", | |
| help="Local Editor directory; omitted means REPO_ID/editor", | |
| ) | |
| parser.add_argument( | |
| "--revision", | |
| default="", | |
| help="Optional Hugging Face revision; defaults to the current model release", | |
| ) | |
| parser.add_argument("--max-pixels", type=int, default=196608) | |
| parser.add_argument("--local-files-only", action="store_true") | |
| parser.add_argument( | |
| "--stage", | |
| choices=("actor", "editor", "judge"), | |
| help=argparse.SUPPRESS, | |
| ) | |
| args = parser.parse_args(argv) | |
| if args.max_pixels < 256: | |
| parser.error("--max-pixels must be at least 256") | |
| return args | |
| def _actor_stage(args: argparse.Namespace, image_path: Path, output_dir: Path) -> None: | |
| from actor_to_editor import ( | |
| atomic_write_json, | |
| atomic_write_text, | |
| generate_actor_completion, | |
| parse_valid_actor_output, | |
| ) | |
| actor_args = argparse.Namespace( | |
| actor_model=args.actor_model, | |
| actor_subfolder=args.actor_subfolder, | |
| actor_revision=( | |
| "" | |
| if Path(args.actor_model).expanduser().is_dir() | |
| else args.revision | |
| ), | |
| device="cuda:0", | |
| dtype="bfloat16", | |
| attn_implementation="sdpa", | |
| max_new_tokens=512, | |
| max_pixels=args.max_pixels, | |
| seed=SEED, | |
| local_files_only=args.local_files_only, | |
| ) | |
| completion = generate_actor_completion(image_path, actor_args) | |
| payload = parse_valid_actor_output(completion) | |
| atomic_write_text(output_dir / "actor_raw.txt", completion) | |
| atomic_write_json(output_dir / "assessment.json", payload) | |
| print(json.dumps(payload, ensure_ascii=False, indent=2)) | |
| def _editor_size(width: int, height: int, max_pixels: int) -> tuple[int, int]: | |
| scale = min(1.0, math.sqrt(float(max_pixels) / float(width * height))) | |
| resized_width = max(16, int(width * scale) // 16 * 16) | |
| resized_height = max(16, int(height * scale) // 16 * 16) | |
| return resized_width, resized_height | |
| def _editor_stage(args: argparse.Namespace, image_path: Path, output_dir: Path) -> None: | |
| import torch | |
| from diffusers import Flux2KleinPipeline | |
| from huggingface_hub import snapshot_download | |
| from PIL import Image | |
| from actor_to_editor import atomic_write_json | |
| assessment_path = output_dir / "assessment.json" | |
| assessment = json.loads(assessment_path.read_text(encoding="utf-8")) | |
| reasoning = assessment.get("reasoning") | |
| if not isinstance(reasoning, dict): | |
| raise ValueError("assessment.json has no reasoning object") | |
| solution = reasoning.get("solution") | |
| if not isinstance(solution, str) or not solution.strip(): | |
| raise ValueError("assessment.json has no usable solution") | |
| if args.editor_model: | |
| editor_path = Path(args.editor_model).expanduser().resolve(strict=True) | |
| else: | |
| download_kwargs: dict[str, Any] = { | |
| "repo_id": REPO_ID, | |
| "allow_patterns": ["editor/**"], | |
| "local_files_only": args.local_files_only, | |
| } | |
| if args.revision: | |
| download_kwargs["revision"] = args.revision | |
| snapshot = Path( | |
| snapshot_download(**download_kwargs) | |
| ) | |
| editor_path = snapshot / "editor" | |
| editor = Flux2KleinPipeline.from_pretrained( | |
| editor_path, | |
| torch_dtype=torch.bfloat16, | |
| local_files_only=True, | |
| ).to("cuda") | |
| with Image.open(image_path) as opened: | |
| source = opened.convert("RGB") | |
| edit_width, edit_height = _editor_size( | |
| source.width, | |
| source.height, | |
| args.max_pixels, | |
| ) | |
| editor_input = source.resize( | |
| (edit_width, edit_height), | |
| Image.Resampling.LANCZOS, | |
| ) | |
| with torch.inference_mode(): | |
| edited = editor( | |
| prompt=solution, | |
| image=editor_input, | |
| width=edit_width, | |
| height=edit_height, | |
| num_inference_steps=4, | |
| sigmas=[1.0, 0.75, 0.5, 0.25], | |
| guidance_scale=1.0, | |
| generator=torch.Generator(device="cuda").manual_seed(SEED), | |
| max_sequence_length=512, | |
| text_encoder_out_layers=(9, 18, 27), | |
| ).images[0].convert("RGB") | |
| if edited.size != source.size: | |
| edited = edited.resize(source.size, Image.Resampling.LANCZOS) | |
| edited_path = output_dir / "edited.png" | |
| edited.save(edited_path) | |
| result = { | |
| "input_image": str(image_path), | |
| "assessment": assessment, | |
| "edited_image": str(edited_path), | |
| "actor_model": ( | |
| f"{args.actor_model}/{args.actor_subfolder}" | |
| if args.actor_subfolder and not Path(args.actor_model).expanduser().is_dir() | |
| else args.actor_model | |
| ), | |
| "editor_model": args.editor_model or f"{REPO_ID}/editor", | |
| "revision": args.revision or None, | |
| "seed": SEED, | |
| "original_size": [source.width, source.height], | |
| "inference_size": [edit_width, edit_height], | |
| "solution_forwarded_verbatim": ( | |
| solution == assessment["reasoning"]["solution"] | |
| ), | |
| "judge": None, | |
| } | |
| atomic_write_json(output_dir / "result.json", result) | |
| def _quality_delta(original: dict[str, Any], edited: dict[str, Any]) -> float: | |
| original_score = original.get("mean") | |
| edited_score = edited.get("mean") | |
| if not isinstance(original_score, (int, float)): | |
| raise ValueError("Judge did not return a valid score for the input image") | |
| if not isinstance(edited_score, (int, float)): | |
| raise ValueError("Judge did not return a valid score for the edited image") | |
| return float(edited_score) - float(original_score) | |
| def _resolve_model_subfolder( | |
| model: str, | |
| subfolder: str, | |
| revision: str, | |
| local_files_only: bool, | |
| ) -> Path: | |
| from huggingface_hub import snapshot_download | |
| local_path = Path(model).expanduser() | |
| if local_path.is_dir(): | |
| candidate = ( | |
| local_path | |
| if (local_path / "config.json").is_file() | |
| else local_path / subfolder | |
| ) | |
| return candidate.resolve(strict=True) | |
| download_kwargs: dict[str, Any] = { | |
| "repo_id": model, | |
| "allow_patterns": [f"{subfolder}/**"], | |
| "local_files_only": local_files_only, | |
| } | |
| if revision: | |
| download_kwargs["revision"] = revision | |
| snapshot = Path(snapshot_download(**download_kwargs)) | |
| return (snapshot / subfolder).resolve(strict=True) | |
| def _judge_stage(args: argparse.Namespace, image_path: Path, output_dir: Path) -> None: | |
| from actor_to_editor import atomic_write_json | |
| judge_path = _resolve_model_subfolder( | |
| args.judge_model, | |
| args.judge_subfolder, | |
| args.revision, | |
| args.local_files_only, | |
| ) | |
| os.environ["VF_JUDGE_MODEL_PATH"] = str(judge_path) | |
| os.environ["VF_JUDGE_MODEL_ID"] = "mr-iqa-2-e5-judge" | |
| os.environ["VF_JUDGE_PROMPT_SCHEMA"] = "e5_training_reasoning_v5" | |
| os.environ["VF_JUDGER_MAX_BATCH_SIZE"] = "1" | |
| from judge.server import FrozenJudger | |
| edited_path = (output_dir / "edited.png").resolve(strict=True) | |
| judger = FrozenJudger(str(judge_path)) | |
| original = judger.score_image(str(image_path), repeats=1) | |
| edited = judger.score_image(str(edited_path), repeats=1) | |
| delta = _quality_delta(original, edited) | |
| evaluation = { | |
| "j0": original["mean"], | |
| "j1": edited["mean"], | |
| "j1_minus_j0": delta, | |
| "original": original, | |
| "edited": edited, | |
| "judge_model": str(judge_path), | |
| "revision": args.revision or None, | |
| } | |
| atomic_write_json(output_dir / "evaluation.json", evaluation) | |
| result_path = output_dir / "result.json" | |
| result = json.loads(result_path.read_text(encoding="utf-8")) | |
| result["judge"] = evaluation | |
| atomic_write_json(result_path, result) | |
| def _stage_command( | |
| *, | |
| conda: str, | |
| environment: str, | |
| python_executable: str, | |
| stage: str, | |
| args: argparse.Namespace, | |
| image_path: Path, | |
| output_dir: Path, | |
| ) -> list[str]: | |
| command = ( | |
| [python_executable] | |
| if python_executable | |
| else [conda, "run", "--no-capture-output", "-n", environment, "python"] | |
| ) | |
| command.extend( | |
| [ | |
| str(Path(__file__).resolve()), | |
| str(image_path), | |
| "--output-dir", | |
| str(output_dir), | |
| "--gpu", | |
| "0", | |
| "--max-pixels", | |
| str(args.max_pixels), | |
| "--actor-model", | |
| args.actor_model, | |
| "--actor-subfolder", | |
| args.actor_subfolder, | |
| "--judge-model", | |
| args.judge_model, | |
| "--judge-subfolder", | |
| args.judge_subfolder, | |
| "--editor-model", | |
| args.editor_model, | |
| "--stage", | |
| stage, | |
| ] | |
| ) | |
| if args.local_files_only: | |
| command.append("--local-files-only") | |
| if args.revision: | |
| command.extend(["--revision", args.revision]) | |
| return command | |
| def run_sequential( | |
| args: argparse.Namespace, | |
| *, | |
| command_runner: Callable[..., Any] = subprocess.run, | |
| ) -> dict[str, Any]: | |
| image_path = Path(args.image).expanduser().resolve(strict=True) | |
| if not image_path.is_file(): | |
| raise FileNotFoundError(f"input image is not a file: {image_path}") | |
| output_dir = Path(args.output_dir).expanduser().resolve() | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| conda = shutil.which("conda") or "" | |
| if not conda and (not args.actor_python or not args.editor_python): | |
| raise RuntimeError("conda is required to run the two validated environments") | |
| child_environment = os.environ.copy() | |
| child_environment["CUDA_VISIBLE_DEVICES"] = str(args.gpu) | |
| child_environment["PYTHONUNBUFFERED"] = "1" | |
| for stage, environment, python_executable in ( | |
| ("actor", args.actor_env, args.actor_python), | |
| ("editor", args.editor_env, args.editor_python), | |
| ("judge", args.actor_env, args.actor_python), | |
| ): | |
| command_runner( | |
| _stage_command( | |
| conda=conda, | |
| environment=environment, | |
| python_executable=python_executable, | |
| stage=stage, | |
| args=args, | |
| image_path=image_path, | |
| output_dir=output_dir, | |
| ), | |
| check=True, | |
| env=child_environment, | |
| ) | |
| return json.loads((output_dir / "result.json").read_text(encoding="utf-8")) | |
| def main(argv: list[str] | None = None) -> int: | |
| args = parse_args(argv) | |
| image_path = Path(args.image).expanduser().resolve(strict=True) | |
| output_dir = Path(args.output_dir).expanduser().resolve() | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| if args.stage == "actor": | |
| _actor_stage(args, image_path, output_dir) | |
| return 0 | |
| if args.stage == "editor": | |
| _editor_stage(args, image_path, output_dir) | |
| return 0 | |
| if args.stage == "judge": | |
| _judge_stage(args, image_path, output_dir) | |
| return 0 | |
| result = run_sequential(args) | |
| print(json.dumps(result, ensure_ascii=False, indent=2)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |