supli6669 commited on
Commit
f9f499d
·
1 Parent(s): fd6554d

feat: complete custom training pipeline bugfixes, dataset crawling, and validation

Browse files
app.py CHANGED
@@ -440,6 +440,16 @@ with st.sidebar:
440
  except Exception as e:
441
  st.error(f"Failed to import settings: {e}")
442
 
 
 
 
 
 
 
 
 
 
 
443
  # ── Training Dashboard ──────────────────────────────────────────────────────
444
  st.markdown("<div class='sidebar-section'>📊 Training Dashboard</div>", unsafe_allow_html=True)
445
  status = get_training_status()
@@ -605,35 +615,37 @@ with tab_single:
605
 
606
  st.markdown("<hr>", unsafe_allow_html=True)
607
 
608
- start_time = time.time()
609
- # Async processing using a background thread
610
- if not st.session_state.get('processing'):
611
- # Initialize state
612
- st.session_state.processing = True
613
- st.session_state.progress_state = {'stage': None, 'progress': 0.0, 'message': '', 'active': False}
614
-
615
- # Progress bar and status
616
- progress_bar = st.progress(0)
617
- status_text = st.empty()
618
- stage_text = st.empty()
619
-
620
- # Cancel button
621
- cancel_col, _ = st.columns([1, 3])
622
- with cancel_col:
623
- cancel_button = st.button("❌ Cancel", type="secondary")
624
-
625
- if cancel_button:
626
- st.session_state.progress_state['cancelled'] = True
627
- st.warning("⚠️ Processing cancelled by user")
628
- st.stop()
629
-
630
- result_container = {}
631
- def _run():
632
- max_retries = 2
633
- retry_count = 0
634
- last_error = None
 
 
635
 
636
- while retry_count <= max_retries:
637
  try:
638
  result = pipeline.process_image(
639
  img,
@@ -649,68 +661,78 @@ with tab_single:
649
  batch_size=4,
650
  face_restore=enable_face_restoration
651
  )
652
- result_container['enhanced_img'] = result
653
- break
 
 
 
654
  except Exception as e:
655
- last_error = e
656
- retry_count += 1
657
- if retry_count <= max_retries:
658
- st.session_state.progress_state['message'] = f"Retry {retry_count}/{max_retries}: {str(e)[:50]}..."
659
- time.sleep(1)
660
- else:
661
- result_container['error'] = str(e)
662
- finally:
663
- st.session_state.processing = False
664
- st.session_state.progress_state['active'] = False
665
-
666
- threading.Thread(target=_run, daemon=True).start()
667
-
668
- # Wait for result (polling with progress updates)
669
- stage_names = {
670
- 'initialization': '🔧 Initializing',
671
- 'detection': '👁️ Detecting Faces',
672
- 'background': '🖼️ Upscaling Background',
673
- 'restoration': '✨ Restoring Faces',
674
- 'blending': '🎨 Blending Faces',
675
- 'complete': '✅ Complete'
676
- }
677
-
678
- while st.session_state.processing:
679
- time.sleep(0.1)
680
- progress_data = st.session_state.progress_state
681
 
682
- # Check for cancellation
683
- if progress_data.get('cancelled', False):
684
- st.session_state.processing = False
685
- st.warning("⚠Processing cancelled by user")
686
- progress_bar.empty()
687
- status_text.empty()
688
- stage_text.empty()
689
- st.stop()
690
 
691
- if progress_data['active']:
 
692
  stage_name = stage_names.get(progress_data['stage'], progress_data['stage'])
693
  stage_text.text(f"**{stage_name}**")
694
  status_text.text(progress_data['message'])
695
  progress_bar.progress(progress_data['progress'])
696
  else:
697
  status_text.text('⏳ Starting...')
698
-
699
- enhanced_img = result_container.get('enhanced_img')
700
- process_duration = time.time() - start_time
701
-
702
- # Clear progress UI
703
- progress_bar.empty()
704
- status_text.empty()
705
- stage_text.empty()
706
-
707
- # Check for errors
708
- if 'error' in result_container:
709
- st.error(f" Processing failed after retries: {result_container['error']}")
710
- st.info("💡 Try reducing the upscale factor or disabling some features.")
 
 
 
711
  st.stop()
712
-
713
- # Save to history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  history_item = {
715
  'name': img_name,
716
  'timestamp': time.time(),
@@ -725,12 +747,9 @@ with tab_single:
725
  'enhanced_shape': enhanced_img.shape[:2]
726
  }
727
  st.session_state.history.insert(0, history_item)
728
- # Keep only last 10 items
729
  if len(st.session_state.history) > 10:
730
  st.session_state.history = st.session_state.history[:10]
731
- else:
732
- st.warning('Processing is already running. Please wait.')
733
- st.stop()
734
 
735
  h_orig, w_orig = img.shape[:2]
736
  h_enh, w_enh = enhanced_img.shape[:2]
@@ -853,6 +872,9 @@ with tab_batch:
853
  )
854
  if uploaded_files:
855
  if st.button("🚀 Process Batch", key="batch_button"):
 
 
 
856
  import zipfile
857
  from io import BytesIO
858
 
 
440
  except Exception as e:
441
  st.error(f"Failed to import settings: {e}")
442
 
443
+ if st.button("🔄 Reset UI State"):
444
+ st.session_state.processing = False
445
+ st.session_state.enhanced_img = None
446
+ st.session_state.processing_error = None
447
+ st.session_state.last_run_params = None
448
+ if pipeline:
449
+ pipeline.cancel_flag = False
450
+ st.success("UI State reset successfully!")
451
+ st.rerun()
452
+
453
  # ── Training Dashboard ──────────────────────────────────────────────────────
454
  st.markdown("<div class='sidebar-section'>📊 Training Dashboard</div>", unsafe_allow_html=True)
455
  status = get_training_status()
 
615
 
616
  st.markdown("<hr>", unsafe_allow_html=True)
617
 
