prasannareddyp commited on
Commit
5dbf895
·
verified ·
1 Parent(s): e43dbe7

Upload 4 files

Browse files
Files changed (4) hide show
  1. LICENSE +21 -0
  2. app.py +130 -0
  3. requirements.txt +3 -0
  4. spm.py +82 -0
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to do so, subject to the
10
+ following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
app.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image
3
+ import numpy as np
4
+ import io, os, zipfile, tempfile, time
5
+ from spm import spm_augment
6
+
7
+ TITLE = "Shuffle PatchMix (SPM) Augmentation"
8
+ DESC = """
9
+ Upload an image, choose **number of patches (N×N)**, and generate SPM-augmented variants.
10
+ For batch processing, upload a .zip of images (PNG/JPG/JPEG), and download a .zip of outputs.
11
+ """
12
+
13
+ def _parse_grid(grid_choice: str) -> int:
14
+ # Expect strings like "2x2", "4x4", "8x8", "16x16"
15
+ try:
16
+ n = int(grid_choice.lower().split("x")[0])
17
+ return max(1, n)
18
+ except Exception:
19
+ return 4
20
+
21
+ def run_single(image, grid_choice, mix_prob, beta_a, beta_b, num_augs, seed):
22
+ if image is None:
23
+ return []
24
+ outs = []
25
+ base_seed = int(seed) if seed is not None else None
26
+ N = _parse_grid(grid_choice)
27
+ for i in range(num_augs):
28
+ s = (base_seed + i) if base_seed is not None else None
29
+ out_img = spm_augment(
30
+ image,
31
+ num_patches=N,
32
+ mix_prob=float(mix_prob),
33
+ beta_a=float(beta_a),
34
+ beta_b=float(beta_b),
35
+ seed=s
36
+ )
37
+ outs.append(out_img)
38
+ return outs
39
+
40
+ def run_batch(zip_file, grid_choice, mix_prob, beta_a, beta_b, seed):
41
+ if zip_file is None:
42
+ return None, "Please upload a .zip file with images."
43
+ tempdir = tempfile.mkdtemp()
44
+ outdir = os.path.join(tempdir, "outputs")
45
+ os.makedirs(outdir, exist_ok=True)
46
+ # Extract
47
+ with zipfile.ZipFile(zip_file, 'r') as zf:
48
+ zf.extractall(tempdir)
49
+ # Collect images
50
+ valid_exts = {".png", ".jpg", ".jpeg"}
51
+ count_in, count_out = 0, 0
52
+ N = _parse_grid(grid_choice)
53
+ for root_dir, _, files in os.walk(tempdir):
54
+ for f in files:
55
+ if f.lower().endswith(tuple(valid_exts)):
56
+ in_path = os.path.join(root_dir, f)
57
+ try:
58
+ img = Image.open(in_path).convert("RGB")
59
+ except Exception:
60
+ continue
61
+ count_in += 1
62
+ out_img = spm_augment(
63
+ img,
64
+ num_patches=N,
65
+ mix_prob=float(mix_prob),
66
+ beta_a=float(beta_a),
67
+ beta_b=float(beta_b),
68
+ seed=int(seed) if seed is not None else None
69
+ )
70
+ rel = os.path.relpath(in_path, tempdir)
71
+ out_path = os.path.join(outdir, rel)
72
+ os.makedirs(os.path.dirname(out_path), exist_ok=True)
73
+ out_img.save(out_path)
74
+ count_out += 1
75
+ # Zip results
76
+ out_zip = os.path.join(tempdir, f"spm_outputs_{int(time.time())}.zip")
77
+ with zipfile.ZipFile(out_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
78
+ for root_dir, _, files in os.walk(outdir):
79
+ for f in files:
80
+ p = os.path.join(root_dir, f)
81
+ arc = os.path.relpath(p, outdir)
82
+ zf.write(p, arcname=arc)
83
+ msg = f"Processed {count_out}/{count_in} files."
84
+ return out_zip, msg
85
+
86
+ with gr.Blocks() as demo:
87
+ gr.Markdown(f"# {TITLE}")
88
+ gr.Markdown(DESC)
89
+ with gr.Tabs():
90
+ with gr.TabItem("Single Image"):
91
+ with gr.Row():
92
+ with gr.Column(scale=1):
93
+ inp = gr.Image(label="Input image", type="pil")
94
+ grid_choice = gr.Radio(choices=["2x2","4x4","8x8","16x16"], value="4x4", label="Grid (N×N)")
95
+ mix_prob = gr.Slider(0, 1, value=0.5, step=0.05, label="Mix probability (per patch)")
96
+ with gr.Row():
97
+ beta_a = gr.Slider(0.1, 8, value=2.0, step=0.1, label="Beta α")
98
+ beta_b = gr.Slider(0.1, 8, value=2.0, step=0.1, label="Beta β")
99
+ num_augs = gr.Slider(1, 12, value=4, step=1, label="Number of variants")
100
+ seed = gr.Number(value=42, precision=0, label="Seed (int, optional)")
101
+ run_btn = gr.Button("Generate")
102
+ with gr.Column(scale=1):
103
+ gallery = gr.Gallery(label="Augmented outputs", columns=2, height="auto")
104
+ run_btn.click(
105
+ fn=run_single,
106
+ inputs=[inp, grid_choice, mix_prob, beta_a, beta_b, num_augs, seed],
107
+ outputs=[gallery]
108
+ )
109
+ with gr.TabItem("Batch (.zip)"):
110
+ with gr.Row():
111
+ with gr.Column(scale=1):
112
+ zip_in = gr.File(label="Upload a .zip of images", file_types=[".zip"])
113
+ grid_choice_b = gr.Radio(choices=["2x2","4x4","8x8","16x16"], value="4x4", label="Grid (N×N)")
114
+ mix_prob_b = gr.Slider(0, 1, value=0.5, step=0.05, label="Mix probability (per patch)")
115
+ with gr.Row():
116
+ beta_a_b = gr.Slider(0.1, 8, value=2.0, step=0.1, label="Beta α")
117
+ beta_b_b = gr.Slider(0.1, 8, value=2.0, step=0.1, label="Beta β")
118
+ seed_b = gr.Number(value=42, precision=0, label="Seed (int, optional)")
119
+ run_b = gr.Button("Process Zip")
120
+ with gr.Column(scale=1):
121
+ zip_out = gr.File(label="Download results (.zip)")
122
+ status = gr.Markdown()
123
+ run_b.click(
124
+ fn=run_batch,
125
+ inputs=[zip_in, grid_choice_b, mix_prob_b, beta_a_b, beta_b_b, seed_b],
126
+ outputs=[zip_out, status]
127
+ )
128
+
129
+ if __name__ == "__main__":
130
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=5.0.0
2
+ pillow
3
+ numpy
spm.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ import numpy as np
3
+
4
+ def _to_divisible_by(img, N):
5
+ """Crop so width and height are divisible by N (top-left anchored)."""
6
+ w, h = img.size
7
+ W = (w // N) * N
8
+ H = (h // N) * N
9
+ if W == 0 or H == 0:
10
+ raise ValueError("N is larger than the image grid (image too small).")
11
+ if W != w or H != h:
12
+ img = img.crop((0, 0, W, H))
13
+ return img, W, H
14
+
15
+ def spm_augment(
16
+ image,
17
+ num_patches=4, # N for an N×N grid
18
+ mix_prob=0.5,
19
+ beta_a=2.0,
20
+ beta_b=2.0,
21
+ seed=None
22
+ ):
23
+ """
24
+ SPM-style augmentation using a global shuffle over an N×N patch grid.
25
+ 1) Divide image into N×N patches (cropping to be divisible by N if needed).
26
+ 2) Globally permute patch indices.
27
+ 3) Per patch, with probability `mix_prob`, replace by a convex blend of
28
+ original and a shuffled patch using alpha~Beta(beta_a,beta_b) (one alpha per image).
29
+ """
30
+ # Normalize input
31
+ if isinstance(image, np.ndarray):
32
+ img = Image.fromarray(image).convert("RGB")
33
+ else:
34
+ img = image.convert("RGB")
35
+
36
+ N = int(num_patches)
37
+ rng = np.random.default_rng(seed)
38
+
39
+ # Ensure divisibility and compute patch size
40
+ img, W, H = _to_divisible_by(img, N)
41
+ arr = np.array(img, dtype=np.uint8)
42
+ ph = H // N
43
+ pw = W // N
44
+
45
+ # Build patch list (row-major)
46
+ patches = []
47
+ for i in range(N):
48
+ for j in range(N):
49
+ y0 = i * ph
50
+ x0 = j * pw
51
+ patches.append(arr[y0:y0+ph, x0:x0+pw])
52
+
53
+ total = N * N
54
+ perm = rng.permutation(total)
55
+
56
+ # Sample one alpha for the whole image
57
+ if beta_a > 0 and beta_b > 0:
58
+ alpha = float(rng.beta(beta_a, beta_b))
59
+ else:
60
+ alpha = 1.0
61
+
62
+ # Patchwise mix
63
+ out = arr.copy()
64
+ mask = rng.random(total) < float(mix_prob)
65
+ idx = 0
66
+ for i in range(N):
67
+ for j in range(N):
68
+ y0 = i * ph
69
+ x0 = j * pw
70
+ if mask[idx]:
71
+ src = patches[idx].astype(np.float32)
72
+ shf = patches[perm[idx]].astype(np.float32)
73
+ if 0.0 < alpha < 1.0:
74
+ mixed = alpha * shf + (1.0 - alpha) * src
75
+ out[y0:y0+ph, x0:x0+pw] = np.clip(mixed, 0, 255).astype(np.uint8)
76
+ else:
77
+ out[y0:y0+ph, x0:x0+pw] = patches[perm[idx]]
78
+ else:
79
+ out[y0:y0+ph, x0:x0+pw] = patches[idx]
80
+ idx += 1
81
+
82
+ return Image.fromarray(out)