import os import shutil from typing import List, Tuple import numpy as np from PIL import Image # ========================= # SETTINGS # ========================= SOURCE_DIR = "data/grayscale_images" TRAIN_DIR = "data/train_images" SUPPORT_DIR = "data/support_set" TEST_DIR = "data/test_images" TRAIN_FAMILIES = ["benign", "banking", "smsware"] ALL_FAMILIES = ["benign", "banking", "smsware", "adware", "riskware"] TRAIN_COUNT = 10 SUPPORT_COUNT = 5 TEST_COUNT = 5 # ========================= # MANUAL SUPPORT SELECTION # ========================= # Agar kisi family ke liye yahan filenames di hui hon, # to support set unhi files se banega. # Baqi split automatically hoga. # # IMPORTANT: # Ye filenames exact waise hi honi chahiye jaisi # SOURCE_DIR/family folder me موجود hain. # # Abhi smsware ke liye manual support ON hai. MANUAL_SUPPORT = { "smsware": [ "020cdc2d622af016d7cbfcee797e078884380a6635ebe70b36a5c527608ec07f.png", "0221511d597a5ab7b6303e12675dabadf6f48db968fa26403ee70a041e3a6826.png", "043b4fbc2b58040754a20844e8bc85139ce38daffd40ce53ba0a91ba052ca84b.png", "0454a5c0ff9fea30a5084af2354ef142f0ee5dbbf3545edb4bf0d07b2242bbeb.png", "015b473e1d56054bed16899430ea95f9ac940a45ab0ec4888a119279667e7916.png", ] } def ensure_dir(path): os.makedirs(path, exist_ok=True) def reset_family_dir(path): if os.path.isdir(path): shutil.rmtree(path) os.makedirs(path, exist_ok=True) def copy_files(files, src_dir, dst_dir): ensure_dir(dst_dir) for f in files: src = os.path.join(src_dir, f) dst = os.path.join(dst_dir, f) shutil.copy(src, dst) def get_image_score(image_path: str) -> float: """ Higher score = better / more informative image. Prefer images with reasonable contrast and non-extreme brightness. """ try: img = Image.open(image_path).convert("L") arr = np.array(img, dtype=np.float32) mean_val = float(arr.mean()) std_val = float(arr.std()) mean_penalty = abs(mean_val - 127.5) / 127.5 score = std_val - (mean_penalty * 20.0) return score except Exception: return -1e9 def get_ranked_images(family_src: str) -> List[str]: images = [ f for f in os.listdir(family_src) if f.lower().endswith(".png") ] scored_images: List[Tuple[str, float]] = [] for f in images: path = os.path.join(family_src, f) score = get_image_score(path) if score > -1e8: scored_images.append((f, score)) scored_images.sort(key=lambda x: x[1], reverse=True) return [f for f, _ in scored_images] def pick_spread_items(images: List[str], count: int) -> List[str]: """ Pick evenly spread samples from a ranked list so support is diverse. """ if len(images) <= count: return images[:count] indices = np.linspace(0, len(images) - 1, count, dtype=int) picked = [images[i] for i in indices] unique_picked = [] for item in picked: if item not in unique_picked: unique_picked.append(item) if len(unique_picked) < count: for item in images: if item not in unique_picked: unique_picked.append(item) if len(unique_picked) == count: break return unique_picked[:count] def validate_manual_support(family: str, family_src: str, manual_files: List[str]) -> List[str]: """ Keep only valid manual support files that actually exist. """ valid = [] missing = [] for f in manual_files: full_path = os.path.join(family_src, f) if os.path.isfile(full_path): valid.append(f) else: missing.append(f) if missing: print(f"[WARNING] Missing manual support files for {family}:") for f in missing: print(f" - {f}") if len(valid) < SUPPORT_COUNT: print( f"[WARNING] Manual support for {family} has only {len(valid)} valid files. " f"Need {SUPPORT_COUNT}. Falling back to auto-fill for remaining." ) return valid def split_seen_family(images: List[str], family: str, family_src: str): """ Seen family split: - support = 5 - train = 10 - test = 5 smsware ke liye manual support allow hai. """ required = SUPPORT_COUNT + TRAIN_COUNT + TEST_COUNT if len(images) < required: print(f"[WARNING] Seen family has fewer than required images: {len(images)} < {required}") pool = images[:max(required, 20)] # ========================= # Manual support mode # ========================= if family in MANUAL_SUPPORT: manual_support = validate_manual_support(family, family_src, MANUAL_SUPPORT[family]) remaining_candidates = [img for img in pool if img not in manual_support] # Agar manual support 5 se kam ho to auto-fill kar do if len(manual_support) < SUPPORT_COUNT: needed = SUPPORT_COUNT - len(manual_support) auto_fill = remaining_candidates[:needed] support = manual_support + auto_fill else: support = manual_support[:SUPPORT_COUNT] remaining = [img for img in pool if img not in support] train = remaining[:TRAIN_COUNT] test = remaining[TRAIN_COUNT:TRAIN_COUNT + TEST_COUNT] return train, support, test # ========================= # Auto split for seen families # ========================= support_candidates = pool[:15] if len(pool) >= 15 else pool support = pick_spread_items(support_candidates, SUPPORT_COUNT) remaining = [img for img in pool if img not in support] train = remaining[:TRAIN_COUNT] test = remaining[TRAIN_COUNT:TRAIN_COUNT + TEST_COUNT] return train, support, test def split_unseen_family(images: List[str]): """ Unseen family split: - train = 0 - support = 5 - test = 5 """ required = SUPPORT_COUNT + TEST_COUNT if len(images) < required: print(f"[WARNING] Unseen family has fewer than required images: {len(images)} < {required}") pool = images[:max(required, 15)] support_candidates = pool[:10] if len(pool) >= 10 else pool support = pick_spread_items(support_candidates, SUPPORT_COUNT) remaining = [img for img in pool if img not in support] test = remaining[:TEST_COUNT] train = [] return train, support, test def main(): print("\n========== DATASET SPLIT START ==========\n") ensure_dir(TRAIN_DIR) ensure_dir(SUPPORT_DIR) ensure_dir(TEST_DIR) for family in ALL_FAMILIES: family_src = os.path.join(SOURCE_DIR, family) if not os.path.isdir(family_src): print(f"[WARNING] Missing source folder: {family_src}") continue images = get_ranked_images(family_src) print(f"\nProcessing: {family}") print("Valid images found:", len(images)) if family in TRAIN_FAMILIES: train, support, test = split_seen_family(images, family, family_src) else: train, support, test = split_unseen_family(images) reset_family_dir(os.path.join(TRAIN_DIR, family)) reset_family_dir(os.path.join(SUPPORT_DIR, family)) reset_family_dir(os.path.join(TEST_DIR, family)) copy_files(train, family_src, os.path.join(TRAIN_DIR, family)) copy_files(support, family_src, os.path.join(SUPPORT_DIR, family)) copy_files(test, family_src, os.path.join(TEST_DIR, family)) print("Train :", len(train)) print("Support:", len(support)) print("Test :", len(test)) if family in TRAIN_FAMILIES: if family in MANUAL_SUPPORT: print("Seen split -> manual support(5), train(10), test(5)") print("Manual support files:") for f in support: print(f" - {f}") else: print("Seen split -> support(diverse 5), train(10), test(5)") else: print("Unseen split -> support(diverse 5), test(5), no train") print("\n========== DATASET SPLIT DONE ==========\n") if __name__ == "__main__": main()