618
+ # Track parameters to auto-rerun if they change
619
+ current_params = {
620
+ 'img_name': img_name,
621
+ 'w': fidelity_weight,
622
+ 'detection_model': face_detector,
623
+ 'upscale': upscale_factor,
624
+ 'blend_softness': blend_softness,
625
+ 'bg_upsampler': 'realesrgan' if bg_upscale_toggle else None,
626
+ 'det_threshold': det_threshold,
627
+ 'sharpen_amount': sharpen_amount,
628
+ 'face_upsample': face_upscale_toggle,
629
+ 'face_restore': enable_face_restoration
630
+ }
631
+
632
+ if st.session_state.get('last_run_params') != current_params:
633
+ st.session_state.enhanced_img = None
634
+ st.session_state.processing_error = None
635
+ st.session_state.processing = False
636
+ st.session_state.process_duration = None
637
+ if pipeline:
638
+ pipeline.cancel_flag = False
639
+
640
+ if st.session_state.enhanced_img is None and st.session_state.get('processing_error') is None:
641
+ if not st.session_state.get('processing'):
642
+ st.session_state.processing = True
643
+ st.session_state.progress_state = {'stage': 'initialization', 'progress': 0.0, 'message': 'Starting...', 'active': True, 'cancelled': False}
644
+ st.session_state.start_time = time.time()
645
+ if pipeline:
646
+ pipeline.cancel_flag = False
647
 
648
+ def _run():
649
  try:
650
  result = pipeline.process_image(
651
  img,
 
661
  batch_size=4,
662
  face_restore=enable_face_restoration
663
  )
664
+ if pipeline.cancel_flag:
665
+ return
666
+ st.session_state.enhanced_img = result
667
+ st.session_state.process_duration = time.time() - st.session_state.start_time
668
+ st.session_state.last_run_params = current_params
669
  except Exception as e:
670
+ import traceback
671
+ traceback.print_exc()
672
+ st.session_state.processing_error = str(e)
673
+ finally:
674
+ st.session_state.processing = False
675
+ st.session_state.progress_state['active'] = False
676
+
677
+ threading.Thread(target=_run, daemon=True).start()
678
+ st.rerun()
679
+
680
+ else:
681
+ # We are actively processing
682
+ progress_bar = st.progress(0)
683
+ status_text = st.empty()
684
+ stage_text = st.empty()
 
 
 
 
 
 
 
 
 
 
 
685
 
686
+ stage_names = {
687
+ 'initialization': '🔧 Initializing',
688
+ 'detection': '👁️ Detecting Faces',
689
+ 'background': '🖼Upscaling Background',
690
+ 'restoration': '✨ Restoring Faces',
691
+ 'blending': '🎨 Blending Faces',
692
+ 'complete': '✅ Complete'
693
+ }
694
 
695
+ progress_data = st.session_state.progress_state
696
+ if progress_data.get('active'):
697
  stage_name = stage_names.get(progress_data['stage'], progress_data['stage'])
698
  stage_text.text(f"**{stage_name}**")
699
  status_text.text(progress_data['message'])
700
  progress_bar.progress(progress_data['progress'])
701
  else:
702
  status_text.text('⏳ Starting...')
703
+
704
+ # Render Cancel button
705
+ cancel_col, _ = st.columns([1, 3])
706
+ with cancel_col:
707
+ cancel_button = st.button("❌ Cancel", type="secondary")
708
+
709
+ if cancel_button:
710
+ if pipeline:
711
+ pipeline.cancel_flag = True
712
+ st.session_state.processing = False
713
+ st.session_state.progress_state['cancelled'] = True
714
+ st.warning("⚠️ Processing cancelled by user")
715
+ st.rerun()
716
+
717
+ time.sleep(0.1)
718
+ st.rerun()
719
  st.stop()
720
+
721
+ if st.session_state.get('processing_error') is not None:
722
+ st.error(f"❌ Processing failed: {st.session_state.processing_error}")
723
+ st.info("💡 Try reducing the upscale factor or disabling some features.")
724
+ if st.button("🔄 Try Again"):
725
+ st.session_state.processing_error = None
726
+ st.session_state.processing = False
727
+ st.rerun()
728
+ st.stop()
729
+
730
+ # If we reach here, we have the enhanced image
731
+ enhanced_img = st.session_state.enhanced_img
732
+ process_duration = st.session_state.process_duration
733
+
734
+ # Save to history
735
+ if st.session_state.get('history_added_for') != current_params:
736
  history_item = {
737
  'name': img_name,
738
  'timestamp': time.time(),
 
747
  'enhanced_shape': enhanced_img.shape[:2]
748
  }
749
  st.session_state.history.insert(0, history_item)
 
750
  if len(st.session_state.history) > 10:
751
  st.session_state.history = st.session_state.history[:10]
752
+ st.session_state.history_added_for = current_params
 
 
753
 
754
  h_orig, w_orig = img.shape[:2]
755
  h_enh, w_enh = enhanced_img.shape[:2]
 
872
  )
873
  if uploaded_files:
874
  if st.button("🚀 Process Batch", key="batch_button"):
875
+ if pipeline is None:
876
+ st.error("Cannot process batch: The AI Enhancer pipeline is offline or failed to initialize. Please check the logs.")
877
+ st.stop()
878
  import zipfile
879
  from io import BytesIO
880
 
datasets/crop_and_align_facexlib.py CHANGED
@@ -16,9 +16,11 @@ from facelib.utils.face_restoration_helper import FaceRestoreHelper
16
  # Configuration
17
  # ----------------------------------------------------------------------
18
  SRC_ROOT = os.path.join(project_dir, "datasets", "game_characters")
19
- DST_ROOT = os.path.join(project_dir, "datasets", "ffhq", "ffhq_512")
20
  DETECTOR = "retinaface_mobile0.25" # Fast CPU detector
21
  BLUR_THRESHOLD = 80.0 # Discard crops with Laplacian variance below this
 
 
22
 
23
  def is_blurry(img, threshold=BLUR_THRESHOLD):
24
  """Check if an image is blurry using the variance of Laplacian method."""
@@ -33,9 +35,7 @@ def main():
33
  # Configure face detector path
34
  os.environ['FACE_DETECTOR_PATH'] = os.path.join(project_dir, "weights", "facelib")
35
 
36
- # Clear and recreate destination folder
37
- if os.path.exists(DST_ROOT):
38
- shutil.rmtree(DST_ROOT)
39
  os.makedirs(DST_ROOT, exist_ok=True)
40
 
41
  # Initialize face helper
@@ -61,6 +61,8 @@ def main():
61
  count = 0
62
  failures = 0
63
  skipped_blur = 0
 
 
64
 
65
  for i, img_path in enumerate(raw_images):
66
  try:
@@ -69,6 +71,12 @@ def main():
69
  failures += 1
70
  continue
71
 
 
 
 
 
 
 
