Use non-overlapping full blocks with boundary-only completion
Browse files- README.md +12 -3
- demo/app.py +1 -1
- demo/attention.py +2 -1
- ltf_final/detector.py +3 -2
- release_manifest.json +8 -6
- utils/tile_dataset_srm.py +6 -8
README.md
CHANGED
|
@@ -51,7 +51,7 @@ do not represent eight entirely held-out generators.
|
|
| 51 |
|
| 52 |
## Download and run
|
| 53 |
|
| 54 |
-
Use **Python 3.11**.
|
| 55 |
|
| 56 |
```bash
|
| 57 |
python -m pip install -U huggingface_hub
|
|
@@ -81,8 +81,8 @@ print(result["prediction"], result["score_fake"])
|
|
| 81 |
print(result["probabilities"])
|
| 82 |
```
|
| 83 |
|
| 84 |
-
The image is decoded as RGB.
|
| 85 |
-
a global view use
|
| 86 |
recompressed. Test tiling depends on image size. Labels are **0=real, 1=fake**.
|
| 87 |
The API returns L1/L2/fusion scores, the selected label and the view count.
|
| 88 |
Loading the model does not download external weights after this repository
|
|
@@ -163,3 +163,12 @@ Please cite the LTF paper:
|
|
| 163 |
archivePrefix={arXiv}
|
| 164 |
}
|
| 165 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
## Download and run
|
| 53 |
|
| 54 |
+
Use **Python 3.11**. The commands below use this model repository.
|
| 55 |
|
| 56 |
```bash
|
| 57 |
python -m pip install -U huggingface_hub
|
|
|
|
| 81 |
print(result["probabilities"])
|
| 82 |
```
|
| 83 |
|
| 84 |
+
The image is decoded as RGB. Non-overlapping full 224×224 local blocks, with right/bottom boundary completion,
|
| 85 |
+
plus a global view use CLIP preprocessing. Input files are not
|
| 86 |
recompressed. Test tiling depends on image size. Labels are **0=real, 1=fake**.
|
| 87 |
The API returns L1/L2/fusion scores, the selected label and the view count.
|
| 88 |
Loading the model does not download external weights after this repository
|
|
|
|
| 163 |
archivePrefix={arXiv}
|
| 164 |
}
|
| 165 |
```
|
| 166 |
+
|
| 167 |
+
## Tiling policy
|
| 168 |
+
|
| 169 |
+
Full 224×224 blocks are taken without overlap. If pixels remain at the right
|
| 170 |
+
or bottom, a final block is aligned to that boundary; only boundary blocks
|
| 171 |
+
may overlap. For width 500, horizontal starts are 0, 224, 276. The global view
|
| 172 |
+
is separate. This corrects earlier uniformly overlapping tiling; weights are
|
| 173 |
+
unchanged, but predictions can change. Historical benchmark results have not
|
| 174 |
+
been recomputed with this preprocessing.
|
demo/app.py
CHANGED
|
@@ -495,7 +495,7 @@ def build_demo():
|
|
| 495 |
elem_classes="attention-image",
|
| 496 |
)
|
| 497 |
gr.Markdown(
|
| 498 |
-
"Tiles are numbered row by row.
|
| 499 |
)
|
| 500 |
with gr.Tab("Tile · Patch Details", id="patch"):
|
| 501 |
gr.Markdown(
|
|
|
|
| 495 |
elem_classes="attention-image",
|
| 496 |
)
|
| 497 |
gr.Markdown(
|
| 498 |
+
"Tiles are numbered row by row. Full 224-pixel blocks do not overlap; only the final right/bottom boundary blocks may overlap. Some labels are omitted on dense grids; complete coordinates are listed below."
|
| 499 |
)
|
| 500 |
with gr.Tab("Tile · Patch Details", id="patch"):
|
| 501 |
gr.Markdown(
|
demo/attention.py
CHANGED
|
@@ -64,7 +64,7 @@ def capture_prediction(detector, path, branch, threshold):
|
|
| 64 |
|
| 65 |
|
| 66 |
def tile_boxes(width, height, tile_size=224):
|
| 67 |
-
#
|
| 68 |
from utils.tile_dataset_srm import get_start_positions
|
| 69 |
|
| 70 |
return [
|
|
@@ -256,6 +256,7 @@ def parameter_rows(detector, branch, threshold, views):
|
|
| 256 |
],
|
| 257 |
["Inference", "CLIP chunk size", str(detector.model.eval_chunk_size)],
|
| 258 |
["Inference", "Views: global + local", f"1 + {views - 1} = {views}"],
|
|
|
|
| 259 |
["Architecture", "Backbone", "Frozen CLIP ViT-L/14"],
|
| 260 |
[
|
| 261 |
"Architecture",
|
|
|
|
| 64 |
|
| 65 |
|
| 66 |
def tile_boxes(width, height, tile_size=224):
|
| 67 |
+
# Match inference: non-overlapping full blocks, then right/bottom boundary blocks.
|
| 68 |
from utils.tile_dataset_srm import get_start_positions
|
| 69 |
|
| 70 |
return [
|
|
|
|
| 256 |
],
|
| 257 |
["Inference", "CLIP chunk size", str(detector.model.eval_chunk_size)],
|
| 258 |
["Inference", "Views: global + local", f"1 + {views - 1} = {views}"],
|
| 259 |
+
["Inference", "Tiling policy", "Non-overlapping full blocks; overlap only at right/bottom boundaries"],
|
| 260 |
["Architecture", "Backbone", "Frozen CLIP ViT-L/14"],
|
| 261 |
[
|
| 262 |
"Architecture",
|
ltf_final/detector.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Offline model loading and
|
| 2 |
from __future__ import annotations
|
| 3 |
|
| 4 |
import contextlib
|
|
@@ -78,7 +78,7 @@ def create_model(config, weights=None, device='cpu'):
|
|
| 78 |
|
| 79 |
|
| 80 |
def prepare_image(path, tile_size=224):
|
| 81 |
-
"""
|
| 82 |
path = Path(path).expanduser().resolve()
|
| 83 |
with Image.open(path) as source:
|
| 84 |
image = source.convert('RGB')
|
|
@@ -122,4 +122,5 @@ class LTFDetector:
|
|
| 122 |
label = int(score >= threshold)
|
| 123 |
return dict(path=str(path), prediction='fake' if label else 'real', label=label,
|
| 124 |
branch=branch, threshold=float(threshold), score_fake=score,
|
|
|
|
| 125 |
views=int(images.shape[0]), probabilities=probabilities)
|
|
|
|
| 1 |
+
"""Offline model loading and non-overlapping tiling with boundary completion."""
|
| 2 |
from __future__ import annotations
|
| 3 |
|
| 4 |
import contextlib
|
|
|
|
| 78 |
|
| 79 |
|
| 80 |
def prepare_image(path, tile_size=224):
|
| 81 |
+
"""Original RGB and CLIP normalization; non-overlapping blocks with boundary completion."""
|
| 82 |
path = Path(path).expanduser().resolve()
|
| 83 |
with Image.open(path) as source:
|
| 84 |
image = source.convert('RGB')
|
|
|
|
| 122 |
label = int(score >= threshold)
|
| 123 |
return dict(path=str(path), prediction='fake' if label else 'real', label=label,
|
| 124 |
branch=branch, threshold=float(threshold), score_fake=score,
|
| 125 |
+
tiling_policy="nonoverlap_then_boundary",
|
| 126 |
views=int(images.shape[0]), probabilities=probabilities)
|
release_manifest.json
CHANGED
|
@@ -11,7 +11,8 @@
|
|
| 11 |
"Tensor-only checkpoint loading",
|
| 12 |
"Portable evaluation configuration",
|
| 13 |
"Preserve Hub snapshot root when source files are symlinks",
|
| 14 |
-
"English source comments and docstrings; executable AST unchanged"
|
|
|
|
| 15 |
],
|
| 16 |
"files": {
|
| 17 |
"ltf_final/__init__.py": {
|
|
@@ -19,8 +20,8 @@
|
|
| 19 |
"bytes": 175
|
| 20 |
},
|
| 21 |
"ltf_final/detector.py": {
|
| 22 |
-
"sha256": "
|
| 23 |
-
"bytes":
|
| 24 |
},
|
| 25 |
"model/LTF.py": {
|
| 26 |
"sha256": "60d12ab01bf5d748ab0d0d453d99e18512d1a5043c742a2030108d9f15cb7f92",
|
|
@@ -63,8 +64,8 @@
|
|
| 63 |
"bytes": 0
|
| 64 |
},
|
| 65 |
"utils/tile_dataset_srm.py": {
|
| 66 |
-
"sha256": "
|
| 67 |
-
"bytes":
|
| 68 |
},
|
| 69 |
"configs/eval.yaml": {
|
| 70 |
"sha256": "4ce443731e70a4ac4eb71fb56103f33373e179fefe766c3e6a02635efdb94efc",
|
|
@@ -78,5 +79,6 @@
|
|
| 78 |
"sha256": "b8cca3fd41ae0c99ba7e8951adf17d267cdb84cd88be6f7c2e0eca1737a03836",
|
| 79 |
"bytes": 932768134
|
| 80 |
}
|
| 81 |
-
}
|
|
|
|
| 82 |
}
|
|
|
|
| 11 |
"Tensor-only checkpoint loading",
|
| 12 |
"Portable evaluation configuration",
|
| 13 |
"Preserve Hub snapshot root when source files are symlinks",
|
| 14 |
+
"English source comments and docstrings; executable AST unchanged",
|
| 15 |
+
"Non-overlapping full 224-pixel blocks with right/bottom boundary completion; historical benchmark numbers not recomputed"
|
| 16 |
],
|
| 17 |
"files": {
|
| 18 |
"ltf_final/__init__.py": {
|
|
|
|
| 20 |
"bytes": 175
|
| 21 |
},
|
| 22 |
"ltf_final/detector.py": {
|
| 23 |
+
"sha256": "fee36f9976efea2de60f0d106f9a3693ebc394cfa5dd4277470bd618f0612307",
|
| 24 |
+
"bytes": 6147
|
| 25 |
},
|
| 26 |
"model/LTF.py": {
|
| 27 |
"sha256": "60d12ab01bf5d748ab0d0d453d99e18512d1a5043c742a2030108d9f15cb7f92",
|
|
|
|
| 64 |
"bytes": 0
|
| 65 |
},
|
| 66 |
"utils/tile_dataset_srm.py": {
|
| 67 |
+
"sha256": "a79a5edc0804aa0331805ba27276d09fa4928b3f2938a08eb3fa398682824c84",
|
| 68 |
+
"bytes": 14576
|
| 69 |
},
|
| 70 |
"configs/eval.yaml": {
|
| 71 |
"sha256": "4ce443731e70a4ac4eb71fb56103f33373e179fefe766c3e6a02635efdb94efc",
|
|
|
|
| 79 |
"sha256": "b8cca3fd41ae0c99ba7e8951adf17d267cdb84cd88be6f7c2e0eca1737a03836",
|
| 80 |
"bytes": 932768134
|
| 81 |
}
|
| 82 |
+
},
|
| 83 |
+
"tiling_policy": "nonoverlap_then_boundary"
|
| 84 |
}
|
utils/tile_dataset_srm.py
CHANGED
|
@@ -52,15 +52,13 @@ def pad_crop_image(image, target_width, target_height):
|
|
| 52 |
return image
|
| 53 |
|
| 54 |
def get_start_positions(image_length, block_size, bias=0):
|
|
|
|
| 55 |
if image_length <= block_size:
|
| 56 |
-
return [
|
| 57 |
-
|
| 58 |
-
if
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
positions = [int(round(i * total_stride) + bias) for i in range(num_blocks - 1)]
|
| 62 |
-
positions.append(image_length - block_size + bias)
|
| 63 |
-
return positions
|
| 64 |
|
| 65 |
def crop_image_to_tiles(image, block_size):
|
| 66 |
"""
|
|
|
|
| 52 |
return image
|
| 53 |
|
| 54 |
def get_start_positions(image_length, block_size, bias=0):
|
| 55 |
+
"""Non-overlapping full blocks, plus one boundary block when needed."""
|
| 56 |
if image_length <= block_size:
|
| 57 |
+
return [bias]
|
| 58 |
+
positions = list(range(0, image_length - block_size + 1, block_size))
|
| 59 |
+
if positions[-1] != image_length - block_size:
|
| 60 |
+
positions.append(image_length - block_size)
|
| 61 |
+
return [position + bias for position in positions]
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
def crop_image_to_tiles(image, block_size):
|
| 64 |
"""
|