Simon commited on
Commit
8759ac4
·
1 Parent(s): 936daf1

create project

Browse files
Files changed (4) hide show
  1. .gitignore +5 -0
  2. README.md +33 -6
  3. app.py +214 -0
  4. requirements.txt +7 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .venv
2
+ .venv/
3
+ env/
4
+ venv/
5
+ ENV/
README.md CHANGED
@@ -1,13 +1,40 @@
1
  ---
2
- title: PrototypeAITechLaw
3
- emoji: 🚀
4
- colorFrom: red
5
  colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.14.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Assistive Diagnostic Framework for Copyright
3
+ emoji: ⚖️
4
+ colorFrom: gray
5
  colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 4.19.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Assistive Diagnostic Framework for Copyright Infringement
14
+
15
+ This prototype application is an "Explainable AI" dashboard designed to assist in evaluating potential visual copyright infringement. Rather than outputting a single, opaque similarity score, this tool processes two images through a multi-model computer vision pipeline to map technical parameters to established legal criteria.
16
+
17
+ ## 🧠 The Architecture
18
+
19
+ The pipeline divides the visual comparison into three distinct legal dimensions, optimized to run within standard memory constraints (e.g., Hugging Face Free Tier).
20
+
21
+ 1. **Semantic Match (The "Idea" Filter)**
22
+ * **Model:** CLIP (`openai/clip-vit-base-patch32`)
23
+ * **Function:** Compares the overarching semantic concept of the images. This acts as a threshold mechanism to determine if the images share the same unprotected subject matter or "referent" before analyzing specific expressions.
24
+
25
+ 2. **Structural Layout (Substantial Similarity)**
26
+ * **Model:** OpenCV Canny Edge Detection
27
+ * **Function:** Strips away style, texture, and color to compare only the fundamental structural outlines. Calculates the Intersection over Union (IoU) of the edge pixels to assess compositional overlap.
28
+
29
+ 3. **Patch Match (Fragmented Literal Similarity)**
30
+ * **Model:** DINOv2 (`facebook/dinov2-base`)
31
+ * **Function:** Identifies "scattered literal copying." By extracting and normalizing local patch features, the model uses a Mutual Nearest Neighbors algorithm to map identical or near-identical fragments between the two images, regardless of their spatial location.
32
+
33
+ ## 🚀 Running Locally
34
+
35
+ To run this application on your local machine, ensure you have Python 3.9+ installed.
36
+
37
+ 1. Clone the repository:
38
+ ```bash
39
+ git clone <your-repo-url>
40
+ cd <your-repo-directory>
app.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import torch.nn.functional as F
4
+ import cv2
5
+ import numpy as np
6
+ from PIL import Image, ImageDraw
7
+ from transformers import AutoImageProcessor, AutoModel, CLIPProcessor, CLIPModel
8
+
9
+ # ==============================================================================
10
+ # 1. Global Initialization & Memory Management
11
+ # ==============================================================================
12
+ # We load models globally so they cache in memory on startup, not on every click.
13
+ device = "cuda" if torch.cuda.is_available() else "cpu"
14
+
15
+ print("Loading DINOv2...")
16
+ dino_processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base")
17
+ dino_model = AutoModel.from_pretrained("facebook/dinov2-base").to(device)
18
+ dino_model.eval() # Prevent gradient tracking
19
+
20
+ print("Loading CLIP...")
21
+ clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
22
+ clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
23
+ clip_model.eval() # Prevent gradient tracking
24
+
25
+ # ==============================================================================
26
+ # 2. Pipeline Functions
27
+ # ==============================================================================
28
+
29
+ def compute_semantic_similarity(img_a: Image.Image, img_b: Image.Image) -> float:
30
+ """
31
+ Model: CLIP
32
+ Legal Concept: The "Idea" / Semantic Referent
33
+ """
34
+ inputs = clip_processor(images=[img_a, img_b], return_tensors="pt").to(device)
35
+
36
+ with torch.no_grad():
37
+ image_features = clip_model.get_image_features(**inputs)
38
+
39
+ # Normalize and compute cosine similarity
40
+ image_features = F.normalize(image_features, p=2, dim=-1)
41
+ score = F.cosine_similarity(image_features[0].unsqueeze(0), image_features[1].unsqueeze(0))
42
+
43
+ return round(score.item(), 4)
44
+
45
+ def compute_structural_similarity(img_a: Image.Image, img_b: Image.Image):
46
+ """
47
+ Model: OpenCV Canny
48
+ Legal Concept: "Substantial Similarity" (Layout / Composition)
49
+ """
50
+ # Convert to grayscale numpy arrays
51
+ arr_a = np.array(img_a.convert('L'))
52
+ arr_b = np.array(img_b.convert('L'))
53
+
54
+ # Extract structural edges
55
+ edges_a = cv2.Canny(arr_a, 100, 200)
56
+ edges_b = cv2.Canny(arr_b, 100, 200)
57
+
58
+ # Calculate structural overlap using Intersection over Union (IoU) of edges
59
+ # We resize edges_b to match edges_a to ensure matrix math works
60
+ edges_b_resized = cv2.resize(edges_b, (edges_a.shape[1], edges_a.shape[0]))
61
+
62
+ intersection = np.logical_and(edges_a > 0, edges_b_resized > 0).sum()
63
+ union = np.logical_or(edges_a > 0, edges_b_resized > 0).sum()
64
+
65
+ iou_score = intersection / union if union != 0 else 0.0
66
+
67
+ return round(iou_score, 4), Image.fromarray(edges_a), Image.fromarray(edges_b)
68
+
69
+ def compute_patch_similarity(img_a: Image.Image, img_b: Image.Image):
70
+ """
71
+ Model: DINOv2
72
+ Legal Concept: "Fragmented Literal Similarity" (Scattered Literal Copying)
73
+ """
74
+ # Resize to a fixed multiple of patch size (14) so we have a known grid
75
+ # 224x224 gives us a 16x16 grid of patches (256 total patches)
76
+ target_size = (224, 224)
77
+ img_a_resized = img_a.resize(target_size)
78
+ img_b_resized = img_b.resize(target_size)
79
+
80
+ inputs_a = dino_processor(images=img_a_resized, return_tensors="pt").to(device)
81
+ inputs_b = dino_processor(images=img_b_resized, return_tensors="pt").to(device)
82
+
83
+ with torch.no_grad():
84
+ out_a = dino_model(**inputs_a)
85
+ out_b = dino_model(**inputs_b)
86
+
87
+ # Isolate patches (skip CLS token) and normalize. Shape: (256, 768)
88
+ emb_a = F.normalize(out_a.last_hidden_state[:, 1:, :].squeeze(0), p=2, dim=-1)
89
+ emb_b = F.normalize(out_b.last_hidden_state[:, 1:, :].squeeze(0), p=2, dim=-1)
90
+
91
+ # Compute N x M similarity matrix using dot product
92
+ sim_matrix = torch.matmul(emb_a, emb_b.T) # Shape: (256, 256)
93
+
94
+ # Mutual Nearest Neighbors logic to filter out noise
95
+ best_b_for_a = torch.argmax(sim_matrix, dim=1)
96
+ best_a_for_b = torch.argmax(sim_matrix, dim=0)
97
+
98
+ matches = []
99
+ # Threshold for what we consider "copied" (adjust based on testing)
100
+ SIMILARITY_THRESHOLD = 0.85
101
+
102
+ for a_idx in range(len(best_b_for_a)):
103
+ b_idx = best_b_for_a[a_idx]
104
+ if best_a_for_b[b_idx] == a_idx: # It's a mutual match
105
+ score = sim_matrix[a_idx, b_idx].item()
106
+ if score >= SIMILARITY_THRESHOLD:
107
+ matches.append((a_idx, b_idx, score))
108
+
109
+ # Calculate overall patch score based on percentage of matching patches
110
+ patch_score = len(matches) / 256.0
111
+
112
+ # --- Visual Evidence Generation ---
113
+ combined_vis = Image.new('RGB', (target_size[0] * 2, target_size[1]))
114
+ combined_vis.paste(img_a_resized, (0, 0))
115
+ combined_vis.paste(img_b_resized, (target_size[0], 0))
116
+ draw = ImageDraw.Draw(combined_vis)
117
+
118
+ grid_size = 16
119
+ patch_size = 14
120
+
121
+ for a_idx, b_idx, score in matches:
122
+ # Image A coordinates
123
+ ay = (a_idx // grid_size) * patch_size
124
+ ax = (a_idx % grid_size) * patch_size
125
+
126
+ # Image B coordinates (shifted X by the width of Image A)
127
+ by = (b_idx // grid_size) * patch_size
128
+ bx = (b_idx % grid_size) * patch_size + target_size[0]
129
+
130
+ # Draw bounding boxes
131
+ draw.rectangle([ax, ay, ax + patch_size, ay + patch_size], outline="red", width=2)
132
+ draw.rectangle([bx, by, bx + patch_size, by + patch_size], outline="red", width=2)
133
+
134
+ # Draw connecting line
135
+ center_a = (ax + patch_size // 2, ay + patch_size // 2)
136
+ center_b = (bx + patch_size // 2, by + patch_size // 2)
137
+ draw.line([center_a, center_b], fill="lime", width=1)
138
+
139
+ return round(patch_score, 4), combined_vis
140
+
141
+ # ==============================================================================
142
+ # 3. Main Orchestration Function
143
+ # ==============================================================================
144
+
145
+ def analyze_images(image_a, image_b):
146
+ if image_a is None or image_b is None:
147
+ raise gr.Error("Please upload both images.")
148
+
149
+ # 1. Semantic Match
150
+ semantic_score = compute_semantic_similarity(image_a, image_b)
151
+
152
+ # 2. Structural Match
153
+ struct_score, edge_a, edge_b = compute_structural_similarity(image_a, image_b)
154
+
155
+ # 3. Patch Match
156
+ patch_score, patch_vis = compute_patch_similarity(image_a, image_b)
157
+
158
+ return (
159
+ semantic_score,
160
+ struct_score,
161
+ patch_score,
162
+ patch_vis,
163
+ edge_a,
164
+ edge_b
165
+ )
166
+
167
+ # ==============================================================================
168
+ # 4. Gradio UI / UX
169
+ # ==============================================================================
170
+
171
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
172
+ gr.Markdown("# Assistive Diagnostic Framework for Copyright Infringement")
173
+ gr.Markdown("Upload two images to compare them across semantic, structural, and literal fragment dimensions.")
174
+
175
+ with gr.Row():
176
+ with gr.Column():
177
+ img_in_a = gr.Image(type="pil", label="Image A (Original)")
178
+ with gr.Column():
179
+ img_in_b = gr.Image(type="pil", label="Image B (Suspected Copy)")
180
+
181
+ btn_analyze = gr.Button("Analyze Similarity", variant="primary")
182
+
183
+ gr.Markdown("### Assessment Metrics")
184
+ with gr.Row():
185
+ score_semantic = gr.Number(label="Semantic Match (CLIP) - Idea", show_label=True)
186
+ score_struct = gr.Number(label="Structural Match (Edge IoU) - Layout", show_label=True)
187
+ score_patch = gr.Number(label="Patch Match (DINOv2) - Fragmented Literal", show_label=True)
188
+
189
+ gr.Markdown("### Visual Evidence")
190
+ with gr.Tabs():
191
+ with gr.TabItem("Fragmented Literal Similarity (DINOv2)"):
192
+ gr.Markdown("**Red boxes and green lines indicate mutually correlating local patches (Similarity > 0.85)**")
193
+ vis_patch = gr.Image(label="Patch Mapping Visualization", type="pil")
194
+
195
+ with gr.TabItem("Substantial Similarity (Edge Detection)"):
196
+ with gr.Row():
197
+ vis_edge_a = gr.Image(label="Image A Edges", type="pil")
198
+ vis_edge_b = gr.Image(label="Image B Edges", type="pil")
199
+
200
+ btn_analyze.click(
201
+ fn=analyze_images,
202
+ inputs=[img_in_a, img_in_b],
203
+ outputs=[
204
+ score_semantic,
205
+ score_struct,
206
+ score_patch,
207
+ vis_patch,
208
+ vis_edge_a,
209
+ vis_edge_b
210
+ ]
211
+ )
212
+
213
+ if __name__ == "__main__":
214
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ torch>=2.0.0
3
+ torchvision
4
+ transformers>=4.33.0
5
+ opencv-python-headless
6
+ numpy
7
+ Pillow