72
  face_helper.clean_all()
73
  face_helper.read_image(img)
74
 
@@ -87,18 +95,29 @@ def main():
87
  face_helper.align_warp_face()
88
 
89
  # Save cropped faces
90
- for cropped_face in face_helper.cropped_faces:
 
 
 
 
 
 
 
 
 
 
 
91
  # Blur filter check
92
  if is_blurry(cropped_face):
93
  skipped_blur += 1
94
  continue
95
 
96
  count += 1
97
- dest_path = os.path.join(DST_ROOT, f"face_{count:06d}.png")
98
  cv2.imwrite(dest_path, cropped_face)
99
 
100
  if (i + 1) % 100 == 0:
101
- print(f" Processed {i + 1}/{len(raw_images)} images. Extracted {count} faces (skipped {skipped_blur} blurry)...")
102
 
103
  except Exception as e:
104
  # print(f"Error processing {img_path}: {e}")
@@ -108,6 +127,8 @@ def main():
108
  print(f" Total raw images: {len(raw_images)}")
109
  print(f" Extracted face crops: {count}")
110
  print(f" Skipped blurry crops: {skipped_blur}")
 
 
111
  print(f" Failed loads: {failures}")
112
  print(f" Destination: {DST_ROOT}\n")
113
 
 
16
  # Configuration
17
  # ----------------------------------------------------------------------
18
  SRC_ROOT = os.path.join(project_dir, "datasets", "game_characters")
19
+ DST_ROOT = os.path.join(project_dir, "models", "CodeFormer", "datasets", "ffhq", "ffhq_512")
20
  DETECTOR = "retinaface_mobile0.25" # Fast CPU detector
21
  BLUR_THRESHOLD = 80.0 # Discard crops with Laplacian variance below this
22
+ MIN_RESOLUTION = 128 # Discard raw images smaller than this
23
+ MIN_FACE_COVERAGE = 0.15 # Discard face crops that cover less than 15% of the original image area
24
 
25
  def is_blurry(img, threshold=BLUR_THRESHOLD):
26
  """Check if an image is blurry using the variance of Laplacian method."""
 
35
  # Configure face detector path
36
  os.environ['FACE_DETECTOR_PATH'] = os.path.join(project_dir, "weights", "facelib")
37
 
38
+ # Ensure destination folder exists
 
 
39
  os.makedirs(DST_ROOT, exist_ok=True)
40
 
41
  # Initialize face helper
 
61
  count = 0
62
  failures = 0
63
  skipped_blur = 0
64
+ skipped_res = 0
65
+ skipped_coverage = 0
66
 
67
  for i, img_path in enumerate(raw_images):
68
  try:
 
71
  failures += 1
72
  continue
73
 
74
+ # Resolution check
75
+ h_img, w_img, _ = img.shape
76
+ if h_img < MIN_RESOLUTION or w_img < MIN_RESOLUTION:
77
+ skipped_res += 1
78
+ continue
79
+
80
  face_helper.clean_all()
81
  face_helper.read_image(img)
82
 
 
95
  face_helper.align_warp_face()
96
 
97
  # Save cropped faces
98
+ for idx, cropped_face in enumerate(face_helper.cropped_faces):
99
+ # Face coverage check
100
+ if hasattr(face_helper, 'face_det_list') and idx < len(face_helper.face_det_list):
101
+ det_box = face_helper.face_det_list[idx]
102
+ x1, y1, x2, y2 = det_box[:4]
103
+ face_area = (x2 - x1) * (y2 - y1)
104
+ img_area = w_img * h_img
105
+ ratio = face_area / img_area
106
+ if ratio < MIN_FACE_COVERAGE:
107
+ skipped_coverage += 1
108
+ continue
109
+
110
  # Blur filter check
111
  if is_blurry(cropped_face):
112
  skipped_blur += 1
113
  continue
114
 
115
  count += 1
116
+ dest_path = os.path.join(DST_ROOT, f"face_align_{count:06d}.png")
117
  cv2.imwrite(dest_path, cropped_face)
118
 
119
  if (i + 1) % 100 == 0:
120
+ print(f" Processed {i + 1}/{len(raw_images)} images. Extracted {count} faces (skipped {skipped_blur} blurry, {skipped_coverage} small, {skipped_res} low-res)...")
121
 
122
  except Exception as e:
123
  # print(f"Error processing {img_path}: {e}")
 
127
  print(f" Total raw images: {len(raw_images)}")
128
  print(f" Extracted face crops: {count}")
129
  print(f" Skipped blurry crops: {skipped_blur}")
130
+ print(f" Skipped low-res images: {skipped_res}")
131
+ print(f" Skipped small face crops: {skipped_coverage}")
132
  print(f" Failed loads: {failures}")
133
  print(f" Destination: {DST_ROOT}\n")
134
 
datasets/resize_and_clean.py CHANGED
@@ -10,7 +10,7 @@ import os, cv2, glob
10
  # Source folder containing the raw character folders
11
  SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "game_characters"))
12
  # Destination folder expected by CodeFormer
13
- DST_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "ffhq", "ffhq_512"))
14
 
15
  os.makedirs(DST_ROOT, exist_ok=True)
16
 
 
10
  # Source folder containing the raw character folders
11
  SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "game_characters"))
12
  # Destination folder expected by CodeFormer
13
+ DST_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "models", "CodeFormer", "datasets", "ffhq", "ffhq_512"))
14
 
15
  os.makedirs(DST_ROOT, exist_ok=True)
16
 
download_starrail.py CHANGED
@@ -355,7 +355,22 @@ def main():
355
  print("#" * 60)
356
  # A. Official Skins (113 characters)
357
  download_official_skins("bluearchive", bluearchive_dir)
358
- # B. Safebooru Mix
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  download_from_safebooru("blue_archive", bluearchive_dir, "ba_general", 300)
360
 
361
  # ─────────────────────────────────────────────────────────────
 
355
  print("#" * 60)
356
  # A. Official Skins (113 characters)
357
  download_official_skins("bluearchive", bluearchive_dir)
