bened commited on
Commit
2a48dd4
·
verified ·
1 Parent(s): 6007db6

Update watermark/engine.py

Browse files
Files changed (1) hide show
  1. watermark/engine.py +392 -563
watermark/engine.py CHANGED
@@ -1,16 +1,14 @@
1
  from pathlib import Path
2
  import os
3
  import urllib.request
4
- import types
5
 
6
  import numpy as np
7
  import torch
8
-
9
  from PIL import Image, ImageDraw, ImageFilter
10
 
11
 
12
  # ============================================================
13
- # Global model state
14
  # ============================================================
15
 
16
  MODEL_CACHE = None
@@ -22,7 +20,37 @@ LAMA_MODEL = None
22
 
23
 
24
  # ============================================================
25
- # Cache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  # ============================================================
27
 
28
  def _get_writable_cache():
@@ -53,9 +81,10 @@ def _get_writable_cache():
53
  encoding="utf-8",
54
  )
55
 
56
- test.unlink(
57
- missing_ok=True
58
- )
 
59
 
60
  print(
61
  f"[Watermark] Cache directory: {p}"
@@ -63,10 +92,7 @@ def _get_writable_cache():
63
 
64
  return p
65
 
66
- except (
67
- PermissionError,
68
- OSError,
69
- ):
70
  pass
71
 
72
  raise RuntimeError(
@@ -84,20 +110,21 @@ def _cache_dir():
84
 
85
 
86
  # ============================================================
87
- # Florence-2 compatibility
88
  # ============================================================
89
 
90
- def _patch_florence_config():
91
  """
92
- Compatibility patch for older Florence-2 custom configuration.
93
-
94
- Some Florence-2 revisions access:
95
- self.forced_bos_token_id
96
 
97
- Newer Transformers configurations may not expose this
98
- attribute directly.
99
  """
100
 
 
 
 
 
101
  try:
102
  from transformers import PretrainedConfig
103
 
@@ -108,239 +135,46 @@ def _patch_florence_config():
108
  PretrainedConfig.forced_bos_token_id = None
109
 
110
  print(
111
- "[Watermark] Applied "
112
- "forced_bos_token_id compatibility patch."
113
  )
114
 
115
  except Exception as e:
116
  print(
117
- "[Watermark] Florence config patch skipped:",
118
- e,
119
  )
120
 
121
-
122
- # ============================================================
123
- # Florence generation compatibility patch
124
- # ============================================================
125
-
126
- def _patch_florence_generation(model):
127
- """
128
- Patch Florence-2 language_model.prepare_inputs_for_generation.
129
-
130
- Problem:
131
-
132
- past_key_values[0][0].shape
133
-
134
- can fail with:
135
-
136
- AttributeError:
137
- 'NoneType' object has no attribute 'shape'
138
-
139
- This happens because newer Transformers generation/cache
140
- behavior can provide a cache structure whose first element
141
- is None, while older Florence-2 remote code assumes a tensor.
142
-
143
- We normalize malformed/empty caches to None and explicitly
144
- handle the first generation step.
145
- """
146
 
147
  try:
 
148
 
149
- language_model = getattr(
150
- model,
151
- "language_model",
152
- None,
153
- )
154
-
155
- if language_model is None:
156
- print(
157
- "[Watermark] No Florence language_model found; "
158
- "generation patch skipped."
159
- )
160
- return
161
-
162
- original_method = getattr(
163
- language_model,
164
- "prepare_inputs_for_generation",
165
- None,
166
- )
167
-
168
- if original_method is None:
169
- print(
170
- "[Watermark] Florence language model does not "
171
- "provide prepare_inputs_for_generation."
172
- )
173
- return
174
-
175
- # Avoid installing the patch twice.
176
- if getattr(
177
- language_model,
178
- "_watermark_generation_patched",
179
- False,
180
- ):
181
- return
182
-
183
- def patched_prepare_inputs(
184
- self,
185
- decoder_input_ids,
186
- past_key_values=None,
187
- attention_mask=None,
188
- decoder_attention_mask=None,
189
- head_mask=None,
190
- decoder_head_mask=None,
191
- cross_attn_head_mask=None,
192
- use_cache=None,
193
- encoder_outputs=None,
194
- **kwargs,
195
  ):
196
- """
197
- Robust Florence-2 generation input preparation.
198
- """
199
-
200
- # ------------------------------------------------
201
- # Normalize malformed cache.
202
- # ------------------------------------------------
203
-
204
- cache_valid = False
205
-
206
- if past_key_values is not None:
207
-
208
- try:
209
-
210
- if len(past_key_values) > 0:
211
-
212
- first_layer = past_key_values[0]
213
-
214
- if first_layer is not None:
215
-
216
- if (
217
- isinstance(
218
- first_layer,
219
- (tuple, list),
220
- )
221
- and len(first_layer) >= 2
222
- ):
223
-
224
- key_tensor = first_layer[0]
225
-
226
- value_tensor = first_layer[1]
227
-
228
- if (
229
- key_tensor is not None
230
- and value_tensor is not None
231
- and hasattr(
232
- key_tensor,
233
- "shape",
234
- )
235
- and hasattr(
236
- value_tensor,
237
- "shape",
238
- )
239
- ):
240
- cache_valid = True
241
-
242
- except Exception:
243
- cache_valid = False
244
 
245
- # ------------------------------------------------
246
- # Empty / malformed cache.
247
- #
248
- # Treat this as the first generation step.
249
- # ------------------------------------------------
250
-
251
- if not cache_valid:
252
-
253
- past_key_values = None
254
-
255
- remove_prefix_length = max(
256
- 0,
257
- decoder_input_ids.shape[1] - 1,
258
- )
259
-
260
- else:
261
-
262
- try:
263
-
264
- past_length = int(
265
- past_key_values[0][0].shape[2]
266
- )
267
-
268
- except Exception:
269
-
270
- past_key_values = None
271
-
272
- remove_prefix_length = max(
273
- 0,
274
- decoder_input_ids.shape[1] - 1,
275
- )
276
-
277
- else:
278
-
279
- if (
280
- decoder_input_ids.shape[1]
281
- > past_length
282
- ):
283
- remove_prefix_length = (
284
- past_length
285
- )
286
- else:
287
- remove_prefix_length = (
288
- decoder_input_ids.shape[1]
289
- - 1
290
- )
291
-
292
- # ------------------------------------------------
293
- # Keep only the required decoder tokens.
294
- # ------------------------------------------------
295
-
296
- decoder_input_ids = (
297
- decoder_input_ids[
298
- :,
299
- remove_prefix_length:
300
- ]
301
  )
302
 
303
- # Never pass malformed cache.
304
- if not cache_valid:
305
- past_key_values = None
306
-
307
- return {
308
- "input_ids": None,
309
- "encoder_outputs": encoder_outputs,
310
- "past_key_values": past_key_values,
311
- "decoder_input_ids": decoder_input_ids,
312
- "attention_mask": attention_mask,
313
- "decoder_attention_mask": decoder_attention_mask,
314
- "head_mask": head_mask,
315
- "decoder_head_mask": decoder_head_mask,
316
- "cross_attn_head_mask": cross_attn_head_mask,
317
- "use_cache": use_cache,
318
- }
319
-
320
- language_model.prepare_inputs_for_generation = (
321
- types.MethodType(
322
- patched_prepare_inputs,
323
- language_model,
324
- )
325
- )
326
-
327
- language_model._watermark_generation_patched = True
328
-
329
- print(
330
- "[Watermark] Florence generation compatibility "
331
- "patch installed."
332
- )
333
-
334
  except Exception as e:
335
-
336
  print(
337
- "[Watermark] Generation patch failed:",
338
- repr(e),
339
  )
340
 
341
 
342
  # ============================================================
343
- # Florence model loading
344
  # ============================================================
345
 
346
  def _load_models():
@@ -349,10 +183,7 @@ def _load_models():
349
  global FLORENCE_PROCESSOR
350
  global LAMA_MODEL
351
 
352
- # --------------------------------------------------------
353
  # Already loaded
354
- # --------------------------------------------------------
355
-
356
  if (
357
  FLORENCE_MODEL is not None
358
  and FLORENCE_PROCESSOR is not None
@@ -360,11 +191,7 @@ def _load_models():
360
  ):
361
  return
362
 
363
- # --------------------------------------------------------
364
- # Florence compatibility
365
- # --------------------------------------------------------
366
-
367
- _patch_florence_config()
368
 
369
  from transformers import (
370
  AutoProcessor,
@@ -378,13 +205,46 @@ def _load_models():
378
  "microsoft/Florence-2-large",
379
  )
380
 
 
 
 
381
  print(
382
- f"[Watermark] Loading Florence-2: {model_id}"
383
  )
384
 
385
- # ========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  # Processor
387
- # ========================================================
388
 
389
  FLORENCE_PROCESSOR = (
390
  AutoProcessor.from_pretrained(
@@ -396,134 +256,69 @@ def _load_models():
396
  )
397
  )
398
 
399
- print(
400
- "[Watermark] Florence processor loaded."
401
- )
402
-
403
- # ========================================================
404
- # Model
405
- # ========================================================
406
 
407
  model_kwargs = {
408
- "torch_dtype": torch.bfloat16,
409
  "cache_dir": str(
410
  cache / "hf"
411
  ),
412
  "trust_remote_code": True,
413
  }
414
 
415
- # --------------------------------------------------------
416
- # Prefer eager attention.
417
- #
418
- # This avoids the earlier:
419
- #
420
- # AttributeError:
421
- # Florence2ForConditionalGeneration
422
- # object has no attribute '_supports_sdpa'
423
- # --------------------------------------------------------
424
-
425
- try:
426
 
427
- FLORENCE_MODEL = (
428
- AutoModelForCausalLM.from_pretrained(
429
- model_id,
430
- attn_implementation="eager",
431
- **model_kwargs,
432
- )
433
- )
434
-
435
- print(
436
- "[Watermark] Florence loaded with "
437
- "eager attention."
438
- )
439
-
440
- except TypeError:
441
-
442
- # Older Transformers may not accept
443
- # attn_implementation.
444
-
445
- print(
446
- "[Watermark] attn_implementation is not "
447
- "supported; loading Florence without it."
448
- )
449
-
450
- FLORENCE_MODEL = (
451
- AutoModelForCausalLM.from_pretrained(
452
- model_id,
453
- **model_kwargs,
454
- )
455
  )
 
456
 
457
- # --------------------------------------------------------
458
- # CUDA
459
- # --------------------------------------------------------
460
-
461
  FLORENCE_MODEL = (
462
  FLORENCE_MODEL
463
- .to("cuda")
464
  .eval()
465
  )
466
 
467
- # ========================================================
468
- # Generation configuration
469
- # ========================================================
470
 
471
  try:
472
- FLORENCE_MODEL.config.use_cache = False
473
-
474
- print(
475
- "[Watermark] Florence config.use_cache=False"
476
- )
477
 
478
- except Exception as e:
 
479
 
480
- print(
481
- "[Watermark] Unable to set "
482
- "config.use_cache=False:",
483
- e,
484
- )
485
 
486
  try:
487
-
488
- FLORENCE_MODEL.generation_config.use_cache = False
 
489
 
490
  print(
491
- "[Watermark] Florence generation_config.use_cache=False"
 
492
  )
493
 
494
  except Exception as e:
495
-
496
  print(
497
- "[Watermark] Unable to set "
498
- "generation_config.use_cache=False:",
499
- e,
500
  )
501
 
502
- # --------------------------------------------------------
503
- # Remove obsolete early_stopping warning
504
- # --------------------------------------------------------
505
-
506
- try:
507
-
508
- if hasattr(
509
- FLORENCE_MODEL.generation_config,
510
- "early_stopping",
511
- ):
512
- FLORENCE_MODEL.generation_config.early_stopping = False
513
-
514
- except Exception:
515
- pass
516
-
517
  # ========================================================
518
- # Install robust generation patch
519
- # ========================================================
520
-
521
- _patch_florence_generation(
522
- FLORENCE_MODEL
523
- )
524
-
525
- # ========================================================
526
- # LaMa
527
  # ========================================================
528
 
529
  lama_path = (
@@ -534,12 +329,11 @@ def _load_models():
534
 
535
  url = os.getenv(
536
  "LAMA_URL",
537
- "https://github.com/Sanster/models/releases/"
538
- "download/add_big_lama/big-lama.pt",
539
  )
540
 
541
  print(
542
- "[Watermark] Downloading LaMa model..."
543
  )
544
 
545
  urllib.request.urlretrieve(
@@ -548,12 +342,12 @@ def _load_models():
548
  )
549
 
550
  print(
551
- "[Watermark] Loading LaMa..."
552
  )
553
 
554
  LAMA_MODEL = torch.jit.load(
555
  str(lama_path),
556
- map_location="cuda",
557
  ).eval()
558
 
559
  print(
@@ -562,7 +356,7 @@ def _load_models():
562
 
563
 
564
  # ============================================================
565
- # Florence watermark detection
566
  # ============================================================
567
 
568
  def _boxes(
@@ -571,6 +365,13 @@ def _boxes(
571
  ):
572
  """
573
  Detect watermark bounding boxes using Florence-2.
 
 
 
 
 
 
 
574
  """
575
 
576
  _load_models()
@@ -580,6 +381,24 @@ def _boxes(
580
 
581
  image = image.convert("RGB")
582
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  task = (
584
  "<OPEN_VOCABULARY_DETECTION>"
585
  )
@@ -589,13 +408,11 @@ def _boxes(
589
  or "watermark"
590
  )
591
 
592
- text = (
593
- task + prompt
594
- )
595
 
596
- # ========================================================
597
  # Processor
598
- # ========================================================
599
 
600
  inputs = FLORENCE_PROCESSOR(
601
  text=text,
@@ -603,78 +420,140 @@ def _boxes(
603
  return_tensors="pt",
604
  )
605
 
606
- # ========================================================
607
- # CUDA
608
- # ========================================================
609
 
610
- inputs = {
611
- key: (
612
- value.to("cuda")
613
- if hasattr(value, "to")
614
- else value
615
- )
616
- for key, value in inputs.items()
617
- }
618
 
619
- # ========================================================
620
- # Generation
621
- # ========================================================
622
 
623
- generation_kwargs = {
624
- "max_new_tokens": 256,
625
- "num_beams": 3,
626
 
627
- # Critical compatibility option.
628
- "use_cache": False,
 
629
 
630
- # Avoid generation object overhead.
631
- "return_dict_in_generate": False,
632
- }
633
 
634
  # --------------------------------------------------------
635
- # Important:
 
 
 
 
636
  #
637
- # Florence's custom generate() expects:
638
  #
639
- # input_ids
640
- # pixel_values
641
  #
642
- # Do NOT pass None values.
 
 
643
  # --------------------------------------------------------
644
 
645
- input_ids = inputs.get(
646
- "input_ids"
647
- )
648
-
649
- pixel_values = inputs.get(
650
- "pixel_values"
651
- )
652
 
653
- if input_ids is None:
654
- raise RuntimeError(
655
- "Florence processor did not return input_ids."
 
 
 
 
656
  )
657
 
658
- if pixel_values is None:
659
- raise RuntimeError(
660
- "Florence processor did not return pixel_values."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  )
662
 
663
- # ========================================================
664
- # Generate
665
- # ========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
 
667
  with torch.inference_mode():
668
 
669
- generated = FLORENCE_MODEL.generate(
670
- input_ids=input_ids,
671
- pixel_values=pixel_values,
672
- **generation_kwargs,
673
  )
674
 
675
- # ========================================================
676
  # Decode
677
- # ========================================================
678
 
679
  decoded = (
680
  FLORENCE_PROCESSOR.batch_decode(
@@ -683,24 +562,24 @@ def _boxes(
683
  )[0]
684
  )
685
 
686
- # ========================================================
687
- # Post process
688
- # ========================================================
 
 
 
 
 
689
 
690
  parsed = (
691
- FLORENCE_PROCESSOR.post_process_generation(
 
692
  decoded,
693
  task=task,
694
  image_size=image.size,
695
  )
696
  )
697
 
698
- if not isinstance(
699
- parsed,
700
- dict,
701
- ):
702
- return []
703
-
704
  data = parsed.get(
705
  task,
706
  parsed,
@@ -720,9 +599,9 @@ def _boxes(
720
  or []
721
  )
722
 
723
- # ========================================================
724
  # Validate boxes
725
- # ========================================================
726
 
727
  valid_boxes = []
728
 
@@ -750,9 +629,7 @@ def _boxes(
750
  ):
751
  continue
752
 
753
- # ----------------------------------------------------
754
  # Clamp
755
- # ----------------------------------------------------
756
 
757
  x1 = max(
758
  0.0,
@@ -762,19 +639,19 @@ def _boxes(
762
  ),
763
  )
764
 
765
- x2 = max(
766
  0.0,
767
  min(
768
- float(image.width),
769
- x2,
770
  ),
771
  )
772
 
773
- y1 = max(
774
  0.0,
775
  min(
776
- float(image.height),
777
- y1,
778
  ),
779
  )
780
 
@@ -786,10 +663,10 @@ def _boxes(
786
  ),
787
  )
788
 
789
- if x2 <= x1:
790
- continue
791
-
792
- if y2 <= y1:
793
  continue
794
 
795
  valid_boxes.append(
@@ -802,15 +679,15 @@ def _boxes(
802
  )
803
 
804
  print(
805
- "[Watermark] Florence detected "
806
- f"{len(valid_boxes)} valid boxes."
807
  )
808
 
809
  return valid_boxes
810
 
811
 
812
  # ============================================================
813
- # Build mask
814
  # ============================================================
815
 
816
  def _mask(
@@ -818,11 +695,6 @@ def _mask(
818
  boxes,
819
  max_pct,
820
  ):
821
- """
822
- Convert bounding boxes to an inpainting mask.
823
- """
824
-
825
- image = image.convert("RGB")
826
 
827
  mask = Image.new(
828
  "L",
@@ -839,80 +711,67 @@ def _mask(
839
  * image.height
840
  )
841
 
842
- if total <= 0:
843
- return mask
844
-
845
  for box in boxes:
846
 
847
  if len(box) != 4:
848
  continue
849
 
850
- try:
851
-
852
- x1, y1, x2, y2 = map(
853
- float,
854
- box,
855
- )
856
-
857
- except (
858
- TypeError,
859
- ValueError,
860
- ):
861
- continue
862
 
863
  x1 = max(
864
- 0.0,
865
  min(
866
- float(image.width),
867
  x1,
868
  ),
869
  )
870
 
871
  x2 = max(
872
- 0.0,
873
  min(
874
- float(image.width),
875
  x2,
876
  ),
877
  )
878
 
879
  y1 = max(
880
- 0.0,
881
  min(
882
- float(image.height),
883
  y1,
884
  ),
885
  )
886
 
887
  y2 = max(
888
- 0.0,
889
  min(
890
- float(image.height),
891
  y2,
892
  ),
893
  )
894
 
895
  area = (
896
  max(
897
- 0.0,
898
  x2 - x1,
899
  )
900
  *
901
  max(
902
- 0.0,
903
  y2 - y1,
904
  )
905
  )
906
 
907
- area_pct = (
908
- area
909
- / total
910
- * 100.0
911
- )
912
-
913
- if area_pct <= float(
914
- max_pct
915
  ):
 
916
  draw.rectangle(
917
  (
918
  x1,
@@ -923,12 +782,12 @@ def _mask(
923
  fill=255,
924
  )
925
 
926
- # --------------------------------------------------------
927
- # Small expansion around watermark.
928
- # --------------------------------------------------------
929
 
930
  return mask.filter(
931
- ImageFilter.MaxFilter(7)
 
 
932
  )
933
 
934
 
@@ -942,15 +801,16 @@ def _lama(
942
  boxes=None,
943
  ):
944
  """
945
- Run LaMa inpainting.
946
 
947
- When boxes exist, process local regions instead of
948
- the entire frame. This is significantly cheaper for
949
- video processing.
950
  """
951
 
952
  _load_models()
953
 
 
 
954
  image = image.convert(
955
  "RGB"
956
  )
@@ -960,7 +820,7 @@ def _lama(
960
  )
961
 
962
  # ========================================================
963
- # Region-based processing
964
  # ========================================================
965
 
966
  if boxes:
@@ -970,7 +830,9 @@ def _lama(
970
  pad = max(
971
  16,
972
  int(
973
- min(image.size)
 
 
974
  * 0.02
975
  ),
976
  )
@@ -980,18 +842,10 @@ def _lama(
980
  if len(box) != 4:
981
  continue
982
 
983
- try:
984
-
985
- x1, y1, x2, y2 = map(
986
- float,
987
- box,
988
- )
989
-
990
- except (
991
- TypeError,
992
- ValueError,
993
- ):
994
- continue
995
 
996
  left = max(
997
  0,
@@ -1013,10 +867,10 @@ def _lama(
1013
  int(y2) + pad,
1014
  )
1015
 
1016
- if right <= left:
1017
- continue
1018
-
1019
- if bottom <= top:
1020
  continue
1021
 
1022
  crop = image.crop(
@@ -1037,10 +891,6 @@ def _lama(
1037
  )
1038
  )
1039
 
1040
- # ------------------------------------------------
1041
- # Image tensor
1042
- # ------------------------------------------------
1043
-
1044
  crop_np = (
1045
  np.asarray(
1046
  crop,
@@ -1067,8 +917,11 @@ def _lama(
1067
  1,
1068
  )
1069
  .unsqueeze(0)
1070
- .cuda()
1071
- * 2.0
 
 
 
1072
  - 1.0
1073
  )
1074
 
@@ -1078,17 +931,13 @@ def _lama(
1078
  )
1079
  .unsqueeze(0)
1080
  .unsqueeze(0)
1081
- .cuda()
1082
  )
1083
 
1084
  mask_t = (
1085
  mask_t > 0.5
1086
  ).float()
1087
 
1088
- # ------------------------------------------------
1089
- # LaMa
1090
- # ------------------------------------------------
1091
-
1092
  with torch.inference_mode():
1093
 
1094
  out = LAMA_MODEL(
@@ -1127,7 +976,8 @@ def _lama(
1127
 
1128
  restored = Image.fromarray(
1129
  (
1130
- out * 255
 
1131
  )
1132
  .clip(
1133
  0,
@@ -1149,7 +999,7 @@ def _lama(
1149
  return result
1150
 
1151
  # ========================================================
1152
- # Full-frame fallback
1153
  # ========================================================
1154
 
1155
  image_np = (
@@ -1178,8 +1028,11 @@ def _lama(
1178
  1,
1179
  )
1180
  .unsqueeze(0)
1181
- .cuda()
1182
- * 2.0
 
 
 
1183
  - 1.0
1184
  )
1185
 
@@ -1189,7 +1042,7 @@ def _lama(
1189
  )
1190
  .unsqueeze(0)
1191
  .unsqueeze(0)
1192
- .cuda()
1193
  )
1194
 
1195
  mask_t = (
@@ -1234,7 +1087,8 @@ def _lama(
1234
 
1235
  return Image.fromarray(
1236
  (
1237
- result * 255
 
1238
  )
1239
  .clip(
1240
  0,
@@ -1255,25 +1109,22 @@ def preview(
1255
  max_bbox_percent=30,
1256
  detection_prompt="watermark",
1257
  ):
1258
- """
1259
- Detect watermark and draw bounding boxes.
1260
- """
1261
 
1262
  if image is None:
1263
  raise ValueError(
1264
  "Please upload an image."
1265
  )
1266
 
1267
- image = image.convert(
1268
- "RGB"
1269
- )
1270
-
1271
  boxes = _boxes(
1272
  image,
1273
  detection_prompt,
1274
  )
1275
 
1276
- out = image.copy()
 
 
 
 
1277
 
1278
  draw = ImageDraw.Draw(
1279
  out
@@ -1281,19 +1132,18 @@ def preview(
1281
 
1282
  for box in boxes:
1283
 
1284
- if len(box) != 4:
1285
- continue
1286
 
1287
- draw.rectangle(
1288
- tuple(
1289
- map(
1290
- float,
1291
- box,
1292
- )
1293
- ),
1294
- outline="red",
1295
- width=4,
1296
- )
1297
 
1298
  return out
1299
 
@@ -1308,58 +1158,37 @@ def remove_image(
1308
  detection_prompt="watermark",
1309
  transparent=False,
1310
  ):
1311
- """
1312
- Detect and remove watermark from image.
1313
- """
1314
 
1315
  if image is None:
1316
  raise ValueError(
1317
  "Please upload an image."
1318
  )
1319
 
1320
- image = image.convert(
1321
- "RGB"
1322
- )
1323
-
1324
- # --------------------------------------------------------
1325
- # Detect
1326
- # --------------------------------------------------------
1327
-
1328
  boxes = _boxes(
1329
  image,
1330
  detection_prompt,
1331
  )
1332
 
1333
- # --------------------------------------------------------
1334
- # Mask
1335
- # --------------------------------------------------------
1336
-
1337
  mask = _mask(
1338
  image,
1339
  boxes,
1340
  max_bbox_percent,
1341
  )
1342
 
1343
- # --------------------------------------------------------
1344
- # Nothing detected
1345
- # --------------------------------------------------------
1346
-
1347
  if not boxes:
1348
 
1349
- result = image.convert(
1350
  "RGBA"
1351
  if transparent
1352
  else "RGB"
1353
  )
1354
 
1355
- return result, mask
1356
-
1357
- # --------------------------------------------------------
1358
- # LaMa
1359
- #
1360
- # IMPORTANT:
1361
- # Pass boxes here so LaMa uses region processing.
1362
- # --------------------------------------------------------
1363
 
1364
  result = _lama(
1365
  image,
@@ -1367,10 +1196,6 @@ def remove_image(
1367
  boxes=boxes,
1368
  )
1369
 
1370
- # --------------------------------------------------------
1371
- # Transparent output
1372
- # --------------------------------------------------------
1373
-
1374
  if transparent:
1375
 
1376
  result = result.convert(
@@ -1379,8 +1204,12 @@ def remove_image(
1379
 
1380
  result.putalpha(
1381
  mask.point(
1382
- lambda p: 255 - p
 
1383
  )
1384
  )
1385
 
1386
- return result, mask
 
 
 
 
1
  from pathlib import Path
2
  import os
3
  import urllib.request
 
4
 
5
  import numpy as np
6
  import torch
 
7
  from PIL import Image, ImageDraw, ImageFilter
8
 
9
 
10
  # ============================================================
11
+ # Global model cache
12
  # ============================================================
13
 
14
  MODEL_CACHE = None
 
20
 
21
 
22
  # ============================================================
23
+ # Device / dtype helpers
24
+ # ============================================================
25
+
26
+ def _get_device():
27
+ if torch.cuda.is_available():
28
+ return torch.device("cuda")
29
+ return torch.device("cpu")
30
+
31
+
32
+ def _get_model_dtype():
33
+ """
34
+ Select a dtype that is actually usable by the current GPU.
35
+
36
+ ZeroGPU normally provides CUDA.
37
+ Prefer BF16 when supported, otherwise FP16.
38
+ """
39
+
40
+ if not torch.cuda.is_available():
41
+ return torch.float32
42
+
43
+ try:
44
+ if torch.cuda.is_bf16_supported():
45
+ return torch.bfloat16
46
+ except Exception:
47
+ pass
48
+
49
+ return torch.float16
50
+
51
+
52
+ # ============================================================
53
+ # Writable cache
54
  # ============================================================
55
 
56
  def _get_writable_cache():
 
81
  encoding="utf-8",
82
  )
83
 
84
+ try:
85
+ test.unlink()
86
+ except Exception:
87
+ pass
88
 
89
  print(
90
  f"[Watermark] Cache directory: {p}"
 
92
 
93
  return p
94
 
95
+ except (PermissionError, OSError):
 
 
 
96
  pass
97
 
98
  raise RuntimeError(
 
110
 
111
 
112
  # ============================================================
113
+ # Florence compatibility patches
114
  # ============================================================
115
 
116
+ def _patch_florence_compatibility():
117
  """
118
+ Compatibility layer for Microsoft Florence-2 custom code.
 
 
 
119
 
120
+ Handles older Florence-2 remote-code expectations when used
121
+ with newer Transformers versions.
122
  """
123
 
124
+ # --------------------------------------------------------
125
+ # forced_bos_token_id
126
+ # --------------------------------------------------------
127
+
128
  try:
129
  from transformers import PretrainedConfig
130
 
 
135
  PretrainedConfig.forced_bos_token_id = None
136
 
137
  print(
138
+ "[Florence] "
139
+ "Applied forced_bos_token_id compatibility patch."
140
  )
141
 
142
  except Exception as e:
143
  print(
144
+ "[Florence] "
145
+ f"forced_bos_token_id patch skipped: {e}"
146
  )
147
 
148
+ # --------------------------------------------------------
149
+ # _supports_sdpa
150
+ #
151
+ # Some Florence remote-code revisions do not define this
152
+ # attribute while newer Transformers expects it.
153
+ # --------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  try:
156
+ from transformers import PreTrainedModel
157
 
158
+ if not hasattr(
159
+ PreTrainedModel,
160
+ "_supports_sdpa",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  ):
162
+ PreTrainedModel._supports_sdpa = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ print(
165
+ "[Florence] "
166
+ "Applied _supports_sdpa compatibility patch."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  )
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  except Exception as e:
 
170
  print(
171
+ "[Florence] "
172
+ f"_supports_sdpa patch skipped: {e}"
173
  )
174
 
175
 
176
  # ============================================================
177
+ # Load models
178
  # ============================================================
179
 
180
  def _load_models():
 
183
  global FLORENCE_PROCESSOR
184
  global LAMA_MODEL
185
 
 
186
  # Already loaded
 
 
187
  if (
188
  FLORENCE_MODEL is not None
189
  and FLORENCE_PROCESSOR is not None
 
191
  ):
192
  return
193
 
194
+ _patch_florence_compatibility()
 
 
 
 
195
 
196
  from transformers import (
197
  AutoProcessor,
 
205
  "microsoft/Florence-2-large",
206
  )
207
 
208
+ device = _get_device()
209
+ dtype = _get_model_dtype()
210
+
211
  print(
212
+ "[Florence] =================================="
213
  )
214
 
215
+ print(
216
+ f"[Florence] Model: {model_id}"
217
+ )
218
+
219
+ print(
220
+ f"[Florence] Device: {device}"
221
+ )
222
+
223
+ print(
224
+ f"[Florence] CUDA available: "
225
+ f"{torch.cuda.is_available()}"
226
+ )
227
+
228
+ if torch.cuda.is_available():
229
+ try:
230
+ print(
231
+ f"[Florence] BF16 supported: "
232
+ f"{torch.cuda.is_bf16_supported()}"
233
+ )
234
+ except Exception:
235
+ pass
236
+
237
+ print(
238
+ f"[Florence] dtype: {dtype}"
239
+ )
240
+
241
+ print(
242
+ "[Florence] =================================="
243
+ )
244
+
245
+ # --------------------------------------------------------
246
  # Processor
247
+ # --------------------------------------------------------
248
 
249
  FLORENCE_PROCESSOR = (
250
  AutoProcessor.from_pretrained(
 
256
  )
257
  )
258
 
259
+ # --------------------------------------------------------
260
+ # Florence model
261
+ # --------------------------------------------------------
 
 
 
 
262
 
263
  model_kwargs = {
 
264
  "cache_dir": str(
265
  cache / "hf"
266
  ),
267
  "trust_remote_code": True,
268
  }
269
 
270
+ # Newer Transformers supports torch_dtype.
271
+ model_kwargs["torch_dtype"] = dtype
 
 
 
 
 
 
 
 
 
272
 
273
+ FLORENCE_MODEL = (
274
+ AutoModelForCausalLM.from_pretrained(
275
+ model_id,
276
+ **model_kwargs,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  )
278
+ )
279
 
280
+ # Explicit device placement
 
 
 
281
  FLORENCE_MODEL = (
282
  FLORENCE_MODEL
283
+ .to(device)
284
  .eval()
285
  )
286
 
287
+ # --------------------------------------------------------
288
+ # Extra compatibility patch directly on the model
289
+ # --------------------------------------------------------
290
 
291
  try:
292
+ if not hasattr(
293
+ FLORENCE_MODEL,
294
+ "_supports_sdpa",
295
+ ):
296
+ FLORENCE_MODEL._supports_sdpa = False
297
 
298
+ except Exception:
299
+ pass
300
 
301
+ # --------------------------------------------------------
302
+ # Print actual model dtype
303
+ # --------------------------------------------------------
 
 
304
 
305
  try:
306
+ actual_dtype = next(
307
+ FLORENCE_MODEL.parameters()
308
+ ).dtype
309
 
310
  print(
311
+ f"[Florence] Actual model dtype: "
312
+ f"{actual_dtype}"
313
  )
314
 
315
  except Exception as e:
 
316
  print(
317
+ f"[Florence] Unable to determine model dtype: {e}"
 
 
318
  )
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  # ========================================================
321
+ # Load LaMa
 
 
 
 
 
 
 
 
322
  # ========================================================
323
 
324
  lama_path = (
 
329
 
330
  url = os.getenv(
331
  "LAMA_URL",
332
+ "https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt",
 
333
  )
334
 
335
  print(
336
+ "[LaMa] Downloading model..."
337
  )
338
 
339
  urllib.request.urlretrieve(
 
342
  )
343
 
344
  print(
345
+ f"[LaMa] Loading: {lama_path}"
346
  )
347
 
348
  LAMA_MODEL = torch.jit.load(
349
  str(lama_path),
350
+ map_location=device,
351
  ).eval()
352
 
353
  print(
 
356
 
357
 
358
  # ============================================================
359
+ # Florence detection
360
  # ============================================================
361
 
362
  def _boxes(
 
365
  ):
366
  """
367
  Detect watermark bounding boxes using Florence-2.
368
+
369
+ IMPORTANT:
370
+ Florence model weights and pixel_values must use the
371
+ same floating-point dtype.
372
+
373
+ This function explicitly converts pixel_values to the
374
+ actual dtype of the Florence model.
375
  """
376
 
377
  _load_models()
 
381
 
382
  image = image.convert("RGB")
383
 
384
+ device = _get_device()
385
+
386
+ # --------------------------------------------------------
387
+ # Get actual model dtype
388
+ # --------------------------------------------------------
389
+
390
+ try:
391
+ model_dtype = next(
392
+ FLORENCE_MODEL.parameters()
393
+ ).dtype
394
+
395
+ except Exception:
396
+ model_dtype = _get_model_dtype()
397
+
398
+ # --------------------------------------------------------
399
+ # Florence task
400
+ # --------------------------------------------------------
401
+
402
  task = (
403
  "<OPEN_VOCABULARY_DETECTION>"
404
  )
 
408
  or "watermark"
409
  )
410
 
411
+ text = task + prompt
 
 
412
 
413
+ # --------------------------------------------------------
414
  # Processor
415
+ # --------------------------------------------------------
416
 
417
  inputs = FLORENCE_PROCESSOR(
418
  text=text,
 
420
  return_tensors="pt",
421
  )
422
 
423
+ # --------------------------------------------------------
424
+ # Move tensors to CUDA
425
+ # --------------------------------------------------------
426
 
427
+ prepared = {}
 
 
 
 
 
 
 
428
 
429
+ for key, value in inputs.items():
 
 
430
 
431
+ if hasattr(value, "to"):
 
 
432
 
433
+ value = value.to(
434
+ device
435
+ )
436
 
437
+ prepared[key] = value
 
 
438
 
439
  # --------------------------------------------------------
440
+ # CRITICAL FIX
441
+ #
442
+ # Processor normally returns pixel_values as FP32.
443
+ #
444
+ # Florence-2-large can be loaded as BF16/FP16.
445
  #
446
+ # Conv2D requires:
447
  #
448
+ # input dtype == weight/bias dtype
 
449
  #
450
+ # Therefore:
451
+ #
452
+ # pixel_values -> model dtype
453
  # --------------------------------------------------------
454
 
455
+ if "pixel_values" in prepared:
 
 
 
 
 
 
456
 
457
+ prepared[
458
+ "pixel_values"
459
+ ] = prepared[
460
+ "pixel_values"
461
+ ].to(
462
+ device=device,
463
+ dtype=model_dtype,
464
  )
465
 
466
+ # --------------------------------------------------------
467
+ # Token tensors must stay integer
468
+ # --------------------------------------------------------
469
+
470
+ if "input_ids" in prepared:
471
+
472
+ prepared[
473
+ "input_ids"
474
+ ] = prepared[
475
+ "input_ids"
476
+ ].long()
477
+
478
+ if "attention_mask" in prepared:
479
+
480
+ prepared[
481
+ "attention_mask"
482
+ ] = prepared[
483
+ "attention_mask"
484
+ ].long()
485
+
486
+ # --------------------------------------------------------
487
+ # Debug
488
+ # --------------------------------------------------------
489
+
490
+ pixel_dtype = None
491
+
492
+ if "pixel_values" in prepared:
493
+ pixel_dtype = (
494
+ prepared[
495
+ "pixel_values"
496
+ ].dtype
497
  )
498
 
499
+ print(
500
+ "[Florence] "
501
+ f"model_dtype={model_dtype}, "
502
+ f"pixel_dtype={pixel_dtype}, "
503
+ f"device={device}"
504
+ )
505
+
506
+ # --------------------------------------------------------
507
+ # Generation
508
+ #
509
+ # Do NOT pass early_stopping.
510
+ #
511
+ # use_cache=False avoids the Florence remote-code
512
+ # past_key_values=None issue encountered previously.
513
+ # --------------------------------------------------------
514
+
515
+ generation_kwargs = {
516
+ "input_ids": prepared.get(
517
+ "input_ids"
518
+ ),
519
+
520
+ "pixel_values": prepared.get(
521
+ "pixel_values"
522
+ ),
523
+
524
+ "max_new_tokens": 1024,
525
+
526
+ "num_beams": 3,
527
+
528
+ "do_sample": False,
529
+
530
+ "use_cache": False,
531
+ }
532
+
533
+ # Remove None values
534
+
535
+ generation_kwargs = {
536
+ key: value
537
+ for key, value
538
+ in generation_kwargs.items()
539
+ if value is not None
540
+ }
541
+
542
+ # --------------------------------------------------------
543
+ # Florence inference
544
+ # --------------------------------------------------------
545
 
546
  with torch.inference_mode():
547
 
548
+ generated = (
549
+ FLORENCE_MODEL.generate(
550
+ **generation_kwargs
551
+ )
552
  )
553
 
554
+ # --------------------------------------------------------
555
  # Decode
556
+ # --------------------------------------------------------
557
 
558
  decoded = (
559
  FLORENCE_PROCESSOR.batch_decode(
 
562
  )[0]
563
  )
564
 
565
+ print(
566
+ "[Florence] decoded:",
567
+ decoded[:500],
568
+ )
569
+
570
+ # --------------------------------------------------------
571
+ # Post processing
572
+ # --------------------------------------------------------
573
 
574
  parsed = (
575
+ FLORENCE_PROCESSOR
576
+ .post_process_generation(
577
  decoded,
578
  task=task,
579
  image_size=image.size,
580
  )
581
  )
582
 
 
 
 
 
 
 
583
  data = parsed.get(
584
  task,
585
  parsed,
 
599
  or []
600
  )
601
 
602
+ # --------------------------------------------------------
603
  # Validate boxes
604
+ # --------------------------------------------------------
605
 
606
  valid_boxes = []
607
 
 
629
  ):
630
  continue
631
 
 
632
  # Clamp
 
633
 
634
  x1 = max(
635
  0.0,
 
639
  ),
640
  )
641
 
642
+ y1 = max(
643
  0.0,
644
  min(
645
+ float(image.height),
646
+ y1,
647
  ),
648
  )
649
 
650
+ x2 = max(
651
  0.0,
652
  min(
653
+ float(image.width),
654
+ x2,
655
  ),
656
  )
657
 
 
663
  ),
664
  )
665
 
666
+ if (
667
+ x2 <= x1
668
+ or y2 <= y1
669
+ ):
670
  continue
671
 
672
  valid_boxes.append(
 
679
  )
680
 
681
  print(
682
+ f"[Florence] detected "
683
+ f"{len(valid_boxes)} bounding boxes"
684
  )
685
 
686
  return valid_boxes
687
 
688
 
689
  # ============================================================
690
+ # Create mask
691
  # ============================================================
692
 
693
  def _mask(
 
695
  boxes,
696
  max_pct,
697
  ):
 
 
 
 
 
698
 
699
  mask = Image.new(
700
  "L",
 
711
  * image.height
712
  )
713
 
 
 
 
714
  for box in boxes:
715
 
716
  if len(box) != 4:
717
  continue
718
 
719
+ x1, y1, x2, y2 = map(
720
+ float,
721
+ box,
722
+ )
 
 
 
 
 
 
 
 
723
 
724
  x1 = max(
725
+ 0,
726
  min(
727
+ image.width,
728
  x1,
729
  ),
730
  )
731
 
732
  x2 = max(
733
+ 0,
734
  min(
735
+ image.width,
736
  x2,
737
  ),
738
  )
739
 
740
  y1 = max(
741
+ 0,
742
  min(
743
+ image.height,
744
  y1,
745
  ),
746
  )
747
 
748
  y2 = max(
749
+ 0,
750
  min(
751
+ image.height,
752
  y2,
753
  ),
754
  )
755
 
756
  area = (
757
  max(
758
+ 0,
759
  x2 - x1,
760
  )
761
  *
762
  max(
763
+ 0,
764
  y2 - y1,
765
  )
766
  )
767
 
768
+ if (
769
+ total
770
+ and
771
+ area / total * 100
772
+ <= max_pct
 
 
 
773
  ):
774
+
775
  draw.rectangle(
776
  (
777
  x1,
 
782
  fill=255,
783
  )
784
 
785
+ # Expand mask slightly
 
 
786
 
787
  return mask.filter(
788
+ ImageFilter.MaxFilter(
789
+ 7
790
+ )
791
  )
792
 
793
 
 
801
  boxes=None,
802
  ):
803
  """
804
+ Run LaMa on watermark regions.
805
 
806
+ For video, process only the detected regions instead of
807
+ the complete 1080p/4K frame.
 
808
  """
809
 
810
  _load_models()
811
 
812
+ device = _get_device()
813
+
814
  image = image.convert(
815
  "RGB"
816
  )
 
820
  )
821
 
822
  # ========================================================
823
+ # Region-based inpainting
824
  # ========================================================
825
 
826
  if boxes:
 
830
  pad = max(
831
  16,
832
  int(
833
+ min(
834
+ image.size
835
+ )
836
  * 0.02
837
  ),
838
  )
 
842
  if len(box) != 4:
843
  continue
844
 
845
+ x1, y1, x2, y2 = map(
846
+ float,
847
+ box,
848
+ )
 
 
 
 
 
 
 
 
849
 
850
  left = max(
851
  0,
 
867
  int(y2) + pad,
868
  )
869
 
870
+ if (
871
+ right <= left
872
+ or bottom <= top
873
+ ):
874
  continue
875
 
876
  crop = image.crop(
 
891
  )
892
  )
893
 
 
 
 
 
894
  crop_np = (
895
  np.asarray(
896
  crop,
 
917
  1,
918
  )
919
  .unsqueeze(0)
920
+ .to(device)
921
+ )
922
+
923
+ image_t = (
924
+ image_t * 2.0
925
  - 1.0
926
  )
927
 
 
931
  )
932
  .unsqueeze(0)
933
  .unsqueeze(0)
934
+ .to(device)
935
  )
936
 
937
  mask_t = (
938
  mask_t > 0.5
939
  ).float()
940
 
 
 
 
 
941
  with torch.inference_mode():
942
 
943
  out = LAMA_MODEL(
 
976
 
977
  restored = Image.fromarray(
978
  (
979
+ out
980
+ * 255
981
  )
982
  .clip(
983
  0,
 
999
  return result
1000
 
1001
  # ========================================================
1002
+ # Full image fallback
1003
  # ========================================================
1004
 
1005
  image_np = (
 
1028
  1,
1029
  )
1030
  .unsqueeze(0)
1031
+ .to(device)
1032
+ )
1033
+
1034
+ image_t = (
1035
+ image_t * 2.0
1036
  - 1.0
1037
  )
1038
 
 
1042
  )
1043
  .unsqueeze(0)
1044
  .unsqueeze(0)
1045
+ .to(device)
1046
  )
1047
 
1048
  mask_t = (
 
1087
 
1088
  return Image.fromarray(
1089
  (
1090
+ result
1091
+ * 255
1092
  )
1093
  .clip(
1094
  0,
 
1109
  max_bbox_percent=30,
1110
  detection_prompt="watermark",
1111
  ):
 
 
 
1112
 
1113
  if image is None:
1114
  raise ValueError(
1115
  "Please upload an image."
1116
  )
1117
 
 
 
 
 
1118
  boxes = _boxes(
1119
  image,
1120
  detection_prompt,
1121
  )
1122
 
1123
+ out = (
1124
+ image
1125
+ .convert("RGB")
1126
+ .copy()
1127
+ )
1128
 
1129
  draw = ImageDraw.Draw(
1130
  out
 
1132
 
1133
  for box in boxes:
1134
 
1135
+ if len(box) == 4:
 
1136
 
1137
+ draw.rectangle(
1138
+ tuple(
1139
+ map(
1140
+ float,
1141
+ box,
1142
+ )
1143
+ ),
1144
+ outline="red",
1145
+ width=4,
1146
+ )
1147
 
1148
  return out
1149
 
 
1158
  detection_prompt="watermark",
1159
  transparent=False,
1160
  ):
 
 
 
1161
 
1162
  if image is None:
1163
  raise ValueError(
1164
  "Please upload an image."
1165
  )
1166
 
 
 
 
 
 
 
 
 
1167
  boxes = _boxes(
1168
  image,
1169
  detection_prompt,
1170
  )
1171
 
 
 
 
 
1172
  mask = _mask(
1173
  image,
1174
  boxes,
1175
  max_bbox_percent,
1176
  )
1177
 
 
 
 
 
1178
  if not boxes:
1179
 
1180
+ output_mode = (
1181
  "RGBA"
1182
  if transparent
1183
  else "RGB"
1184
  )
1185
 
1186
+ return (
1187
+ image.convert(
1188
+ output_mode
1189
+ ),
1190
+ mask,
1191
+ )
 
 
1192
 
1193
  result = _lama(
1194
  image,
 
1196
  boxes=boxes,
1197
  )
1198
 
 
 
 
 
1199
  if transparent:
1200
 
1201
  result = result.convert(
 
1204
 
1205
  result.putalpha(
1206
  mask.point(
1207
+ lambda p:
1208
+ 255 - p
1209
  )
1210
  )
1211
 
1212
+ return (
1213
+ result,
1214
+ mask,
1215
+ )