demark 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- demark-0.0.1/PKG-INFO +195 -0
- demark-0.0.1/README.md +154 -0
- demark-0.0.1/demark/__init__.py +45 -0
- demark-0.0.1/demark/__main__.py +4 -0
- demark-0.0.1/demark/__version__.py +1 -0
- demark-0.0.1/demark/api.py +227 -0
- demark-0.0.1/demark/cli.py +188 -0
- demark-0.0.1/demark/core.py +1830 -0
- demark-0.0.1/demark/harmonic.py +278 -0
- demark-0.0.1/demark/tools.py +119 -0
- demark-0.0.1/demark.egg-info/PKG-INFO +195 -0
- demark-0.0.1/demark.egg-info/SOURCES.txt +17 -0
- demark-0.0.1/demark.egg-info/dependency_links.txt +1 -0
- demark-0.0.1/demark.egg-info/entry_points.txt +2 -0
- demark-0.0.1/demark.egg-info/requires.txt +19 -0
- demark-0.0.1/demark.egg-info/top_level.txt +1 -0
- demark-0.0.1/pyproject.toml +53 -0
- demark-0.0.1/setup.cfg +4 -0
- demark-0.0.1/tests/test_unified_api.py +284 -0
demark-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demark
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Automatic image watermark detection and removal toolkit (CV + DL methods)
|
|
5
|
+
Author-email: akino <6130092+cycleuser@users.noreply.github.com>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/cycleuser/demark
|
|
8
|
+
Project-URL: Repository, https://github.com/cycleuser/demark
|
|
9
|
+
Project-URL: Documentation, https://github.com/cycleuser/demark#readme
|
|
10
|
+
Project-URL: Changelog, https://github.com/cycleuser/demark/releases
|
|
11
|
+
Keywords: watermark,watermark-removal,inpainting,image-processing,opencv,lama,alpha-matting,line-art
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Multimedia :: Graphics :: Editors
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Requires-Dist: opencv-python>=4.8
|
|
27
|
+
Requires-Dist: scipy>=1.10
|
|
28
|
+
Requires-Dist: scikit-image>=0.20
|
|
29
|
+
Requires-Dist: Pillow>=10.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy; extra == "dev"
|
|
34
|
+
Provides-Extra: deep
|
|
35
|
+
Requires-Dist: torch>=2.0; extra == "deep"
|
|
36
|
+
Requires-Dist: simple-lama-inpainting; extra == "deep"
|
|
37
|
+
Provides-Extra: sd
|
|
38
|
+
Requires-Dist: torch>=2.0; extra == "sd"
|
|
39
|
+
Requires-Dist: diffusers>=0.20; extra == "sd"
|
|
40
|
+
Requires-Dist: transformers>=4.30; extra == "sd"
|
|
41
|
+
|
|
42
|
+
# DeMark
|
|
43
|
+
|
|
44
|
+
Automatic image watermark detection and removal toolkit. Multiple methods from classic CV to deep learning, pick what fits your watermark type.
|
|
45
|
+
|
|
46
|
+
## What it does
|
|
47
|
+
|
|
48
|
+
Detects watermark regions in images and removes them while preserving underlying content. Ten methods are implemented:
|
|
49
|
+
|
|
50
|
+
| Method | Type | Best for | Lossless? |
|
|
51
|
+
|--------|------|----------|-----------|
|
|
52
|
+
| `auto` | — | Auto-selects best method | — |
|
|
53
|
+
| `cv_telea` | CV inpaint [6] | Small/thin watermarks | No |
|
|
54
|
+
| `cv_ns` | CV inpaint [7] | Smooth gradients around watermark | No |
|
|
55
|
+
| `fft_notch` | Frequency | Repeating/tiled periodic watermarks | Partial |
|
|
56
|
+
| `color_mask` | CV segmentation | Solid-color text (white on photo) | No |
|
|
57
|
+
| `edge_inpaint` | Edge + inpaint [5] | Text/logos with sharp edges | No |
|
|
58
|
+
| `template` | Template match | Known watermark logo | No |
|
|
59
|
+
| `alpha_invert` | Alpha matting [8] | Known watermark + transparency | **Yes** |
|
|
60
|
+
| `lama` | Deep learning [1] | Large masked regions | No |
|
|
61
|
+
| `sd_inpaint` | Diffusion [2] | Generative content fill | No |
|
|
62
|
+
| `precise` | Alpha inversion + line protection | Semi-transparent text on line art | Partial |
|
|
63
|
+
| `harmonic` | Multi-scale + auto-optimisation | Any scale, self-tuning | Partial |
|
|
64
|
+
|
|
65
|
+
## What it does NOT do
|
|
66
|
+
|
|
67
|
+
- No batch GUI — CLI and Python API only. For a GUI, use IOPaint (23k stars) which wraps LaMa.
|
|
68
|
+
- No video watermark removal — for video use ProPainter (sczhou/ProPainter).
|
|
69
|
+
- No invisible/digital watermark removal — those require diffusion-based attacks (see [10]).
|
|
70
|
+
- `alpha_invert` is the only truly lossless method, and it requires the watermark pattern and alpha to be known.
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
- Python 3.9+
|
|
75
|
+
- OpenCV, NumPy, SciPy, scikit-image, Pillow
|
|
76
|
+
- For deep learning methods: `pip install demark[deep]` (LaMa) or `pip install demark[sd]` (Stable Diffusion)
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# From PyPI
|
|
82
|
+
pip install demark
|
|
83
|
+
|
|
84
|
+
# Optional deep learning extras (LaMa / Stable Diffusion)
|
|
85
|
+
pip install "demark[deep]" # LaMa inpainting
|
|
86
|
+
pip install "demark[sd]" # Stable Diffusion inpainting
|
|
87
|
+
|
|
88
|
+
# From source
|
|
89
|
+
git clone https://github.com/cycleuser/demark.git
|
|
90
|
+
cd demark
|
|
91
|
+
pip install -e .
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Auto-detect and remove
|
|
98
|
+
demark remove watermarked.jpg clean.jpg
|
|
99
|
+
|
|
100
|
+
# Use specific method
|
|
101
|
+
demark remove watermarked.jpg clean.jpg --method fft_notch
|
|
102
|
+
|
|
103
|
+
# Known watermark logo — template matching
|
|
104
|
+
demark remove watermarked.jpg clean.jpg --method template --template logo.png
|
|
105
|
+
|
|
106
|
+
# Known watermark overlay + alpha — lossless removal
|
|
107
|
+
demark remove watermarked.jpg clean.jpg --method alpha_invert --watermark wm_overlay.png --alpha 0.4
|
|
108
|
+
|
|
109
|
+
# Scale-adaptive multi-scale + auto-optimisation (works at any scale)
|
|
110
|
+
demark remove watermarked.jpg clean.jpg --method harmonic --bbox 1056 1249 1304 1305
|
|
111
|
+
|
|
112
|
+
# Just detect the mask
|
|
113
|
+
demark detect watermarked.jpg mask.png
|
|
114
|
+
|
|
115
|
+
# JSON output
|
|
116
|
+
demark --json remove watermarked.jpg clean.jpg
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
### CLI
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
demark [-V] [-v] [-o OUTPUT] [--json] [-q] <command> ...
|
|
125
|
+
|
|
126
|
+
Commands:
|
|
127
|
+
remove <input> <output> [--method M] [--mask M.png] [--template T.png]
|
|
128
|
+
[--watermark W.png] [--alpha 0.4] [--radius 5]
|
|
129
|
+
detect <input> <output> [--method auto|edge|color|otsu]
|
|
130
|
+
list-methods
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Python API
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from demark import remove_watermark, detect_watermark
|
|
137
|
+
|
|
138
|
+
# Auto remove
|
|
139
|
+
r = remove_watermark(input_path="wm.jpg", output_path="clean.jpg")
|
|
140
|
+
print(r.success) # True
|
|
141
|
+
print(r.data["method"]) # "edge_inpaint"
|
|
142
|
+
print(r.data["elapsed"])# 0.123
|
|
143
|
+
|
|
144
|
+
# Specific method
|
|
145
|
+
r = remove_watermark(
|
|
146
|
+
input_path="wm.jpg",
|
|
147
|
+
output_path="clean.jpg",
|
|
148
|
+
method="fft_notch"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Detect only
|
|
152
|
+
r = detect_watermark(input_path="wm.jpg", output_path="mask.png")
|
|
153
|
+
print(r.data["coverage"]) # 0.0523
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Agent Integration (OpenAI Function Calling)
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from demark.tools import TOOLS, dispatch
|
|
160
|
+
|
|
161
|
+
# Pass TOOLS to your LLM's function-calling API
|
|
162
|
+
result = dispatch("demark_remove_watermark", {
|
|
163
|
+
"input_path": "/data/wm.jpg",
|
|
164
|
+
"output_path": "/data/clean.jpg",
|
|
165
|
+
"method": "auto"
|
|
166
|
+
})
|
|
167
|
+
print(result["success"]) # True
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## References
|
|
171
|
+
|
|
172
|
+
| # | Paper | Year | Venue |
|
|
173
|
+
|---|-------|------|-------|
|
|
174
|
+
| [1] | Suvorov et al., "Resolution-robust Large Mask Inpainting with Fourier Convolutions" | 2021 | WACV 2022 |
|
|
175
|
+
| [2] | Rombach et al., "High-Resolution Image Synthesis with Latent Diffusion Models" | 2022 | CVPR |
|
|
176
|
+
| [3] | Liu et al., "WDNet: Watermark-Decomposition Network for Visible Watermark Removal" | 2020 | WACV 2021 |
|
|
177
|
+
| [4] | Cun & Pun, "Split then Refine: Stacked Attention-guided ResUNets for Blind Watermark Removal" | 2020 | AAAI 2021 |
|
|
178
|
+
| [5] | Robinette & Johnson, "Blind Visible Watermark Removal with Morphological Dilation" | 2025 | arXiv:2502.02676 |
|
|
179
|
+
| [6] | Telea, "An Image Inpainting Technique Based on the Fast Marching Method" | 2004 | J. Graphics Tools |
|
|
180
|
+
| [7] | Bertalmio et al., "Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting" | 2001 | CVPR |
|
|
181
|
+
| [8] | Levin et al., "A Closed-Form Solution to Natural Image Matting" | 2006 | IEEE TPAMI |
|
|
182
|
+
| [9] | Huo et al., "WMFormer++: Nested Transformer for Visible Watermark Removal" | 2023 | arXiv:2308.10195 |
|
|
183
|
+
| [10] | "Vanishing Watermarks: Diffusion-Based Image Editing Undermines Robust Invisible Watermarking" | 2026 | arXiv:2602.20680 |
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install -e ".[dev]"
|
|
189
|
+
pytest tests/test_unified_api.py -v
|
|
190
|
+
ruff check demark/
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
GPL-3.0
|
demark-0.0.1/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# DeMark
|
|
2
|
+
|
|
3
|
+
Automatic image watermark detection and removal toolkit. Multiple methods from classic CV to deep learning, pick what fits your watermark type.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Detects watermark regions in images and removes them while preserving underlying content. Ten methods are implemented:
|
|
8
|
+
|
|
9
|
+
| Method | Type | Best for | Lossless? |
|
|
10
|
+
|--------|------|----------|-----------|
|
|
11
|
+
| `auto` | — | Auto-selects best method | — |
|
|
12
|
+
| `cv_telea` | CV inpaint [6] | Small/thin watermarks | No |
|
|
13
|
+
| `cv_ns` | CV inpaint [7] | Smooth gradients around watermark | No |
|
|
14
|
+
| `fft_notch` | Frequency | Repeating/tiled periodic watermarks | Partial |
|
|
15
|
+
| `color_mask` | CV segmentation | Solid-color text (white on photo) | No |
|
|
16
|
+
| `edge_inpaint` | Edge + inpaint [5] | Text/logos with sharp edges | No |
|
|
17
|
+
| `template` | Template match | Known watermark logo | No |
|
|
18
|
+
| `alpha_invert` | Alpha matting [8] | Known watermark + transparency | **Yes** |
|
|
19
|
+
| `lama` | Deep learning [1] | Large masked regions | No |
|
|
20
|
+
| `sd_inpaint` | Diffusion [2] | Generative content fill | No |
|
|
21
|
+
| `precise` | Alpha inversion + line protection | Semi-transparent text on line art | Partial |
|
|
22
|
+
| `harmonic` | Multi-scale + auto-optimisation | Any scale, self-tuning | Partial |
|
|
23
|
+
|
|
24
|
+
## What it does NOT do
|
|
25
|
+
|
|
26
|
+
- No batch GUI — CLI and Python API only. For a GUI, use IOPaint (23k stars) which wraps LaMa.
|
|
27
|
+
- No video watermark removal — for video use ProPainter (sczhou/ProPainter).
|
|
28
|
+
- No invisible/digital watermark removal — those require diffusion-based attacks (see [10]).
|
|
29
|
+
- `alpha_invert` is the only truly lossless method, and it requires the watermark pattern and alpha to be known.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Python 3.9+
|
|
34
|
+
- OpenCV, NumPy, SciPy, scikit-image, Pillow
|
|
35
|
+
- For deep learning methods: `pip install demark[deep]` (LaMa) or `pip install demark[sd]` (Stable Diffusion)
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# From PyPI
|
|
41
|
+
pip install demark
|
|
42
|
+
|
|
43
|
+
# Optional deep learning extras (LaMa / Stable Diffusion)
|
|
44
|
+
pip install "demark[deep]" # LaMa inpainting
|
|
45
|
+
pip install "demark[sd]" # Stable Diffusion inpainting
|
|
46
|
+
|
|
47
|
+
# From source
|
|
48
|
+
git clone https://github.com/cycleuser/demark.git
|
|
49
|
+
cd demark
|
|
50
|
+
pip install -e .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Auto-detect and remove
|
|
57
|
+
demark remove watermarked.jpg clean.jpg
|
|
58
|
+
|
|
59
|
+
# Use specific method
|
|
60
|
+
demark remove watermarked.jpg clean.jpg --method fft_notch
|
|
61
|
+
|
|
62
|
+
# Known watermark logo — template matching
|
|
63
|
+
demark remove watermarked.jpg clean.jpg --method template --template logo.png
|
|
64
|
+
|
|
65
|
+
# Known watermark overlay + alpha — lossless removal
|
|
66
|
+
demark remove watermarked.jpg clean.jpg --method alpha_invert --watermark wm_overlay.png --alpha 0.4
|
|
67
|
+
|
|
68
|
+
# Scale-adaptive multi-scale + auto-optimisation (works at any scale)
|
|
69
|
+
demark remove watermarked.jpg clean.jpg --method harmonic --bbox 1056 1249 1304 1305
|
|
70
|
+
|
|
71
|
+
# Just detect the mask
|
|
72
|
+
demark detect watermarked.jpg mask.png
|
|
73
|
+
|
|
74
|
+
# JSON output
|
|
75
|
+
demark --json remove watermarked.jpg clean.jpg
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### CLI
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
demark [-V] [-v] [-o OUTPUT] [--json] [-q] <command> ...
|
|
84
|
+
|
|
85
|
+
Commands:
|
|
86
|
+
remove <input> <output> [--method M] [--mask M.png] [--template T.png]
|
|
87
|
+
[--watermark W.png] [--alpha 0.4] [--radius 5]
|
|
88
|
+
detect <input> <output> [--method auto|edge|color|otsu]
|
|
89
|
+
list-methods
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Python API
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from demark import remove_watermark, detect_watermark
|
|
96
|
+
|
|
97
|
+
# Auto remove
|
|
98
|
+
r = remove_watermark(input_path="wm.jpg", output_path="clean.jpg")
|
|
99
|
+
print(r.success) # True
|
|
100
|
+
print(r.data["method"]) # "edge_inpaint"
|
|
101
|
+
print(r.data["elapsed"])# 0.123
|
|
102
|
+
|
|
103
|
+
# Specific method
|
|
104
|
+
r = remove_watermark(
|
|
105
|
+
input_path="wm.jpg",
|
|
106
|
+
output_path="clean.jpg",
|
|
107
|
+
method="fft_notch"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Detect only
|
|
111
|
+
r = detect_watermark(input_path="wm.jpg", output_path="mask.png")
|
|
112
|
+
print(r.data["coverage"]) # 0.0523
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Agent Integration (OpenAI Function Calling)
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from demark.tools import TOOLS, dispatch
|
|
119
|
+
|
|
120
|
+
# Pass TOOLS to your LLM's function-calling API
|
|
121
|
+
result = dispatch("demark_remove_watermark", {
|
|
122
|
+
"input_path": "/data/wm.jpg",
|
|
123
|
+
"output_path": "/data/clean.jpg",
|
|
124
|
+
"method": "auto"
|
|
125
|
+
})
|
|
126
|
+
print(result["success"]) # True
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## References
|
|
130
|
+
|
|
131
|
+
| # | Paper | Year | Venue |
|
|
132
|
+
|---|-------|------|-------|
|
|
133
|
+
| [1] | Suvorov et al., "Resolution-robust Large Mask Inpainting with Fourier Convolutions" | 2021 | WACV 2022 |
|
|
134
|
+
| [2] | Rombach et al., "High-Resolution Image Synthesis with Latent Diffusion Models" | 2022 | CVPR |
|
|
135
|
+
| [3] | Liu et al., "WDNet: Watermark-Decomposition Network for Visible Watermark Removal" | 2020 | WACV 2021 |
|
|
136
|
+
| [4] | Cun & Pun, "Split then Refine: Stacked Attention-guided ResUNets for Blind Watermark Removal" | 2020 | AAAI 2021 |
|
|
137
|
+
| [5] | Robinette & Johnson, "Blind Visible Watermark Removal with Morphological Dilation" | 2025 | arXiv:2502.02676 |
|
|
138
|
+
| [6] | Telea, "An Image Inpainting Technique Based on the Fast Marching Method" | 2004 | J. Graphics Tools |
|
|
139
|
+
| [7] | Bertalmio et al., "Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting" | 2001 | CVPR |
|
|
140
|
+
| [8] | Levin et al., "A Closed-Form Solution to Natural Image Matting" | 2006 | IEEE TPAMI |
|
|
141
|
+
| [9] | Huo et al., "WMFormer++: Nested Transformer for Visible Watermark Removal" | 2023 | arXiv:2308.10195 |
|
|
142
|
+
| [10] | "Vanishing Watermarks: Diffusion-Based Image Editing Undermines Robust Invisible Watermarking" | 2026 | arXiv:2602.20680 |
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
pytest tests/test_unified_api.py -v
|
|
149
|
+
ruff check demark/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
GPL-3.0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeMark — Automatic image watermark detection and removal toolkit.
|
|
3
|
+
|
|
4
|
+
Implements multiple methods from classic CV to deep learning, so the user
|
|
5
|
+
can pick whichever suits the watermark type (semi-transparent logo, tiled
|
|
6
|
+
repeating pattern, solid-color text, known template, etc.).
|
|
7
|
+
|
|
8
|
+
Public API:
|
|
9
|
+
remove_watermark — auto-detect + remove (method auto or specified)
|
|
10
|
+
detect_watermark — detect watermark mask only
|
|
11
|
+
Methods enum — all available removal strategies
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .__version__ import __version__
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
"ToolResult",
|
|
20
|
+
"remove_watermark",
|
|
21
|
+
"detect_watermark",
|
|
22
|
+
"Methods",
|
|
23
|
+
"harmonic_loss",
|
|
24
|
+
"coordinate_descent",
|
|
25
|
+
"grid_search",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def __getattr__(name: str):
|
|
30
|
+
if name == "ToolResult":
|
|
31
|
+
from .api import ToolResult
|
|
32
|
+
return ToolResult
|
|
33
|
+
if name == "remove_watermark":
|
|
34
|
+
from .api import remove_watermark
|
|
35
|
+
return remove_watermark
|
|
36
|
+
if name == "detect_watermark":
|
|
37
|
+
from .api import detect_watermark
|
|
38
|
+
return detect_watermark
|
|
39
|
+
if name == "Methods":
|
|
40
|
+
from .core import Methods
|
|
41
|
+
return Methods
|
|
42
|
+
if name in ("harmonic_loss", "coordinate_descent", "grid_search"):
|
|
43
|
+
from . import harmonic
|
|
44
|
+
return getattr(harmonic, name)
|
|
45
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Python API for DeMark.
|
|
3
|
+
|
|
4
|
+
Every function returns ToolResult so callers never need try/except.
|
|
5
|
+
Path inputs accept both str and pathlib.Path.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
from .__version__ import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ToolResult:
|
|
20
|
+
success: bool
|
|
21
|
+
data: Any = None
|
|
22
|
+
error: Optional[str] = None
|
|
23
|
+
metadata: dict = field(default_factory=dict)
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict:
|
|
26
|
+
return {
|
|
27
|
+
"success": self.success,
|
|
28
|
+
"data": self.data,
|
|
29
|
+
"error": self.error,
|
|
30
|
+
"metadata": self.metadata,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def remove_watermark(
|
|
35
|
+
*,
|
|
36
|
+
input_path: str,
|
|
37
|
+
output_path: str,
|
|
38
|
+
method: str = "auto",
|
|
39
|
+
mask_path: Optional[str] = None,
|
|
40
|
+
template_path: Optional[str] = None,
|
|
41
|
+
watermark_path: Optional[str] = None,
|
|
42
|
+
watermark_text: Optional[str] = None,
|
|
43
|
+
region: str = "bottom-right",
|
|
44
|
+
ratio: float = 0.3,
|
|
45
|
+
alpha: Optional[float] = None,
|
|
46
|
+
inpaint_radius: int = 5,
|
|
47
|
+
bbox: Optional[tuple] = None,
|
|
48
|
+
char_bboxes: Optional[list] = None,
|
|
49
|
+
scale_levels: int = 3,
|
|
50
|
+
harmonic_auto: bool = True,
|
|
51
|
+
) -> ToolResult:
|
|
52
|
+
"""Remove watermark from an image and save the result.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
input_path: Path to input image (jpg/png/bmp/webp).
|
|
56
|
+
output_path: Path to save the de-watermarked image.
|
|
57
|
+
method: Removal strategy — one of: auto, cv_telea, cv_ns,
|
|
58
|
+
fft_notch, color_mask, edge_inpaint, template,
|
|
59
|
+
alpha_invert, lama, sd_inpaint, fuse, background_aware,
|
|
60
|
+
precise. ``precise`` requires ``bbox`` (x1,y1,x2,y2).
|
|
61
|
+
mask_path: Optional pre-made mask image (white=watermark).
|
|
62
|
+
If None, DeMark auto-detects.
|
|
63
|
+
template_path: Path to watermark template image (for method=template).
|
|
64
|
+
watermark_path: Path to watermark overlay image (for method=alpha_invert).
|
|
65
|
+
watermark_text: Watermark text content (e.g. "千问AI生成") for text-based
|
|
66
|
+
detection. DeMark renders the text and template-matches it.
|
|
67
|
+
region: Region hint for detection — "bottom-right", "full", "bottom", etc.
|
|
68
|
+
ratio: Region size fraction (0.3 = bottom 30%).
|
|
69
|
+
alpha: Alpha transparency value 0-1 (for method=alpha_invert).
|
|
70
|
+
If None, auto-estimated.
|
|
71
|
+
inpaint_radius: Inpaint radius in pixels (for CV methods).
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
ToolResult with data={"method": str, "output": str, "elapsed": float}
|
|
75
|
+
and metadata={"version": str}.
|
|
76
|
+
|
|
77
|
+
>>> from demark import remove_watermark
|
|
78
|
+
>>> r = remove_watermark(input_path="watermarked.jpg", output_path="clean.jpg")
|
|
79
|
+
>>> r.success
|
|
80
|
+
True
|
|
81
|
+
>>> r.data["method"]
|
|
82
|
+
'edge_inpaint'
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
import cv2
|
|
86
|
+
import numpy as np
|
|
87
|
+
from .core import Methods, remove, detect_watermark_smart, detect_auto
|
|
88
|
+
|
|
89
|
+
inp = str(Path(input_path).resolve())
|
|
90
|
+
outp = str(Path(output_path).resolve())
|
|
91
|
+
|
|
92
|
+
if not os.path.exists(inp):
|
|
93
|
+
return ToolResult(success=False, error=f"Input not found: {inp}",
|
|
94
|
+
metadata={"version": __version__})
|
|
95
|
+
|
|
96
|
+
img = cv2.imread(inp)
|
|
97
|
+
if img is None:
|
|
98
|
+
return ToolResult(success=False, error=f"Cannot read image: {inp}",
|
|
99
|
+
metadata={"version": __version__})
|
|
100
|
+
|
|
101
|
+
mask = None
|
|
102
|
+
if mask_path and os.path.exists(mask_path):
|
|
103
|
+
mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
|
|
104
|
+
elif method == "auto" or method in ("cv_telea", "cv_ns", "edge_inpaint",
|
|
105
|
+
"color_mask", "fft_notch"):
|
|
106
|
+
# smart detection: text-based if watermark_text given, else region
|
|
107
|
+
mask = detect_watermark_smart(img, watermark_text, region, ratio)
|
|
108
|
+
|
|
109
|
+
# build kwargs for the specific method
|
|
110
|
+
kwargs = {}
|
|
111
|
+
if method == "template" and template_path:
|
|
112
|
+
kwargs["template_path"] = template_path
|
|
113
|
+
if method == "alpha_invert":
|
|
114
|
+
if watermark_path:
|
|
115
|
+
kwargs["watermark_img"] = watermark_path
|
|
116
|
+
if alpha is not None:
|
|
117
|
+
kwargs["alpha"] = alpha
|
|
118
|
+
if method in ("cv_telea", "cv_ns", "color_mask", "edge_inpaint", "template"):
|
|
119
|
+
kwargs["inpaint_radius"] = inpaint_radius
|
|
120
|
+
if method == "precise":
|
|
121
|
+
if bbox is None:
|
|
122
|
+
return ToolResult(success=False,
|
|
123
|
+
error="precise method requires bbox=(x1,y1,x2,y2) pixel coords",
|
|
124
|
+
metadata={"version": __version__})
|
|
125
|
+
kwargs["bbox"] = bbox
|
|
126
|
+
if char_bboxes is not None:
|
|
127
|
+
kwargs["char_bboxes"] = char_bboxes
|
|
128
|
+
if method == "lama_precise":
|
|
129
|
+
if bbox is None:
|
|
130
|
+
return ToolResult(success=False,
|
|
131
|
+
error="lama_precise method requires bbox=(x1,y1,x2,y2) pixel coords",
|
|
132
|
+
metadata={"version": __version__})
|
|
133
|
+
kwargs["bbox"] = bbox
|
|
134
|
+
if method == "harmonic":
|
|
135
|
+
if bbox is None:
|
|
136
|
+
return ToolResult(success=False,
|
|
137
|
+
error="harmonic method requires bbox=(x1,y1,x2,y2) pixel coords",
|
|
138
|
+
metadata={"version": __version__})
|
|
139
|
+
kwargs["bbox"] = bbox
|
|
140
|
+
kwargs["scale_levels"] = scale_levels
|
|
141
|
+
kwargs["auto"] = harmonic_auto
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
method_enum = Methods(method)
|
|
145
|
+
except ValueError:
|
|
146
|
+
return ToolResult(success=False,
|
|
147
|
+
error=f"Unknown method: {method}. Valid: {[m.value for m in Methods]}",
|
|
148
|
+
metadata={"version": __version__})
|
|
149
|
+
|
|
150
|
+
result = remove(img, method_enum, mask=mask, **kwargs)
|
|
151
|
+
|
|
152
|
+
os.makedirs(os.path.dirname(outp) or ".", exist_ok=True)
|
|
153
|
+
cv2.imwrite(outp, result.image)
|
|
154
|
+
|
|
155
|
+
# also save the mask alongside output for inspection
|
|
156
|
+
mask_out = outp.rsplit(".", 1)[0] + "_mask.png"
|
|
157
|
+
cv2.imwrite(mask_out, result.mask)
|
|
158
|
+
|
|
159
|
+
return ToolResult(
|
|
160
|
+
success=True,
|
|
161
|
+
data={
|
|
162
|
+
"method": result.method,
|
|
163
|
+
"output": outp,
|
|
164
|
+
"mask": mask_out,
|
|
165
|
+
"elapsed": round(result.elapsed, 3),
|
|
166
|
+
},
|
|
167
|
+
metadata={"version": __version__, "image_size": img.shape[:2]},
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
return ToolResult(success=False, error=str(e),
|
|
171
|
+
metadata={"version": __version__})
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def detect_watermark(
|
|
175
|
+
*,
|
|
176
|
+
input_path: str,
|
|
177
|
+
output_path: str,
|
|
178
|
+
method: str = "auto",
|
|
179
|
+
) -> ToolResult:
|
|
180
|
+
"""Detect watermark mask and save it as a binary image.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
input_path: Path to input image.
|
|
184
|
+
output_path: Path to save the mask (white=watermark, black=clean).
|
|
185
|
+
method: Detection method — auto, edge, color, or otsu.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
ToolResult with data={"output": str, "coverage": float}.
|
|
189
|
+
|
|
190
|
+
>>> from demark import detect_watermark
|
|
191
|
+
>>> r = detect_watermark(input_path="img.jpg", output_path="mask.png")
|
|
192
|
+
>>> r.success
|
|
193
|
+
True
|
|
194
|
+
>>> r.data["coverage"] < 1.0
|
|
195
|
+
True
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
import cv2
|
|
199
|
+
import numpy as np
|
|
200
|
+
from .core import detect_only
|
|
201
|
+
|
|
202
|
+
inp = str(Path(input_path).resolve())
|
|
203
|
+
outp = str(Path(output_path).resolve())
|
|
204
|
+
|
|
205
|
+
if not os.path.exists(inp):
|
|
206
|
+
return ToolResult(success=False, error=f"Input not found: {inp}",
|
|
207
|
+
metadata={"version": __version__})
|
|
208
|
+
|
|
209
|
+
img = cv2.imread(inp)
|
|
210
|
+
if img is None:
|
|
211
|
+
return ToolResult(success=False, error=f"Cannot read image: {inp}",
|
|
212
|
+
metadata={"version": __version__})
|
|
213
|
+
|
|
214
|
+
mask = detect_only(img, method)
|
|
215
|
+
os.makedirs(os.path.dirname(outp) or ".", exist_ok=True)
|
|
216
|
+
cv2.imwrite(outp, mask)
|
|
217
|
+
|
|
218
|
+
coverage = float(np.count_nonzero(mask)) / mask.size
|
|
219
|
+
|
|
220
|
+
return ToolResult(
|
|
221
|
+
success=True,
|
|
222
|
+
data={"output": outp, "coverage": round(coverage, 4)},
|
|
223
|
+
metadata={"version": __version__, "image_size": img.shape[:2]},
|
|
224
|
+
)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
return ToolResult(success=False, error=str(e),
|
|
227
|
+
metadata={"version": __version__})
|