358
+ # B. Safebooru Characters
359
+ ba_chars = {
360
+ "shiroko": "shiroko_(blue_archive)",
361
+ "aru": "aru_(blue_archive)",
362
+ "hoshino": "hoshino_(blue_archive)",
363
+ "nonomi": "nonomi_(blue_archive)",
364
+ "serika": "serika_(blue_archive)",
365
+ "momoi": "momoi_(blue_archive)",
366
+ "midori": "midori_(blue_archive)",
367
+ "yuzu": "yuzu_(blue_archive)",
368
+ "mutsuki": "mutsuki_(blue_archive)",
369
+ "iori": "iori_(blue_archive)"
370
+ }
371
+ for key, tag in ba_chars.items():
372
+ download_from_safebooru(tag, bluearchive_dir, f"ba_{key}", 30)
373
+ # C. Safebooru Mix
374
  download_from_safebooru("blue_archive", bluearchive_dir, "ba_general", 300)
375
 
376
  # ─────────────────────────────────────────────────────────────
pipeline.py CHANGED
@@ -38,6 +38,7 @@ class LocalAIEnhancerPipeline:
38
  self.device = torch.device(device)
39
 
40
  self.progress_callback = progress_callback
 
41
 
42
  print(f"[Pipeline] Initializing pipeline on device: {self.device}")
43
 
@@ -104,12 +105,7 @@ class LocalAIEnhancerPipeline:
104
 
105
  def _check_cancelled(self):
106
  """Check if processing was cancelled by user."""
107
- if self.progress_callback:
108
- # Check session state through callback
109
- import streamlit as st
110
- if hasattr(st, 'session_state') and 'progress_state' in st.session_state:
111
- return st.session_state.progress_state.get('cancelled', False)
112
- return False
113
 
114
  def _get_onnx_session(self, path, providers=None):
115
  """Get or create cached ONNX session."""
@@ -154,7 +150,7 @@ class LocalAIEnhancerPipeline:
154
 
155
  def run_onnx_batch(self, faces_np, w_val):
156
  """Helper to run ONNX batch inference."""
157
- w_np = np.full((faces_np.shape[0], 1), w_val, dtype=np.float32)
158
  ort_inputs = {
159
  self.ort_session_cf.get_inputs()[0].name: faces_np,
160
  self.ort_session_cf.get_inputs()[1].name: w_np
 
38
  self.device = torch.device(device)
39
 
40
  self.progress_callback = progress_callback
41
+ self.cancel_flag = False
42
 
43
  print(f"[Pipeline] Initializing pipeline on device: {self.device}")
44
 
 
105
 
106
  def _check_cancelled(self):
107
  """Check if processing was cancelled by user."""
108
+ return self.cancel_flag
 
 
 
 
 
109
 
110
  def _get_onnx_session(self, path, providers=None):
111
  """Get or create cached ONNX session."""
 
150
 
151
  def run_onnx_batch(self, faces_np, w_val):
152
  """Helper to run ONNX batch inference."""
153
+ w_np = np.full((faces_np.shape[0],), w_val, dtype=np.float32)
154
  ort_inputs = {
155
  self.ort_session_cf.get_inputs()[0].name: faces_np,
156
  self.ort_session_cf.get_inputs()[1].name: w_np
requirements.txt CHANGED
@@ -116,3 +116,4 @@ websockets==16.0
116
  Werkzeug==3.1.8
117
  wheel==0.47.0
118
  yapf==0.43.0
 
 
116
  Werkzeug==3.1.8
117
  wheel==0.47.0
118
  yapf==0.43.0
119
+ streamlit-image-comparison
tools/download_ffhq_extended.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import io
4
+ import pandas as pd
5
+ from PIL import Image
6
+ import requests
7
+ import time
8
+
9
+ SHARDS = [
10
+ "train-00001-of-00054-c76023902046cea3.parquet",
11
+ "train-00002-of-00054-b43f6d454561b047.parquet",
12
+ "train-00003-of-00054-0637d6a1c5f28946.parquet",
13
+ "train-00004-of-00054-17a50ec36be02fa9.parquet",
14
+ "train-00005-of-00054-b370d8ca7905b127.parquet",
15
+ "train-00006-of-00054-e7f70526908ad428.parquet",
16
+ "train-00007-of-00054-36469d3079484e03.parquet",
17
+ "train-00008-of-00054-564af51540d658a8.parquet",
18
+ "train-00009-of-00054-57224ef56d82da23.parquet"
19
+ ]
20
+
21
+ def load_parquet_safe(url):
22
+ print(f" Loading: {url}")
23
+ try:
24
+ df = pd.read_parquet(url)
25
+ print(f" [OK] Loaded {len(df)} rows.")
26
+ return df
27
+ except Exception as e:
28
+ print(f" [FAIL] Failed: {e}")
29
+ return None
30
+
31
+ def find_image_column(df):
32
+ for col in df.columns:
33
+ sample = df[col].iloc[0]
34
+ if isinstance(sample, dict) and "bytes" in sample:
35
+ return col
36
+ if isinstance(sample, bytes):
37
+ return col
38
+ return None
39
+
40
+ def extract_image_bytes(cell):
41
+ if isinstance(cell, dict):
42
+ return cell.get("bytes")
43
+ if isinstance(cell, bytes):
44
+ return cell
45
+ return None
46
+
47
+ def main():
48
+ project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
49
+ codeformer_dir = os.path.join(project_dir, "models", "CodeFormer")
50
+ save_dir = os.path.join(codeformer_dir, "datasets", "ffhq", "ffhq_512", "faces")
51
+ os.makedirs(save_dir, exist_ok=True)
52
+
53
+ print("=" * 55)
54
+ print(" EXTENDED FFHQ REAL FACE DOWNLOADER")
55
+ print(f" Target directory: {save_dir}")
56
+ print("=" * 55)
57
+
58
+ # Check current count in faces subdirectory to determine start_idx
59
+ existing = [f for f in os.listdir(save_dir) if f.endswith('.png') and f.startswith('face')]
60
+ highest_idx = -1
61
+ for f in existing:
62
+ try:
63
+ idx = int(f.replace('face_', '').replace('.png', ''))
64
+ highest_idx = max(highest_idx, idx)
65
+ except ValueError:
66
+ continue
67
+ start_idx = highest_idx + 1
68
+ print(f"Starting index for new files: face_{start_idx:05d}.png")
69
+
70
+ images_per_shard = 300
71
+ total_saved = 0
72
+
73
+ for shard_idx, shard_name in enumerate(SHARDS):
74
+ print(f"\n[{shard_idx + 1}/{len(SHARDS)}] Processing {shard_name}...")
75
+ url = f"https://huggingface.co/datasets/Ryan-sjtu/ffhq512-caption/resolve/main/data/{shard_name}"
76
+
77
+ df = load_parquet_safe(url)
78
+ if df is None:
79
+ continue
80
+
81
+ img_col = find_image_column(df)
82
+ if img_col is None:
83
+ print(" No image column found in dataframe.")
84
+ continue
85
+
86
+ saved_in_shard = 0
87
+ for i in range(len(df)):
88
+ if saved_in_shard >= images_per_shard:
89
+ break
90
+ try:
91
+ cell = df[img_col].iloc[i]
92
+ img_bytes = extract_image_bytes(cell)
93
+ if not img_bytes:
94
+ continue
95
+
96
+ img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
97
+ img = img.resize((512, 512), Image.Resampling.LANCZOS)
98
+
99
+ filename = f"face_{start_idx + total_saved:05d}.png"
100
+ filepath = os.path.join(save_dir, filename)
101
+ img.save(filepath, "PNG")
102
+
103
+ saved_in_shard += 1
104
+ total_saved += 1
105
+
106
+ if saved_in_shard % 50 == 0 or saved_in_shard == images_per_shard:
107
+ print(f" Saved {saved_in_shard}/{images_per_shard} from this shard...")
108
+
109
+ except Exception as e:
110
+ print(f" [warn] Row {i} error: {e}")
111
+ continue
112
+
113
+ print(f" Shard completed. Saved {saved_in_shard} images.")
114
+ time.sleep(0.5)
115
+
116
+ print("\n" + "=" * 55)
117
+ print(" DOWNLOAD COMPLETED")
118
+ print(f" Total downloaded this run: {total_saved}")
119
+ print(f" Total images in directory: {len(os.listdir(save_dir))}")
120
+ print("=" * 55)
121
+
122
+ if __name__ == "__main__":
123
+ main()
tools/download_highres_datasets.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import urllib.request
5
+ import zipfile
6
+ import shutil
7
+ from PIL import Image
8
+
9
+ # Setup sys.path to bypass local "datasets" folder conflict
10
+ PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11
+ DATASET_BASE = os.path.join(PROJECT_DIR, "models", "CodeFormer", "datasets", "ffhq", "ffhq_512")
12
+
13
+ # Setup clean path for HuggingFace datasets
14
+ sys.path = [p for p in sys.path if p not in ('', '.')]
15
+ from datasets import load_dataset
16
+
17
+ def download_div2k(test_run=False):
18
+ print("\n" + "="*50)
19
+ print(" DOWNLOADING DIV2K DATASET (2K RESOLUTION)")
20
+ print("="*50)
21
+
22
+ target_dir = os.path.join(DATASET_BASE, "div2k")
23
+ os.makedirs(target_dir, exist_ok=True)
24
+
25
+ # Check if we already have images
26
+ existing = len([f for f in os.listdir(target_dir) if f.endswith(".png")])
27
+ if existing >= (5 if test_run else 800):
28
+ print(f"[OK] DIV2K already contains {existing} images. Skipping download.")
29
+ return
30
+
31
+ zip_url = "http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_HR.zip"
32
+ zip_path = os.path.join(PROJECT_DIR, "DIV2K_train_HR.zip")
33
+ extract_dir = os.path.join(PROJECT_DIR, "DIV2K_temp")
34
+
35
+ # In test mode we don't download 3.5GB zip, we just download 5 images directly if possible
36
+ if test_run:
37
+ print("[Test Mode] Downloading 5 sample images directly from DIV2K repo...")
38
+ for i in range(1, 6):
39
+ img_url = f"https://raw.githubusercontent.com/eugenesiow/super-image-data/master/div2k/DIV2K_train_HR/{i:04d}.png"
40
+ dest = os.path.join(target_dir, f"div2k_{i:04d}.png")
41
+ try:
42
+ urllib.request.urlretrieve(img_url, dest)
43
+ print(f" Saved sample {i}/5 -> {dest}")
44
+ except Exception as e:
45
+ print(f" Failed sample {i}: {e}")
46
+ return
47
+
48
+ print(f"Downloading {zip_url} (approx. 3.5 GB)...")
49
+ def report_hook(block_num, block_size, total_size):
50
+ read_so_far = block_num * block_size
51
+ if total_size > 0:
52
+ percent = min(100, read_so_far * 100 / total_size)
53
+ sys.stdout.write(f"\rProgress: {percent:.2f}% ({read_so_far / 1024 / 1024:.2f} MB of {total_size / 1024 / 1024:.2f} MB)")
54
+ else:
55
+ sys.stdout.write(f"\rProgress: {read_so_far / 1024 / 1024:.2f} MB")
56
+ sys.stdout.flush()
57
+
58
+ try:
59
+ urllib.request.urlretrieve(zip_url, zip_path, reporthook=report_hook)
60
+ print("\n[OK] DIV2K Zip downloaded. Extracting...")
61
+
62
+ os.makedirs(extract_dir, exist_ok=True)
63
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
64
+ zip_ref.extractall(extract_dir)
65
+
66
+ print("[*] Copying images to target directory...")
67
+ copied = 0
68
+ for root, _, files in os.walk(extract_dir):
69
+ for file in files:
70
+ if file.lower().endswith(".png"):
71
+ src = os.path.join(root, file)
72
+ dest = os.path.join(target_dir, f"div2k_{file}")
73
+ shutil.copy2(src, dest)
74
+ copied += 1
75
+
76
+ print(f"[OK] Successfully extracted and copied {copied} DIV2K images.")
77
+
78
+ except Exception as e:
79
+ print(f"\n[FAIL] Error occurred during DIV2K setup: {e}")
80
+ finally:
81
+ # Cleanup
82
+ try:
83
+ if os.path.exists(zip_path):
84
+ os.remove(zip_path)
85
+ if os.path.exists(extract_dir):
86
+ shutil.rmtree(extract_dir)
87
+ except Exception:
88
+ pass
89
+
90
+ def download_huggingface_dataset(repo_name, target_subdir, max_images, image_key='image', prefix='img', test_run=False):
91
+ print("\n" + "="*50)
92
+ print(f" STREAMING DATASET FROM HF: {repo_name}")
93
+ print("="*50)
94
+
95
+ target_dir = os.path.join(DATASET_BASE, target_subdir)
96
+ os.makedirs(target_dir, exist_ok=True)
97
+
98
+ limit = 5 if test_run else max_images
99
+
100
+ existing = len([f for f in os.listdir(target_dir) if f.endswith(".png")])
101
+ if existing >= limit:
102
+ print(f"[OK] {target_subdir} already contains {existing} images. Skipping.")
103
+ return
104
+
105
+ print(f"Streaming from HF hub. Target count: {limit} images...")
106
+ try:
107
+ ds = load_dataset(repo_name, split="train", streaming=True)
108
+ saved = 0
109
+ for i, item in enumerate(ds):
110
+ if saved >= limit:
111
+ break
112
+
113
+ dest = os.path.join(target_dir, f"{prefix}_{saved:06d}.png")
114
+ if os.path.exists(dest):
115
+ saved += 1
116
+ continue
117
+
118
+ try:
119
+ # Retrieve the PIL image
120
+ pil_img = item[image_key]
121
+ if not isinstance(pil_img, Image.Image):
122
+ # In case it's a dict with bytes
123
+ import io
124
+ pil_img = Image.open(io.BytesIO(pil_img['bytes']))
125
+
126
+ # Keep high-res, save as PNG
127
+ pil_img.convert("RGB").save(dest, "PNG")
128
+ saved += 1
129
+ if saved % 100 == 0 or test_run:
130
+ print(f" -> Saved {saved}/{limit} images")
131
+ except Exception as e:
132
+ print(f" [Error] Failed to save image index {i}: {e}")
133
+
134
+ print(f"[OK] Successfully streamed and saved {saved} images to {target_dir}")
135
+ except Exception as e:
136
+ print(f"[FAIL] Error streaming dataset {repo_name}: {e}")
137
+
138
+ def main():
139
+ parser = argparse.ArgumentParser(description="Download and integrate high-resolution datasets for super-resolution training.")
140
+ parser.add_argument("--test-run", action="store_true", help="Only download 5 images from each dataset for verification.")
141
+ args = parser.parse_args()
142
+
143
+ # 1. Download DIV2K (2K resolution)
144
+ download_div2k(test_run=args.test_run)
145
+
146
+ # 2. Stream Flickr2K Subset (2K resolution, yangtao9009/Flickr2K)
147
+ download_huggingface_dataset(
148
+ repo_name="yangtao9009/Flickr2K",
149
+ target_subdir="flickr2k_subset",
150
+ max_images=1000,
151
+ image_key="image",
152
+ prefix="flickr2k",
153
+ test_run=args.test_run
154
+ )
155
+
156
+ # 3. Stream FFHQ-1024x1024 Subset (1024px resolution, Iceclear/FFHQ-HQ1024)
157
+ download_huggingface_dataset(
158
+ repo_name="Iceclear/FFHQ-HQ1024",
159
+ target_subdir="ffhq1024_subset",
160
+ max_images=10000,
161
+ image_key="image",
162
+ prefix="ffhq1024",
163
+ test_run=args.test_run
164
+ )
165
+
166
+ if __name__ == "__main__":
167
+ main()
tools/download_weights.py CHANGED
@@ -31,11 +31,11 @@ def download_file(url, save_path):
31
 
32
  def main():
33
  weights_to_download = {
34
- "weights/CodeFormer/codeformer.pth": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth",
35
- "weights/facelib/detection_Resnet50_Final.pth": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/detection_Resnet50_Final.pth",
36
- "weights/facelib/parsing_parsenet.pth": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/parsing_parsenet.pth",
37
- "weights/facelib/yolov5l-face.pth": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth",
38
- "weights/realesrgan/RealESRGAN_x2plus.pth": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/RealESRGAN_x2plus.pth"
39
  }
40
 
41
  for save_path, url in weights_to_download.items():
 
31
 
32
  def main():
33
  weights_to_download = {
34
+ "weights/CodeFormer/codeformer.pth": "https://huggingface.co/spaces/sczhou/CodeFormer/resolve/main/weights/CodeFormer/codeformer.pth",
35
+ "weights/facelib/detection_Resnet50_Final.pth": "https://huggingface.co/spaces/sczhou/CodeFormer/resolve/main/weights/facelib/detection_Resnet50_Final.pth",
36
+ "weights/facelib/parsing_parsenet.pth": "https://huggingface.co/spaces/sczhou/CodeFormer/resolve/main/weights/facelib/parsing_parsenet.pth",
37
+ "weights/facelib/yolov5l-face.pth": "https://huggingface.co/spaces/sczhou/CodeFormer/resolve/main/weights/facelib/yolov5l-face.pth",
38
+ "weights/realesrgan/RealESRGAN_x2plus.pth": "https://huggingface.co/spaces/sczhou/CodeFormer/resolve/main/weights/realesrgan/RealESRGAN_x2plus.pth"
39
  }
40
 
41
  for save_path, url in weights_to_download.items():
tools/export_onnx.py CHANGED
@@ -104,7 +104,7 @@ def export_codeformer():
104
  print(f"[WARNING] ONNX validation failed: {e}")
105
  return True
106
 
107
- def export_realesrgan():
108
  print("\n--- Exporting Real-ESRGAN to ONNX ---")
109
  device = torch.device("cpu")
110
 
@@ -115,21 +115,26 @@ def export_realesrgan():
115
  if checkpoint_path:
116
  print(f"Found custom Real-ESRGAN checkpoint: {checkpoint_path}")
117
  scale = 4
 
118
  else:
119
  checkpoint_path = os.path.join(project_dir, "weights", "realesrgan", "RealESRGAN_x2plus.pth")
120
  print(f"No custom checkpoint found. Using pretrained weights: {checkpoint_path}")
121
  scale = 2
 
122
 
123
  if not os.path.exists(checkpoint_path):
124
  print(f"[ERROR] Real-ESRGAN weights not found at: {checkpoint_path}")
125
  return False
126
 
 
 
 
127
  # 2. Instantiate RRDBNet architecture with correct scale
128
  net = RRDBNet(
129
  num_in_ch=3,
130
  num_out_ch=3,
131
  num_feat=64,
132
- num_block=23,
133
  num_grow_ch=32,
134
  scale=scale
135
  )
@@ -175,8 +180,13 @@ def export_realesrgan():
175
  return True
176
 
177
  def main():
 
 
 
 
 
178
  success_cf = export_codeformer()
179
- success_re = export_realesrgan()
180
  if success_cf and success_re:
181
  print("\n=== ALL MODELS EXPORTED SUCCESSFULLY ===")
182
  else:
 
104
  print(f"[WARNING] ONNX validation failed: {e}")
105
  return True
106
 
107
+ def export_realesrgan(num_block=None):
108
  print("\n--- Exporting Real-ESRGAN to ONNX ---")
109
  device = torch.device("cpu")
110
 
 
115
  if checkpoint_path:
116
  print(f"Found custom Real-ESRGAN checkpoint: {checkpoint_path}")
117
  scale = 4
118
+ default_blocks = 6
119
  else:
120
  checkpoint_path = os.path.join(project_dir, "weights", "realesrgan", "RealESRGAN_x2plus.pth")
121
  print(f"No custom checkpoint found. Using pretrained weights: {checkpoint_path}")
122
  scale = 2
123
+ default_blocks = 23
124
 
125
  if not os.path.exists(checkpoint_path):
126
  print(f"[ERROR] Real-ESRGAN weights not found at: {checkpoint_path}")
127
  return False
128
 
129
+ blocks = num_block if num_block is not None else default_blocks
130
+ print(f"Instantiating RRDBNet with scale={scale}, num_block={blocks}")
131
+
132
  # 2. Instantiate RRDBNet architecture with correct scale
133
  net = RRDBNet(
134
  num_in_ch=3,
135
  num_out_ch=3,
136
  num_feat=64,
137
+ num_block=blocks,
138
  num_grow_ch=32,
139
  scale=scale
140
  )
 
180
  return True
181
 
182
  def main():
183
+ import argparse
184
+ parser = argparse.ArgumentParser(description="Export CodeFormer and Real-ESRGAN models to ONNX")
185
+ parser.add_argument('--num-block', type=int, default=None, help="Number of RRDB blocks for Real-ESRGAN (defaults to 6 for custom checkpoint, 23 for pretrained)")
186
+ args = parser.parse_args()
187
+
188
  success_cf = export_codeformer()
189
+ success_re = export_realesrgan(num_block=args.num_block)
190
  if success_cf and success_re:
191
  print("\n=== ALL MODELS EXPORTED SUCCESSFULLY ===")
192
  else:
tools/test_pipeline.py CHANGED
@@ -36,7 +36,7 @@ def main():
36
  enhanced_img = pipeline.process_image(
37
  img,
38
  w=0.5,
39
- detection_model='retinaface_resnet50',
40
  upscale=upscale,
41
  blend_softness=0.5
42
  )
 
36
  enhanced_img = pipeline.process_image(
37
  img,
38
  w=0.5,
39
+ detection_model='retinaface_mobile0.25',
40
  upscale=upscale,
41
  blend_softness=0.5
42
  )
train_custom.py CHANGED
@@ -7,7 +7,13 @@ import yaml
7
  import subprocess
8
  import glob
9
 
 
 
10
  def main():
 
 
 
 
11
  project_dir = os.path.dirname(os.path.abspath(__file__))
12
  codeformer_dir = os.path.join(project_dir, "models", "CodeFormer")
13
 
@@ -84,24 +90,31 @@ def main():
84
  except ValueError:
85
  continue
86
 
87
- if latest_state and latest_state_iter < config.get("train", {}).get("total_iter", 50):
88
  print(f"\n>>> RESUME MODE: Found checkpoint at iteration {latest_state_iter}")
89
  print(f" State file: {latest_state}")
90
  config["path"]["resume_state"] = latest_state
91
- # When resuming, we don't need pretrain_network_g as it will be loaded from state
92
  config["path"]["pretrain_network_g"] = None
93
- print(f" Resuming training from iter {latest_state_iter} to {config['train']['total_iter']}...")
94
- elif latest_state and latest_state_iter >= config.get("train", {}).get("total_iter", 50):
95
- print(f"\n>>> Training already completed ({latest_state_iter} >= {config['train']['total_iter']} total_iter)")
96
- print(" To train more iterations, increase total_iter in the config.")
97
- sys.exit(0)
 
 
 
98
  else:
99
  print("\n>>> FRESH START: No previous checkpoint found. Starting from pretrained weights.")
 
 
 
 
 
100
 
101
  # Write back the updated configuration
102
  with open(config_path, "w", encoding="utf-8") as f:
103
  yaml.dump(config, f, default_flow_style=False, sort_keys=False)
104
- print(f"Updated configuration file for device={device.upper()}, num_gpu={num_gpus}")
105
 
106
  # 5. Run the training process
107
  train_script = os.path.join("basicsr", "train.py")
 
7
  import subprocess
8
  import glob
9
 
10
+ import argparse
11
+
12
  def main():
13
+ parser = argparse.ArgumentParser(description="Train CodeFormer with custom parameters.")
14
+ parser.add_argument("--verify", action="store_true", help="Run 2 iterations for verification purposes.")
15
+ args = parser.parse_args()
16
+
17
  project_dir = os.path.dirname(os.path.abspath(__file__))
18
  codeformer_dir = os.path.join(project_dir, "models", "CodeFormer")
19
 
 
90
  except ValueError:
91
  continue
92
 
93
+ if latest_state:
94
  print(f"\n>>> RESUME MODE: Found checkpoint at iteration {latest_state_iter}")
95
  print(f" State file: {latest_state}")
96
  config["path"]["resume_state"] = latest_state
 
97
  config["path"]["pretrain_network_g"] = None
98
+ if args.verify:
99
+ config["train"]["total_iter"] = latest_state_iter + 2
100
+ print(f" Resuming training from iter {latest_state_iter} to {config['train']['total_iter']} (verification mode)...")
101
+ else:
102
+ if latest_state_iter >= config.get("train", {}).get("total_iter", 20000):
103
+ print(f"\n>>> Training already completed ({latest_state_iter} >= {config.get('train', {}).get('total_iter', 20000)} total_iter)")
104
+ sys.exit(0)
105
+ print(f" Resuming training from iter {latest_state_iter} to {config['train']['total_iter']}...")
106
  else:
107
  print("\n>>> FRESH START: No previous checkpoint found. Starting from pretrained weights.")
108
+ if args.verify:
109
+ config["train"]["total_iter"] = 2
110
+ print(f" Training from scratch to {config['train']['total_iter']} (verification mode)...")
111
+ else:
112
+ print(f" Training from scratch to {config.get('train', {}).get('total_iter', 20000)}...")
113
 
114
  # Write back the updated configuration
115
  with open(config_path, "w", encoding="utf-8") as f:
116
  yaml.dump(config, f, default_flow_style=False, sort_keys=False)
117
+ print(f"Updated configuration file: device={device.upper()}, num_gpu={num_gpus}, total_iter={config['train']['total_iter']}")
118
 
119
  # 5. Run the training process
120
  train_script = os.path.join("basicsr", "train.py")
train_realesrgan.py CHANGED
@@ -24,6 +24,9 @@ ANIME_DIR = os.path.join(DATASET_BASE, "anime")
24
  FACE_DIR = os.path.join(DATASET_BASE, "faces")
25
  STARRAIL_DIR = os.path.join(DATASET_BASE, "starrail")
26
  BLUEARCHIVE_DIR = os.path.join(DATASET_BASE, "bluearchive")
 
 
 
27
 
28
  # Combined GT (high-quality) folder for Real-ESRGAN training
29
  REALESRGAN_GT_DIR = os.path.join(PROJECT_DIR, "datasets", "realesrgan_gt")
@@ -76,6 +79,9 @@ def prepare_gt_dataset():
76
  "face": FACE_DIR,
77
  "starrail": STARRAIL_DIR,
78
  "bluearchive": BLUEARCHIVE_DIR,
 
 
 
79
  }
80
 
81
  for category, src_dir in sources.items():
@@ -300,6 +306,11 @@ def find_latest_state() -> str | None:
300
 
301
 
302
  def main():
 
 
 
 
 
303
  print("=" * 55)
304
  print(" Real-ESRGAN Custom Training Runner")
305
  print(" Target: Landscape + Anime + Face enhancement")
@@ -347,10 +358,27 @@ def main():
347
  cfg = yaml.safe_load(f)
348
  cfg["path"]["resume_state"] = latest_state
349
  cfg["path"]["pretrain_network_g"] = None
 
 
 
 
 
 
 
 
350
  with open(config_path, "w", encoding="utf-8") as f:
351
  yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)
352
  else:
353
  print("\n>>> FRESH START: No previous checkpoint. Starting from scratch.")
 
 
 
 
 
 
 
 
 
354
 
355
  # 6. Run training
356
  train_script = os.path.join("realesrgan", "train.py")
@@ -384,12 +412,8 @@ def main():
384
  # Real-ESRGAN's train.py imports from basicsr.
385
  # CodeFormer's basicsr lacks degradations module, so we use the full
386
  # BasicSR source cloned at D:\Temp\BasicSR_src (no install needed).
387
- basicsr_src = r"D:\Temp\BasicSR_src"
388
- codeformer_dir = os.path.join(PROJECT_DIR, "models", "CodeFormer")
389
  env["PYTHONPATH"] = os.path.pathsep.join([
390
  REALESRGAN_DIR,
391
- basicsr_src, # full BasicSR with degradations
392
- codeformer_dir, # fallback
393
  env.get("PYTHONPATH", ""),
394
  ])
395
 
 
24
  FACE_DIR = os.path.join(DATASET_BASE, "faces")
25
  STARRAIL_DIR = os.path.join(DATASET_BASE, "starrail")
26
  BLUEARCHIVE_DIR = os.path.join(DATASET_BASE, "bluearchive")
27
+ DIV2K_DIR = os.path.join(DATASET_BASE, "div2k")
28
+ FLICKR2K_DIR = os.path.join(DATASET_BASE, "flickr2k_subset")
29
+ FFHQ1024_DIR = os.path.join(DATASET_BASE, "ffhq1024_subset")
30
 
31
  # Combined GT (high-quality) folder for Real-ESRGAN training
32
  REALESRGAN_GT_DIR = os.path.join(PROJECT_DIR, "datasets", "realesrgan_gt")
 
79
  "face": FACE_DIR,
80
  "starrail": STARRAIL_DIR,
81
  "bluearchive": BLUEARCHIVE_DIR,
82
+ "div2k": DIV2K_DIR,
83
+ "flickr2k": FLICKR2K_DIR,
84
+ "ffhq1024": FFHQ1024_DIR,
85
  }
86
 
87
  for category, src_dir in sources.items():
 
306
 
307
 
308
  def main():
309
+ import argparse
310
+ parser = argparse.ArgumentParser(description="Train Real-ESRGAN with custom parameters.")
311
+ parser.add_argument("--verify", action="store_true", help="Run 2 iterations for verification purposes.")
312
+ args = parser.parse_args()
313
+
314
  print("=" * 55)
315
  print(" Real-ESRGAN Custom Training Runner")
316
  print(" Target: Landscape + Anime + Face enhancement")
 
358
  cfg = yaml.safe_load(f)
359
  cfg["path"]["resume_state"] = latest_state
360
  cfg["path"]["pretrain_network_g"] = None
361
+ if args.verify:
362
+ cfg["train"]["total_iter"] = iter_num + 2
363
+ print(f" Resuming training from iter {iter_num} to {cfg['train']['total_iter']} (verification mode)...")
364
+ else:
365
+ if iter_num >= cfg.get("train", {}).get("total_iter", 15000):
366
+ print(f"\n>>> Training already completed ({iter_num} >= {cfg.get('train', {}).get('total_iter', 15000)} total_iter)")
367
+ sys.exit(0)
368
+ print(f" Resuming training from iter {iter_num} to {cfg['train']['total_iter']}...")
369
  with open(config_path, "w", encoding="utf-8") as f:
370
  yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)
371
  else:
372
  print("\n>>> FRESH START: No previous checkpoint. Starting from scratch.")
373
+ with open(config_path, "r", encoding="utf-8") as f:
374
+ cfg = yaml.safe_load(f)
375
+ if args.verify:
376
+ cfg["train"]["total_iter"] = 2
377
+ print(f" Training from scratch to {cfg['train']['total_iter']} (verification mode)...")
378
+ else:
379
+ print(f" Training from scratch to {cfg.get('train', {}).get('total_iter', 15000)}...")
380
+ with open(config_path, "w", encoding="utf-8") as f:
381
+ yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)
382
 
383
  # 6. Run training
384
  train_script = os.path.join("realesrgan", "train.py")
 
412
  # Real-ESRGAN's train.py imports from basicsr.
413
  # CodeFormer's basicsr lacks degradations module, so we use the full
414
  # BasicSR source cloned at D:\Temp\BasicSR_src (no install needed).
 
 
415
  env["PYTHONPATH"] = os.path.pathsep.join([
416
  REALESRGAN_DIR,
 
 
417
  env.get("PYTHONPATH", ""),
418
  ])
419