diff --git a/README.md b/README.md index 1ab3042..ffe436d 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,19 @@ MarkDiffusion is an open-source Python toolkit for generative watermarking of latent diffusion models. As the use of diffusion-based generative models expands, ensuring the authenticity and origin of generated media becomes critical. MarkDiffusion simplifies the access, understanding, and assessment of watermarking technologies, making it accessible to both researchers and the broader community. *Note: if you are interested in LLM watermarking (text watermark), please refer to the [MarkLLM](https://github.com/THU-BPM/MarkLLM) toolkit from our group.* -The toolkit comprises three key components: a unified implementation framework for streamlined watermarking algorithm integrations and user-friendly interfaces; a mechanism visualization suite that intuitively showcases added and extracted watermark patterns to aid public understanding; and a comprehensive evaluation module offering standard implementations of 24 tools across three essential aspects—detectability, robustness, and output quality, plus 8 automated evaluation pipelines. +The toolkit comprises three key components: a unified implementation framework for streamlined watermarking algorithm integrations and user-friendly interfaces; a mechanism visualization suite that intuitively showcases added and extracted watermark patterns to aid public understanding; and a comprehensive evaluation module offering standard implementations of 31 tools across three essential aspects—detectability, robustness, and output quality, plus 6 automated evaluation pipelines. MarkDiffusion Overview ### 💍 Key Features -- **Unified Implementation Framework:** MarkDiffusion provides a modular architecture supporting eight state-of-the-art generative image/video watermarking algorithms of LDMs. +- **Unified Implementation Framework:** MarkDiffusion provides a modular architecture supporting eleven state-of-the-art generative image/video watermarking algorithms of LDMs. -- **Comprehensive Algorithm Support:** Currently implements 8 watermarking algorithms from two major categories: Pattern-based methods (Tree-Ring, Ring-ID, ROBIN, WIND) and Key-based methods (Gaussian-Shading, PRC, SEAL, VideoShield). +- **Comprehensive Algorithm Support:** Currently implements 11 watermarking algorithms from two major categories: Pattern-based methods (Tree-Ring, Ring-ID, ROBIN, WIND, SFW) and Key-based methods (Gaussian-Shading, PRC, SEAL, VideoShield, GaussMarker, VideoMark). - **Visualization Solutions:** The toolkit includes custom visualization tools that enable clear and insightful views into how different watermarking algorithms operate under various scenarios. These visualizations help demystify the algorithms' mechanisms, making them more understandable for users. -- **Evaluation Module:** With 20 evaluation tools covering detectability, robustness, and impact on output quality, MarkDiffusion provides comprehensive assessment capabilities. It features 5 automated evaluation pipelines: Watermark Detection Pipeline, Image Quality Analysis Pipeline, Video Quality Analysis Pipeline, and specialized robustness assessment tools. +- **Evaluation Module:** With 31 evaluation tools covering detectability, robustness, and impact on output quality, MarkDiffusion provides comprehensive assessment capabilities. It features 6 automated evaluation pipelines: Watermark Detection Pipeline, Image Quality Analysis Pipeline, Video Quality Analysis Pipeline, and specialized robustness assessment tools. ### ✨ Implemented Algorithms diff --git a/README_es.md b/README_es.md index a3c3f54..89488fe 100644 --- a/README_es.md +++ b/README_es.md @@ -4,39 +4,44 @@ # Un Kit de Herramientas de Código Abierto para Marcas de Agua Generativas de Modelos de Difusión Latente -[![Homepage](https://img.shields.io/badge/Homepage-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) +[![Home](https://img.shields.io/badge/Home-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) [![Paper](https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white)](https://arxiv.org/abs/2509.10569) -[![HF Models](https://img.shields.io/badge/HF--Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Models](https://img.shields.io/badge/Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Colab](https://img.shields.io/badge/Google--Colab-%23D97700?style=for-the-badge&logo=Google-colab&logoColor=white)](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing) +[![DOC](https://img.shields.io/badge/Readthedocs-%2300A89C?style=for-the-badge&logo=readthedocs&logoColor=#8CA1AF)](https://markdiffusion.readthedocs.io) +[![PYPI](https://img.shields.io/badge/PYPI-%23193440?style=for-the-badge&logo=pypi&logoColor=#3775A9)](https://pypi.org/project/markdiffusion) +[![CONDA-FORGE](https://img.shields.io/badge/Conda--Forge-%23000000?style=for-the-badge&logo=condaforge&logoColor=#FFFFFF)](https://github.com/conda-forge/markdiffusion-feedstock) + -**Versiones de idioma:** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) +**Versiones de idioma:** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) > 🔥 **¡Como un proyecto recién lanzado, damos la bienvenida a PRs!** Si has implementado un algoritmo de marcas de agua LDM o estás interesado en contribuir con uno, nos encantaría incluirlo en MarkDiffusion. ¡Únete a nuestra comunidad y ayuda a hacer las marcas de agua generativas más accesibles para todos! ## Contenidos -- [Notas](#-notas) - [Actualizaciones](#-actualizaciones) -- [Introducción a MarkDiffusion](#introducción-a-markdiffusion) - - [Descripción general](#descripción-general) - - [Características clave](#características-clave) - - [Algoritmos implementados](#algoritmos-implementados) - - [Módulo de evaluación](#módulo-de-evaluación) -- [Instalación](#instalación) -- [Inicio rápido](#inicio-rápido) -- [Cómo usar el kit de herramientas](#cómo-usar-el-kit-de-herramientas) - - [Generación y detección de medios con marcas de agua](#generación-y-detección-de-medios-con-marcas-de-agua) - - [Visualización de mecanismos de marcas de agua](#visualización-de-mecanismos-de-marcas-de-agua) - - [Pipelines de evaluación](#pipelines-de-evaluación) +- [Introducción a MarkDiffusion](#-introducción-a-markdiffusion) + - [Descripción general](#-descripción-general) + - [Características clave](#-características-clave) + - [Algoritmos implementados](#-algoritmos-implementados) + - [Módulo de evaluación](#-módulo-de-evaluación) +- [Inicio rápido](#-inicio-rápido) + - [Demo de Google Colab](#demo-de-google-colab) + - [Instalación](#instalación) + - [Cómo usar el kit de herramientas](#cómo-usar-el-kit-de-herramientas) +- [Módulos de prueba](#-módulos-de-prueba) - [Citación](#citación) -## ❗❗❗ Notas -A medida que el contenido del repositorio MarkDiffusion se vuelve cada vez más rico y su tamaño crece, hemos creado un repositorio de almacenamiento de modelos en Hugging Face llamado [Generative-Watermark-Toolkits](https://huggingface.co/Generative-Watermark-Toolkits) para facilitar su uso. Este repositorio contiene varios modelos predeterminados para algoritmos de marcas de agua que involucran modelos auto-entrenados. Hemos eliminado los pesos de los modelos de las carpetas `ckpts/` correspondientes de estos algoritmos de marcas de agua en el repositorio principal. **Al usar el código, primero descarga los modelos correspondientes del repositorio de Hugging Face según las rutas de configuración y guárdalos en el directorio `ckpts/` antes de ejecutar el código.** ## 🔥 Actualizaciones +🛠 **(2025.12.19)** Agregada una suite de pruebas completa para todas las funcionalidades con 454 casos de prueba. + +🛠 **(2025.12.10)** Agregado un sistema de pruebas de integración continua usando GitHub Actions. + 🎯 **(2025.10.10)** Agregadas herramientas de ataque de imagen *Mask, Overlay, AdaptiveNoiseInjection*, ¡gracias a Zheyu Fu por su PR! -🎯 **(2025.10.09)** Agregadas herramientas de ataque de video *VideoCodecAttack, FrameRateAdapter, FrameInterpolationAttack*, ¡gracias a Luyang Si por su PR! +🎯 **(2025.10.09)** Agregadas herramientas de ataque de video *FrameRateAdapter, FrameInterpolationAttack*, ¡gracias a Luyang Si por su PR! 🎯 **(2025.10.08)** Agregados analizadores de calidad de imagen *SSIM, BRISQUE, VIF, FSIM*, ¡gracias a Huan Wang por su PR! @@ -46,27 +51,27 @@ A medida que el contenido del repositorio MarkDiffusion se vuelve cada vez más ✨ **(2025.9.29)** Agregado el método de marca de agua [GaussMarker](https://arxiv.org/abs/2506.11444), ¡gracias a Luyang Si por su PR! -## Introducción a MarkDiffusion +## 🔓 Introducción a MarkDiffusion -### Descripción general +### 👀 Descripción general MarkDiffusion es un kit de herramientas de Python de código abierto para marcas de agua generativas de modelos de difusión latente. A medida que se expande el uso de modelos generativos basados en difusión, garantizar la autenticidad y el origen de los medios generados se vuelve crítico. MarkDiffusion simplifica el acceso, la comprensión y la evaluación de tecnologías de marcas de agua, haciéndolo accesible tanto para investigadores como para la comunidad en general. *Nota: si estás interesado en marcas de agua LLM (marca de agua de texto), consulta el kit de herramientas [MarkLLM](https://github.com/THU-BPM/MarkLLM) de nuestro grupo.* -El kit de herramientas comprende tres componentes clave: un marco de implementación unificado para integraciones simplificadas de algoritmos de marcas de agua e interfaces fáciles de usar; un conjunto de visualización de mecanismos que muestra intuitivamente los patrones de marcas de agua agregados y extraídos para ayudar a la comprensión pública; y un módulo de evaluación integral que ofrece implementaciones estándar de 24 herramientas en tres aspectos esenciales: detectabilidad, robustez y calidad de salida, además de 8 pipelines de evaluación automatizados. +El kit de herramientas comprende tres componentes clave: un marco de implementación unificado para integraciones simplificadas de algoritmos de marcas de agua e interfaces fáciles de usar; un conjunto de visualización de mecanismos que muestra intuitivamente los patrones de marcas de agua agregados y extraídos para ayudar a la comprensión pública; y un módulo de evaluación integral que ofrece implementaciones estándar de 31 herramientas en tres aspectos esenciales: detectabilidad, robustez y calidad de salida, además de 6 pipelines de evaluación automatizados. MarkDiffusion Overview -### Características clave +### 💍 Características clave -- **Marco de implementación unificado:** MarkDiffusion proporciona una arquitectura modular que admite ocho algoritmos de marcas de agua generativas de imagen/video de última generación para LDMs. +- **Marco de implementación unificado:** MarkDiffusion proporciona una arquitectura modular que admite once algoritmos de marcas de agua generativas de imagen/video de última generación para LDMs. -- **Soporte integral de algoritmos:** Actualmente implementa 8 algoritmos de marcas de agua de dos categorías principales: métodos basados en patrones (Tree-Ring, Ring-ID, ROBIN, WIND) y métodos basados en claves (Gaussian-Shading, PRC, SEAL, VideoShield). +- **Soporte integral de algoritmos:** Actualmente implementa 11 algoritmos de marcas de agua de dos categorías principales: métodos basados en patrones (Tree-Ring, Ring-ID, ROBIN, WIND, SFW) y métodos basados en claves (Gaussian-Shading, PRC, SEAL, VideoShield, GaussMarker, VideoMark). - **Soluciones de visualización:** El kit de herramientas incluye herramientas de visualización personalizadas que permiten vistas claras y perspicaces sobre cómo operan los diferentes algoritmos de marcas de agua en varios escenarios. Estas visualizaciones ayudan a desmitificar los mecanismos de los algoritmos, haciéndolos más comprensibles para los usuarios. -- **Módulo de evaluación:** Con 20 herramientas de evaluación que cubren detectabilidad, robustez e impacto en la calidad de salida, MarkDiffusion proporciona capacidades de evaluación integral. Cuenta con 5 pipelines de evaluación automatizados: Pipeline de detección de marcas de agua, Pipeline de análisis de calidad de imagen, Pipeline de análisis de calidad de video y herramientas especializadas de evaluación de robustez. +- **Módulo de evaluación:** Con 31 herramientas de evaluación que cubren detectabilidad, robustez e impacto en la calidad de salida, MarkDiffusion proporciona capacidades de evaluación integral. Cuenta con 6 pipelines de evaluación automatizados: Pipeline de detección de marcas de agua, Pipeline de análisis de calidad de imagen, Pipeline de análisis de calidad de video y herramientas especializadas de evaluación de robustez. -### Algoritmos implementados +### ✨ Algoritmos implementados | **Algoritmo** | **Categoría** | **Objetivo** | **Referencia** | |---------------|-------------|------------|---------------| @@ -82,7 +87,7 @@ El kit de herramientas comprende tres componentes clave: un marco de implementac | VideoShield | Clave | Video | [VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking](https://arxiv.org/abs/2501.14195) | | VideoMark | Clave | Video | [VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models](https://arxiv.org/abs/2504.16359) | -### Módulo de evaluación +### 🎯 Módulo de evaluación #### Pipelines de evaluación MarkDiffusion admite ocho pipelines, dos para detección (WatermarkedMediaDetectionPipeline y UnWatermarkedMediaDetectionPipeline), y seis para análisis de calidad. La tabla a continuación detalla los pipelines de análisis de calidad. @@ -116,7 +121,6 @@ MarkDiffusion admite ocho pipelines, dos para detección (WatermarkedMediaDetect | MPEG4Compression | Robustez (Video) | Ataque de compresión de video MPEG-4, probando la robustez de compresión de marca de agua de video | Fotogramas de video comprimidos | | FrameAverage | Robustez (Video) | Ataque de promedio de fotogramas, destruyendo marcas de agua a través del promedio entre fotogramas | Fotogramas de video promediados | | FrameSwap | Robustez (Video) | Ataque de intercambio de fotogramas, probando la robustez cambiando secuencias de fotogramas | Fotogramas de video intercambiados | -| VideoCodecAttack | Robustez (Video) | Ataque de recodificación de códec simulando transcodificación de plataforma (H.264/H.265/VP9/AV1) | Fotogramas de video recodificados | | FrameRateAdapter | Robustez (Video) | Ataque de conversión de velocidad de fotogramas que remuestrea fotogramas preservando la duración | Secuencia de fotogramas remuestreada | | FrameInterpolationAttack | Robustez (Video) | Ataque de interpolación de fotogramas insertando fotogramas mezclados para alterar la densidad temporal | Fotogramas de video interpolados | | **Analizadores de calidad de imagen** | | | | @@ -137,326 +141,130 @@ MarkDiffusion admite ocho pipelines, dos para detección (WatermarkedMediaDetect | DynamicDegreeAnalyzer | Calidad (Video) | Medir nivel dinámico y magnitud de cambio en video | Valor de grado dinámico | | ImagingQualityAnalyzer | Calidad (Video) | Evaluación integral de calidad de imagen de video | Puntuación de calidad de imagen | -## Instalación - -### Configuración del entorno - -- Python 3.10+ -- PyTorch -- Instalar dependencias: +## 🧩 Inicio rápido +### Demo de Google Colab +Si deseas probar MarkDiffusion sin instalar nada, puedes usar [Google Colab](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing#scrollTo=-kWt7m9Y3o-G) para ver cómo funciona. +### Instalación +**(Recomendado)** Hemos publicado un paquete pypi para MarkDiffusion. Puedes instalarlo directamente con pip: ```bash -pip install -r requirements.txt -``` - -*Nota:* Algunos algoritmos pueden requerir pasos de configuración adicionales. Consulta la documentación de algoritmos individuales para requisitos específicos. - -## Inicio rápido - -Aquí hay un ejemplo simple para comenzar con MarkDiffusion: - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig -from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - -# Configuración del dispositivo -device = 'cuda' if torch.cuda.is_available() else 'cpu' - -# Configurar pipeline de difusión -scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") -pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) -diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" -) - -# Cargar algoritmo de marca de agua -watermark = AutoWatermark.load('TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config) - -# Generar medios con marca de agua -prompt = "A beautiful sunset over the ocean" -watermarked_image = watermark.generate_watermarked_media(prompt) - -# Detectar marca de agua -detection_result = watermark.detect_watermark_in_media(watermarked_image) -print(f"Watermark detected: {detection_result}") +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +pip install markdiffusion[optional] ``` -## Cómo usar el kit de herramientas - -Proporcionamos ejemplos extensos en `MarkDiffusion_demo.ipynb`. - -### Generación y detección de medios con marcas de agua - -#### Casos para generar y detectar medios con marcas de agua - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig - -# Cargar algoritmo de marca de agua -mywatermark = AutoWatermark.load( - 'GS', - algorithm_config=f'config/GS.json', - diffusion_config=diffusion_config -) - -# Generar imagen con marca de agua -watermarked_image = mywatermark.generate_watermarked_media( - input_data="A beautiful landscape with a river and mountains" -) - -# Visualizar la imagen con marca de agua -watermarked_image.show() - -# Detectar marca de agua -detection_result = mywatermark.detect_watermark_in_media(watermarked_image) -print(detection_result) +(Alternativa) Para usuarios que están *restringidos solo al uso del entorno conda*, también proporcionamos un paquete conda-forge, que se puede instalar con los siguientes comandos: +```bash +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +conda config --add channels conda-forge +conda config --set channel_priority strict +conda install markdiffusion ``` +Sin embargo, ten en cuenta que algunas características avanzadas requieren paquetes adicionales que no están disponibles en conda y no se pueden incluir en la versión. Necesitarás instalarlos por separado si es necesario. -### Visualización de mecanismos de marcas de agua - -El kit de herramientas incluye herramientas de visualización personalizadas que permiten vistas claras y perspicaces sobre cómo operan los diferentes algoritmos de marcas de agua en varios escenarios. Estas visualizaciones ayudan a desmitificar los mecanismos de los algoritmos, haciéndolos más comprensibles para los usuarios. - -Watermarking Mechanism Visualization +### Cómo usar el kit de herramientas -#### Casos para visualizar mecanismos de marcas de agua +Después de la instalación, hay dos formas de usar MarkDiffusion: -```python -from visualize.auto_visualization import AutoVisualizer - -# Obtener datos para visualización -data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - -# Cargar visualizador -visualizer = AutoVisualizer.load('GS', - data_for_visualization=data_for_visualization) - -# Dibujar diagramas en el lienzo de Matplotlib -fig = visualizer.visualize(rows=2, cols=2, - methods=['draw_watermark_bits', - 'draw_reconstructed_watermark_bits', - 'draw_inverted_latents', - 'draw_inverted_latents_fft']) -``` +1. **Clonar el repositorio para probar las demos o usarlo para desarrollo personalizado.** El notebook `MarkDiffusion_demo.ipynb` ofrece demostraciones detalladas para varios casos de uso — por favor revísalo para obtener orientación. Aquí hay un ejemplo rápido de generación y detección de imagen con marca de agua usando el algoritmo TR: -### Pipelines de evaluación - -#### Casos para evaluación - -1. **Pipeline de detección de marcas de agua** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset -from evaluation.pipelines.detection import ( - WatermarkedMediaDetectionPipeline, - UnWatermarkedMediaDetectionPipeline, - DetectionPipelineReturnType -) -from evaluation.tools.image_editor import JPEGCompression -from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator - -# Conjunto de datos -my_dataset = StableDiffusionPromptsDataset(max_samples=200) - -# Configurar pipelines de detección -pipeline1 = WatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[JPEGCompression(quality=60)], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -pipeline2 = UnWatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -# Configurar parámetros de detección -detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, -} -# Calcular tasas de éxito -calculator = DynamicThresholdSuccessRateCalculator( - labels=labels, - rule=rules, - target_fpr=target_fpr -) - -results = calculator.calculate( - pipeline1.evaluate(my_watermark, detection_kwargs=detection_kwargs), - pipeline2.evaluate(my_watermark, detection_kwargs=detection_kwargs) -) -print(results) -``` + ```python + import torch + from watermark.auto_watermark import AutoWatermark + from utils.diffusion_config import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler -2. **Pipeline de análisis de calidad de imagen** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset, MSCOCODataset -from evaluation.pipelines.image_quality_analysis import ( - DirectImageQualityAnalysisPipeline, - ReferencedImageQualityAnalysisPipeline, - GroupImageQualityAnalysisPipeline, - RepeatImageQualityAnalysisPipeline, - ComparedImageQualityAnalysisPipeline, - QualityPipelineReturnType -) -from evaluation.tools.image_quality_analyzer import ( - NIQECalculator, CLIPScoreCalculator, FIDCalculator, - InceptionScoreCalculator, LPIPSAnalyzer, PSNRAnalyzer -) - -# Ejemplos de diferentes métricas de calidad: - -# NIQE (Evaluador de calidad de imagen natural) -if metric == 'NIQE': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = DirectImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[NIQECalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Configuración del dispositivo + device = 'cuda' if torch.cuda.is_available() else 'cpu' -# Puntuación CLIP -elif metric == 'CLIP': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = ReferencedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[CLIPScoreCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # Configurar pipeline de difusión + scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) + diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + num_inference_steps=50, + guidance_scale=7.5, + gen_seed=42, + inversion_type="ddim" ) -# FID (Distancia de Inception de Fréchet) -elif metric == 'FID': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[FIDCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # Cargar algoritmo de marca de agua + watermark = AutoWatermark.load('TR', + algorithm_config='config/TR.json', + diffusion_config=diffusion_config) + + # Generar medios con marca de agua + prompt = "A beautiful sunset over the ocean" + watermarked_image = watermark.generate_watermarked_media(prompt) + watermarked_image.save("watermarked_image.png") + + # Detectar marca de agua + detection_result = watermark.detect_watermark_in_media(watermarked_image) + print(f"Watermark detected: {detection_result}") + ``` + +2. **Importar la biblioteca markdiffusion directamente en tu código sin clonar el repositorio.** El notebook `MarkDiffusion_pypi_demo.ipynb` proporciona ejemplos completos para usar MarkDiffusion a través de la biblioteca markdiffusion — por favor revísalo para obtener orientación. Aquí hay un ejemplo rápido: + + ```python + import torch + from markdiffusion.watermark import AutoWatermark + from markdiffusion.utils import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler + + # Dispositivo + device = "cuda" if torch.cuda.is_available() else "cpu" + print(f"Using device: {device}") + + # Ruta del modelo + MODEL_PATH = "huanzi05/stable-diffusion-2-1-base" + + # Inicializar planificador y pipeline + scheduler = DPMSolverMultistepScheduler.from_pretrained(MODEL_PATH, subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained( + MODEL_PATH, + scheduler=scheduler, + torch_dtype=torch.float16 if device == "cuda" else torch.float32, + safety_checker=None, + ).to(device) + + # Crear DiffusionConfig para generación de imágenes + image_diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + guidance_scale=7.5, + num_inference_steps=50, + gen_seed=42, + inversion_type="ddim" ) -# IS (Puntuación Inception) -elif metric == 'IS': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[InceptionScoreCalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Cargar algoritmo de marca de agua Tree-Ring + tr_watermark = AutoWatermark.load('TR', diffusion_config=image_diffusion_config) + print("TR watermark algorithm loaded successfully!") -# LPIPS (Similitud de parche de imagen perceptual aprendida) -elif metric == 'LPIPS': - my_dataset = StableDiffusionPromptsDataset(max_samples=10) - pipeline = RepeatImageQualityAnalysisPipeline( - dataset=my_dataset, - prompt_per_image=20, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[LPIPSAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Generar imagen con marca de agua + prompt = "A beautiful landscape with mountains and a river at sunset" -# PSNR (Relación señal-ruido de pico) -elif metric == 'PSNR': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + watermarked_image = tr_watermark.generate_watermarked_media(input_data=prompt) -# Cargar marca de agua y evaluar -my_watermark = AutoWatermark.load( - f'{algorithm_name}', - algorithm_config=f'config/{algorithm_name}.json', - diffusion_config=diffusion_config -) + # Mostrar la imagen con marca de agua + watermarked_image.save("watermarked_image.png") + print("Watermarked image generated!") -print(pipeline.evaluate(my_watermark)) -``` + # Detectar marca de agua en la imagen con marca de agua + detection_result = tr_watermark.detect_watermark_in_media(watermarked_image) + print("Watermarked image detection result:") + print(detection_result) + ``` -3. **Pipeline de análisis de calidad de video** - -```python -from evaluation.dataset import VBenchDataset -from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline -from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - MotionSmoothnessAnalyzer, - DynamicDegreeAnalyzer, - BackgroundConsistencyAnalyzer, - ImagingQualityAnalyzer -) - -# Cargar conjunto de datos VBench -my_dataset = VBenchDataset(max_samples=200, dimension=dimension) - -# Inicializar analizador según métrica -if metric == 'subject_consistency': - analyzer = SubjectConsistencyAnalyzer(device=device) -elif metric == 'motion_smoothness': - analyzer = MotionSmoothnessAnalyzer(device=device) -elif metric == 'dynamic_degree': - analyzer = DynamicDegreeAnalyzer(device=device) -elif metric == 'background_consistency': - analyzer = BackgroundConsistencyAnalyzer(device=device) -elif metric == 'imaging_quality': - analyzer = ImagingQualityAnalyzer(device=device) -else: - raise ValueError(f'Invalid metric: {metric}. Supported metrics: - subject_consistency, motion_smoothness, dynamic_degree, - background_consistency, imaging_quality') - -# Crear pipeline de análisis de calidad de video -pipeline = DirectVideoQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[analyzer], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES -) - -print(pipeline.evaluate(my_watermark)) -``` +## 🛠 Módulos de prueba +Proporcionamos un conjunto completo de módulos de prueba para garantizar la calidad del código. El módulo incluye 454 pruebas unitarias con aproximadamente un 90% de cobertura de código. Consulta el directorio `test/` para más detalles. ## Citación ``` diff --git a/README_fr.md b/README_fr.md index ee43302..33d529e 100644 --- a/README_fr.md +++ b/README_fr.md @@ -4,39 +4,44 @@ # Une Boîte à Outils Open-Source pour le Tatouage Numérique Génératif des Modèles de Diffusion Latente -[![Homepage](https://img.shields.io/badge/Homepage-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) +[![Home](https://img.shields.io/badge/Home-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) [![Paper](https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white)](https://arxiv.org/abs/2509.10569) -[![HF Models](https://img.shields.io/badge/HF--Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Models](https://img.shields.io/badge/Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Colab](https://img.shields.io/badge/Google--Colab-%23D97700?style=for-the-badge&logo=Google-colab&logoColor=white)](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing) +[![DOC](https://img.shields.io/badge/Readthedocs-%2300A89C?style=for-the-badge&logo=readthedocs&logoColor=#8CA1AF)](https://markdiffusion.readthedocs.io) +[![PYPI](https://img.shields.io/badge/PYPI-%23193440?style=for-the-badge&logo=pypi&logoColor=#3775A9)](https://pypi.org/project/markdiffusion) +[![CONDA-FORGE](https://img.shields.io/badge/Conda--Forge-%23000000?style=for-the-badge&logo=condaforge&logoColor=#FFFFFF)](https://github.com/conda-forge/markdiffusion-feedstock) + -**Versions linguistiques :** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) +**Versions linguistiques :** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) > 🔥 **En tant que projet récemment publié, nous accueillons les PR !** Si vous avez implémenté un algorithme de tatouage numérique LDM ou si vous êtes intéressé à en contribuer un, nous serions ravis de l'inclure dans MarkDiffusion. Rejoignez notre communauté et aidez à rendre le tatouage numérique génératif plus accessible à tous ! ## Sommaire -- [Remarques](#-remarques) - [Mises à jour](#-mises-à-jour) -- [Introduction à MarkDiffusion](#introduction-à-markdiffusion) - - [Vue d'ensemble](#vue-densemble) - - [Caractéristiques clés](#caractéristiques-clés) - - [Algorithmes implémentés](#algorithmes-implémentés) - - [Module d'évaluation](#module-dévaluation) -- [Installation](#installation) -- [Démarrage rapide](#démarrage-rapide) -- [Comment utiliser la boîte à outils](#comment-utiliser-la-boîte-à-outils) - - [Génération et détection de médias tatoués](#génération-et-détection-de-médias-tatoués) - - [Visualisation des mécanismes de tatouage](#visualisation-des-mécanismes-de-tatouage) - - [Pipelines d'évaluation](#pipelines-dévaluation) +- [Introduction à MarkDiffusion](#-introduction-à-markdiffusion) + - [Vue d'ensemble](#-vue-densemble) + - [Caractéristiques clés](#-caractéristiques-clés) + - [Algorithmes implémentés](#-algorithmes-implémentés) + - [Module d'évaluation](#-module-dévaluation) +- [Démarrage rapide](#-démarrage-rapide) + - [Démo Google Colab](#démo-google-colab) + - [Installation](#installation) + - [Comment utiliser la boîte à outils](#comment-utiliser-la-boîte-à-outils) +- [Modules de test](#-modules-de-test) - [Citation](#citation) -## ❗❗❗ Remarques -Au fur et à mesure que le contenu du dépôt MarkDiffusion s'enrichit et que sa taille augmente, nous avons créé un dépôt de stockage de modèles sur Hugging Face appelé [Generative-Watermark-Toolkits](https://huggingface.co/Generative-Watermark-Toolkits) pour faciliter l'utilisation. Ce dépôt contient divers modèles par défaut pour les algorithmes de tatouage numérique qui impliquent des modèles auto-entraînés. Nous avons supprimé les poids des modèles des dossiers `ckpts/` correspondants de ces algorithmes de tatouage dans le dépôt principal. **Lors de l'utilisation du code, veuillez d'abord télécharger les modèles correspondants depuis le dépôt Hugging Face selon les chemins de configuration et les enregistrer dans le répertoire `ckpts/` avant d'exécuter le code.** ## 🔥 Mises à jour +🛠 **(2025.12.19)** Ajout d'une suite de tests complète pour toutes les fonctionnalités avec 454 cas de test. + +🛠 **(2025.12.10)** Ajout d'un système de tests d'intégration continue utilisant GitHub Actions. + 🎯 **(2025.10.10)** Ajout des outils d'attaque d'image *Mask, Overlay, AdaptiveNoiseInjection*, merci à Zheyu Fu pour sa PR ! -🎯 **(2025.10.09)** Ajout des outils d'attaque vidéo *VideoCodecAttack, FrameRateAdapter, FrameInterpolationAttack*, merci à Luyang Si pour sa PR ! +🎯 **(2025.10.09)** Ajout des outils d'attaque vidéo *FrameRateAdapter, FrameInterpolationAttack*, merci à Luyang Si pour sa PR ! 🎯 **(2025.10.08)** Ajout des analyseurs de qualité d'image *SSIM, BRISQUE, VIF, FSIM*, merci à Huan Wang pour sa PR ! @@ -46,27 +51,27 @@ Au fur et à mesure que le contenu du dépôt MarkDiffusion s'enrichit et que sa ✨ **(2025.9.29)** Ajout de la méthode de tatouage [GaussMarker](https://arxiv.org/abs/2506.11444), merci à Luyang Si pour sa PR ! -## Introduction à MarkDiffusion +## 🔓 Introduction à MarkDiffusion -### Vue d'ensemble +### 👀 Vue d'ensemble MarkDiffusion est une boîte à outils Python open-source pour le tatouage numérique génératif des modèles de diffusion latente. Alors que l'utilisation des modèles génératifs basés sur la diffusion s'étend, garantir l'authenticité et l'origine des médias générés devient crucial. MarkDiffusion simplifie l'accès, la compréhension et l'évaluation des technologies de tatouage numérique, les rendant accessibles tant aux chercheurs qu'à la communauté au sens large. *Remarque : si vous êtes intéressé par le tatouage LLM (tatouage de texte), veuillez vous référer à la boîte à outils [MarkLLM](https://github.com/THU-BPM/MarkLLM) de notre groupe.* -La boîte à outils comprend trois composants clés : un cadre d'implémentation unifié pour des intégrations rationalisées d'algorithmes de tatouage et des interfaces conviviales ; une suite de visualisation de mécanismes qui présente intuitivement les motifs de tatouage ajoutés et extraits pour aider à la compréhension du public ; et un module d'évaluation complet offrant des implémentations standard de 24 outils couvrant trois aspects essentiels — détectabilité, robustesse et qualité de sortie, plus 8 pipelines d'évaluation automatisés. +La boîte à outils comprend trois composants clés : un cadre d'implémentation unifié pour des intégrations rationalisées d'algorithmes de tatouage et des interfaces conviviales ; une suite de visualisation de mécanismes qui présente intuitivement les motifs de tatouage ajoutés et extraits pour aider à la compréhension du public ; et un module d'évaluation complet offrant des implémentations standard de 31 outils couvrant trois aspects essentiels — détectabilité, robustesse et qualité de sortie, plus 6 pipelines d'évaluation automatisés. MarkDiffusion Overview -### Caractéristiques clés +### 💍 Caractéristiques clés -- **Cadre d'implémentation unifié :** MarkDiffusion fournit une architecture modulaire prenant en charge huit algorithmes de tatouage d'image/vidéo génératifs de pointe pour les LDM. +- **Cadre d'implémentation unifié :** MarkDiffusion fournit une architecture modulaire prenant en charge onze algorithmes de tatouage d'image/vidéo génératifs de pointe pour les LDM. -- **Support algorithmique complet :** Implémente actuellement 8 algorithmes de tatouage de deux catégories principales : méthodes basées sur les motifs (Tree-Ring, Ring-ID, ROBIN, WIND) et méthodes basées sur les clés (Gaussian-Shading, PRC, SEAL, VideoShield). +- **Support algorithmique complet :** Implémente actuellement 11 algorithmes de tatouage de deux catégories principales : méthodes basées sur les motifs (Tree-Ring, Ring-ID, ROBIN, WIND, SFW) et méthodes basées sur les clés (Gaussian-Shading, PRC, SEAL, VideoShield, GaussMarker, VideoMark). - **Solutions de visualisation :** La boîte à outils comprend des outils de visualisation personnalisés qui permettent des vues claires et perspicaces sur le fonctionnement des différents algorithmes de tatouage dans divers scénarios. Ces visualisations aident à démystifier les mécanismes des algorithmes, les rendant plus compréhensibles pour les utilisateurs. -- **Module d'évaluation :** Avec 20 outils d'évaluation couvrant la détectabilité, la robustesse et l'impact sur la qualité de sortie, MarkDiffusion fournit des capacités d'évaluation complètes. Il comprend 5 pipelines d'évaluation automatisés : Pipeline de détection de tatouage, Pipeline d'analyse de qualité d'image, Pipeline d'analyse de qualité vidéo et outils d'évaluation de robustesse spécialisés. +- **Module d'évaluation :** Avec 31 outils d'évaluation couvrant la détectabilité, la robustesse et l'impact sur la qualité de sortie, MarkDiffusion fournit des capacités d'évaluation complètes. Il comprend 6 pipelines d'évaluation automatisés : Pipeline de détection de tatouage, Pipeline d'analyse de qualité d'image, Pipeline d'analyse de qualité vidéo et outils d'évaluation de robustesse spécialisés. -### Algorithmes implémentés +### ✨ Algorithmes implémentés | **Algorithme** | **Catégorie** | **Cible** | **Référence** | |---------------|-------------|------------|---------------| @@ -82,7 +87,7 @@ La boîte à outils comprend trois composants clés : un cadre d'implémentation | VideoShield | Clé | Vidéo | [VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking](https://arxiv.org/abs/2501.14195) | | VideoMark | Clé | Vidéo | [VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models](https://arxiv.org/abs/2504.16359) | -### Module d'évaluation +### 🎯 Module d'évaluation #### Pipelines d'évaluation MarkDiffusion prend en charge huit pipelines, deux pour la détection (WatermarkedMediaDetectionPipeline et UnWatermarkedMediaDetectionPipeline), et six pour l'analyse de qualité. Le tableau ci-dessous détaille les pipelines d'analyse de qualité. @@ -116,7 +121,6 @@ MarkDiffusion prend en charge huit pipelines, deux pour la détection (Watermark | MPEG4Compression | Robustesse (Vidéo) | Attaque par compression vidéo MPEG-4, testant la robustesse du tatouage vidéo à la compression | Cadres vidéo compressés | | FrameAverage | Robustesse (Vidéo) | Attaque par moyennage de cadres, détruisant les tatouages par moyennage inter-cadres | Cadres vidéo moyennés | | FrameSwap | Robustesse (Vidéo) | Attaque par échange de cadres, testant la robustesse en changeant les séquences de cadres | Cadres vidéo échangés | -| VideoCodecAttack | Robustesse (Vidéo) | Attaque par ré-encodage de codec simulant le transcodage de plateforme (H.264/H.265/VP9/AV1) | Cadres vidéo ré-encodés | | FrameRateAdapter | Robustesse (Vidéo) | Attaque par conversion de fréquence d'images qui rééchantillonne les cadres tout en préservant la durée | Séquence de cadres rééchantillonnée | | FrameInterpolationAttack | Robustesse (Vidéo) | Attaque par interpolation de cadres insérant des cadres mélangés pour modifier la densité temporelle | Cadres vidéo interpolés | | **Analyseurs de qualité d'image** | | | | @@ -137,326 +141,130 @@ MarkDiffusion prend en charge huit pipelines, deux pour la détection (Watermark | DynamicDegreeAnalyzer | Qualité (Vidéo) | Mesurer le niveau dynamique et l'amplitude de changement dans la vidéo | Valeur de degré dynamique | | ImagingQualityAnalyzer | Qualité (Vidéo) | Évaluation complète de la qualité d'imagerie vidéo | Score de qualité d'imagerie | -## Installation - -### Configuration de l'environnement - -- Python 3.10+ -- PyTorch -- Installer les dépendances : +## 🧩 Démarrage rapide +### Démo Google Colab +Si vous souhaitez essayer MarkDiffusion sans rien installer, vous pouvez utiliser [Google Colab](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing#scrollTo=-kWt7m9Y3o-G) pour voir comment cela fonctionne. +### Installation +**(Recommandé)** Nous avons publié un package pypi pour MarkDiffusion. Vous pouvez l'installer directement avec pip : ```bash -pip install -r requirements.txt -``` - -*Remarque :* Certains algorithmes peuvent nécessiter des étapes de configuration supplémentaires. Veuillez vous référer à la documentation des algorithmes individuels pour les exigences spécifiques. - -## Démarrage rapide - -Voici un exemple simple pour vous aider à démarrer avec MarkDiffusion : - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig -from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - -# Configuration du périphérique -device = 'cuda' if torch.cuda.is_available() else 'cpu' - -# Configuration du pipeline de diffusion -scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") -pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) -diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" -) - -# Charger l'algorithme de tatouage -watermark = AutoWatermark.load('TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config) - -# Générer un média tatoué -prompt = "A beautiful sunset over the ocean" -watermarked_image = watermark.generate_watermarked_media(prompt) - -# Détecter le tatouage -detection_result = watermark.detect_watermark_in_media(watermarked_image) -print(f"Watermark detected: {detection_result}") +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +pip install markdiffusion[optional] ``` -## Comment utiliser la boîte à outils - -Nous fournissons de nombreux exemples dans `MarkDiffusion_demo.ipynb`. - -### Génération et détection de médias tatoués - -#### Cas de génération et de détection de médias tatoués - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig - -# Charger l'algorithme de tatouage -mywatermark = AutoWatermark.load( - 'GS', - algorithm_config=f'config/GS.json', - diffusion_config=diffusion_config -) - -# Générer une image tatouée -watermarked_image = mywatermark.generate_watermarked_media( - input_data="A beautiful landscape with a river and mountains" -) - -# Visualiser l'image tatouée -watermarked_image.show() - -# Détecter le tatouage -detection_result = mywatermark.detect_watermark_in_media(watermarked_image) -print(detection_result) +(Alternative) Pour les utilisateurs qui sont *restreints uniquement à l'utilisation de l'environnement conda*, nous fournissons également un package conda-forge, qui peut être installé avec les commandes suivantes : +```bash +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +conda config --add channels conda-forge +conda config --set channel_priority strict +conda install markdiffusion ``` +Cependant, veuillez noter que certaines fonctionnalités avancées nécessitent des packages supplémentaires qui ne sont pas disponibles sur conda et ne peuvent pas être inclus dans la version. Vous devrez les installer séparément si nécessaire. -### Visualisation des mécanismes de tatouage - -La boîte à outils comprend des outils de visualisation personnalisés qui permettent des vues claires et perspicaces sur le fonctionnement des différents algorithmes de tatouage dans divers scénarios. Ces visualisations aident à démystifier les mécanismes des algorithmes, les rendant plus compréhensibles pour les utilisateurs. - -Watermarking Mechanism Visualization +### Comment utiliser la boîte à outils -#### Cas de visualisation du mécanisme de tatouage +Après l'installation, il existe deux façons d'utiliser MarkDiffusion : -```python -from visualize.auto_visualization import AutoVisualizer - -# Obtenir les données pour la visualisation -data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - -# Charger le visualiseur -visualizer = AutoVisualizer.load('GS', - data_for_visualization=data_for_visualization) - -# Dessiner des diagrammes sur le canevas Matplotlib -fig = visualizer.visualize(rows=2, cols=2, - methods=['draw_watermark_bits', - 'draw_reconstructed_watermark_bits', - 'draw_inverted_latents', - 'draw_inverted_latents_fft']) -``` +1. **Cloner le dépôt pour essayer les démos ou l'utiliser pour un développement personnalisé.** Le notebook `MarkDiffusion_demo.ipynb` offre des démonstrations détaillées pour divers cas d'utilisation — veuillez le consulter pour obtenir des conseils. Voici un exemple rapide de génération et de détection d'image tatouée avec l'algorithme TR : -### Pipelines d'évaluation - -#### Cas d'évaluation - -1. **Pipeline de détection de tatouage** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset -from evaluation.pipelines.detection import ( - WatermarkedMediaDetectionPipeline, - UnWatermarkedMediaDetectionPipeline, - DetectionPipelineReturnType -) -from evaluation.tools.image_editor import JPEGCompression -from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator - -# Jeu de données -my_dataset = StableDiffusionPromptsDataset(max_samples=200) - -# Configurer les pipelines de détection -pipeline1 = WatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[JPEGCompression(quality=60)], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -pipeline2 = UnWatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -# Configurer les paramètres de détection -detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, -} -# Calculer les taux de réussite -calculator = DynamicThresholdSuccessRateCalculator( - labels=labels, - rule=rules, - target_fpr=target_fpr -) - -results = calculator.calculate( - pipeline1.evaluate(my_watermark, detection_kwargs=detection_kwargs), - pipeline2.evaluate(my_watermark, detection_kwargs=detection_kwargs) -) -print(results) -``` + ```python + import torch + from watermark.auto_watermark import AutoWatermark + from utils.diffusion_config import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler -2. **Pipeline d'analyse de qualité d'image** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset, MSCOCODataset -from evaluation.pipelines.image_quality_analysis import ( - DirectImageQualityAnalysisPipeline, - ReferencedImageQualityAnalysisPipeline, - GroupImageQualityAnalysisPipeline, - RepeatImageQualityAnalysisPipeline, - ComparedImageQualityAnalysisPipeline, - QualityPipelineReturnType -) -from evaluation.tools.image_quality_analyzer import ( - NIQECalculator, CLIPScoreCalculator, FIDCalculator, - InceptionScoreCalculator, LPIPSAnalyzer, PSNRAnalyzer -) - -# Exemples de différentes métriques de qualité : - -# NIQE (Évaluateur de qualité d'image naturelle) -if metric == 'NIQE': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = DirectImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[NIQECalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Configuration du périphérique + device = 'cuda' if torch.cuda.is_available() else 'cpu' -# Score CLIP -elif metric == 'CLIP': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = ReferencedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[CLIPScoreCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # Configuration du pipeline de diffusion + scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) + diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + num_inference_steps=50, + guidance_scale=7.5, + gen_seed=42, + inversion_type="ddim" ) -# FID (Distance d'Inception de Fréchet) -elif metric == 'FID': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[FIDCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # Charger l'algorithme de tatouage + watermark = AutoWatermark.load('TR', + algorithm_config='config/TR.json', + diffusion_config=diffusion_config) + + # Générer un média tatoué + prompt = "A beautiful sunset over the ocean" + watermarked_image = watermark.generate_watermarked_media(prompt) + watermarked_image.save("watermarked_image.png") + + # Détecter le tatouage + detection_result = watermark.detect_watermark_in_media(watermarked_image) + print(f"Watermark detected: {detection_result}") + ``` + +2. **Importer la bibliothèque markdiffusion directement dans votre code sans cloner le dépôt.** Le notebook `MarkDiffusion_pypi_demo.ipynb` fournit des exemples complets pour utiliser MarkDiffusion via la bibliothèque markdiffusion — veuillez le consulter pour obtenir des conseils. Voici un exemple rapide : + + ```python + import torch + from markdiffusion.watermark import AutoWatermark + from markdiffusion.utils import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler + + # Périphérique + device = "cuda" if torch.cuda.is_available() else "cpu" + print(f"Using device: {device}") + + # Chemin du modèle + MODEL_PATH = "huanzi05/stable-diffusion-2-1-base" + + # Initialiser le planificateur et le pipeline + scheduler = DPMSolverMultistepScheduler.from_pretrained(MODEL_PATH, subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained( + MODEL_PATH, + scheduler=scheduler, + torch_dtype=torch.float16 if device == "cuda" else torch.float32, + safety_checker=None, + ).to(device) + + # Créer DiffusionConfig pour la génération d'images + image_diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + guidance_scale=7.5, + num_inference_steps=50, + gen_seed=42, + inversion_type="ddim" ) -# IS (Score Inception) -elif metric == 'IS': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[InceptionScoreCalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Charger l'algorithme de tatouage Tree-Ring + tr_watermark = AutoWatermark.load('TR', diffusion_config=image_diffusion_config) + print("TR watermark algorithm loaded successfully!") -# LPIPS (Similarité de patch d'image perceptuelle apprise) -elif metric == 'LPIPS': - my_dataset = StableDiffusionPromptsDataset(max_samples=10) - pipeline = RepeatImageQualityAnalysisPipeline( - dataset=my_dataset, - prompt_per_image=20, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[LPIPSAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # Générer une image tatouée + prompt = "A beautiful landscape with mountains and a river at sunset" -# PSNR (Rapport signal sur bruit de crête) -elif metric == 'PSNR': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + watermarked_image = tr_watermark.generate_watermarked_media(input_data=prompt) -# Charger le tatouage et évaluer -my_watermark = AutoWatermark.load( - f'{algorithm_name}', - algorithm_config=f'config/{algorithm_name}.json', - diffusion_config=diffusion_config -) + # Afficher l'image tatouée + watermarked_image.save("watermarked_image.png") + print("Watermarked image generated!") -print(pipeline.evaluate(my_watermark)) -``` + # Détecter le tatouage dans l'image tatouée + detection_result = tr_watermark.detect_watermark_in_media(watermarked_image) + print("Watermarked image detection result:") + print(detection_result) + ``` -3. **Pipeline d'analyse de qualité vidéo** - -```python -from evaluation.dataset import VBenchDataset -from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline -from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - MotionSmoothnessAnalyzer, - DynamicDegreeAnalyzer, - BackgroundConsistencyAnalyzer, - ImagingQualityAnalyzer -) - -# Charger le jeu de données VBench -my_dataset = VBenchDataset(max_samples=200, dimension=dimension) - -# Initialiser l'analyseur en fonction de la métrique -if metric == 'subject_consistency': - analyzer = SubjectConsistencyAnalyzer(device=device) -elif metric == 'motion_smoothness': - analyzer = MotionSmoothnessAnalyzer(device=device) -elif metric == 'dynamic_degree': - analyzer = DynamicDegreeAnalyzer(device=device) -elif metric == 'background_consistency': - analyzer = BackgroundConsistencyAnalyzer(device=device) -elif metric == 'imaging_quality': - analyzer = ImagingQualityAnalyzer(device=device) -else: - raise ValueError(f'Invalid metric: {metric}. Supported metrics: - subject_consistency, motion_smoothness, dynamic_degree, - background_consistency, imaging_quality') - -# Créer le pipeline d'analyse de qualité vidéo -pipeline = DirectVideoQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[analyzer], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES -) - -print(pipeline.evaluate(my_watermark)) -``` +## 🛠 Modules de test +Nous fournissons un ensemble complet de modules de test pour assurer la qualité du code. Le module comprend 454 tests unitaires avec environ 90% de couverture de code. Veuillez vous référer au répertoire `test/` pour plus de détails. ## Citation ``` diff --git a/README_zh.md b/README_zh.md index 105b261..7d72a56 100644 --- a/README_zh.md +++ b/README_zh.md @@ -4,39 +4,44 @@ # 潜在扩散模型生成式水印的开源工具包 -[![Homepage](https://img.shields.io/badge/Homepage-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) +[![Home](https://img.shields.io/badge/Home-5F259F?style=for-the-badge&logo=homepage&logoColor=white)](https://generative-watermark.github.io/) [![Paper](https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white)](https://arxiv.org/abs/2509.10569) -[![HF Models](https://img.shields.io/badge/HF--Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Models](https://img.shields.io/badge/Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black)](https://huggingface.co/Generative-Watermark-Toolkits) +[![Colab](https://img.shields.io/badge/Google--Colab-%23D97700?style=for-the-badge&logo=Google-colab&logoColor=white)](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing) +[![DOC](https://img.shields.io/badge/Readthedocs-%2300A89C?style=for-the-badge&logo=readthedocs&logoColor=#8CA1AF)](https://markdiffusion.readthedocs.io) +[![PYPI](https://img.shields.io/badge/PYPI-%23193440?style=for-the-badge&logo=pypi&logoColor=#3775A9)](https://pypi.org/project/markdiffusion) +[![CONDA-FORGE](https://img.shields.io/badge/Conda--Forge-%23000000?style=for-the-badge&logo=condaforge&logoColor=#FFFFFF)](https://github.com/conda-forge/markdiffusion-feedstock) + -**语言版本:** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) +**语言版本:** [English](README.md) | [中文](README_zh.md) | [Français](README_fr.md) | [Español](README_es.md) > 🔥 **作为一个新发布的项目,我们欢迎 PR!** 如果您已经实现了 LDM 水印算法或有兴趣贡献一个算法,我们很乐意将其包含在 MarkDiffusion 中。加入我们的社区,帮助让生成式水印技术对每个人都更易用! ## 目录 -- [注意事项](#-注意事项) - [更新日志](#-更新日志) -- [MarkDiffusion 简介](#markdiffusion-简介) - - [概述](#概述) - - [核心特性](#核心特性) - - [已实现算法](#已实现算法) - - [评估模块](#评估模块) -- [安装](#安装) -- [快速开始](#快速开始) -- [如何使用工具包](#如何使用工具包) - - [生成和检测水印媒体](#生成和检测水印媒体) - - [可视化水印机制](#可视化水印机制) - - [评估流水线](#评估流水线) +- [MarkDiffusion 简介](#-markdiffusion-简介) + - [概述](#-概述) + - [核心特性](#-核心特性) + - [已实现算法](#-已实现算法) + - [评估模块](#-评估模块) +- [快速开始](#-快速开始) + - [Google Colab 演示](#google-colab-演示) + - [安装](#安装) + - [如何使用工具包](#如何使用工具包) +- [测试模块](#-测试模块) - [引用](#引用) -## ❗❗❗ 注意事项 -随着 MarkDiffusion 仓库内容日益丰富且体积不断增大,我们在 Hugging Face 上创建了一个名为 [Generative-Watermark-Toolkits](https://huggingface.co/Generative-Watermark-Toolkits) 的模型存储仓库以便于使用。该仓库包含了各种涉及自训练模型的水印算法的默认模型。我们已从主仓库中这些水印算法对应的 `ckpts/` 文件夹中移除了模型权重。**使用代码时,请首先根据配置路径从 Hugging Face 仓库下载相应的模型,并将其保存到 `ckpts/` 目录后再运行代码。** ## 🔥 更新日志 +🛠 **(2025.12.19)** 为所有功能添加了包含454个测试用例的完整测试套件。 + +🛠 **(2025.12.10)** 使用 GitHub Actions 添加了持续集成测试系统。 + 🎯 **(2025.10.10)** 添加 *Mask、Overlay、AdaptiveNoiseInjection* 图像攻击工具,感谢付哲语的 PR! -🎯 **(2025.10.09)** 添加 *VideoCodecAttack、FrameRateAdapter、FrameInterpolationAttack* 视频攻击工具,感谢司路阳的 PR! +🎯 **(2025.10.09)** 添加 *FrameRateAdapter、FrameInterpolationAttack* 视频攻击工具,感谢司路阳的 PR! 🎯 **(2025.10.08)** 添加 *SSIM、BRISQUE、VIF、FSIM* 图像质量分析器,感谢王欢的 PR! @@ -46,27 +51,27 @@ ✨ **(2025.9.29)** 添加 [GaussMarker](https://arxiv.org/abs/2506.11444) 水印方法,感谢司路阳的 PR! -## MarkDiffusion 简介 +## 🔓 MarkDiffusion 简介 -### 概述 +### 👀 概述 MarkDiffusion 是一个用于潜在扩散模型生成式水印的开源 Python 工具包。随着基于扩散的生成模型应用范围的扩大,确保生成媒体的真实性和来源变得至关重要。MarkDiffusion 简化了水印技术的访问、理解和评估,使研究人员和更广泛的社区都能轻松使用。*注意:如果您对 LLM 水印(文本水印)感兴趣,请参考我们团队的 [MarkLLM](https://github.com/THU-BPM/MarkLLM) 工具包。* -该工具包包含三个关键组件:统一的实现框架,用于简化水印算法集成和用户友好的界面;机制可视化套件,直观地展示添加和提取的水印模式,帮助公众理解;以及全面的评估模块,提供 24 个工具的标准实现,涵盖三个关键方面——可检测性、鲁棒性和输出质量,以及 8 个自动化评估流水线。 +该工具包包含三个关键组件:统一的实现框架,用于简化水印算法集成和用户友好的界面;机制可视化套件,直观地展示添加和提取的水印模式,帮助公众理解;以及全面的评估模块,提供 31 个工具的标准实现,涵盖三个关键方面——可检测性、鲁棒性和输出质量,以及 6 个自动化评估流水线。 MarkDiffusion Overview -### 核心特性 +### 💍 核心特性 -- **统一实现框架:** MarkDiffusion 提供了一个模块化架构,支持八种最先进的 LDM 生成式图像/视频水印算法。 +- **统一实现框架:** MarkDiffusion 提供了一个模块化架构,支持十一种最先进的 LDM 生成式图像/视频水印算法。 -- **全面的算法支持:** 目前实现了来自两大类别的 8 种水印算法:基于模式的方法(Tree-Ring、Ring-ID、ROBIN、WIND)和基于密钥的方法(Gaussian-Shading、PRC、SEAL、VideoShield)。 +- **全面的算法支持:** 目前实现了来自两大类别的 11 种水印算法:基于模式的方法(Tree-Ring、Ring-ID、ROBIN、WIND、SFW)和基于密钥的方法(Gaussian-Shading、PRC、SEAL、VideoShield、GaussMarker、VideoMark)。 - **可视化解决方案:** 该工具包包含定制的可视化工具,能够清晰而深入地展示不同水印算法在各种场景下的运行方式。这些可视化有助于揭示算法机制,使其对用户更易理解。 -- **评估模块:** 拥有 20 个评估工具,涵盖可检测性、鲁棒性和对输出质量的影响,MarkDiffusion 提供全面的评估能力。它具有 5 个自动化评估流水线:水印检测流水线、图像质量分析流水线、视频质量分析流水线以及专门的鲁棒性评估工具。 +- **评估模块:** 拥有 31 个评估工具,涵盖可检测性、鲁棒性和对输出质量的影响,MarkDiffusion 提供全面的评估能力。它具有 6 个自动化评估流水线:水印检测流水线、图像质量分析流水线、视频质量分析流水线以及专门的鲁棒性评估工具。 -### 已实现算法 +### ✨ 已实现算法 | **算法** | **类别** | **目标** | **参考文献** | |---------------|-------------|------------|---------------| @@ -82,7 +87,7 @@ MarkDiffusion 是一个用于潜在扩散模型生成式水印的开源 Python | VideoShield | 密钥 | 视频 | [VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking](https://arxiv.org/abs/2501.14195) | | VideoMark | 密钥 | 视频 | [VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models](https://arxiv.org/abs/2504.16359) | -### 评估模块 +### 🎯 评估模块 #### 评估流水线 MarkDiffusion 支持八个流水线,两个用于检测(WatermarkedMediaDetectionPipeline 和 UnWatermarkedMediaDetectionPipeline),六个用于质量分析。下表详细说明了质量分析流水线。 @@ -116,7 +121,6 @@ MarkDiffusion 支持八个流水线,两个用于检测(WatermarkedMediaDetec | MPEG4Compression | 鲁棒性(视频) | MPEG-4 视频压缩攻击,测试视频水印的压缩鲁棒性 | 压缩后的视频帧 | | FrameAverage | 鲁棒性(视频) | 帧平均攻击,通过帧间平均破坏水印 | 平均后的视频帧 | | FrameSwap | 鲁棒性(视频) | 帧交换攻击,通过改变帧序列测试鲁棒性 | 交换后的视频帧 | -| VideoCodecAttack | 鲁棒性(视频) | 编解码器重编码攻击,模拟平台转码(H.264/H.265/VP9/AV1) | 重编码后的视频帧 | | FrameRateAdapter | 鲁棒性(视频) | 帧率转换攻击,在保持时长的同时重采样帧 | 重采样后的帧序列 | | FrameInterpolationAttack | 鲁棒性(视频) | 帧插值攻击,插入混合帧以改变时间密度 | 插值后的视频帧 | | **图像质量分析器** | | | | @@ -137,326 +141,130 @@ MarkDiffusion 支持八个流水线,两个用于检测(WatermarkedMediaDetec | DynamicDegreeAnalyzer | 质量(视频) | 测量视频中的动态水平和变化幅度 | 动态度值 | | ImagingQualityAnalyzer | 质量(视频) | 综合评估视频成像质量 | 成像质量分数 | -## 安装 - -### 环境设置 - -- Python 3.10+ -- PyTorch -- 安装依赖: +## 🧩 快速开始 +### Google Colab 演示 +如果您想在不安装任何内容的情况下试用 MarkDiffusion,可以使用 [Google Colab](https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing#scrollTo=-kWt7m9Y3o-G) 查看其工作方式。 +### 安装 +**(推荐)** 我们为 MarkDiffusion 发布了 pypi 包。您可以直接使用 pip 安装: ```bash -pip install -r requirements.txt -``` - -*注意:* 某些算法可能需要额外的设置步骤。请参考各个算法文档了解具体要求。 - -## 快速开始 - -这里有一个简单的示例帮助您开始使用 MarkDiffusion: - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig -from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - -# 设备设置 -device = 'cuda' if torch.cuda.is_available() else 'cpu' - -# 配置扩散流水线 -scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") -pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) -diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" -) - -# 加载水印算法 -watermark = AutoWatermark.load('TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config) - -# 生成带水印的媒体 -prompt = "A beautiful sunset over the ocean" -watermarked_image = watermark.generate_watermarked_media(prompt) - -# 检测水印 -detection_result = watermark.detect_watermark_in_media(watermarked_image) -print(f"Watermark detected: {detection_result}") +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +pip install markdiffusion[optional] ``` -## 如何使用工具包 - -我们在 `MarkDiffusion_demo.ipynb` 中提供了大量示例。 - -### 生成和检测水印媒体 - -#### 生成和检测水印媒体的案例 - -```python -import torch -from watermark.auto_watermark import AutoWatermark -from utils.diffusion_config import DiffusionConfig - -# 加载水印算法 -mywatermark = AutoWatermark.load( - 'GS', - algorithm_config=f'config/GS.json', - diffusion_config=diffusion_config -) - -# 生成带水印的图像 -watermarked_image = mywatermark.generate_watermarked_media( - input_data="A beautiful landscape with a river and mountains" -) - -# 可视化带水印的图像 -watermarked_image.show() - -# 检测水印 -detection_result = mywatermark.detect_watermark_in_media(watermarked_image) -print(detection_result) +(替代方案)对于*仅限于使用 conda 环境*的用户,我们还提供了 conda-forge 包,可以使用以下命令安装: +```bash +conda create -n markdiffusion python=3.11 +conda activate markdiffusion +conda config --add channels conda-forge +conda config --set channel_priority strict +conda install markdiffusion ``` +但是,请注意,某些高级功能需要 conda 上不可用的额外包,因此无法包含在发布版本中。如有必要,您需要单独安装这些包。 -### 可视化水印机制 - -该工具包包含定制的可视化工具,能够清晰而深入地展示不同水印算法在各种场景下的运行方式。这些可视化有助于揭示算法机制,使其对用户更易理解。 - -Watermarking Mechanism Visualization +### 如何使用工具包 -#### 可视化水印机制的案例 +安装后,有两种方式使用 MarkDiffusion: -```python -from visualize.auto_visualization import AutoVisualizer - -# 获取用于可视化的数据 -data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - -# 加载可视化器 -visualizer = AutoVisualizer.load('GS', - data_for_visualization=data_for_visualization) - -# 在 Matplotlib 画布上绘制图表 -fig = visualizer.visualize(rows=2, cols=2, - methods=['draw_watermark_bits', - 'draw_reconstructed_watermark_bits', - 'draw_inverted_latents', - 'draw_inverted_latents_fft']) -``` +1. **克隆仓库以尝试演示或用于自定义开发。** `MarkDiffusion_demo.ipynb` notebook 提供了各种用例的详细演示——请查看以获取指导。以下是使用 TR 算法生成和检测带水印图像的快速示例: -### 评估流水线 - -#### 评估案例 - -1. **水印检测流水线** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset -from evaluation.pipelines.detection import ( - WatermarkedMediaDetectionPipeline, - UnWatermarkedMediaDetectionPipeline, - DetectionPipelineReturnType -) -from evaluation.tools.image_editor import JPEGCompression -from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator - -# 数据集 -my_dataset = StableDiffusionPromptsDataset(max_samples=200) - -# 设置检测流水线 -pipeline1 = WatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[JPEGCompression(quality=60)], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -pipeline2 = UnWatermarkedMediaDetectionPipeline( - dataset=my_dataset, - media_editor_list=[], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES -) - -# 配置检测参数 -detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, -} -# 计算成功率 -calculator = DynamicThresholdSuccessRateCalculator( - labels=labels, - rule=rules, - target_fpr=target_fpr -) - -results = calculator.calculate( - pipeline1.evaluate(my_watermark, detection_kwargs=detection_kwargs), - pipeline2.evaluate(my_watermark, detection_kwargs=detection_kwargs) -) -print(results) -``` + ```python + import torch + from watermark.auto_watermark import AutoWatermark + from utils.diffusion_config import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler -2. **图像质量分析流水线** - -```python -from evaluation.dataset import StableDiffusionPromptsDataset, MSCOCODataset -from evaluation.pipelines.image_quality_analysis import ( - DirectImageQualityAnalysisPipeline, - ReferencedImageQualityAnalysisPipeline, - GroupImageQualityAnalysisPipeline, - RepeatImageQualityAnalysisPipeline, - ComparedImageQualityAnalysisPipeline, - QualityPipelineReturnType -) -from evaluation.tools.image_quality_analyzer import ( - NIQECalculator, CLIPScoreCalculator, FIDCalculator, - InceptionScoreCalculator, LPIPSAnalyzer, PSNRAnalyzer -) - -# 不同质量指标的示例: - -# NIQE(无参考图像质量评估器) -if metric == 'NIQE': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = DirectImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[NIQECalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # 设备设置 + device = 'cuda' if torch.cuda.is_available() else 'cpu' -# CLIP 分数 -elif metric == 'CLIP': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = ReferencedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[CLIPScoreCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # 配置扩散流水线 + scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) + diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + num_inference_steps=50, + guidance_scale=7.5, + gen_seed=42, + inversion_type="ddim" ) -# FID(Fréchet Inception Distance) -elif metric == 'FID': - my_dataset = MSCOCODataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[FIDCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES + # 加载水印算法 + watermark = AutoWatermark.load('TR', + algorithm_config='config/TR.json', + diffusion_config=diffusion_config) + + # 生成带水印的媒体 + prompt = "A beautiful sunset over the ocean" + watermarked_image = watermark.generate_watermarked_media(prompt) + watermarked_image.save("watermarked_image.png") + + # 检测水印 + detection_result = watermark.detect_watermark_in_media(watermarked_image) + print(f"Watermark detected: {detection_result}") + ``` + +2. **在代码中直接导入 markdiffusion 库,无需克隆仓库。** `MarkDiffusion_pypi_demo.ipynb` notebook 提供了通过 markdiffusion 库使用 MarkDiffusion 的全面示例——请查看以获取指导。以下是一个快速示例: + + ```python + import torch + from markdiffusion.watermark import AutoWatermark + from markdiffusion.utils import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler + + # 设备 + device = "cuda" if torch.cuda.is_available() else "cpu" + print(f"Using device: {device}") + + # 模型路径 + MODEL_PATH = "huanzi05/stable-diffusion-2-1-base" + + # 初始化调度器和流水线 + scheduler = DPMSolverMultistepScheduler.from_pretrained(MODEL_PATH, subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained( + MODEL_PATH, + scheduler=scheduler, + torch_dtype=torch.float16 if device == "cuda" else torch.float32, + safety_checker=None, + ).to(device) + + # 创建用于图像生成的 DiffusionConfig + image_diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + guidance_scale=7.5, + num_inference_steps=50, + gen_seed=42, + inversion_type="ddim" ) -# IS(Inception Score) -elif metric == 'IS': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = GroupImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[InceptionScoreCalculator()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # 加载 Tree-Ring 水印算法 + tr_watermark = AutoWatermark.load('TR', diffusion_config=image_diffusion_config) + print("TR watermark algorithm loaded successfully!") -# LPIPS(学习感知图像块相似度) -elif metric == 'LPIPS': - my_dataset = StableDiffusionPromptsDataset(max_samples=10) - pipeline = RepeatImageQualityAnalysisPipeline( - dataset=my_dataset, - prompt_per_image=20, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[LPIPSAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + # 生成带水印的图像 + prompt = "A beautiful landscape with mountains and a river at sunset" -# PSNR(峰值信噪比) -elif metric == 'PSNR': - my_dataset = StableDiffusionPromptsDataset(max_samples=max_samples) - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) + watermarked_image = tr_watermark.generate_watermarked_media(input_data=prompt) -# 加载水印并评估 -my_watermark = AutoWatermark.load( - f'{algorithm_name}', - algorithm_config=f'config/{algorithm_name}.json', - diffusion_config=diffusion_config -) + # 显示带水印的图像 + watermarked_image.save("watermarked_image.png") + print("Watermarked image generated!") -print(pipeline.evaluate(my_watermark)) -``` + # 检测带水印图像中的水印 + detection_result = tr_watermark.detect_watermark_in_media(watermarked_image) + print("Watermarked image detection result:") + print(detection_result) + ``` -3. **视频质量分析流水线** - -```python -from evaluation.dataset import VBenchDataset -from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline -from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - MotionSmoothnessAnalyzer, - DynamicDegreeAnalyzer, - BackgroundConsistencyAnalyzer, - ImagingQualityAnalyzer -) - -# 加载 VBench 数据集 -my_dataset = VBenchDataset(max_samples=200, dimension=dimension) - -# 根据指标初始化分析器 -if metric == 'subject_consistency': - analyzer = SubjectConsistencyAnalyzer(device=device) -elif metric == 'motion_smoothness': - analyzer = MotionSmoothnessAnalyzer(device=device) -elif metric == 'dynamic_degree': - analyzer = DynamicDegreeAnalyzer(device=device) -elif metric == 'background_consistency': - analyzer = BackgroundConsistencyAnalyzer(device=device) -elif metric == 'imaging_quality': - analyzer = ImagingQualityAnalyzer(device=device) -else: - raise ValueError(f'Invalid metric: {metric}. Supported metrics: - subject_consistency, motion_smoothness, dynamic_degree, - background_consistency, imaging_quality') - -# 创建视频质量分析流水线 -pipeline = DirectVideoQualityAnalysisPipeline( - dataset=my_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[analyzer], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES -) - -print(pipeline.evaluate(my_watermark)) -``` +## 🛠 测试模块 +我们提供了一套全面的测试模块来确保代码质量。该模块包含454个单元测试,覆盖率约为90%。详情请参考 `test/` 目录。 ## 引用 ``` diff --git a/docs/BUILD.md b/docs/BUILD.md index e0e2b0a..9b3abff 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -131,11 +131,9 @@ docs/ │ └── configuration.rst ├── api/ # API reference │ ├── watermark.rst -│ ├── detection.rst │ ├── visualization.rst -│ ├── evaluation.rst -│ └── utils.rst -├── changelog.rst # Changelog +│ ├── utils.rst +│ └── evaluation.rst ├── contributing.rst # Contributing guide ├── citation.rst # Citation information ├── _static/ # Static files (CSS, images) diff --git a/docs/_build/doctrees/BUILD.doctree b/docs/_build/doctrees/BUILD.doctree deleted file mode 100644 index ad89115..0000000 Binary files a/docs/_build/doctrees/BUILD.doctree and /dev/null differ diff --git a/docs/_build/doctrees/api/detection.doctree b/docs/_build/doctrees/api/detection.doctree deleted file mode 100644 index b6f0f8f..0000000 Binary files a/docs/_build/doctrees/api/detection.doctree and /dev/null differ diff --git a/docs/_build/doctrees/api/evaluation.doctree b/docs/_build/doctrees/api/evaluation.doctree deleted file mode 100644 index 563b198..0000000 Binary files a/docs/_build/doctrees/api/evaluation.doctree and /dev/null differ diff --git a/docs/_build/doctrees/api/utils.doctree b/docs/_build/doctrees/api/utils.doctree deleted file mode 100644 index 725eb86..0000000 Binary files a/docs/_build/doctrees/api/utils.doctree and /dev/null differ diff --git a/docs/_build/doctrees/api/visualization.doctree b/docs/_build/doctrees/api/visualization.doctree deleted file mode 100644 index c4e2f0b..0000000 Binary files a/docs/_build/doctrees/api/visualization.doctree and /dev/null differ diff --git a/docs/_build/doctrees/api/watermark.doctree b/docs/_build/doctrees/api/watermark.doctree deleted file mode 100644 index 72000e6..0000000 Binary files a/docs/_build/doctrees/api/watermark.doctree and /dev/null differ diff --git a/docs/_build/doctrees/changelog.doctree b/docs/_build/doctrees/changelog.doctree deleted file mode 100644 index 1ce9334..0000000 Binary files a/docs/_build/doctrees/changelog.doctree and /dev/null differ diff --git a/docs/_build/doctrees/citation.doctree b/docs/_build/doctrees/citation.doctree deleted file mode 100644 index a9cf1b4..0000000 Binary files a/docs/_build/doctrees/citation.doctree and /dev/null differ diff --git a/docs/_build/doctrees/code_of_conduct.doctree b/docs/_build/doctrees/code_of_conduct.doctree deleted file mode 100644 index a07e735..0000000 Binary files a/docs/_build/doctrees/code_of_conduct.doctree and /dev/null differ diff --git a/docs/_build/doctrees/contributing.doctree b/docs/_build/doctrees/contributing.doctree deleted file mode 100644 index c2c57b4..0000000 Binary files a/docs/_build/doctrees/contributing.doctree and /dev/null differ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle deleted file mode 100644 index 4a4c317..0000000 Binary files a/docs/_build/doctrees/environment.pickle and /dev/null differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree deleted file mode 100644 index 2e3eaf9..0000000 Binary files a/docs/_build/doctrees/index.doctree and /dev/null differ diff --git a/docs/_build/doctrees/installation.doctree b/docs/_build/doctrees/installation.doctree deleted file mode 100644 index a3f10fa..0000000 Binary files a/docs/_build/doctrees/installation.doctree and /dev/null differ diff --git a/docs/_build/doctrees/quickstart.doctree b/docs/_build/doctrees/quickstart.doctree deleted file mode 100644 index cdced12..0000000 Binary files a/docs/_build/doctrees/quickstart.doctree and /dev/null differ diff --git a/docs/_build/doctrees/tutorial.doctree b/docs/_build/doctrees/tutorial.doctree deleted file mode 100644 index 17e205a..0000000 Binary files a/docs/_build/doctrees/tutorial.doctree and /dev/null differ diff --git a/docs/_build/doctrees/user_guide/algorithms.doctree b/docs/_build/doctrees/user_guide/algorithms.doctree deleted file mode 100644 index 06786d6..0000000 Binary files a/docs/_build/doctrees/user_guide/algorithms.doctree and /dev/null differ diff --git a/docs/_build/doctrees/user_guide/evaluation.doctree b/docs/_build/doctrees/user_guide/evaluation.doctree deleted file mode 100644 index 7426b9e..0000000 Binary files a/docs/_build/doctrees/user_guide/evaluation.doctree and /dev/null differ diff --git a/docs/_build/doctrees/user_guide/visualization.doctree b/docs/_build/doctrees/user_guide/visualization.doctree deleted file mode 100644 index c3abf65..0000000 Binary files a/docs/_build/doctrees/user_guide/visualization.doctree and /dev/null differ diff --git a/docs/_build/doctrees/user_guide/watermarking.doctree b/docs/_build/doctrees/user_guide/watermarking.doctree deleted file mode 100644 index 78eb0ec..0000000 Binary files a/docs/_build/doctrees/user_guide/watermarking.doctree and /dev/null differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo deleted file mode 100644 index 17acfd6..0000000 --- a/docs/_build/html/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: e8649eef295dd15fd61c57a06ff7b183 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/BUILD.html b/docs/_build/html/BUILD.html deleted file mode 100644 index 72ac430..0000000 --- a/docs/_build/html/BUILD.html +++ /dev/null @@ -1,1046 +0,0 @@ - - - - - - - - - Building MarkDiffusion Documentation — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Building MarkDiffusion Documentation

-

This guide explains how to build the documentation locally.

-
-

Prerequisites

-
    -
  1. Python 3.10 or higher

  2. -
  3. Sphinx and related packages

  4. -
-
-
-

Installation

-

Install documentation dependencies:

-
cd docs
-pip install -r requirements.txt
-
-
-
-
-

Building HTML Documentation

-
-

On Linux/Mac:

-
cd docs
-make html
-
-
-
-
-

On Windows:

-
cd docs
-make.bat html
-
-
-

The built documentation will be in docs/_build/html/. Open docs/_build/html/index.html in your browser.

-
-
-
-

Building Other Formats

-
-

PDF

-
make latexpdf
-
-
-
-
-

ePub

-
make epub
-
-
-
-
-

All formats

-
make html epub latexpdf
-
-
-
-
-
-

Cleaning Build Files

-
make clean
-
-
-
-
-

Live Reload (Development)

-

For development with auto-reload:

-
pip install sphinx-autobuild
-sphinx-autobuild docs docs/_build/html
-
-
-

Then open http://127.0.0.1:8000 in your browser.

-
-
-

Read the Docs

-

The documentation is automatically built and hosted on Read the Docs when you push to the main branch.

-

Configuration file: .readthedocs.yaml

-
-
-

Troubleshooting

-
-

Missing Dependencies

-

If you get import errors:

-
pip install -r requirements.txt
-pip install -r docs/requirements.txt
-
-
-
-
-

Build Warnings

-

To see detailed warnings:

-
make html SPHINXOPTS="-W"
-
-
-

To fail on warnings:

-
make html SPHINXOPTS="-W --keep-going"
-
-
-
-
-

Clear Cache

-

Sometimes you need to clear the build cache:

-
make clean
-rm -rf docs/_build
-
-
-
-
-
-

Documentation Structure

-
docs/
-├── conf.py                 # Sphinx configuration
-├── index.rst              # Main documentation page
-├── installation.rst       # Installation guide
-├── quickstart.rst         # Quick start guide
-├── tutorial.rst           # Tutorials
-├── user_guide/           # User guides
-│   ├── algorithms.rst
-│   ├── watermarking.rst
-│   ├── visualization.rst
-│   └── evaluation.rst
-├── advanced/             # Advanced topics
-│   ├── custom_algorithms.rst
-│   ├── evaluation_pipelines.rst
-│   └── configuration.rst
-├── api/                  # API reference
-│   ├── watermark.rst
-│   ├── detection.rst
-│   ├── visualization.rst
-│   ├── evaluation.rst
-│   └── utils.rst
-├── changelog.rst         # Changelog
-├── contributing.rst      # Contributing guide
-├── citation.rst          # Citation information
-├── _static/             # Static files (CSS, images)
-├── _templates/          # Custom templates
-├── Makefile             # Build script (Unix)
-├── make.bat             # Build script (Windows)
-└── requirements.txt     # Documentation dependencies
-
-
-
-
-

Contributing to Documentation

-

See Contributing Guide for details on:

-
    -
  • Writing documentation

  • -
  • Adding new pages

  • -
  • Updating API docs

  • -
  • Style guidelines

  • -
-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/base.html b/docs/_build/html/_modules/detection/base.html deleted file mode 100644 index 8dd5fdc..0000000 --- a/docs/_build/html/_modules/detection/base.html +++ /dev/null @@ -1,928 +0,0 @@ - - - - - - - - detection.base — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.base

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from abc import ABC, abstractmethod
-import torch
-
-
-[docs] -class BaseDetector(ABC): - -
-[docs] - def __init__(self, - threshold: float, - device: torch.device): - - self.threshold = threshold - self.device = device
- - -
-[docs] - @abstractmethod - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor = None, - detector_type: str = "l1_distance") -> float: - pass
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/gm/gm_detection.html b/docs/_build/html/_modules/detection/gm/gm_detection.html deleted file mode 100644 index 48663f3..0000000 --- a/docs/_build/html/_modules/detection/gm/gm_detection.html +++ /dev/null @@ -1,1182 +0,0 @@ - - - - - - - - detection.gm.gm_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.gm.gm_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""GaussMarker detection utilities.
-
-This module adapts the official GaussMarker detection pipeline to the
-MarkDiffusion detection API. It evaluates recovered diffusion latents to
-decide whether a watermark is present, reporting both hard decisions and
-auxiliary scores (bit/message accuracies, frequency-domain distances).
-"""
-
-from __future__ import annotations
-
-from pathlib import Path
-from typing import Dict, Optional, Union
-
-import numpy as np
-import torch
-
-import joblib
-
-from detection.base import BaseDetector
-from watermark.gm.gm import GaussianShadingChaCha, extract_complex_sign
-from watermark.gm.gnr import GNRRestorer
-
-
-
-[docs] -class GMDetector(BaseDetector): - """Detector for GaussMarker watermarks. - - Args: - watermark_generator: Instance of :class:`GaussianShadingChaCha` that - holds the original watermark bits and ChaCha20 key stream. - watermarking_mask: Frequency-domain mask (or label map) indicating the - region that carries the watermark. - gt_patch: Reference watermark pattern in the frequency domain. - w_measurement: Measurement mode (e.g., ``"l1_complex"`` or - ``"signal_complex"``), mirroring the official implementation. - device: Torch device used for evaluation. - bit_threshold: Optional override for the bit-accuracy decision - threshold. Defaults to the generator's ``tau_bits`` value. - message_threshold: Optional threshold for message accuracy decisions. - l1_threshold: Optional threshold for frequency L1 distance decisions - (smaller is better). - """ - -
-[docs] - def __init__( - self, - watermark_generator: GaussianShadingChaCha, - watermarking_mask: torch.Tensor, - gt_patch: torch.Tensor, - w_measurement: str, - device: Union[str, torch.device], - bit_threshold: Optional[float] = None, - message_threshold: Optional[float] = None, - l1_threshold: Optional[float] = None, - gnr_checkpoint: Optional[Union[str, Path]] = None, - gnr_classifier_type: int = 0, - gnr_model_nf: int = 128, - gnr_binary_threshold: float = 0.5, - gnr_use_for_decision: bool = True, - gnr_threshold: Optional[float] = None, - fuser_checkpoint: Optional[Union[str, Path]] = None, - fuser_threshold: Optional[float] = None, - fuser_frequency_scale: float = 0.01, - ) -> None: - self.generator = watermark_generator - device = torch.device(device) - # Ensure watermark/message buffers are initialised - _, base_message = self.generator.create_watermark_and_return_w_m() - self.base_message = base_message.to(device) - watermarking_mask = watermarking_mask.to(device) - gt_patch = gt_patch.to(device) - - threshold = bit_threshold if bit_threshold is not None else ( - self.generator.tau_bits or 0.5 - ) - super().__init__(threshold=float(threshold), device=device) - - self.watermarking_mask = watermarking_mask - self.gt_patch = gt_patch - self.w_measurement = w_measurement.lower() - self.message_threshold = ( - float(message_threshold) if message_threshold is not None else float(self.generator.tau_onebit or threshold) - ) - self.l1_threshold = float(l1_threshold) if l1_threshold is not None else None - self.gnr_binary_threshold = gnr_binary_threshold - self.gnr_use_for_decision = gnr_use_for_decision - self.gnr_threshold = float(gnr_threshold) if gnr_threshold is not None else None - self.gnr_restorer = self._build_gnr_restorer( - checkpoint=gnr_checkpoint, - device=device, - classifier_type=gnr_classifier_type, - nf=gnr_model_nf, - ) - self.fuser = self._load_fuser(fuser_checkpoint) - self.fuser_threshold = float(fuser_threshold) if fuser_threshold is not None else 0.5 - self.fuser_frequency_scale = float(fuser_frequency_scale)
- - - # ------------------------------------------------------------------ - # Helper computations - # ------------------------------------------------------------------ - def _complex_l1(self, reversed_latents: torch.Tensor) -> float: - fft_latents = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2)) - if self.watermarking_mask.dtype == torch.bool: - selector = self.watermarking_mask - else: - selector = self.watermarking_mask != 0 - if selector.sum() == 0: - return 0.0 - diff = torch.abs(fft_latents[selector] - self.gt_patch[selector]) - return float(diff.mean().item()) - - def _signal_accuracy(self, reversed_latents: torch.Tensor) -> float: - fft_latents = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2)) - if self.watermarking_mask.dtype == torch.bool: - selector = self.watermarking_mask - else: - selector = self.watermarking_mask != 0 - if selector.sum() == 0: - return 0.0 - latents_sign = extract_complex_sign(fft_latents[selector]) - target_sign = extract_complex_sign(self.gt_patch[selector]) - return float((latents_sign == target_sign).float().mean().item()) - - def _build_gnr_restorer( - self, - checkpoint: Optional[Union[str, Path]], - device: torch.device, - classifier_type: int, - nf: int, - ) -> Optional[GNRRestorer]: - if not checkpoint: - return None - candidates = [Path(checkpoint)] - base_dir = Path(__file__).resolve().parent - candidates.append(base_dir / checkpoint) - candidates.append(base_dir.parent.parent / checkpoint) - for candidate in candidates: - if candidate.is_file(): - checkpoint_path = candidate - break - else: - raise FileNotFoundError(f"GNR checkpoint not found at '{checkpoint}'") - latent_channels = self.base_message.shape[1] - in_channels = latent_channels * (2 if classifier_type == 1 else 1) - return GNRRestorer( - checkpoint_path=checkpoint_path, - in_channels=in_channels, - out_channels=latent_channels, - nf=nf, - device=device, - classifier_type=classifier_type, - base_message=self.base_message if classifier_type == 1 else None, - ) - - def _load_fuser(self, checkpoint: Optional[Union[str, Path]]): - if not checkpoint: - return None - if joblib is None: - raise ImportError( - "joblib is required to load the GaussMarker fuser. Install joblib or disable the fuser." - ) - candidates = [Path(checkpoint)] - base_dir = Path(__file__).resolve().parent - candidates.append(base_dir / checkpoint) - candidates.append(base_dir.parent.parent / checkpoint) - for candidate in candidates: - if candidate.is_file(): - return joblib.load(candidate) - raise FileNotFoundError(f"Fuser checkpoint not found at '{checkpoint}'") - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ -
-[docs] - def eval_watermark( - self, - reversed_latents: torch.Tensor, - reference_latents: Optional[torch.Tensor] = None, - detector_type: str = "bit_acc", - ) -> Dict[str, Union[bool, float]]: - detector_type = detector_type.lower() - reversed_latents = reversed_latents.to(self.device, dtype=torch.float32) - - # Bit-level reconstruction - bit_watermark = self.generator.pred_w_from_latent(reversed_latents) - reference_bits = self.generator.watermark_tensor(self.device) - bit_acc = float((bit_watermark == reference_bits).float().mean().item()) - - # Message bit accuracy (post ChaCha decryption) - reversed_m = self.generator.pred_m_from_latent(reversed_latents) - message_bits = torch.from_numpy(self.generator.message_bits.astype("float32")).to(self.device) - message_acc = float((reversed_m.flatten().float() == message_bits).float().mean().item()) - - # Frequency-domain statistics - complex_l1 = self._complex_l1(reversed_latents) - signal_acc = self._signal_accuracy(reversed_latents) if "signal" in self.w_measurement else None - - gnr_bit_acc = None - gnr_message_acc = None - if self.gnr_restorer is not None: - restored_binary = self.gnr_restorer.restore_binary(reversed_m, threshold=self.gnr_binary_threshold) - restored_w = self.generator.pred_w_from_m(restored_binary) - gnr_bit_acc = float((restored_w == reference_bits).float().mean().item()) - gnr_message_acc = float((restored_binary.flatten() == message_bits).float().mean().item()) - - frequency_score = -complex_l1 * self.fuser_frequency_scale - metrics: Dict[str, Union[bool, float]] = { - "bit_acc": bit_acc, - "message_acc": message_acc, - "complex_l1": complex_l1, - "frequency_score": frequency_score, - "tau_bits": float(self.generator.tau_bits or 0.5), - "tau_onebit": float(self.generator.tau_onebit or 0.5), - } - if signal_acc is not None: - metrics["signal_acc"] = signal_acc - if gnr_bit_acc is not None: - metrics["gnr_bit_acc"] = gnr_bit_acc - if gnr_message_acc is not None: - metrics["gnr_message_acc"] = gnr_message_acc - - # Determine binary decision based on requested detector type - decision_threshold = self.threshold if self.gnr_threshold is None else self.gnr_threshold - decision_bit_acc = bit_acc - if self.gnr_restorer is not None and self.gnr_use_for_decision and gnr_bit_acc is not None: - decision_bit_acc = max(decision_bit_acc, gnr_bit_acc) - metrics["decision_bit_acc"] = decision_bit_acc - metrics["decision_threshold"] = decision_threshold - - fused_score = None - fused_threshold = self.fuser_threshold if self.fuser is not None else None - if self.fuser is not None: - spatial_score = gnr_bit_acc if gnr_bit_acc is not None else bit_acc - frequency_score = metrics["frequency_score"] - features = np.array([[spatial_score, frequency_score]], dtype=np.float32) - if hasattr(self.fuser, "predict_proba"): - fused_score = float(self.fuser.predict_proba(features)[0, 1]) - elif hasattr(self.fuser, "decision_function"): - fused_score = float(self.fuser.decision_function(features)[0]) - else: - raise AttributeError("Unsupported fuser model: missing predict_proba/decision_function") - metrics["fused_score"] = fused_score - metrics["fused_threshold"] = fused_threshold - - if detector_type == "message_acc": - is_watermarked = message_acc >= self.message_threshold - elif detector_type == "complex_l1": - threshold = self.l1_threshold if self.l1_threshold is not None else self.threshold - is_watermarked = complex_l1 <= threshold - elif detector_type == "signal_acc": - if signal_acc is None: - raise ValueError("Signal accuracy requested but watermark measurement does not use signal mode.") - is_watermarked = signal_acc >= self.threshold - elif detector_type == "gnr_bit_acc": - if gnr_bit_acc is None: - raise ValueError("GNR checkpoint not provided, cannot compute GNR-based accuracy.") - is_watermarked = gnr_bit_acc >= decision_threshold - elif detector_type == "fused": - if fused_score is None: - raise ValueError("Fuser checkpoint not provided, cannot compute fused score.") - is_watermarked = fused_score >= fused_threshold - elif detector_type == "all": - if fused_score is not None: - is_watermarked = fused_score >= fused_threshold - else: - is_watermarked = decision_bit_acc >= decision_threshold - else: - if detector_type in {"bit_acc", "is_watermarked"} and fused_score is not None: - is_watermarked = fused_score >= fused_threshold - elif detector_type in {"bit_acc", "is_watermarked"}: - is_watermarked = decision_bit_acc >= decision_threshold - else: - raise ValueError(f"Unsupported detector_type '{detector_type}' for GaussMarker.") - - metrics["is_watermarked"] = bool(is_watermarked) - return metrics
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/gs/gs_detection.html b/docs/_build/html/_modules/detection/gs/gs_detection.html deleted file mode 100644 index fb87035..0000000 --- a/docs/_build/html/_modules/detection/gs/gs_detection.html +++ /dev/null @@ -1,999 +0,0 @@ - - - - - - - - detection.gs.gs_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.gs.gs_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import numpy as np
-from Crypto.Random import get_random_bytes
-from Crypto.Cipher import ChaCha20
-from scipy.stats import truncnorm, norm
-from functools import reduce
-from detection.base import BaseDetector
-from typing import Union
-
-
-[docs] -class GSDetector(BaseDetector): - -
-[docs] - def __init__(self, - watermarking_mask: torch.Tensor, - chacha: bool, - wm_key: Union[int, tuple[int, int]], - channel_copy: int, - hw_copy: int, - vote_threshold: int, - threshold: float, - device: torch.device): - super().__init__(threshold, device) - self.chacha = chacha - self.channel_copy = channel_copy - self.hw_copy = hw_copy - self.watermark = watermarking_mask - self.vote_threshold = vote_threshold - if self.chacha: - self.chacha_key, self.chacha_nonce = wm_key - else: - self.key = wm_key
- - - def _stream_key_encrypt(self, sd): - """Encrypt the watermark using ChaCha20 cipher.""" - cipher = ChaCha20.new(key=self.chacha_key, nonce=self.chacha_nonce) - m_byte = cipher.encrypt(np.packbits(sd).tobytes()) - m_bit = np.unpackbits(np.frombuffer(m_byte, dtype=np.uint8)) - return m_bit - - def _truncSampling(self, message): - """Truncated Gaussian sampling for watermarking.""" - z = np.zeros(self.latentlength) - denominator = 2.0 - ppf = [norm.ppf(j / denominator) for j in range(int(denominator) + 1)] - for i in range(self.latentlength): - dec_mes = reduce(lambda a, b: 2 * a + b, message[i : i + 1]) - dec_mes = int(dec_mes) - z[i] = truncnorm.rvs(ppf[dec_mes], ppf[dec_mes + 1]) - z = torch.from_numpy(z).reshape(1, 4, 64, 64).half() - return z.cuda() - - def _stream_key_decrypt(self, reversed_m): - """Decrypt the watermark using ChaCha20 cipher.""" - cipher = ChaCha20.new(key=self.chacha_key, nonce=self.chacha_nonce) - sd_byte = cipher.decrypt(np.packbits(reversed_m).tobytes()) - sd_bit = np.unpackbits(np.frombuffer(sd_byte, dtype=np.uint8)) - sd_tensor = torch.from_numpy(sd_bit).reshape(1, 4, 64, 64).to(torch.uint8) - return sd_tensor.cuda() - - def _diffusion_inverse(self, reversed_sd): - """Inverse the diffusion process to extract the watermark.""" - ch_stride = 4 // self.channel_copy - hw_stride = 64 // self.hw_copy - ch_list = [ch_stride] * self.channel_copy - hw_list = [hw_stride] * self.hw_copy - split_dim1 = torch.cat(torch.split(reversed_sd, tuple(ch_list), dim=1), dim=0) - split_dim2 = torch.cat(torch.split(split_dim1, tuple(hw_list), dim=2), dim=0) - split_dim3 = torch.cat(torch.split(split_dim2, tuple(hw_list), dim=3), dim=0) - vote = torch.sum(split_dim3, dim=0).clone() - vote[vote <= self.vote_threshold] = 0 - vote[vote > self.vote_threshold] = 1 - return vote - -
-[docs] - def eval_watermark(self, reversed_latents: torch.Tensor, detector_type: str = "bit_acc") -> float: - """Evaluate watermark in reversed latents.""" - if detector_type != 'bit_acc': - raise ValueError(f'Detector type {detector_type} is not supported for Gaussian Shading.') - reversed_m = (reversed_latents > 0).int() - if self.chacha: - reversed_sd = self._stream_key_decrypt(reversed_m.flatten().cpu().numpy()) - else: - reversed_sd = (reversed_m + self.key) % 2 - reversed_watermark = self._diffusion_inverse(reversed_sd) - correct = (reversed_watermark == self.watermark).float().mean().item() - - return { - 'is_watermarked': bool(correct > self.threshold), - 'bit_acc': correct - }
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/prc/prc_detection.html b/docs/_build/html/_modules/detection/prc/prc_detection.html deleted file mode 100644 index 466cde8..0000000 --- a/docs/_build/html/_modules/detection/prc/prc_detection.html +++ /dev/null @@ -1,1068 +0,0 @@ - - - - - - - - detection.prc.prc_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.prc.prc_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import numpy as np
-from typing import Tuple, Type
-from scipy.special import erf
-from detection.base import BaseDetector
-from ldpc import bp_decoder
-from galois import FieldArray
-   
-
-[docs] -class PRCDetector(BaseDetector): - -
-[docs] - def __init__(self, - var: int, - decoding_key: tuple, - GF: Type[FieldArray], - threshold: float, - device: torch.device): - super().__init__(threshold, device) - - self.var = var - self.decoding_key = decoding_key - self.GF = GF
- - - def _recover_posteriors(self, z, basis=None, variances=None): - if variances is None: - default_variance = 1.5 - denominators = np.sqrt(2 * default_variance * (1+default_variance)) * torch.ones_like(z) - elif type(variances) is float: - denominators = np.sqrt(2 * variances * (1 + variances)) - else: - denominators = torch.sqrt(2 * variances * (1 + variances)) - - if basis is None: - return erf(z / denominators) - else: - return erf((z @ basis) / denominators) - - def _detect_watermark(self, posteriors: torch.Tensor) -> Tuple[bool, float]: - """Detect watermark in posteriors.""" - generator_matrix, parity_check_matrix, one_time_pad, false_positive_rate, noise_rate, test_bits, g, max_bp_iter, t = self.decoding_key - posteriors = (1 - 2 * noise_rate) * (1 - 2 * np.array(one_time_pad, dtype=float)) * posteriors.numpy(force=True) - - r = parity_check_matrix.shape[0] - Pi = np.prod(posteriors[parity_check_matrix.indices.reshape(r, t)], axis=1) - log_plus = np.log((1 + Pi) / 2) - log_minus = np.log((1 - Pi) / 2) - log_prod = log_plus + log_minus - - const = 0.5 * np.sum(np.power(log_plus, 2) + np.power(log_minus, 2) - 0.5 * np.power(log_prod, 2)) - threshold = np.sqrt(2 * const * np.log(1 / false_positive_rate)) + 0.5 * log_prod.sum() - #print(f"threshold: {threshold}") - return log_plus.sum() >= threshold, log_plus.sum() - - def _boolean_row_reduce(self, A, print_progress=False): - """Given a GF(2) matrix, do row elimination and return the first k rows of A that form an invertible matrix - - Args: - A (np.ndarray): A GF(2) matrix - print_progress (bool, optional): Whether to print the progress. Defaults to False. - - Returns: - np.ndarray: The first k rows of A that form an invertible matrix - """ - n, k = A.shape - A_rr = A.copy() - perm = np.arange(n) - for j in range(k): - idxs = j + np.nonzero(A_rr[j:, j])[0] - if idxs.size == 0: - print("The given matrix is not invertible") - return None - A_rr[[j, idxs[0]]] = A_rr[[idxs[0], j]] # For matrices you have to swap them this way - (perm[j], perm[idxs[0]]) = (perm[idxs[0]], perm[j]) # Weirdly, this is MUCH faster if you swap this way instead of using perm[[i,j]]=perm[[j,i]] - A_rr[idxs[1:]] += A_rr[j] - if print_progress and (j%5==0 or j+1==k): - sys.stdout.write(f'\rDecoding progress: {j + 1} / {k}') - sys.stdout.flush() - if print_progress: print() - return perm[:k] - - def _decode_message(self, posteriors, print_progress=False, max_bp_iter=None): - generator_matrix, parity_check_matrix, one_time_pad, false_positive_rate_key, noise_rate, test_bits, g, max_bp_iter_key, t = self.decoding_key - if max_bp_iter is None: - max_bp_iter = max_bp_iter_key - - posteriors = (1 - 2 * noise_rate) * (1 - 2 * np.array(one_time_pad, dtype=float)) * posteriors.numpy(force=True) - channel_probs = (1 - np.abs(posteriors)) / 2 - x_recovered = (1 - np.sign(posteriors)) // 2 - - - # Apply the belief-propagation decoder. - if print_progress: - print("Running belief propagation...") - bpd = bp_decoder(parity_check_matrix, channel_probs=channel_probs, max_iter=max_bp_iter, bp_method="product_sum") - x_decoded = bpd.decode(x_recovered) - - # Compute a confidence score. - bpd_probs = 1 / (1 + np.exp(bpd.log_prob_ratios)) - confidences = 2 * np.abs(0.5 - bpd_probs) - - # Order codeword bits by confidence. - confidence_order = np.argsort(-confidences) - ordered_generator_matrix = generator_matrix[confidence_order] - ordered_x_decoded = x_decoded[confidence_order].astype(int) - - # Find the first (according to the confidence order) linearly independent set of rows of the generator matrix. - top_invertible_rows = self._boolean_row_reduce(ordered_generator_matrix, print_progress=print_progress) - if top_invertible_rows is None: - return None - - # Solve the system. - if print_progress: - print("Solving linear system...") - recovered_string = np.linalg.solve(ordered_generator_matrix[top_invertible_rows], self.GF(ordered_x_decoded[top_invertible_rows])) - - if not (recovered_string[:len(test_bits)] == test_bits).all(): - return None - return np.array(recovered_string[len(test_bits) + g:]) - - def _binary_array_to_str(self, binary_array: np.ndarray) -> str: - """Convert binary array back to string.""" - # Ensure the binary array length is divisible by 8 (1 byte = 8 bits) - assert len(binary_array) % 8 == 0, "Binary array length must be a multiple of 8" - - # Group the binary array into chunks of 8 bits - byte_chunks = binary_array.reshape(-1, 8) - - # Convert each byte (8 bits) to a character - chars = [chr(int(''.join(map(str, byte)), 2)) for byte in byte_chunks] - - # Join the characters to form the original string - return ''.join(chars) - -
-[docs] - def eval_watermark(self, reversed_latents: torch.Tensor, reference_latents: torch.Tensor = None, detector_type: str = "is_watermarked") -> float: - """Evaluate watermark in reversed latents.""" - if detector_type != 'is_watermarked': - raise ValueError(f'Detector type {detector_type} is not supported for PRC. Use "is_watermarked" instead.') - reversed_prc = self._recover_posteriors(reversed_latents.to(torch.float64).flatten().cpu(), variances=self.var).flatten().cpu() - self.recovered_prc = reversed_prc - detect_result, score = self._detect_watermark(reversed_prc) - decoding_result = self._decode_message(reversed_prc) - if decoding_result is None: - return { - 'is_watermarked': False, - "score": score, # Keep the score for potential future use - 'decoding_result': decoding_result, - "decoded_message": None - } - decoded_message = self._binary_array_to_str(decoding_result) - combined_result = detect_result or (decoding_result is not None) - #print(f"detection_result: {detect_result}, decoding_result: {decoding_result}, combined_result: {combined_result}") - return { - 'is_watermarked': bool(combined_result), - "score": score, # Keep the score for potential future use - 'decoding_result': decoding_result, - "decoded_message": decoded_message - }
-
- - - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/robin/robin_detection.html b/docs/_build/html/_modules/detection/robin/robin_detection.html deleted file mode 100644 index 7f34f70..0000000 --- a/docs/_build/html/_modules/detection/robin/robin_detection.html +++ /dev/null @@ -1,963 +0,0 @@ - - - - - - - - detection.robin.robin_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for detection.robin.robin_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-from detection.base import BaseDetector
-from scipy.stats import ncx2
-from torch.nn import functional as F
-
-
-[docs] -class ROBINDetector(BaseDetector): - -
-[docs] - def __init__(self, - watermarking_mask: torch.Tensor, - gt_patch: torch.Tensor, - threshold: float, - device: torch.device): - super().__init__(threshold, device) - self.watermarking_mask = watermarking_mask - self.gt_patch = gt_patch
- - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor = None, - detector_type: str = "l1_distance") -> float: - reversed_latents_fft = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2)) - - if detector_type == 'l1_distance': - target_patch = self.gt_patch #[self.watermarking_mask].flatten() - l1_distance = torch.abs(reversed_latents_fft[self.watermarking_mask] - target_patch[self.watermarking_mask]).mean().item() - return { - 'is_watermarked': bool(l1_distance < self.threshold), - 'l1_distance': l1_distance - } - elif detector_type == 'p_value': - reversed_latents_fft_wm_area = reversed_latents_fft[self.watermarking_mask].flatten() - target_patch = self.gt_patch[self.watermarking_mask].flatten() - target_patch = torch.concatenate([target_patch.real, target_patch.imag]) - reversed_latents_fft_wm_area = torch.concatenate([reversed_latents_fft_wm_area.real, reversed_latents_fft_wm_area.imag]) - sigma_ = reversed_latents_fft_wm_area.std() - lambda_ = (target_patch ** 2 / sigma_ ** 2).sum().item() - x = (((reversed_latents_fft_wm_area - target_patch) / sigma_) ** 2).sum().item() - p = ncx2.cdf(x=x, df=len(target_patch), nc=lambda_) - return { - 'is_watermarked': p < self.threshold, - 'p_value': p - } - elif detector_type == 'cosine_similarity': - reversed_latents_fft_wm_area = reversed_latents_fft[self.watermarking_mask].flatten() - target_patch = self.gt_patch[self.watermarking_mask].flatten() - cosine_similarity = F.cosine_similarity(reversed_latents_fft_wm_area.real, target_patch.real, dim=0) - return { - 'is_watermarked': cosine_similarity > self.threshold, - 'cosine_similarity': cosine_similarity - } - else: - raise ValueError(f"Tree Ring's watermark detector type {self.detector_type} not supported")
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/seal/seal_detection.html b/docs/_build/html/_modules/detection/seal/seal_detection.html deleted file mode 100644 index dcf84b4..0000000 --- a/docs/_build/html/_modules/detection/seal/seal_detection.html +++ /dev/null @@ -1,987 +0,0 @@ - - - - - - - - detection.seal.seal_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for detection.seal.seal_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-from detection.base import BaseDetector
-from transformers import Blip2Processor, Blip2ForConditionalGeneration
-from sentence_transformers import SentenceTransformer
-from PIL import Image
-import math
-
-
-[docs] -class SEALDetector(BaseDetector): - -
-[docs] - def __init__(self, - k: int, - b: int, - theta_mid: int, - cap_processor: Blip2Processor, - cap_model: Blip2ForConditionalGeneration, - sentence_transformer: SentenceTransformer, - patch_distance_threshold: float, - device: torch.device): - super().__init__(patch_distance_threshold, device) - self.k = k - self.b = b - self.theta_mid = theta_mid - self.cap_processor = cap_processor - self.cap_model = cap_model - self.sentence_transformer = sentence_transformer - self.patch_distance_threshold = patch_distance_threshold - - # Calculate the match threshold - # m^{\text{match}} = \left\lfloor n \rho(\theta^{\text{mid}}) \right\rfloor - # \rho(\theta) = \left( 1 - \frac{\theta}{180^\circ} \right)^b - # n = k - self.match_threshold = math.floor(self.k * ((1 - self.theta_mid / 180) ** self.b))
- - - def _calculate_patch_l2(self, noise1: torch.Tensor, noise2: torch.Tensor, k: int) -> torch.Tensor: - """ - Calculate L2 distances patch by patch. Returns a list of L2 values for the first k patches. - """ - l2_list = [] - patch_per_side_h = int(math.ceil(math.sqrt(k))) - patch_per_side_w = int(math.ceil(k / patch_per_side_h)) - patch_height = 64 // patch_per_side_h - patch_width = 64 // patch_per_side_w - patch_count = 0 - for i in range(patch_per_side_h): - for j in range(patch_per_side_w): - if patch_count >= k: - break - y_start = i * patch_height - x_start = j * patch_width - y_end = min(y_start + patch_height, 64) - x_end = min(x_start + patch_width, 64) - patch1 = noise1[:, :, y_start:y_end, x_start:x_end] - patch2 = noise2[:, :, y_start:y_end, x_start:x_end] - l2_val = torch.norm(patch1 - patch2).item() - l2_list.append(l2_val) - patch_count += 1 - return l2_list - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor, - detector_type: str = "patch_accuracy") -> float: - - if detector_type != "patch_accuracy": - raise ValueError(f"Detector type {detector_type} is not supported for SEAL detector") - - l2_patch_list = self._calculate_patch_l2(reversed_latents, reference_latents, self.k) - - # Count the number of patches that are less than the threshold - num_patches_below_threshold = sum(1 for l2 in l2_patch_list if l2 < self.patch_distance_threshold) - - return { - "is_watermarked": bool(num_patches_below_threshold >= self.match_threshold), - "patch_accuracy": num_patches_below_threshold / self.k, - }
-
- - - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/sfw/sfw_detection.html b/docs/_build/html/_modules/detection/sfw/sfw_detection.html deleted file mode 100644 index d1ba4a2..0000000 --- a/docs/_build/html/_modules/detection/sfw/sfw_detection.html +++ /dev/null @@ -1,992 +0,0 @@ - - - - - - - - detection.sfw.sfw_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.sfw.sfw_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-from detection.base import BaseDetector
-from scipy.stats import ncx2
-
-
-[docs] -class SFWDetector(BaseDetector): - -
-[docs] - def __init__(self, - watermarking_mask: torch.Tensor, - gt_patch: torch.Tensor, - w_channel: int, - threshold: float, - device: torch.device, - wm_type: str = "HSTR"): - super().__init__(threshold, device) - self.watermarking_mask = watermarking_mask - self.gt_patch = gt_patch - self.w_channel = w_channel - self.wm_type = wm_type
- - - @torch.no_grad() - def get_distance_hsqr(self,qr_gt_bool, target_fft,p=1): - """ - qr_gt_bool : (c_wm,42,42) boolean - target_fft : (1,4,64,64) complex64 - """ - qr_gt_bool = qr_gt_bool.squeeze(0) - center_row = target_fft.shape[-2] // 2 # 32 - qr_pix_len = qr_gt_bool.shape[-1] # 42 - qr_pix_half = (qr_pix_len + 1) // 2 # 21 - qr_gt_f32 = torch.where(qr_gt_bool, torch.tensor(45.0), torch.tensor(-45.0)).to(torch.float32) # (c_wm,42,42) boolean -> float32 - qr_left = qr_gt_f32[0,:, :qr_pix_half] # (42,21) float32 - qr_right = qr_gt_f32[0,:, qr_pix_half:] # (42,21) float32 - qr_complex = torch.complex(qr_left, qr_right).to(target_fft.device) # (42,21) complex64 - row_start = 10 + 1 # 11 - row_end = row_start + qr_pix_len # 53 = 11+42 - col_start = center_row + 1 # 33 = 32+1 - col_end = col_start + qr_pix_half # 54 = 33+21 - qr_slice = (0, self.w_channel, slice(row_start, row_end), slice(col_start, col_end)) # (42,21) - diff = torch.abs(qr_complex - target_fft[qr_slice]) # (42,21) - return torch.mean(diff).item() - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor = None, - detector_type: str = "l1_distance") -> float: - start, end = 10, 54 - center_slice = (slice(None), slice(None), slice(start, end), slice(start, end)) - reversed_latents_fft = torch.zeros_like(reversed_latents, dtype=torch.complex64) - reversed_latents_fft[center_slice] = torch.fft.fftshift(torch.fft.fft2(reversed_latents[center_slice]), dim=(-1, -2)) - if self.wm_type == "HSQR": - if detector_type == 'l1_distance': - hsqr_distance = self.get_distance_hsqr(qr_gt_bool=self.gt_patch, target_fft=reversed_latents_fft) - return { - 'is_watermarked': hsqr_distance < self.threshold, - 'l1_distance': hsqr_distance - } - else: - raise ValueError(f"SFW(HSQR)'s watermark detector type {self.detector_type} not supported") - else: - if detector_type == 'l1_distance': - target_patch = self.gt_patch #[self.watermarking_mask].flatten() - l1_distance = torch.abs(reversed_latents_fft[self.watermarking_mask] - target_patch[self.watermarking_mask]).mean().item() - return { - 'is_watermarked': l1_distance < self.threshold, - 'l1_distance': l1_distance - } - elif detector_type == 'p_value': - reversed_latents_fft = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2))[self.watermarking_mask].flatten() - target_patch = self.gt_patch[self.watermarking_mask].flatten() - target_patch = torch.concatenate([target_patch.real, target_patch.imag]) - reversed_latents_fft = torch.concatenate([reversed_latents_fft.real, reversed_latents_fft.imag]) - sigma_ = reversed_latents_fft.std() - lambda_ = (target_patch ** 2 / sigma_ ** 2).sum().item() - x = (((reversed_latents_fft - target_patch) / sigma_) ** 2).sum().item() - p = ncx2.cdf(x=x, df=len(target_patch), nc=lambda_) - return { - 'is_watermarked': bool(p < self.threshold), - 'p_value': p - } - else: - raise ValueError(f"SFW(HSTR)'s watermark detector type {self.detector_type} not supported")
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/tr/tr_detection.html b/docs/_build/html/_modules/detection/tr/tr_detection.html deleted file mode 100644 index fe5653c..0000000 --- a/docs/_build/html/_modules/detection/tr/tr_detection.html +++ /dev/null @@ -1,953 +0,0 @@ - - - - - - - - detection.tr.tr_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for detection.tr.tr_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-from detection.base import BaseDetector
-from scipy.stats import ncx2
-
-
-[docs] -class TRDetector(BaseDetector): - -
-[docs] - def __init__(self, - watermarking_mask: torch.Tensor, - gt_patch: torch.Tensor, - threshold: float, - device: torch.device): - super().__init__(threshold, device) - self.watermarking_mask = watermarking_mask - self.gt_patch = gt_patch
- - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor = None, - detector_type: str = "l1_distance") -> float: - if detector_type == 'l1_distance': - reversed_latents_fft = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2)) #[self.watermarking_mask].flatten() - target_patch = self.gt_patch #[self.watermarking_mask].flatten() - l1_distance = torch.abs(reversed_latents_fft[self.watermarking_mask] - target_patch[self.watermarking_mask]).mean().item() - return { - 'is_watermarked': l1_distance < self.threshold, - 'l1_distance': l1_distance - } - elif detector_type == 'p_value': - reversed_latents_fft = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2))[self.watermarking_mask].flatten() - target_patch = self.gt_patch[self.watermarking_mask].flatten() - target_patch = torch.concatenate([target_patch.real, target_patch.imag]) - reversed_latents_fft = torch.concatenate([reversed_latents_fft.real, reversed_latents_fft.imag]) - sigma_ = reversed_latents_fft.std() - lambda_ = (target_patch ** 2 / sigma_ ** 2).sum().item() - x = (((reversed_latents_fft - target_patch) / sigma_) ** 2).sum().item() - p = ncx2.cdf(x=x, df=len(target_patch), nc=lambda_) - return { - 'is_watermarked': bool(p < self.threshold), - 'p_value': p - } - else: - raise ValueError(f"Tree Ring's watermark detector type {self.detector_type} not supported")
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/videomark/videomark_detection.html b/docs/_build/html/_modules/detection/videomark/videomark_detection.html deleted file mode 100644 index ba3810e..0000000 --- a/docs/_build/html/_modules/detection/videomark/videomark_detection.html +++ /dev/null @@ -1,1230 +0,0 @@ - - - - - - - - detection.videomark.videomark_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for detection.videomark.videomark_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import numpy as np
-from typing import Tuple, Type
-from scipy.special import erf
-from detection.base import BaseDetector
-from ldpc import bp_decoder
-from galois import FieldArray
-import sys        
-from Levenshtein import hamming
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-[docs] -class VideoMarkDetector(BaseDetector): - -
-[docs] - def __init__(self, - message_sequence: np.ndarray, - watermark: np.ndarray, - num_frames: int, - var: int, - decoding_key: tuple, - GF: Type[FieldArray], - threshold: float, - device: torch.device): - super().__init__(threshold, device) - - self.message_sequence = message_sequence - self.watermark = watermark - self.num_frames = num_frames - self.var = var - self.decoding_key = decoding_key - self.GF = GF - self.threshold = threshold - self.code_length = self.decoding_key[0].shape[0]
- - - def _recover_posteriors(self, z, basis=None, variances=None): - if variances is None: - default_variance = 1.5 - denominators = np.sqrt(2 * default_variance * (1+default_variance)) * torch.ones_like(z) - elif type(variances) is float: - denominators = np.sqrt(2 * variances * (1 + variances)) - else: - denominators = torch.sqrt(2 * variances * (1 + variances)) - - if basis is None: - return erf(z / denominators) - else: - return erf((z @ basis) / denominators) - - def _align_posteriors_length(self, posteriors: torch.Tensor) -> torch.Tensor: - """Ensure posterior vector length matches the code length expected by LDPC decoder.""" - current_len = posteriors.numel() - if current_len == self.code_length: - return posteriors - if current_len > self.code_length: - logger.warning( - "Detected per-frame feature length %s is greater than the encoding length %s. The excess part has been truncated.", - current_len, - self.code_length, - ) - return posteriors[:self.code_length] - - pad_len = self.code_length - current_len - logger.warning( - "Detected per-frame feature length %s is less than the encoding length %s. %s elements have been padded with zeros at the end.", - current_len, - self.code_length, - pad_len, - ) - pad = torch.zeros(pad_len, dtype=posteriors.dtype, device=posteriors.device) - return torch.cat([posteriors, pad], dim=0) - - def _detect_watermark(self, posteriors: torch.Tensor) -> Tuple[bool, float]: - """Detect watermark in posteriors.""" - generator_matrix, parity_check_matrix, one_time_pad, false_positive_rate, noise_rate, test_bits, g, max_bp_iter, t = self.decoding_key - posteriors = self._align_posteriors_length(posteriors) - posteriors = (1 - 2 * noise_rate) * (1 - 2 * np.array(one_time_pad, dtype=float)) * posteriors.numpy(force=True) - - r = parity_check_matrix.shape[0] - Pi = np.prod(posteriors[parity_check_matrix.indices.reshape(r, t)], axis=1) - log_plus = np.log((1 + Pi) / 2) - log_minus = np.log((1 - Pi) / 2) - log_prod = log_plus + log_minus - - const = 0.5 * np.sum(np.power(log_plus, 2) + np.power(log_minus, 2) - 0.5 * np.power(log_prod, 2)) - threshold = np.sqrt(2 * const * np.log(1 / false_positive_rate)) + 0.5 * log_prod.sum() - #print(f"threshold: {threshold}") - return log_plus.sum() >= threshold#, log_plus.sum() - - def _boolean_row_reduce(self, A, print_progress=False): - """Given a GF(2) matrix, do row elimination and return the first k rows of A that form an invertible matrix - - Args: - A (np.ndarray): A GF(2) matrix - print_progress (bool, optional): Whether to print the progress. Defaults to False. - - Returns: - np.ndarray: The first k rows of A that form an invertible matrix - """ - n, k = A.shape - A_rr = A.copy() - perm = np.arange(n) - for j in range(k): - idxs = j + np.nonzero(A_rr[j:, j])[0] - if idxs.size == 0: - print("The given matrix is not invertible") - return None - A_rr[[j, idxs[0]]] = A_rr[[idxs[0], j]] # For matrices you have to swap them this way - (perm[j], perm[idxs[0]]) = (perm[idxs[0]], perm[j]) # Weirdly, this is MUCH faster if you swap this way instead of using perm[[i,j]]=perm[[j,i]] - A_rr[idxs[1:]] += A_rr[j] - if print_progress and (j%5==0 or j+1==k): - sys.stdout.write(f'\rDecoding progress: {j + 1} / {k}') - sys.stdout.flush() - if print_progress: print() - return perm[:k] - - def _decode_message(self, posteriors, print_progress=False, max_bp_iter=None): - generator_matrix, parity_check_matrix, one_time_pad, false_positive_rate_key, noise_rate, test_bits, g, max_bp_iter_key, t = self.decoding_key - if max_bp_iter is None: - max_bp_iter = max_bp_iter_key - - posteriors = self._align_posteriors_length(posteriors) - posteriors = (1 - 2 * noise_rate) * (1 - 2 * np.array(one_time_pad, dtype=float)) * posteriors.numpy(force=True) - channel_probs = (1 - np.abs(posteriors)) / 2 - x_recovered = (1 - np.sign(posteriors)) // 2 - - - # Apply the belief-propagation decoder. - if print_progress: - print("Running belief propagation...") - bpd = bp_decoder(parity_check_matrix, channel_probs=channel_probs, max_iter=max_bp_iter, bp_method="product_sum") - x_decoded = bpd.decode(x_recovered) - - # Compute a confidence score. - bpd_probs = 1 / (1 + np.exp(bpd.log_prob_ratios)) - confidences = 2 * np.abs(0.5 - bpd_probs) - - # Order codeword bits by confidence. - confidence_order = np.argsort(-confidences) - ordered_generator_matrix = generator_matrix[confidence_order] - ordered_x_decoded = x_decoded[confidence_order].astype(int) - - # Find the first (according to the confidence order) linearly independent set of rows of the generator matrix. - top_invertible_rows = self._boolean_row_reduce(ordered_generator_matrix, print_progress=print_progress) - if top_invertible_rows is None: - return None - - # Solve the system. - if print_progress: - print("Solving linear system...") - recovered_string = np.linalg.solve(ordered_generator_matrix[top_invertible_rows], self.GF(ordered_x_decoded[top_invertible_rows])) - - return np.array(recovered_string[len(test_bits) + g:]) - -
-[docs] - def bits_to_string(self, bits): - return ''.join(map(str, bits))
- - -
-[docs] - def recover(self, idx_list, message_list, distance_list, message_length): - """ - Recover the original message sequence from indices, messages, and distances. - - - If idx != -1: use sorted valid entries (normal recovery) - - If idx == -1: use the original message_list and distance_list directly - """ - - valid_entries = [ - (idx, msg, dist) - for idx, msg, dist in zip(idx_list, message_list, distance_list) - if idx != -1 - ] - valid_entries.sort(key=lambda x: x[0]) - - sorted_order, recovered_message, recovered_distance = [], [], [] - valid_idx = 0 - - for i, idx in enumerate(idx_list): - if idx == -1: - - sorted_order.append(-1) - recovered_message.append(message_list[i]) - recovered_distance.append(distance_list[i]) - else: - sorted_order.append(valid_entries[valid_idx][0]) - recovered_message.append(valid_entries[valid_idx][1]) - recovered_distance.append(valid_entries[valid_idx][2]) - valid_idx += 1 - - return ( - np.array(sorted_order), - np.array(recovered_message), - np.array(recovered_distance) - )
- - - - - -
-[docs] - def eval_watermark(self, reversed_latents: torch.Tensor, reference_latents: torch.Tensor = None, detector_type: str = "bit_acc") -> float: - """Evaluate watermark in reversed latents.""" - if detector_type != 'bit_acc': - raise ValueError(f'Detector type {detector_type} is not supported for VideoMark. Use "bit_acc" instead.') - - if reversed_latents.dim() < 3: - raise ValueError("VideoMark detector expects at least 3D reversed latents for video inputs.") - - latents = reversed_latents - - if latents.dim() == 4: - latents = latents.unsqueeze(0) - - if latents.dim() != 5: - raise ValueError("Unsupported reversed_latents dimensionality for VideoMark detector.") - - expected_frames = self.num_frames or 0 - - channel_axis = None - for axis in range(1, 5): - if latents.shape[axis] == 4: - channel_axis = axis - break - if channel_axis is not None and channel_axis != 1: - latents = latents.movedim(channel_axis, 1) - - candidate_axes = [axis for axis in range(2, 5)] - if expected_frames: - frame_axis = min(candidate_axes, key=lambda axis: abs(latents.shape[axis] - expected_frames)) - else: - frame_axis = 2 - if frame_axis != 2: - latents = latents.movedim(frame_axis, 2) - - available_frames = latents.shape[2] - frames_to_use = available_frames - - if expected_frames: - if available_frames != expected_frames: - logger.warning( - "Frame count mismatch detected: received %s frames, expected %s frames.", - available_frames, - expected_frames, - ) - frames_to_use = min(available_frames, expected_frames) - logger.info("Truncated to the first %d frames for detection.", frames_to_use) - - if frames_to_use <= 1: - logger.error("There are not enough frames for VideoMark detection.") - return { - 'is_watermarked': False, - "bit_acc": 0.0, - "recovered_index": np.array([]), - "recovered_message": np.array([]), - "recovered_distance": np.array([]), - } - - latents = latents[:, :, :frames_to_use, ...] - - idx_list, message_list, distance_list = [], [], [] - message_length = self.message_sequence.shape[1] - message_sequence_str = [self.bits_to_string(msg) for msg in self.message_sequence] - - for frame_index in range(frames_to_use): - frame_latents = latents[:, :, frame_index, ...].to(torch.float64) - reversed_prc = self._recover_posteriors( - frame_latents.flatten().cpu(), - variances=self.var - ).flatten().cpu() - aligned_prc = self._align_posteriors_length(reversed_prc) - self.recovered_prc = aligned_prc - - detect_result = self._detect_watermark(aligned_prc) - decode_message = self._decode_message(aligned_prc) - if decode_message is None: - decode_message = np.zeros((message_length,), dtype=int) - decode_message_str = "0" * message_length - else: - decode_message = np.asarray(decode_message).astype(int) - decode_message_str = self.bits_to_string(decode_message) - - distances = np.array([ - 2 * (hamming(decode_message_str, msg) / len(msg) - 0.5) - for msg in message_sequence_str - ]) - min_distance = distances.min() - idx = -1 if not detect_result else distances.argmin() - - message_list.append(decode_message) - distance_list.append(min_distance) - idx_list.append(idx) - - recovered_index, recovered_message, recovered_distance = self.recover( - idx_list, message_list, distance_list, message_length - ) - - watermark_reference = np.asarray(self.watermark[:frames_to_use]) - recovered_message = np.asarray(recovered_message) - - if recovered_message.size == 0 or watermark_reference.size == 0: - bit_acc = 0.0 - else: - frame_limit = min(recovered_message.shape[0], watermark_reference.shape[0]) - if frame_limit <= 0: - bit_acc = 0.0 - else: - bit_acc = np.mean( - recovered_message[:frame_limit] == watermark_reference[:frame_limit] - ) - - return { - 'is_watermarked': float(bit_acc) >= self.threshold, - "bit_acc": float(bit_acc), - "recovered_index": recovered_index, - "recovered_message": recovered_message, - "recovered_distance": recovered_distance, - }
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/videoshield/videoshield_detection.html b/docs/_build/html/_modules/detection/videoshield/videoshield_detection.html deleted file mode 100644 index 3e82c7d..0000000 --- a/docs/_build/html/_modules/detection/videoshield/videoshield_detection.html +++ /dev/null @@ -1,1153 +0,0 @@ - - - - - - - - detection.videoshield.videoshield_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for detection.videoshield.videoshield_detection

-import torch
-import numpy as np
-from typing import Dict, Optional, Tuple, Union
-from detection.base import BaseDetector
-from Crypto.Cipher import ChaCha20
-from scipy.stats import truncnorm, norm
-from functools import reduce
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-
-[docs] -class VideoShieldDetector(BaseDetector): - """VideoShield watermark detector class.""" - -
-[docs] - def __init__(self, - watermark: torch.Tensor, - threshold: float, - device: torch.device, - chacha_key: Optional[bytes] = None, - chacha_nonce: Optional[bytes] = None, - height: int = 64, - width: int = 64, - num_frames: int = 0, - k_f: int = 8, - k_c: int = 1, - k_h: int = 4, - k_w: int = 4) -> None: - """Initialize the VideoShield detector. - - Args: - watermark: The watermarking bits - threshold: Threshold for watermark detection - device: The device to use for computation - chacha_key: ChaCha20 encryption key (optional) - chacha_nonce: ChaCha20 nonce (optional) - height: Height of the video - width: Width of the video - num_frames: Number of frames in the video - k_f: Frame repetition factor - k_c: Channel repetition factor - k_h: Height repetition factor - k_w: Width repetition factor - """ - super().__init__(threshold, device) - self.watermark = watermark.to(device) - self.chacha_key = chacha_key - self.chacha_nonce = chacha_nonce - self.num_frames = num_frames - self.height = height - self.width = width - # Repetition factors - self.k_f = k_f - self.k_c = k_c - self.k_h = k_h - self.k_w = k_w - - # Calculate voting threshold - if k_f == 1 and k_c == 1 and k_h == 1 and k_w == 1: - self.vote_threshold = 1 - else: - self.vote_threshold = (k_f * k_c * k_h * k_w) // 2
- - - def _stream_key_decrypt(self, reversed_m: np.ndarray) -> np.ndarray: - """Decrypt the watermark using ChaCha20 cipher.""" - if self.chacha_key is None or self.chacha_nonce is None: - # If no encryption keys provided, return as-is - return reversed_m - - cipher = ChaCha20.new(key=self.chacha_key, nonce=self.chacha_nonce) - sd_byte = cipher.decrypt(np.packbits(reversed_m).tobytes()) - sd_bit = np.unpackbits(np.frombuffer(sd_byte, dtype=np.uint8)) - - return sd_bit - - def _diffusion_inverse(self, watermark_r: torch.Tensor, is_video: bool = False) -> torch.Tensor: - """Inverse the diffusion process to extract the watermark through voting. - - Args: - watermark_r: The reversed watermark tensor - is_video: Whether this is video (5D) or image (4D) data - - Returns: - Extracted watermark through voting - """ - if is_video and watermark_r.dim() == 5: - return self._video_diffusion_inverse(watermark_r) - else: - return self._image_diffusion_inverse(watermark_r) - - def _video_diffusion_inverse(self, watermark_r: torch.Tensor) -> torch.Tensor: - """Video-specific diffusion inverse with frame dimension handling.""" - batch, channels, frames, height, width = watermark_r.shape - - expected_frames = getattr(self, "num_frames", 0) - frames_to_use = frames - - if expected_frames: - if frames != expected_frames: - logger.warning( - "Frame count mismatch detected: received %s frames, expected %s frames.", - frames, - expected_frames, - ) - frames_to_use = min(frames, expected_frames) - logger.info("Truncated to the first %d frames for detection.", frames_to_use) - - if frames_to_use != frames: - watermark_r = watermark_r[:, :, :frames_to_use, :, :] - frames = frames_to_use - - if frames < self.k_f: - logger.error( - "VideoShield detector cannot process %s frames with repetition factor %s.", - frames, - self.k_f, - ) - return torch.zeros_like(self.watermark) - - remainder = frames % self.k_f - if remainder: - aligned_frames = frames - remainder - if aligned_frames <= 0: - logger.error( - "Unable to align frame count (%s) with repetition factor %s.", - frames, - self.k_f, - ) - return torch.zeros_like(self.watermark) - logger.info( - "Aligning detection frames to %s frames to satisfy repetition factor %s.", - self.k_f, - aligned_frames, - ) - watermark_r = watermark_r[:, :, :aligned_frames, :, :] - frames = aligned_frames - - ch_stride = channels // self.k_c - frame_stride = frames // self.k_f - h_stride = height // self.k_h - w_stride = width // self.k_w - - if not all([ch_stride, frame_stride, h_stride, w_stride]): - logger.error( - "Invalid strides detected (c:%s, f:%s, h:%s, w:%s).", - ch_stride, - frame_stride, - h_stride, - w_stride, - ) - return torch.zeros_like(self.watermark) - - ch_list = [ch_stride] * self.k_c - frame_list = [frame_stride] * self.k_f - h_list = [h_stride] * self.k_h - w_list = [w_stride] * self.k_w - - try: - split_dim1 = torch.cat(torch.split(watermark_r, tuple(ch_list), dim=1), dim=0) - split_dim2 = torch.cat(torch.split(split_dim1, tuple(frame_list), dim=2), dim=0) - split_dim3 = torch.cat(torch.split(split_dim2, tuple(h_list), dim=3), dim=0) - split_dim4 = torch.cat(torch.split(split_dim3, tuple(w_list), dim=4), dim=0) - - vote = torch.sum(split_dim4, dim=0).clone() - vote[vote <= self.vote_threshold] = 0 - vote[vote > self.vote_threshold] = 1 - - return vote - except Exception as e: - logger.error(f"Video diffusion inverse failed: {e}") - return torch.zeros_like(self.watermark) - - def _image_diffusion_inverse(self, watermark_r: torch.Tensor) -> torch.Tensor: - """Image-specific diffusion inverse.""" - # Handle both 4D and 5D tensors by squeezing if needed - if watermark_r.dim() == 5: - watermark_r = watermark_r.squeeze(2) # Remove frame dimension - - batch, channels, height, width = watermark_r.shape - - ch_stride = channels // self.k_c - h_stride = height // self.k_h - w_stride = width // self.k_w - - ch_list = [ch_stride] * self.k_c - h_list = [h_stride] * self.k_h - w_list = [w_stride] * self.k_w - - try: - split_dim1 = torch.cat(torch.split(watermark_r, tuple(ch_list), dim=1), dim=0) - split_dim2 = torch.cat(torch.split(split_dim1, tuple(h_list), dim=2), dim=0) - split_dim3 = torch.cat(torch.split(split_dim2, tuple(w_list), dim=3), dim=0) - - vote = torch.sum(split_dim3, dim=0).clone() - vote[vote <= self.vote_threshold] = 0 - vote[vote > self.vote_threshold] = 1 - - return vote - except Exception as e: - logger.error(f"Image diffusion inverse failed: {e}") - # Return a fallback result - return torch.zeros_like(self.watermark) - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - detector_type: str = "bit_acc") -> Dict[str, Union[bool, float]]: - """Evaluate the watermark in the reversed latents. - - Args: - reversed_latents: The reversed latents from forward diffusion - detector_type: The type of detector to use ('bit_acc', 'standard', etc.) - - Returns: - Dict containing detection results and confidence scores - """ - if detector_type not in ['bit_acc']: - raise ValueError(f'Detector type {detector_type} is not supported for VideoShield.') - - # Basic validation - if reversed_latents.numel() == 0: - return {'is_watermarked': False, 'bit_acc': 0.0, 'confidence': 0.0} - - # Convert latents to binary bits - reversed_m = (reversed_latents > 0).int() - - # Decrypt if encryption keys are available - if self.chacha_key is not None and self.chacha_nonce is not None: - reversed_sd = self._stream_key_decrypt(reversed_m.flatten().cpu().numpy()) - else: - # No decryption, use reversed bits directly - reversed_sd = reversed_m - - # Reshape back to tensor format - if reversed_latents.dim() == 5: - # Video case - # [B, C, F, H, W] for T2V model - # [B, F, C, H, W] for I2V model - batch, channels_or_frames, frames_or_channels, height, width = reversed_latents.shape - reversed_sd_tensor = torch.from_numpy(reversed_sd).reshape( - batch, channels_or_frames, frames_or_channels, height, width - ).to(torch.uint8).to(self.device) - else: - # Image case - batch, channels, height, width = reversed_latents.shape - reversed_sd_tensor = torch.from_numpy(reversed_sd).reshape( - batch, channels, height, width - ).to(torch.uint8).to(self.device) - - # Extract watermark through voting - is_video = reversed_latents.dim() == 5 - reversed_watermark = self._diffusion_inverse(reversed_sd_tensor, is_video) - - correct = (reversed_watermark == self.watermark).float().mean().item() - - return { - 'is_watermarked': bool(correct > self.threshold), - 'bit_acc': correct, - }
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/detection/wind/wind_detection.html b/docs/_build/html/_modules/detection/wind/wind_detection.html deleted file mode 100644 index 798e494..0000000 --- a/docs/_build/html/_modules/detection/wind/wind_detection.html +++ /dev/null @@ -1,1057 +0,0 @@ - - - - - - - - detection.wind.wind_detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for detection.wind.wind_detection

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-from detection.base import BaseDetector
-import torch.nn.functional as F
-import numpy as np
-import logging
-from typing import Dict, Any
-
-
-[docs] -class WINDetector(BaseDetector): - """WIND Watermark Detector (Two-Stage Robust Detection)""" - -
-[docs] - def __init__(self, - noise_groups: Dict[int, torch.Tensor], - group_patterns: Dict[int, torch.Tensor], - threshold: float, - device: torch.device, - group_radius: int = 10): - super().__init__(threshold, device) - - self.noise_groups = { - int(g): [noise.to(device) for noise in noise_list] - for g, noise_list in noise_groups.items() - } - - self.group_patterns = { - int(g): pattern.to(device) - for g, pattern in group_patterns.items() - } - - self.group_radius = group_radius - self.device = device - self.group_threshold = 0.7 - self.logger = logging.getLogger(__name__)
- - - - def _circle_mask(self, size: int, r: int) -> torch.Tensor: - """Using circle mask(same with Utils)""" - y, x = torch.meshgrid( - torch.arange(size, device=self.device), - torch.arange(size, device=self.device), - indexing='ij' - ) - center = size // 2 - dist = (x - center)**2 + (y - center)**2 - return ((dist >= (r-2)**2) & (dist <= r**2)).float() - - def _fft_transform(self, latents: torch.Tensor) -> torch.Tensor: - """Convert to Fourier space with shift""" - return torch.fft.fftshift(torch.fft.fft2(latents), dim=(-1, -2)) - - - def _retrieve_group(self, z_fft: torch.Tensor) -> int: - similarities = [] - mask = self._circle_mask(z_fft.shape[-1], self.group_radius) - - for group_id, pattern in self.group_patterns.items(): - try: - z_masked = torch.abs(z_fft) * mask - pattern_masked = torch.abs(pattern) * mask - - sim = F.cosine_similarity( - z_masked.flatten().unsqueeze(0), - pattern_masked.flatten().unsqueeze(0) - ).item() - - similarities.append((group_id, sim)) - except Exception as e: - self.logger.warning(f"Error processing group {group_id}: {str(e)}") - continue - return max(similarities, key=lambda x: x[1])[0] if similarities else -1 - - def _match_noise(self, z: torch.Tensor, group_id: int) -> Dict[str, Any]: - if group_id not in self.noise_groups or group_id == -1: - return {'cosine_similarity': 0.0, 'best_match': None} - - mask = self._circle_mask(z.shape[-1], self.group_radius) - z_fft = torch.fft.fftshift(torch.fft.fft2(z), dim=(-1, -2)) - z_fft = z_fft - self.group_patterns[group_id] * mask - z_cleaned = torch.fft.ifft2(torch.fft.ifftshift(z_fft)).real - - max_sim = -1.0 - best_noise = None - - for candidate in self.noise_groups[group_id]: - sim = F.cosine_similarity( - z_cleaned.flatten().unsqueeze(0), - candidate.flatten().unsqueeze(0) - ).item() - if sim > max_sim: - max_sim = sim - best_noise = candidate - - return { - 'cosine_similarity': max(max_sim, 0.0), - 'best_match': best_noise - } - -
-[docs] - def eval_watermark(self, - reversed_latents: torch.Tensor, - reference_latents: torch.Tensor = None, - detector_type: str = "cosine_similarity") -> Dict[str, Any]: - """ - Two-stage watermark detection - - Args: - reversed_latents: Latents obtained through reverse diffusion [C,H,W] - reference_latents: Not used (for API compatibility) - detector_type: Detection method ('cosine_similarity' only supported) - - Returns: - Dictionary containing detection results: - - group_id: Identified group ID - - similarity: Highest similarity score - - is_watermarked: Detection result - - best_match: Best matching noise tensor - """ - if detector_type != "cosine_similarity": - raise ValueError(f"WIND detector only supports 'cosine' method, got {detector_type}") - - try: - # Input validation - if not isinstance(reversed_latents, torch.Tensor): - reversed_latents = torch.tensor(reversed_latents, device=self.device) - reversed_latents = reversed_latents.to(self.device) - - # Stage 1: Group identification - z_fft = self._fft_transform(reversed_latents) - group_id = self._retrieve_group(z_fft) - - # Stage 2: Noise matching - match_result = self._match_noise(reversed_latents, group_id) - - return { - 'group_id': group_id, - 'cosine_similarity': match_result['cosine_similarity'], - 'is_watermarked': bool(match_result['cosine_similarity'] > self.threshold), - #'best_match': match_result['best_match'] - } - - except Exception as e: - self.logger.error(f"Detection failed: {str(e)}") - return { - 'group_id': -1, - 'cosine_similarity': 0.0, - 'is_watermarked': False, - # 'best_match': None - }
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/dataset.html b/docs/_build/html/_modules/evaluation/dataset.html deleted file mode 100644 index d067279..0000000 --- a/docs/_build/html/_modules/evaluation/dataset.html +++ /dev/null @@ -1,1141 +0,0 @@ - - - - - - - - evaluation.dataset — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for evaluation.dataset

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import ujson as json
-from datasets import load_dataset
-import pandas as pd
-from PIL import Image
-import requests
-from io import BytesIO
-from tqdm import tqdm
-import random
-from typing import List
-
-
-[docs] -class BaseDataset: - """Base dataset class.""" - -
-[docs] - def __init__(self, max_samples: int = 200): - """Initialize the dataset. - - Parameters: - max_samples: Maximum number of samples to load. - """ - self.max_samples = max_samples - self.prompts = [] - self.references = []
- - - @property - def num_samples(self) -> int: - """Number of samples in the dataset.""" - return len(self.prompts) - - @property - def num_references(self) -> int: - """Number of references in the dataset.""" - return len(self.references) - -
-[docs] - def get_prompt(self, idx) -> str: - """Get the prompt at the given index.""" - return self.prompts[idx]
- - -
-[docs] - def get_reference(self, idx) -> Image.Image: - """Get the reference Image at the given index.""" - return self.references[idx]
- - -
-[docs] - def __len__(self) -> int: - """Number of samples in the dataset.(Equivalent to num_samples)""" - return self.num_samples
- - -
-[docs] - def __getitem__(self, idx) -> tuple[str, Image.Image]: - """Get the prompt (and reference Image if available) at the given index.""" - if len(self.references) == 0: - return self.prompts[idx] - else: - return self.prompts[idx], self.references[idx]
- - - def _load_data(self): - """Load data from the dataset.""" - pass
- - - -
-[docs] -class StableDiffusionPromptsDataset(BaseDataset): - """Stable Diffusion prompts dataset.""" - -
-[docs] - def __init__(self, max_samples: int = 200, split: str = "test", shuffle: bool = False): - """Initialize the dataset. - - Parameters: - max_samples: Maximum number of samples to load. - split: Split to load. - shuffle: Whether to shuffle the dataset. - """ - super().__init__(max_samples) - self.split = split - self.shuffle = shuffle - self._load_data()
- - - @property - def name(self): - """Name of the dataset.""" - return "Stable Diffusion Prompts" - - def _load_data(self): - dataset = load_dataset("dataset/stable_diffusion_prompts", split=self.split) - if self.shuffle: - dataset = dataset.shuffle() - for prompt in dataset["Prompt"][:self.max_samples]: - self.prompts.append(prompt)
- - -
-[docs] -class MSCOCODataset(BaseDataset): - """MSCOCO 2017 dataset.""" - -
-[docs] - def __init__(self, max_samples: int = 200, shuffle: bool = False): - """Initialize the dataset. - - Parameters: - max_samples: Maximum number of samples to load. - shuffle: Whether to shuffle the dataset. - """ - super().__init__(max_samples) - self.shuffle = shuffle - self._load_data()
- - - @property - def name(self): - """Name of the dataset.""" - return "MS-COCO 2017" - - def _load_image_from_url(self, url): - """Load image from url.""" - try: - response = requests.get(url) - response.raise_for_status() - image = Image.open(BytesIO(response.content)) - return image - except Exception as e: - print(f"Load image from url failed: {e}") - return None - - def _load_data(self): - """Load data from the MSCOCO 2017 dataset.""" - df = pd.read_parquet("dataset/mscoco/mscoco.parquet") - if self.shuffle: - df = df.sample(frac=1).reset_index(drop=True) - for i in tqdm(range(self.max_samples), desc="Loading MSCOCO dataset"): - item = df.iloc[i] - self.prompts.append(item['TEXT']) - self.references.append(self._load_image_from_url(item['URL']))
- - -
-[docs] -class VBenchDataset(BaseDataset): - """VBench dataset.""" - -
-[docs] - def __init__(self, max_samples: int, dimension: str = "subject_consistency", shuffle: bool = False): - """Initialize the dataset. - - Args: - max_samples (int): Maximum number of samples to load. - dimension (str, optional): Dimensions to load. Selected from "subject_consistency", "background_consistency", "imaging_quality", "motion_smoothness", "dynamic_degree". - shuffle (bool, optional): Whether to shuffle the dataset. Defaults to False. - """ - super().__init__(max_samples) - self.shuffle = shuffle - self.dimension = dimension - self._load_data()
- - - @property - def name(self): - """Name of the dataset.""" - return "VBench" - - def _load_data(self): - """Load data from the VBench dataset.""" - with open(f"dataset/vbench/prompts_per_dimension/{self.dimension}.txt", "r") as f: - prompts = [line.strip() for line in f.readlines()] - if self.shuffle: - random.shuffle(prompts) - self.prompts.extend(prompts[:self.max_samples])
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/pipelines/detection.html b/docs/_build/html/_modules/evaluation/pipelines/detection.html deleted file mode 100644 index 604f353..0000000 --- a/docs/_build/html/_modules/evaluation/pipelines/detection.html +++ /dev/null @@ -1,1123 +0,0 @@ - - - - - - - - evaluation.pipelines.detection — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.pipelines.detection

-from watermark.base import BaseWatermark
-from evaluation.dataset import BaseDataset
-from tqdm import tqdm
-from enum import Enum, auto
-from PIL import Image
-from evaluation.tools.image_editor import ImageEditor
-from evaluation.tools.video_editor import VideoEditor
-from typing import List, Union
-
-
-[docs] -class DetectionPipelineReturnType(Enum): - FULL = auto() - SCORES = auto() - IS_WATERMARKED = auto()
- - -
-[docs] -class WatermarkDetectionResult: - -
-[docs] - def __init__(self, - generated_or_retrieved_media, - edited_media, - detect_result) -> None: - self.generated_or_retrieved_media = generated_or_retrieved_media - self.edited_media = edited_media - self.detect_result = detect_result - pass
- - - def __str__(self): - return f"WatermarkDetectionResult(generated_or_retrieved_media={self.generated_or_retrieved_media}, edited_media={self.edited_media}, detect_result={self.detect_result})"
- - -
-[docs] -class WatermarkDetectionPipeline: -
-[docs] - def __init__(self, - dataset: BaseDataset, - media_editor_list: List[Union[ImageEditor, VideoEditor]] = [], - show_progress: bool = True, - detector_type: str = "l1_distance", - return_type: DetectionPipelineReturnType = DetectionPipelineReturnType.SCORES): - self.dataset = dataset - self.media_editor_list = media_editor_list - self.show_progress = show_progress - self.return_type = return_type - self.detector_type = detector_type
- - - def _edit_media(self, media: List[Image.Image]) -> List[Image.Image]: - """ - Edit the media using the media editor list. - - Args: - media (List[Image.Image]): The media to edit. - - Raises: - ValueError: If the editor type is not supported. - - Returns: - List[Image.Image]: The edited media. - """ - results = media - - for editor in self.media_editor_list: - if isinstance(editor, ImageEditor): - for i in range(len(results)): - results[i] = editor.edit(results[i]) # return single edited image - elif isinstance(editor, VideoEditor): - results = editor.edit(results) # return a list of edited videos - else: - raise ValueError(f"Invalid media type: {type(media)}") - - return results - - def _detect_watermark(self, media:List[Image.Image], watermark: BaseWatermark, **kwargs): - detect_result = watermark.detect_watermark_in_media(media, detector_type=self.detector_type, **kwargs) - print(detect_result) - return detect_result - - def _get_iterable(self): - pass - - def _get_progress_bar(self, iterable): - if self.show_progress: - return tqdm(iterable, desc="Processing") - return iterable - - def _generate_or_retrieve_media(self, index: int, watermark: BaseWatermark, **kwargs) -> List[Image.Image]: - pass - -
-[docs] - def evaluate(self, watermark: BaseWatermark, detection_kwargs={}, generation_kwargs={}): - evaluation_results = [] - bar = self._get_progress_bar(self._get_iterable()) - - for index in bar: - generated_or_retrieved_media = self._generate_or_retrieve_media(index, watermark,**generation_kwargs) - edited_media = self._edit_media(generated_or_retrieved_media) - - detect_result = self._detect_watermark(edited_media, watermark, **detection_kwargs) - evaluation_results.append(WatermarkDetectionResult(generated_or_retrieved_media, edited_media, detect_result)) - - if self.return_type == DetectionPipelineReturnType.FULL: - return evaluation_results - elif self.return_type == DetectionPipelineReturnType.SCORES: - return [result.detect_result[self.detector_type] for result in evaluation_results] - elif self.return_type == DetectionPipelineReturnType.IS_WATERMARKED: - return [result.detect_result['is_watermarked'] for result in evaluation_results]
-
- - -
-[docs] -class WatermarkedMediaDetectionPipeline(WatermarkDetectionPipeline): -
-[docs] - def __init__(self, dataset: BaseDataset, media_editor_list: List[Union[ImageEditor, VideoEditor]], - show_progress: bool = True, - detector_type: str = "l1_distance", - return_type: DetectionPipelineReturnType = DetectionPipelineReturnType.SCORES, - *args, **kwargs): - super().__init__(dataset, media_editor_list, show_progress, detector_type, return_type, *args, **kwargs)
- - - def _get_iterable(self): - return range(self.dataset.num_samples) - - def _generate_or_retrieve_media(self, index: int, watermark: BaseWatermark, **generation_kwargs): - prompt = self.dataset.get_prompt(index) - generated_media = watermark.generate_watermarked_media(input_data=prompt, **generation_kwargs) - if isinstance(generated_media, Image.Image): - return [generated_media] - elif isinstance(generated_media, list): - return generated_media - else: - raise ValueError(f"Invalid media type: {type(generated_media)}")
- - -
-[docs] -class UnWatermarkedMediaDetectionPipeline(WatermarkDetectionPipeline): -
-[docs] - def __init__(self, dataset: BaseDataset, media_editor_list: List[Union[ImageEditor, VideoEditor]], media_source_mode : str ="ground_truth", - show_progress: bool = True, - detector_type: str = "l1_distance", - return_type: DetectionPipelineReturnType = DetectionPipelineReturnType.SCORES, - *args, **kwargs): - super().__init__(dataset, media_editor_list, show_progress, detector_type, return_type, *args, **kwargs) - self.media_source_mode = media_source_mode
- - - def _get_iterable(self): - if self.media_source_mode == "ground_truth": - assert self.dataset.num_references != 0, "This dataset does not have ground truth images or videos" - return range(self.dataset.num_references) - elif self.media_source_mode == "generated": - return range(self.dataset.num_samples) - else: - raise ValueError(f"Invalid media source mode: {self.media_source_mode}") - - def _generate_or_retrieve_media(self, index: int, watermark: BaseWatermark, **generation_kwargs): - if self.media_source_mode == "ground_truth": - return [self.dataset.get_reference(index)] - elif self.media_source_mode == "generated": - prompt = self.dataset.get_prompt(index) - generated_media = watermark.generate_unwatermarked_media(input_data=prompt, **generation_kwargs) - if isinstance(generated_media, Image.Image): - return [generated_media] - elif isinstance(generated_media, list): - return generated_media - else: - raise ValueError(f"Invalid media type: {type(generated_media)}") - else: - raise ValueError(f"Invalid media source mode: {self.media_source_mode}")
- - - - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/pipelines/image_quality_analysis.html b/docs/_build/html/_modules/evaluation/pipelines/image_quality_analysis.html deleted file mode 100644 index e6282ba..0000000 --- a/docs/_build/html/_modules/evaluation/pipelines/image_quality_analysis.html +++ /dev/null @@ -1,1635 +0,0 @@ - - - - - - - - evaluation.pipelines.image_quality_analysis — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.pipelines.image_quality_analysis

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from watermark.base import BaseWatermark
-from evaluation.dataset import BaseDataset
-from tqdm import tqdm
-from enum import Enum, auto
-from PIL import Image
-from evaluation.tools.image_editor import ImageEditor
-from typing import List, Dict, Union, Tuple, Any, Optional
-import numpy as np
-from dataclasses import dataclass, field
-import os
-import random
-from evaluation.tools.image_quality_analyzer import (
-    ImageQualityAnalyzer
-)
-import lpips
-
-
-[docs] -class QualityPipelineReturnType(Enum): - """Return type of the image quality analysis pipeline.""" - FULL = auto() - SCORES = auto() - MEAN_SCORES = auto()
- - -
-[docs] -@dataclass -class DatasetForEvaluation: - """Dataset for evaluation.""" - watermarked_images: List[Union[Image.Image, List[Image.Image]]] = field(default_factory=list) - unwatermarked_images: List[Union[Image.Image, List[Image.Image]]] = field(default_factory=list) - reference_images: List[Image.Image] = field(default_factory=list) - indexes: List[int] = field(default_factory=list) - prompts: List[str] = field(default_factory=list)
- - -
-[docs] -class QualityComparisonResult: - """Result of image quality comparison.""" - -
-[docs] - def __init__(self, - store_path: str, - watermarked_quality_scores: Dict[str, List[float]], - unwatermarked_quality_scores: Dict[str, List[float]], - prompts: List[str], - ) -> None: - """ - Initialize the image quality comparison result. - - Parameters: - store_path: The path to store the results. - watermarked_quality_scores: The quality scores of the watermarked image. - unwatermarked_quality_scores: The quality scores of the unwatermarked image. - prompts: The prompts used to generate the images. - """ - self.store_path = store_path - self.watermarked_quality_scores = watermarked_quality_scores - self.unwatermarked_quality_scores = unwatermarked_quality_scores - self.prompts = prompts
-
- - - -
-[docs] -class ImageQualityAnalysisPipeline: - """Pipeline for image quality analysis.""" - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - """ - Initialize the image quality analysis pipeline. - - Parameters: - dataset: The dataset for evaluation. - watermarked_image_editor_list: The list of image editors for watermarked images. - unwatermarked_image_editor_list: The list of image editors for unwatermarked images. - analyzers: List of quality analyzers for images. - unwatermarked_image_source: The source of unwatermarked images ('natural' or 'generated'). - reference_image_source: The source of reference images ('natural' or 'generated'). - show_progress: Whether to show progress. - store_path: The path to store the results. If None, the generated images will not be stored. - return_type: The return type of the pipeline. - """ - if unwatermarked_image_source not in ['natural', 'generated']: - raise ValueError(f"Invalid unwatermarked_image_source: {unwatermarked_image_source}") - - self.dataset = dataset - self.watermarked_image_editor_list = watermarked_image_editor_list - self.unwatermarked_image_editor_list = unwatermarked_image_editor_list - self.analyzers = analyzers or [] - self.unwatermarked_image_source = unwatermarked_image_source - self.reference_image_source = reference_image_source - self.show_progress = show_progress - self.store_path = store_path - self.return_type = return_type
- - - def _check_compatibility(self): - """Check if the pipeline is compatible with the dataset.""" - pass - - def _get_iterable(self): - """Return an iterable for the dataset.""" - pass - - def _get_progress_bar(self, iterable): - """Return an iterable possibly wrapped with a progress bar.""" - if self.show_progress: - return tqdm(iterable, desc="Processing", leave=True) - return iterable - - def _get_prompt(self, index: int) -> str: - """Get prompt from dataset.""" - return self.dataset.get_prompt(index) - - def _get_watermarked_image(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> Union[Image.Image, List[Image.Image]]: - """Generate watermarked image from dataset.""" - prompt = self._get_prompt(index) - image = watermark.generate_watermarked_media(input_data=prompt, **generation_kwargs) - return image - - def _get_unwatermarked_image(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> Union[Image.Image, List[Image.Image]]: - """Generate or retrieve unwatermarked image from dataset.""" - if self.unwatermarked_image_source == 'natural': - return self.dataset.get_reference(index) - elif self.unwatermarked_image_source == 'generated': - prompt = self._get_prompt(index) - image = watermark.generate_unwatermarked_media(input_data=prompt, **generation_kwargs) - return image - - def _edit_watermarked_image(self, image: Union[Image.Image, List[Image.Image]]) -> Union[Image.Image, List[Image.Image]]: - """Edit watermarked image using image editors.""" - if isinstance(image, list): - edited_images = [] - for img in image: - for image_editor in self.watermarked_image_editor_list: - img = image_editor.edit(img) - edited_images.append(img) - return edited_images - else: - for image_editor in self.watermarked_image_editor_list: - image = image_editor.edit(image) - return image - - def _edit_unwatermarked_image(self, image: Union[Image.Image, List[Image.Image]]) -> Union[Image.Image, List[Image.Image]]: - """Edit unwatermarked image using image editors.""" - if isinstance(image, list): - edited_images = [] - for img in image: - for image_editor in self.unwatermarked_image_editor_list: - img = image_editor.edit(img) - edited_images.append(img) - return edited_images - else: - for image_editor in self.unwatermarked_image_editor_list: - image = image_editor.edit(image) - return image - - def _prepare_dataset(self, watermark: BaseWatermark, **generation_kwargs) -> DatasetForEvaluation: - """ - Prepare and generate all necessary data for quality analysis. - - This method should be overridden by subclasses to implement specific - data preparation logic based on the analysis requirements. - - Parameters: - watermark: The watermark algorithm instance. - generation_kwargs: Additional generation parameters. - - Returns: - DatasetForEvaluation object containing all prepared data. - """ - dataset_eval = DatasetForEvaluation() - - # Generate all images - bar = self._get_progress_bar(self._get_iterable()) - bar.set_description("Generating images for quality analysis") - for index in bar: - # Generate and edit watermarked image - watermarked_image = self._get_watermarked_image(watermark, index, **generation_kwargs) - watermarked_image = self._edit_watermarked_image(watermarked_image) - - # Generate and edit unwatermarked image - unwatermarked_image = self._get_unwatermarked_image(watermark, index, **generation_kwargs) - unwatermarked_image = self._edit_unwatermarked_image(unwatermarked_image) - - dataset_eval.watermarked_images.append(watermarked_image) - dataset_eval.unwatermarked_images.append(unwatermarked_image) - if hasattr(self, "prompt_per_image"): - index = index // self.prompt_per_image - dataset_eval.indexes.append(index) - dataset_eval.prompts.append(self._get_prompt(index)) - - if self.reference_image_source == 'natural': - if self.dataset.num_references > 0: - reference_image = self.dataset.get_reference(index) - dataset_eval.reference_images.append(reference_image) - else: - # For text-based analyzers, add None placeholder - dataset_eval.reference_images.append(None) - else: - dataset_eval.reference_images.append(unwatermarked_image) - - return dataset_eval - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """ - Prepare input for quality analyzer. - - Parameters: - prepared_dataset: The prepared dataset. - """ - pass - - def _store_results(self, prepared_dataset: DatasetForEvaluation): - """Store results.""" - os.makedirs(self.store_path, exist_ok=True) - dataset_name = self.dataset.name - - for (index, watermarked_image, unwatermarked_image, prompt) in zip(prepared_dataset.indexes, prepared_dataset.watermarked_images, prepared_dataset.unwatermarked_images, prepared_dataset.prompts): - watermarked_image.save(os.path.join(self.store_path, f"{self.__class__.__name__}_{dataset_name}_watermarked_prompt_{index}.png")) - unwatermarked_image.save(os.path.join(self.store_path, f"{self.__class__.__name__}_{dataset_name}_unwatermarked_prompt_{index}.png")) - -
-[docs] - def analyze_quality(self, prepared_data, analyzer): - """Analyze quality of watermarked and unwatermarked images.""" - pass
- - -
-[docs] - def evaluate(self, watermark: BaseWatermark, generation_kwargs={}): - """Conduct evaluation utilizing the pipeline.""" - # Check compatibility - self._check_compatibility() - print(self.store_path) - - # Prepare dataset - prepared_dataset = self._prepare_dataset(watermark, **generation_kwargs) - - # Store results - if self.store_path: - self._store_results(prepared_dataset) - - # Prepare input for quality analyzer - prepared_data = self._prepare_input_for_quality_analyzer( - prepared_dataset - ) - - # Analyze quality - all_scores = {} - for analyzer in self.analyzers: - w_scores, u_scores = self.analyze_quality(prepared_data, analyzer) - analyzer_name = analyzer.__class__.__name__ - all_scores[analyzer_name] = (w_scores, u_scores) - - # Get prompts and indexes - prompts = [] - - # For other pipelines - for idx in prepared_dataset.indexes: - prompts.append(self._get_prompt(idx)) - - # Create result - watermarked_scores = {} - unwatermarked_scores = {} - - for analyzer_name, (w_scores, u_scores) in all_scores.items(): - watermarked_scores[analyzer_name] = w_scores - unwatermarked_scores[analyzer_name] = u_scores - - result = QualityComparisonResult( - store_path=self.store_path, - watermarked_quality_scores=watermarked_scores, - unwatermarked_quality_scores=unwatermarked_scores, - prompts=prompts, - ) - - # Format results based on return_type - return self._format_results(result)
- - - def _format_results(self, result: QualityComparisonResult): - """Format results based on return_type.""" - if self.return_type == QualityPipelineReturnType.FULL: - return result - elif self.return_type == QualityPipelineReturnType.SCORES: - return { - 'watermarked': result.watermarked_quality_scores, - 'unwatermarked': result.unwatermarked_quality_scores, - 'prompts': result.prompt - } - elif self.return_type == QualityPipelineReturnType.MEAN_SCORES: - # Calculate mean scores for each analyzer - mean_watermarked = {} - mean_unwatermarked = {} - - for analyzer_name, scores in result.watermarked_quality_scores.items(): - if isinstance(scores, list) and len(scores) > 0: - mean_watermarked[analyzer_name] = np.mean(scores) - else: - mean_watermarked[analyzer_name] = scores - - for analyzer_name, scores in result.unwatermarked_quality_scores.items(): - if isinstance(scores, list) and len(scores) > 0: - mean_unwatermarked[analyzer_name] = np.mean(scores) - else: - mean_unwatermarked[analyzer_name] = scores - - return { - 'watermarked': mean_watermarked, - 'unwatermarked': mean_unwatermarked - }
- - -
-[docs] -class DirectImageQualityAnalysisPipeline(ImageQualityAnalysisPipeline): - """ - Pipeline for direct image quality analysis. - - This class analyzes the quality of images by directly comparing the characteristics - of watermarked images with unwatermarked images. It evaluates metrics such as PSNR, - SSIM, LPIPS, FID, BRISQUE without the need for any external reference image. - - Use this pipeline to assess the impact of watermarking on image quality directly. - """ - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - - super().__init__(dataset, watermarked_image_editor_list, unwatermarked_image_editor_list, - analyzers, unwatermarked_image_source, reference_image_source, show_progress, store_path, return_type)
- - - def _get_iterable(self): - """Return an iterable for the dataset.""" - return range(self.dataset.num_samples) - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """Prepare input for quality analyzer.""" - return [(watermarked_image, unwatermarked_image) for watermarked_image, unwatermarked_image in zip(prepared_dataset.watermarked_images, prepared_dataset.unwatermarked_images)] - -
-[docs] - def analyze_quality(self, - prepared_data: List[Tuple[Image.Image, Image.Image]], - analyzer: ImageQualityAnalyzer): - """Analyze quality of watermarked and unwatermarked images.""" - bar = self._get_progress_bar(prepared_data) - bar.set_description(f"Analyzing quality for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - # For direct analyzers, we analyze each image independently - for watermarked_image, unwatermarked_image in bar: - # watermarked score - try: - w_score = analyzer.analyze(watermarked_image) - except TypeError: - # analyzer expects a reference -> use unwatermarked_image as reference - w_score = analyzer.analyze(watermarked_image, unwatermarked_image) - # unwatermarked score - try: - u_score = analyzer.analyze(unwatermarked_image) - except TypeError: - u_score = analyzer.analyze(unwatermarked_image, watermarked_image) - w_scores.append(w_score) - u_scores.append(u_score) - - return w_scores, u_scores
-
- - - -
-[docs] -class ReferencedImageQualityAnalysisPipeline(ImageQualityAnalysisPipeline): - """ - Pipeline for referenced image quality analysis. - - This pipeline assesses image quality by comparing both watermarked and unwatermarked - images against a common reference image. It measures the degree of similarity or - deviation from the reference. - - Ideal for scenarios where the impact of watermarking on image quality needs to be - assessed, particularly in relation to specific reference images or ground truth. - """ - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - - super().__init__(dataset, watermarked_image_editor_list, unwatermarked_image_editor_list, - analyzers, unwatermarked_image_source, reference_image_source, show_progress, store_path, return_type)
- - - def _check_compatibility(self): - """Check if the pipeline is compatible with the dataset.""" - # Check if we have analyzers that use text as reference - has_text_analyzer = any(hasattr(analyzer, 'reference_source') and analyzer.reference_source == 'text' - for analyzer in self.analyzers) - - # If all analyzers use text reference, we don't need reference images - if not has_text_analyzer and self.dataset.num_references == 0: - raise ValueError(f"Reference images are required for referenced image quality analysis. Dataset {self.dataset.name} has no reference images.") - - def _get_iterable(self): - """Return an iterable for the dataset.""" - return range(self.dataset.num_samples) - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """Prepare input for quality analyzer.""" - return [(watermarked_image, unwatermarked_image, reference_image, prompt) - for watermarked_image, unwatermarked_image, reference_image, prompt in - zip(prepared_dataset.watermarked_images, prepared_dataset.unwatermarked_images, prepared_dataset.reference_images, prepared_dataset.prompts) - ] - -
-[docs] - def analyze_quality(self, - prepared_data: List[Tuple[Image.Image, Image.Image, Image.Image, str]], - analyzer: ImageQualityAnalyzer): - """Analyze quality of watermarked and unwatermarked images.""" - bar = self._get_progress_bar(prepared_data) - bar.set_description(f"Analyzing quality for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - # For referenced analyzers, we compare against the reference - for watermarked_image, unwatermarked_image, reference_image, prompt in bar: - if analyzer.reference_source == "image": - w_score = analyzer.analyze(watermarked_image, reference_image) - u_score = analyzer.analyze(unwatermarked_image, reference_image) - elif analyzer.reference_source == "text": - w_score = analyzer.analyze(watermarked_image, prompt) - u_score = analyzer.analyze(unwatermarked_image, prompt) - else: - raise ValueError(f"Invalid reference source: {analyzer.reference_source}") - w_scores.append(w_score) - u_scores.append(u_score) - return w_scores, u_scores
-
- - - -
-[docs] -class GroupImageQualityAnalysisPipeline(ImageQualityAnalysisPipeline): - """ - Pipeline for group-based image quality analysis. - - This pipeline analyzes quality metrics that require comparing distributions - of multiple images (e.g., FID). It generates all images upfront and then - performs a single analysis on the entire collection. - """ - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - - super().__init__(dataset, watermarked_image_editor_list, unwatermarked_image_editor_list, - analyzers, unwatermarked_image_source, reference_image_source, show_progress, store_path, return_type)
- - - def _get_iterable(self): - """Return an iterable for analyzers instead of dataset indices.""" - return range(self.dataset.num_samples) - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """Prepare input for group analyzer.""" - return [(prepared_dataset.watermarked_images, prepared_dataset.unwatermarked_images, prepared_dataset.reference_images)] - -
-[docs] - def analyze_quality(self, - prepared_data: List[Tuple[List[Image.Image], List[Image.Image], List[Image.Image]]], - analyzer: ImageQualityAnalyzer): - """Analyze quality of image groups.""" - bar = self._get_progress_bar(prepared_data) - bar.set_description(f"Analyzing quality for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - # For group analyzers, we pass the entire collection - for watermarked_images, unwatermarked_images, reference_images in bar: - w_score = analyzer.analyze(watermarked_images, reference_images) - u_score = analyzer.analyze(unwatermarked_images, reference_images) - w_scores.append(w_score) - u_scores.append(u_score) - return w_scores, u_scores
-
- - - -
-[docs] -class RepeatImageQualityAnalysisPipeline(ImageQualityAnalysisPipeline): - """ - Pipeline for repeat-based image quality analysis. - - This pipeline analyzes diversity metrics by generating multiple images - for each prompt (e.g., LPIPS diversity). It generates multiple versions - per prompt and analyzes the diversity within each group. - """ - -
-[docs] - def __init__(self, - dataset: BaseDataset, - prompt_per_image: int = 20, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - - super().__init__(dataset, watermarked_image_editor_list, unwatermarked_image_editor_list, - analyzers, unwatermarked_image_source, reference_image_source, show_progress, store_path, return_type) - - self.prompt_per_image = prompt_per_image
- - - def _get_iterable(self): - """Return an iterable for the dataset.""" - return range(self.dataset.num_samples * self.prompt_per_image) - - def _get_prompt(self, index: int) -> str: - """Get prompt from dataset.""" - prompt_index = index // self.prompt_per_image - return self.dataset.get_prompt(prompt_index) - - def _get_watermarked_image(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> Union[Image.Image, List[Image.Image]]: - """Get watermarked image.""" - prompt = self._get_prompt(index) - # Randomly select a generation seed - generation_kwargs['gen_seed'] = random.randint(0, 1000000) - return watermark.generate_watermarked_media(input_data=prompt, **generation_kwargs) - - def _get_unwatermarked_image(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> Union[Image.Image, List[Image.Image]]: - """Get unwatermarked image.""" - prompt = self._get_prompt(index) - # Randomly select a generation seed - generation_kwargs['gen_seed'] = random.randint(0, 1000000) - if self.unwatermarked_image_source == 'natural': - return self.dataset.get_reference(index) - else: - return watermark.generate_unwatermarked_media(input_data=prompt, **generation_kwargs) - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """Prepare input for diversity analyzer.""" - # Group images by prompt - watermarked_images = prepared_dataset.watermarked_images - unwatermarked_images = prepared_dataset.unwatermarked_images - - grouped = [] - for i in range(0, len(watermarked_images), self.prompt_per_image): - grouped.append( - (watermarked_images[i:i+self.prompt_per_image], - unwatermarked_images[i:i+self.prompt_per_image]) - ) - return grouped - -
-[docs] - def analyze_quality(self, - prepared_data: List[Tuple[List[Image.Image], List[Image.Image]]], - analyzer: ImageQualityAnalyzer): - """Analyze diversity of image batches.""" - bar = self._get_progress_bar(prepared_data) - bar.set_description(f"Analyzing diversity for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - # For diversity analyzers, we analyze each batch - for watermarked_images, unwatermarked_images in bar: - w_score = analyzer.analyze(watermarked_images) - u_score = analyzer.analyze(unwatermarked_images) - w_scores.append(w_score) - u_scores.append(u_score) - - return w_scores, u_scores
-
- - - -
-[docs] -class ComparedImageQualityAnalysisPipeline(ImageQualityAnalysisPipeline): - """ - Pipeline for compared image quality analysis. - - This pipeline directly compares watermarked and unwatermarked images - to compute metrics like PSNR, SSIM, VIF, FSIM and MS-SSIM. The analyzer receives - both images and outputs a single comparison score. - """ - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_image_editor_list: List[ImageEditor] = [], - unwatermarked_image_editor_list: List[ImageEditor] = [], - analyzers: List[ImageQualityAnalyzer] = None, - unwatermarked_image_source: str = 'generated', - reference_image_source: str = 'natural', - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - - super().__init__(dataset, watermarked_image_editor_list, unwatermarked_image_editor_list, - analyzers, unwatermarked_image_source, reference_image_source, show_progress, store_path, return_type)
- - - def _get_iterable(self): - """Return an iterable for the dataset.""" - return range(self.dataset.num_samples) - - def _prepare_input_for_quality_analyzer(self, - prepared_dataset: DatasetForEvaluation): - """Prepare input for comparison analyzer.""" - return [(watermarked_image, unwatermarked_image) for watermarked_image, unwatermarked_image in zip(prepared_dataset.watermarked_images, prepared_dataset.unwatermarked_images)] - -
-[docs] - def analyze_quality(self, - prepared_data: List[Tuple[Image.Image, Image.Image]], - analyzer: ImageQualityAnalyzer): - """Analyze quality by comparing watermarked and unwatermarked images.""" - bar = self._get_progress_bar(prepared_data) - bar.set_description(f"Analyzing quality for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - # For comparison analyzers, we compute similarity/difference - for watermarked_image, unwatermarked_image in bar: - # Compare watermarked images with unwatermarked images - w_score = analyzer.analyze(watermarked_image, unwatermarked_image) - w_scores.append(w_score) - return w_scores, u_scores # u_scores is not used for comparison analyzers
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/pipelines/video_quality_analysis.html b/docs/_build/html/_modules/evaluation/pipelines/video_quality_analysis.html deleted file mode 100644 index f21ca06..0000000 --- a/docs/_build/html/_modules/evaluation/pipelines/video_quality_analysis.html +++ /dev/null @@ -1,1342 +0,0 @@ - - - - - - - - evaluation.pipelines.video_quality_analysis — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.pipelines.video_quality_analysis

-from enum import Enum, auto
-from dataclasses import dataclass, field
-from typing import List, Union, Dict, Tuple
-from PIL import Image
-from evaluation.tools.image_editor import ImageEditor
-from evaluation.tools.video_editor import VideoEditor
-from evaluation.tools.video_quality_analyzer import VideoQualityAnalyzer
-from evaluation.dataset import BaseDataset
-from watermark.base import BaseWatermark
-import os
-import numpy as np
-from tqdm import tqdm
-
-
-[docs] -class QualityPipelineReturnType(Enum): - """Return type of the image quality analysis pipeline.""" - FULL = auto() - SCORES = auto() - MEAN_SCORES = auto()
- - -
-[docs] -@dataclass -class DatasetForEvaluation: - """Dataset for evaluation.""" - watermarked_videos: List[List[Image.Image]] = field(default_factory=list) - unwatermarked_videos: List[List[Image.Image]] = field(default_factory=list) - reference_videos: List[List[Image.Image]] = field(default_factory=list) - indexes: List[int] = field(default_factory=list)
- - -
-[docs] -class QualityComparisonResult: - """Result of quality comparison.""" - -
-[docs] - def __init__(self, - store_path: str, - watermarked_quality_scores: Dict[str, List[float]], - unwatermarked_quality_scores: Dict[str, List[float]], - prompts: List[str], - ) -> None: - """ - Initialize the image quality comparison result. - - Parameters: - store_path: The path to store the results. - watermarked_quality_scores: The quality scores of the watermarked image. - unwatermarked_quality_scores: The quality scores of the unwatermarked image. - prompts: The prompts used to generate the images. - """ - self.store_path = store_path - self.watermarked_quality_scores = watermarked_quality_scores - self.unwatermarked_quality_scores = unwatermarked_quality_scores - self.prompts = prompts
-
- - - -
-[docs] -class VideoQualityAnalysisPipeline: - """Pipeline for video quality analysis.""" - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_video_editor_list: List[VideoEditor] = [], - unwatermarked_video_editor_list: List[VideoEditor] = [], - watermarked_frame_editor_list: List[ImageEditor] = [], - unwatermarked_frame_editor_list: List[ImageEditor] = [], - analyzers: List[VideoQualityAnalyzer] = None, - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - """Initialize the image quality analysis pipeline. - - Args: - dataset (BaseDataset): The dataset for evaluation. - watermarked_video_editor_list (List[VideoEditor], optional): The list of video editors for watermarked videos. Defaults to []. - unwatermarked_video_editor_list (List[VideoEditor], optional): List of quality analyzers for videos. Defaults to []. - watermarked_frame_editor_list (List[ImageEditor], optional): List of image editors for editing individual watermarked frames. Defaults to []. - unwatermarked_frame_editor_list (List[ImageEditor], optional): List of image editors for editing individual unwatermarked frames. Defaults to []. - analyzers (List[VideoQualityAnalyzer], optional): Whether to show progress. Defaults to None. - show_progress (bool, optional): The path to store the results. Defaults to True. - store_path (str, optional): The path to store the results. Defaults to None. - return_type (QualityPipelineReturnType, optional): The return type of the pipeline. Defaults to QualityPipelineReturnType.MEAN_SCORES. - """ - self.dataset = dataset - self.watermarked_video_editor_list = watermarked_video_editor_list - self.unwatermarked_video_editor_list = unwatermarked_video_editor_list - self.watermarked_frame_editor_list = watermarked_frame_editor_list - self.unwatermarked_frame_editor_list = unwatermarked_frame_editor_list - self.analyzers = analyzers or [] - self.show_progress = show_progress - self.store_path = store_path - self.return_type = return_type
- - - def _check_compatibility(self): - """Check if the pipeline is compatible with the dataset.""" - pass - - def _get_iterable(self): - """Return an iterable for the dataset.""" - pass - - def _get_progress_bar(self, iterable): - """Return an iterable possibly wrapped with a progress bar.""" - if self.show_progress: - return tqdm(iterable, desc="Processing", leave=True) - return iterable - - def _get_prompt(self, index: int) -> str: - """Get prompt from dataset.""" - return self.dataset.get_prompt(index) - - def _get_watermarked_video(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> List[Image.Image]: - """Generate watermarked image from dataset.""" - prompt = self._get_prompt(index) - frames = watermark.generate_watermarked_media(input_data=prompt, **generation_kwargs) - return frames - - def _get_unwatermarked_video(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> List[Image.Image]: - """Generate or retrieve unwatermarked image from dataset.""" - prompt = self._get_prompt(index) - frames = watermark.generate_unwatermarked_media(input_data=prompt, **generation_kwargs) - return frames - - def _edit_watermarked_video(self, frames: List[Image.Image]) -> List[Image.Image]: - """Edit watermarked image using image editors.""" - # Step 1: Edit all frames using video editors - for video_editor in self.watermarked_video_editor_list: - frames = video_editor.edit(frames) - # Step 2: Edit individual frames using image editors - for frame_editor in self.watermarked_frame_editor_list: - frames = [frame_editor.edit(frame) for frame in frames] - return frames - - def _edit_unwatermarked_video(self, frames: List[Image.Image]) -> List[Image.Image]: - """Edit unwatermarked image using image editors.""" - # Step 1: Edit all frames using video editors - for video_editor in self.unwatermarked_video_editor_list: - frames = video_editor.edit(frames) - # Step 2: Edit individual frames using image editors - for frame_editor in self.unwatermarked_frame_editor_list: - frames = [frame_editor.edit(frame) for frame in frames] - return frames - - def _prepare_dataset(self, watermark: BaseWatermark, **generation_kwargs) -> DatasetForEvaluation: - """ - Prepare and generate all necessary data for quality analysis. - - This method should be overridden by subclasses to implement specific - data preparation logic based on the analysis requirements. - - Parameters: - watermark: The watermark algorithm instance. - generation_kwargs: Additional generation parameters. - - Returns: - DatasetForEvaluation object containing all prepared data. - """ - dataset_eval = DatasetForEvaluation() - - # Generate all videos - bar = self._get_progress_bar(self._get_iterable()) - bar.set_description("Generating videos for quality analysis") - for index in bar: - # Generate and edit watermarked image - watermarked_frames = self._get_watermarked_video(watermark, index, **generation_kwargs) - watermarked_frames = self._edit_watermarked_video(watermarked_frames) - - # Generate and edit unwatermarked image - unwatermarked_frames = self._get_unwatermarked_video(watermark, index, **generation_kwargs) - unwatermarked_frames = self._edit_unwatermarked_video(unwatermarked_frames) - - dataset_eval.watermarked_videos.append(watermarked_frames) - dataset_eval.unwatermarked_videos.append(unwatermarked_frames) - dataset_eval.indexes.append(index) - - if self.dataset.num_references > 0: - reference_frames = self.dataset.get_reference(index) - dataset_eval.reference_videos.append(reference_frames) - - return dataset_eval - - def _prepare_input_for_quality_analyzer(self, - watermarked_videos: List[List[Image.Image]], - unwatermarked_videos: List[List[Image.Image]], - reference_videos: List[List[Image.Image]]): - """ Prepare input for quality analyzer. - - Args: - watermarked_videos (List[List[Image.Image]]): Watermarked video(s) - unwatermarked_videos (List[List[Image.Image]]): Unwatermarked video(s) - reference_videos (List[List[Image.Image]]): Reference video if available - """ - pass - - def _store_results(self, prepared_dataset: DatasetForEvaluation): - """Store results.""" - os.makedirs(self.store_path, exist_ok=True) - dataset_name = self.dataset.name - - for (index, watermarked_video, unwatermarked_video) in zip(prepared_dataset.indexes, prepared_dataset.watermarked_videos, prepared_dataset.unwatermarked_videos): - # unwatermarked/watermarked_video is List[Image.Image], so first make a video from the frames - save_dir = os.path.join(self.store_path, f"{self.__class__.__name__}_{dataset_name}_watermarked_prompt{index}") - os.makedirs(save_dir, exist_ok=True) - for i, frame in enumerate(watermarked_video): - frame.save(os.path.join(save_dir, f"frame_{i}.png")) - - save_dir = os.path.join(self.store_path, f"{self.__class__.__name__}_{dataset_name}_unwatermarked_prompt{index}") - os.makedirs(save_dir, exist_ok=True) - for i, frame in enumerate(unwatermarked_video): - frame.save(os.path.join(save_dir, f"frame_{i}.png")) - - if self.dataset.num_references > 0: - reference_frames = self.dataset.get_reference(index) - save_dir = os.path.join(self.store_path, f"{self.__class__.__name__}_{dataset_name}_reference_prompt{index}") - os.makedirs(save_dir, exist_ok=True) - for i, frame in enumerate(reference_frames): - frame.save(os.path.join(save_dir, f"frame_{i}.png")) - -
-[docs] - def analyze_quality(self, prepared_data, analyzer): - """Analyze quality of watermarked and unwatermarked images.""" - pass
- - -
-[docs] - def evaluate(self, watermark: BaseWatermark, generation_kwargs={}): - """Conduct evaluation utilizing the pipeline.""" - # Check compatibility - self._check_compatibility() - - # Prepare dataset - prepared_dataset = self._prepare_dataset(watermark, **generation_kwargs) - - # Store results - if self.store_path: - self._store_results(prepared_dataset) - - # Prepare input for quality analyzer - prepared_data = self._prepare_input_for_quality_analyzer( - prepared_dataset.watermarked_videos, - prepared_dataset.unwatermarked_videos, - prepared_dataset.reference_videos - ) - - # Analyze quality - all_scores = {} - for analyzer in self.analyzers: - w_scores, u_scores = self.analyze_quality(prepared_data, analyzer) - analyzer_name = analyzer.__class__.__name__ - all_scores[analyzer_name] = (w_scores, u_scores) - - # Get prompts and indexes - prompts = [] - - # For other pipelines - for idx in prepared_dataset.indexes: - prompts.append(self._get_prompt(idx)) - - # Create result - watermarked_scores = {} - unwatermarked_scores = {} - - for analyzer_name, (w_scores, u_scores) in all_scores.items(): - watermarked_scores[analyzer_name] = w_scores - unwatermarked_scores[analyzer_name] = u_scores - - result = QualityComparisonResult( - store_path=self.store_path, - watermarked_quality_scores=watermarked_scores, - unwatermarked_quality_scores=unwatermarked_scores, - prompts=prompts, - ) - - # Format results based on return_type - return self._format_results(result)
- - - def _format_results(self, result: QualityComparisonResult): - """Format results based on return_type.""" - if self.return_type == QualityPipelineReturnType.FULL: - return result - elif self.return_type == QualityPipelineReturnType.SCORES: - return { - 'watermarked': result.watermarked_quality_scores, - 'unwatermarked': result.unwatermarked_quality_scores, - 'prompts': result.prompts - } - elif self.return_type == QualityPipelineReturnType.MEAN_SCORES: - # Calculate mean scores for each analyzer - mean_watermarked = {} - mean_unwatermarked = {} - - for analyzer_name, scores in result.watermarked_quality_scores.items(): - if isinstance(scores, list) and len(scores) > 0: - mean_watermarked[analyzer_name] = np.mean(scores) - else: - mean_watermarked[analyzer_name] = scores - - for analyzer_name, scores in result.unwatermarked_quality_scores.items(): - if isinstance(scores, list) and len(scores) > 0: - mean_unwatermarked[analyzer_name] = np.mean(scores) - else: - mean_unwatermarked[analyzer_name] = scores - - return { - 'watermarked': mean_watermarked, - 'unwatermarked': mean_unwatermarked - }
- - -
-[docs] -class DirectVideoQualityAnalysisPipeline(VideoQualityAnalysisPipeline): - """Pipeline for direct video quality analysis.""" - -
-[docs] - def __init__(self, - dataset: BaseDataset, - watermarked_video_editor_list: List[VideoEditor] = [], - unwatermarked_video_editor_list: List[VideoEditor] = [], - watermarked_frame_editor_list: List[ImageEditor] = [], - unwatermarked_frame_editor_list: List[ImageEditor] = [], - analyzers: List[VideoQualityAnalyzer] = None, - show_progress: bool = True, - store_path: str = None, - return_type: QualityPipelineReturnType = QualityPipelineReturnType.MEAN_SCORES) -> None: - """Initialize the video quality analysis pipeline. - - Args: - dataset (BaseDataset): The dataset for evaluation. - watermarked_video_editor_list (List[VideoEditor], optional): The list of video editors for watermarked videos. Defaults to []. - unwatermarked_video_editor_list (List[VideoEditor], optional): List of quality analyzers for videos. Defaults to []. - watermarked_frame_editor_list (List[ImageEditor], optional): List of image editors for editing individual watermarked frames. Defaults to []. - unwatermarked_frame_editor_list (List[ImageEditor], optional): List of image editors for editing individual unwatermarked frames. Defaults to []. - analyzers (List[VideoQualityAnalyzer], optional): Whether to show progress. Defaults to None. - show_progress (bool, optional): Whether to show progress. Defaults to True. - store_path (str, optional): The path to store the results. Defaults to None. - return_type (QualityPipelineReturnType, optional): The return type of the pipeline. Defaults to QualityPipelineReturnType.MEAN_SCORES. - """ - super().__init__(dataset, watermarked_video_editor_list, unwatermarked_video_editor_list, watermarked_frame_editor_list, unwatermarked_frame_editor_list, analyzers, show_progress, store_path, return_type)
- - - def _get_iterable(self): - """Return an iterable for the dataset.""" - return range(self.dataset.num_samples) - - def _get_prompt(self, index: int) -> str: - """Get prompt from dataset.""" - return self.dataset.get_prompt(index) - - def _get_watermarked_video(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> List[Image.Image]: - """Generate watermarked video from dataset.""" - prompt = self._get_prompt(index) - frames = watermark.generate_watermarked_media(input_data=prompt, **generation_kwargs) - return frames - - def _get_unwatermarked_video(self, watermark: BaseWatermark, index: int, **generation_kwargs) -> List[Image.Image]: - """Generate or retrieve unwatermarked video from dataset.""" - prompt = self._get_prompt(index) - frames = watermark.generate_unwatermarked_media(input_data=prompt, **generation_kwargs) - return frames - - def _prepare_input_for_quality_analyzer(self, - watermarked_videos: List[List[Image.Image]], - unwatermarked_videos: List[List[Image.Image]], - reference_videos: List[List[Image.Image]]): - """Prepare input for quality analyzer.""" - # Group videos by prompt - return watermarked_videos, unwatermarked_videos - -
-[docs] - def analyze_quality(self, - prepared_data: Tuple[List[List[Image.Image]], List[List[Image.Image]], List[List[Image.Image]]], - analyzer: VideoQualityAnalyzer): - """Analyze quality of watermarked and unwatermarked videos.""" - watermarked_videos, unwatermarked_videos = prepared_data - - # Create pairs of watermarked and unwatermarked videos - video_pairs = list(zip(watermarked_videos, unwatermarked_videos)) - - bar = self._get_progress_bar(video_pairs) - bar.set_description(f"Analyzing quality for {analyzer.__class__.__name__}") - w_scores, u_scores = [], [] - for watermarked_video, unwatermarked_video in bar: - w_score = analyzer.analyze(watermarked_video) - u_score = analyzer.analyze(unwatermarked_video) - w_scores.append(w_score) - u_scores.append(u_score) - return w_scores, u_scores
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/tools/image_editor.html b/docs/_build/html/_modules/evaluation/tools/image_editor.html deleted file mode 100644 index 5f2b5f9..0000000 --- a/docs/_build/html/_modules/evaluation/tools/image_editor.html +++ /dev/null @@ -1,1248 +0,0 @@ - - - - - - - - evaluation.tools.image_editor — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.tools.image_editor

-from PIL import Image, ImageFilter, ImageEnhance, ImageOps, ImageDraw
-import os
-import argparse
-import sys
-import numpy as np
-import random
-
-
-
-[docs] -class ImageEditor: -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - pass
-
- - -
-[docs] -class JPEGCompression(ImageEditor): -
-[docs] - def __init__(self, quality: int = 95): - super().__init__() - self.quality = quality
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - image.save(f"temp.jpg", quality=self.quality) - compressed_image = Image.open(f"temp.jpg") - os.remove(f"temp.jpg") - return compressed_image
-
- - -
-[docs] -class Rotation(ImageEditor): -
-[docs] - def __init__(self, angle: int = 30, expand: bool = False): - super().__init__() - self.angle = angle - self.expand = expand
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - return image.rotate(self.angle, expand=self.expand)
-
- - -
-[docs] -class CrSc(ImageEditor): -
-[docs] - def __init__(self, crop_ratio: float = 0.8): - super().__init__() - self.crop_ratio = crop_ratio
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - width, height = image.size - new_w = int(width * self.crop_ratio) - new_h = int(height * self.crop_ratio) - - left = (width - new_w) // 2 - top = (height - new_h) // 2 - right = left + new_w - bottom = top + new_h - - return image.crop((left, top, right, bottom)).resize((width, height))
-
- - -
-[docs] -class GaussianBlurring(ImageEditor): -
-[docs] - def __init__(self, radius: int = 2): - super().__init__() - self.radius = radius
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - return image.filter(ImageFilter.GaussianBlur(self.radius))
-
- - -
-[docs] -class GaussianNoise(ImageEditor): -
-[docs] - def __init__(self, sigma: float = 25.0): - super().__init__() - self.sigma = sigma
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - img = image.convert("RGB") - arr = np.array(img).astype(np.float32) - - noise = np.random.normal(0, self.sigma, arr.shape) - noisy_arr = np.clip(arr + noise, 0, 255).astype(np.uint8) - - return Image.fromarray(noisy_arr)
-
- - -
-[docs] -class Brightness(ImageEditor): -
-[docs] - def __init__(self, factor: float = 1.2): - super().__init__() - self.factor = factor
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - enhancer = ImageEnhance.Brightness(image) - return enhancer.enhance(self.factor)
-
- - -
-[docs] -class Mask(ImageEditor): -
-[docs] - def __init__(self, mask_ratio: float = 0.1, num_masks: int = 5): - super().__init__() - self.mask_ratio = mask_ratio - self.num_masks = num_masks
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - img = image.copy() - draw = ImageDraw.Draw(img) - width, height = img.size - - for _ in range(self.num_masks): - max_mask_width = int(width * self.mask_ratio) - max_mask_height = int(height * self.mask_ratio) - - mask_width = random.randint(max_mask_width // 2, max_mask_width) - mask_height = random.randint(max_mask_height // 2, max_mask_height) - - x = random.randint(0, width - mask_width) - y = random.randint(0, height - mask_height) - - draw.rectangle([x, y, x + mask_width, y + mask_height], fill='black') - - return img
-
- - -
-[docs] -class Overlay(ImageEditor): -
-[docs] - def __init__(self, num_strokes: int = 10, stroke_width: int = 5, stroke_type: str = 'random'): - super().__init__() - self.num_strokes = num_strokes - self.stroke_width = stroke_width - self.stroke_type = stroke_type
- - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - img = image.copy() - draw = ImageDraw.Draw(img) - width, height = img.size - - for _ in range(self.num_strokes): - start_x = random.randint(0, width) - start_y = random.randint(0, height) - num_points = random.randint(3, 8) - points = [(start_x, start_y)] - - for i in range(num_points - 1): - last_x, last_y = points[-1] - max_step = min(width, height) // 4 - new_x = max(0, min(width, last_x + random.randint(-max_step, max_step))) - new_y = max(0, min(height, last_y + random.randint(-max_step, max_step))) - points.append((new_x, new_y)) - - if self.stroke_type == 'random': - color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - elif self.stroke_type == 'black': - color = (0, 0, 0) - elif self.stroke_type == 'white': - color = (255, 255, 255) - else: - color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - - draw.line(points, fill=color, width=self.stroke_width) - - return img
-
- - -
-[docs] -class AdaptiveNoiseInjection(ImageEditor): -
-[docs] - def __init__(self, intensity: float = 0.5, auto_select: bool = True): - super().__init__() - self.intensity = intensity - self.auto_select = auto_select
- - - def _analyze_image_features(self, img_array): - if len(img_array.shape) == 3: - gray = np.mean(img_array, axis=2) - else: - gray = img_array - - brightness_mean = np.mean(gray) - brightness_std = np.std(gray) - - sobel_x = np.abs(np.diff(gray, axis=1, prepend=gray[:, :1])) - sobel_y = np.abs(np.diff(gray, axis=0, prepend=gray[:1, :])) - edge_density = np.mean(sobel_x + sobel_y) - - kernel_size = 5 - texture_complexity = 0 - h, w = gray.shape - for i in range(0, h - kernel_size, kernel_size): - for j in range(0, w - kernel_size, kernel_size): - patch = gray[i:i+kernel_size, j:j+kernel_size] - texture_complexity += np.std(patch) - texture_complexity /= ((h // kernel_size) * (w // kernel_size)) - - return { - 'brightness_mean': brightness_mean, - 'brightness_std': brightness_std, - 'edge_density': edge_density, - 'texture_complexity': texture_complexity - } - - def _select_noise_type(self, features): - brightness = features['brightness_mean'] - edge_density = features['edge_density'] - texture = features['texture_complexity'] - - if brightness < 80: - return 'gaussian' - elif edge_density > 30: - return 'salt_pepper' - elif texture > 20: - return 'speckle' - else: - return 'poisson' - - def _add_gaussian_noise(self, img_array, sigma): - noise = np.random.normal(0, sigma, img_array.shape) - noisy = np.clip(img_array + noise, 0, 255) - return noisy.astype(np.uint8) - - def _add_salt_pepper_noise(self, img_array, amount): - noisy = img_array.copy() - h, w = img_array.shape[:2] - num_pixels = h * w - - num_salt = int(amount * num_pixels * 0.5) - salt_coords_y = np.random.randint(0, h, num_salt) - salt_coords_x = np.random.randint(0, w, num_salt) - noisy[salt_coords_y, salt_coords_x] = 255 - - num_pepper = int(amount * num_pixels * 0.5) - pepper_coords_y = np.random.randint(0, h, num_pepper) - pepper_coords_x = np.random.randint(0, w, num_pepper) - noisy[pepper_coords_y, pepper_coords_x] = 0 - - return noisy - - def _add_poisson_noise(self, img_array): - vals = len(np.unique(img_array)) - vals = 2 ** np.ceil(np.log2(vals)) - noisy = np.random.poisson(img_array * vals) / float(vals) - return np.clip(noisy, 0, 255).astype(np.uint8) - - def _add_speckle_noise(self, img_array, variance): - noise = np.random.randn(*img_array.shape) * variance - noisy = img_array + img_array * noise - return np.clip(noisy, 0, 255).astype(np.uint8) - -
-[docs] - def edit(self, image: Image.Image, prompt: str = None) -> Image.Image: - img = image.convert("RGB") - img_array = np.array(img).astype(np.float32) - - features = self._analyze_image_features(img_array) - - if self.auto_select: - noise_type = self._select_noise_type(features) - - if noise_type == 'gaussian': - sigma = 40 * self.intensity - noisy_array = self._add_gaussian_noise(img_array, sigma) - elif noise_type == 'salt_pepper': - amount = 0.15 * self.intensity - noisy_array = self._add_salt_pepper_noise(img_array, amount) - elif noise_type == 'poisson': - noisy_array = self._add_poisson_noise(img_array) - blend_factor = min(0.8, self.intensity * 1.5) - noisy_array = np.clip( - img_array * (1 - blend_factor) + noisy_array * blend_factor, - 0, 255 - ).astype(np.uint8) - else: - variance = 0.5 * self.intensity - noisy_array = self._add_speckle_noise(img_array, variance) - else: - weight = 0.25 - noisy_array = img_array.copy() - - gaussian = self._add_gaussian_noise(img_array, 30 * self.intensity) - noisy_array = noisy_array * (1 - weight) + gaussian * weight - - salt_pepper = self._add_salt_pepper_noise(img_array, 0.08 * self.intensity) - noisy_array = noisy_array * (1 - weight) + salt_pepper * weight - - poisson = self._add_poisson_noise(img_array) - noisy_array = noisy_array * (1 - weight) + poisson * weight - - speckle = self._add_speckle_noise(img_array, 0.4 * self.intensity) - noisy_array = noisy_array * (1 - weight) + speckle * weight - - noisy_array = np.clip(noisy_array, 0, 255).astype(np.uint8) - - return Image.fromarray(noisy_array)
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/tools/image_quality_analyzer.html b/docs/_build/html/_modules/evaluation/tools/image_quality_analyzer.html deleted file mode 100644 index d30e597..0000000 --- a/docs/_build/html/_modules/evaluation/tools/image_quality_analyzer.html +++ /dev/null @@ -1,2142 +0,0 @@ - - - - - - - - evaluation.tools.image_quality_analyzer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.tools.image_quality_analyzer

-from PIL import Image
-from typing import List, Dict, Optional, Union, Tuple
-import torch
-import torch.nn as nn
-import torch.nn.functional as F
-import numpy as np
-from abc import abstractmethod
-import lpips
-import piq
-
-
-[docs] -class ImageQualityAnalyzer: - """Base class for image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - @abstractmethod - def analyze(self): - pass
-
- - -
-[docs] -class DirectImageQualityAnalyzer(ImageQualityAnalyzer): - """Base class for direct image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, image: Image.Image, *args, **kwargs): - pass
-
- - -
-[docs] -class ReferencedImageQualityAnalyzer(ImageQualityAnalyzer): - """Base class for referenced image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, image: Image.Image, reference: Union[Image.Image, str], *args, **kwargs): - pass
-
- - -
-[docs] -class GroupImageQualityAnalyzer(ImageQualityAnalyzer): - """Base class for group image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, images: List[Image.Image], references: List[Image.Image], *args, **kwargs): - pass
-
- - -
-[docs] -class RepeatImageQualityAnalyzer(ImageQualityAnalyzer): - """Base class for repeat image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, images: List[Image.Image], *args, **kwargs): - pass
-
- - -
-[docs] -class ComparedImageQualityAnalyzer(ImageQualityAnalyzer): - """Base class for compare image quality analyzer.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, image: Image.Image, reference: Image.Image, *args, **kwargs): - pass
-
- - -
-[docs] -class InceptionScoreCalculator(RepeatImageQualityAnalyzer): - """Inception Score (IS) calculator for evaluating image generation quality. - - Inception Score measures both the quality and diversity of generated images - by evaluating how confidently an Inception model can classify them and how - diverse the predictions are across the image set. - - Higher IS indicates better image quality and diversity (typical range: 1-10+). - """ - -
-[docs] - def __init__(self, device: str = "cuda", batch_size: int = 32, splits: int = 1): - """Initialize the Inception Score calculator. - - Args: - device: Device to run the model on ("cuda" or "cpu") - batch_size: Batch size for processing images - splits: Number of splits for computing IS (default: 1). The splits must be divisible by the number of images for fair comparison. - For calculating the mean and standard error of IS, the splits should be set greater than 1. - If splits is 1, the IS is calculated on the entire dataset.(Avg = IS, Std = 0) - """ - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.batch_size = batch_size - self.splits = splits - self._load_model()
- - - def _load_model(self): - """Load the Inception v3 model for feature extraction.""" - from torchvision import models, transforms - - # Load pre-trained Inception v3 model - self.model = models.inception_v3(weights=models.Inception_V3_Weights.DEFAULT) - self.model.aux_logits = False # Disable auxiliary output - self.model.eval() - self.model.to(self.device) - - # Keep the original classification layer for proper predictions - # No need to modify model.fc - it should output 1000 classes - - # Define preprocessing pipeline for Inception v3 - self.preprocess = transforms.Compose([ - transforms.Resize((299, 299)), # Inception v3 input size - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # ImageNet statistics - ]) - - def _get_predictions(self, images: List[Image.Image]) -> np.ndarray: - """Extract softmax predictions from images using Inception v3. - - Args: - images: List of PIL images to process - - Returns: - Numpy array of shape (n_images, n_classes) containing softmax predictions - """ - predictions_list = [] - - # Process images in batches for efficiency - for i in range(0, len(images), self.batch_size): - batch_images = images[i:i + self.batch_size] - - # Preprocess batch - batch_tensors = [] - for img in batch_images: - # Ensure RGB format - if img.mode != 'RGB': - img = img.convert('RGB') - tensor = self.preprocess(img) - batch_tensors.append(tensor) - - # Stack into batch tensor - batch_tensor = torch.stack(batch_tensors).to(self.device) - - # Get predictions from Inception model - with torch.no_grad(): - logits = self.model(batch_tensor) - # Apply softmax to get probability distributions - probs = F.softmax(logits, dim=1) - predictions_list.append(probs.cpu().numpy()) - - return np.concatenate(predictions_list, axis=0) - - def _calculate_inception_score(self, predictions: np.ndarray) -> tuple: - """Calculate Inception Score from predictions. - - The IS is calculated as exp(KL divergence between conditional and marginal distributions). - - Args: - predictions: Softmax predictions of shape (n_images, n_classes) - - Returns: - Tuple of (mean_is, std_is) across splits - """ - # Split predictions for more stable estimation - n_samples = predictions.shape[0] # (n_images, n_classes) - split_size = n_samples // self.splits - - splits = self.splits - - split_scores = [] - - for split_idx in range(splits): - # Get current split - start_idx = split_idx * split_size - end_idx = (split_idx + 1) * split_size if split_idx < splits - 1 else n_samples # Last split gets remaining samples - split_preds = predictions[start_idx:end_idx] - - # Calculate marginal distribution p(y) - average across all samples - p_y = np.mean(split_preds, axis=0) - - epsilon = 1e-16 - p_y_x_safe = split_preds + epsilon - p_y_safe = p_y + epsilon - kl_divergences = np.sum( - p_y_x_safe * (np.log(p_y_x_safe / p_y_safe)), - axis=1) - - # Inception Score for this split is exp(mean(KL divergences)) - split_score = np.exp(np.mean(kl_divergences)) - split_scores.append(split_score) - - # Directly return the list of scores for each split - return split_scores - -
-[docs] - def analyze(self, images: List[Image.Image], *args, **kwargs) -> List[float]: - """Calculate Inception Score for a set of generated images. - - Args: - images: List of generated images to evaluate - - Returns: - List[float]: Inception Score values for each split (higher is better, typical range: 1-10+) - """ - if len(images) < self.splits: - raise ValueError(f"Inception Score requires at least {self.splits} images (one per split)") - - if len(images) % self.splits != 0: - raise ValueError(f"Inception Score requires the number of images to be divisible by the number of splits") - - # Get predictions from Inception model - predictions = self._get_predictions(images) - - # Calculate Inception Score - split_scores = self._calculate_inception_score(predictions) - - # Log the standard deviation for reference (but return only mean) - mean_score = np.mean(split_scores) - std_score = np.std(split_scores) - if std_score > 0.5 * mean_score: - print(f"Warning: High standard deviation in IS calculation: {mean_score:.2f} ± {std_score:.2f}") - - return split_scores
-
- - -
-[docs] -class CLIPScoreCalculator(ReferencedImageQualityAnalyzer): - """CLIP score calculator for image quality analysis. - - Calculates CLIP similarity between an image and a reference. - Higher scores indicate better semantic similarity. - """ - -
-[docs] - def __init__(self, device: str = "cuda", model_name: str = "ViT-B/32", reference_source: str = "image"): - """Initialize the CLIP Score calculator. - - Args: - device: Device to run the model on ("cuda" or "cpu") - model_name: CLIP model variant to use - reference_source: The source of reference ('image' or 'text') - """ - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.model_name = model_name - self.reference_source = reference_source - self._load_model()
- - - def _load_model(self): - """Load the CLIP model.""" - try: - import clip - self.model, self.preprocess = clip.load(self.model_name, device=self.device) - self.model.eval() - except ImportError: - raise ImportError("Please install the CLIP library: pip install git+https://github.com/openai/CLIP.git") - -
-[docs] - def analyze(self, image: Image.Image, reference: Union[Image.Image, str], *args, **kwargs) -> float: - """Calculate CLIP similarity between image and reference. - - Args: - image: Input image to evaluate - reference: Reference image or text for comparison - - If reference_source is 'image': expects PIL Image - - If reference_source is 'text': expects string - - Returns: - float: CLIP similarity score (0 to 1) - """ - - # Convert image to RGB if necessary - if image.mode != 'RGB': - image = image.convert('RGB') - - # Preprocess image - img_tensor = self.preprocess(image).unsqueeze(0).to(self.device) - - # Extract features based on reference source - with torch.no_grad(): - # Encode image features - img_features = self.model.encode_image(img_tensor) - - # Encode reference features based on source type - if self.reference_source == 'text': - if not isinstance(reference, str): - raise ValueError(f"Expected string reference for text mode, got {type(reference)}") - - # Tokenize and encode text - text_tokens = clip.tokenize([reference]).to(self.device) - ref_features = self.model.encode_text(text_tokens) - - elif self.reference_source == 'image': - if not isinstance(reference, Image.Image): - raise ValueError(f"Expected PIL Image reference for image mode, got {type(reference)}") - - # Convert reference image to RGB if necessary - if reference.mode != 'RGB': - reference = reference.convert('RGB') - - # Preprocess and encode reference image - ref_tensor = self.preprocess(reference).unsqueeze(0).to(self.device) - ref_features = self.model.encode_image(ref_tensor) - - else: - raise ValueError(f"Invalid reference_source: {self.reference_source}. Must be 'image' or 'text'") - - # Normalize features - img_features = F.normalize(img_features, p=2, dim=1) - ref_features = F.normalize(ref_features, p=2, dim=1) - - # Calculate cosine similarity - similarity = torch.cosine_similarity(img_features, ref_features).item() - - # Convert to 0-1 range - similarity = (similarity + 1) / 2 - - return similarity
-
- - -
-[docs] -class FIDCalculator(GroupImageQualityAnalyzer): - """FID calculator for image quality analysis. - - Calculates Fréchet Inception Distance between two sets of images. - Lower FID indicates better quality and similarity to reference distribution. - """ - -
-[docs] - def __init__(self, device: str = "cuda", batch_size: int = 32, splits: int = 1): - """Initialize the FID calculator. - - Args: - device: Device to run the model on ("cuda" or "cpu") - batch_size: Batch size for processing images - splits: Number of splits for computing FID (default: 5). The splits must be divisible by the number of images for fair comparison. - For calculating the mean and standard error of FID, the splits should be set greater than 1. - If splits is 1, the FID is calculated on the entire dataset.(Avg = FID, Std = 0) - """ - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.batch_size = batch_size - self.splits = splits - self._load_model()
- - - def _load_model(self): - """Load the Inception v3 model for feature extraction.""" - from torchvision import models, transforms - - # Load Inception v3 model - inception = models.inception_v3(weights=models.Inception_V3_Weights.DEFAULT, init_weights=False) - inception.fc = nn.Identity() # Remove final classification layer - inception.aux_logits = False - inception.eval() - inception.to(self.device) - self.model = inception - - # Define preprocessing - self.preprocess = transforms.Compose([ - transforms.Resize((512, 512)), - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # ImageNet statistics - ]) - - def _extract_features(self, images: List[Image.Image]) -> np.ndarray: - """Extract features from a list of images. - - Args: - images: List of PIL images - - Returns: - Feature matrix of shape (n_images, 2048) - """ - features_list = [] - - for i in range(0, len(images), self.batch_size): - batch_images = images[i:i + self.batch_size] - - # Preprocess batch - batch_tensors = [] - for img in batch_images: - if img.mode != 'RGB': - img = img.convert('RGB') - tensor = self.preprocess(img) - batch_tensors.append(tensor) - - batch_tensor = torch.stack(batch_tensors).to(self.device) - - # Extract features - with torch.no_grad(): - features = self.model(batch_tensor) # (batch_size, 2048) - features_list.append(features.cpu().numpy()) - - return np.concatenate(features_list, axis=0) # (n_images, 2048) - - def _calculate_fid(self, features1: np.ndarray, features2: np.ndarray) -> float: - """Calculate FID between two feature sets. - - Args: - features1: First feature set - features2: Second feature set - - Returns: - float: FID score - """ - from scipy.linalg import sqrtm - - # Calculate statistics - mu1, sigma1 = features1.mean(axis=0), np.cov(features1, rowvar=False) - mu2, sigma2 = features2.mean(axis=0), np.cov(features2, rowvar=False) - - # Calculate FID - diff = mu1 - mu2 - covmean = sqrtm(sigma1.dot(sigma2)) - - # Numerical stability - if np.iscomplexobj(covmean): - covmean = covmean.real - - fid = diff.dot(diff) + np.trace(sigma1 + sigma2 - 2 * covmean) - return float(fid) - -
-[docs] - def analyze(self, images: List[Image.Image], references: List[Image.Image], *args, **kwargs) -> List[float]: - """Calculate FID between two sets of images. - - Args: - images: Set of images to evaluate - references: Reference set of images - - Returns: - List[float]: FID values for each split - """ - if len(images) < 2 or len(references) < 2: - raise ValueError("FID requires at least 2 images in each set") - if len(images) % self.splits != 0 or len(references) % self.splits != 0: - raise ValueError("FID requires the number of images to be divisible by the number of splits") - - fid_scores = [] - # Extract features - features1 = self._extract_features(images) - features2 = self._extract_features(references) - - # Calculate FID - # for i in range(self.splits): - # start_idx = i * len(images) // self.splits - # end_idx = (i + 1) * len(images) // self.splits - # fid_scores.append(self._calculate_fid(features1[start_idx:end_idx], features2[start_idx:end_idx])) - - fid_scores = self._calculate_fid(features1, features2) - - return fid_scores
-
- - -
-[docs] -class LPIPSAnalyzer(RepeatImageQualityAnalyzer): - """LPIPS analyzer for image quality analysis. - - Calculates perceptual diversity within a set of images. - Higher LPIPS indicates more diverse/varied images. - """ - -
-[docs] - def __init__(self, device: str = "cuda", net: str = "alex"): - """Initialize the LPIPS analyzer. - - Args: - device: Device to run the model on ("cuda" or "cpu") - net: Network to use ('alex', 'vgg', or 'squeeze') - """ - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.net = net - self._load_model()
- - - def _load_model(self) -> None: - """ - Load the LPIPS model. - """ - self.model = lpips.LPIPS(net=self.net) - self.model.eval() - self.model.to(self.device) - -
-[docs] - def analyze(self, images: List[Image.Image], *args, **kwargs) -> float: - """Calculate average pairwise LPIPS distance within a set of images. - - Args: - images: List of images to analyze diversity - - Returns: - float: Average LPIPS distance (diversity score) - """ - if len(images) < 2: - return 0.0 # No diversity with single image - - # Preprocess all images - tensors = [] - for img in images: - if img.mode != 'RGB': - img = img.convert('RGB') - tensor = lpips.im2tensor(np.array(img).astype(np.uint8)).to(self.device) # Convert to tensor - tensors.append(tensor) - - # Calculate pairwise LPIPS distances - distances = [] - for i in range(len(tensors)): - for j in range(i + 1, len(tensors)): - with torch.no_grad(): - distance = self.model.forward(tensors[i], tensors[j]).item() - distances.append(distance) - - # Return average distance as diversity score - return np.mean(distances) if distances else 0.0
-
- - -
-[docs] -class PSNRAnalyzer(ComparedImageQualityAnalyzer): - """PSNR analyzer for image quality analysis. - - Calculates Peak Signal-to-Noise Ratio between two images. - Higher PSNR indicates better quality/similarity. - """ - -
-[docs] - def __init__(self, max_pixel_value: float = 255.0): - """Initialize the PSNR analyzer. - - Args: - max_pixel_value: Maximum pixel value (255 for 8-bit images) - """ - super().__init__() - self.max_pixel_value = max_pixel_value
- - -
-[docs] - def analyze(self, image: Image.Image, reference: Image.Image, *args, **kwargs) -> float: - """Calculate PSNR between two images. - - Args: - image (Image.Image): Image to evaluate - reference (Image.Image): Reference image - - Returns: - float: PSNR value in dB - """ - # Convert to RGB if necessary - if image.mode != 'RGB': - image = image.convert('RGB') - if reference.mode != 'RGB': - reference = reference.convert('RGB') - - # Resize if necessary - if image.size != reference.size: - reference = reference.resize(image.size, Image.Resampling.BILINEAR) - - # Convert to numpy arrays - img_array = np.array(image, dtype=np.float32) - ref_array = np.array(reference, dtype=np.float32) - - # Calculate MSE - mse = np.mean((img_array - ref_array) ** 2) - - # Avoid division by zero - if mse == 0: - return float('inf') - - # Calculate PSNR - psnr = 20 * np.log10(self.max_pixel_value / np.sqrt(mse)) - - return float(psnr)
-
- - - -
-[docs] -class NIQECalculator(DirectImageQualityAnalyzer): - """Natural Image Quality Evaluator (NIQE) for no-reference image quality assessment. - - NIQE evaluates image quality based on deviations from natural scene statistics. - It uses a pre-trained model of natural image statistics to assess quality without - requiring reference images. - - Lower NIQE scores indicate better/more natural image quality (typical range: 2-8). - """ - -
-[docs] - def __init__(self, - model_path: str = "evaluation/tools/data/niqe_image_params.mat", - patch_size: int = 96, - sigma: float = 7.0/6.0, - C: float = 1.0): - """Initialize NIQE calculator with pre-trained natural image statistics. - - Args: - model_path: Path to the pre-trained NIQE model parameters (.mat file) - patch_size: Size of patches for feature extraction (default: 96) - sigma: Standard deviation for Gaussian window (default: 7/6) - C: Constant for numerical stability in MSCN transform (default: 1.0) - """ - super().__init__() - self.patch_size = patch_size - self.sigma = sigma - self.C = C - - # Load pre-trained natural image statistics - self._load_model_params(model_path) - - # Pre-compute gamma lookup table for AGGD parameter estimation - self._precompute_gamma_table() - - # Generate Gaussian window for local mean/variance computation - self.avg_window = self._generate_gaussian_window(3, self.sigma)
- - - def _load_model_params(self, model_path: str) -> None: - """Load pre-trained NIQE model parameters from MAT file. - - Args: - model_path: Path to the model parameters file - """ - import scipy.io - try: - params = scipy.io.loadmat(model_path) - self.pop_mu = np.ravel(params["pop_mu"]) - self.pop_cov = params["pop_cov"] - except Exception as e: - raise RuntimeError(f"Failed to load NIQE model parameters from {model_path}: {e}") - - def _precompute_gamma_table(self) -> None: - """Pre-compute gamma function values for AGGD parameter estimation.""" - import scipy.special - - self.gamma_range = np.arange(0.2, 10, 0.001) - a = scipy.special.gamma(2.0 / self.gamma_range) - a *= a - b = scipy.special.gamma(1.0 / self.gamma_range) - c = scipy.special.gamma(3.0 / self.gamma_range) - self.prec_gammas = a / (b * c) - - def _generate_gaussian_window(self, window_size: int, sigma: float) -> np.ndarray: - """Generate 1D Gaussian window for filtering. - - Args: - window_size: Half-size of the window (full size = 2*window_size + 1) - sigma: Standard deviation of Gaussian - - Returns: - 1D Gaussian window weights - """ - window_size = int(window_size) - weights = np.zeros(2 * window_size + 1) - weights[window_size] = 1.0 - sum_weights = 1.0 - - sigma_sq = sigma * sigma - for i in range(1, window_size + 1): - tmp = np.exp(-0.5 * i * i / sigma_sq) - weights[window_size + i] = tmp - weights[window_size - i] = tmp - sum_weights += 2.0 * tmp - - weights /= sum_weights - return weights - - def _compute_mscn_transform(self, image: np.ndarray, extend_mode: str = 'constant') -> tuple: - """Compute Mean Subtracted Contrast Normalized (MSCN) coefficients. - - MSCN transformation normalizes image patches by local mean and variance, - making the coefficients more suitable for statistical modeling. - - Args: - image: Input image array - extend_mode: Boundary extension mode for filtering - - Returns: - Tuple of (mscn_coefficients, local_variance, local_mean) - """ - import scipy.ndimage - - assert len(image.shape) == 2, "Input must be grayscale image" - h, w = image.shape - - # Allocate arrays for local statistics - mu_image = np.zeros((h, w), dtype=np.float32) - var_image = np.zeros((h, w), dtype=np.float32) - image_float = image.astype(np.float32) - - # Compute local mean using separable Gaussian filtering - scipy.ndimage.correlate1d(image_float, self.avg_window, 0, mu_image, mode=extend_mode) - scipy.ndimage.correlate1d(mu_image, self.avg_window, 1, mu_image, mode=extend_mode) - - # Compute local variance - scipy.ndimage.correlate1d(image_float**2, self.avg_window, 0, var_image, mode=extend_mode) - scipy.ndimage.correlate1d(var_image, self.avg_window, 1, var_image, mode=extend_mode) - - # Variance = E[X^2] - E[X]^2 - var_image = np.sqrt(np.abs(var_image - mu_image**2)) - - # MSCN transform - mscn = (image_float - mu_image) / (var_image + self.C) - - return mscn, var_image, mu_image - - def _compute_aggd_features(self, coefficients: np.ndarray) -> tuple: - """Compute Asymmetric Generalized Gaussian Distribution (AGGD) parameters. - - AGGD models the distribution of MSCN coefficients and their products, - capturing shape and asymmetry characteristics. - - Args: - coefficients: MSCN coefficients - - Returns: - Tuple of (alpha, N, bl, br, left_std, right_std) - """ - import scipy.special - - # Flatten coefficients - coeffs_flat = coefficients.flatten() - coeffs_squared = coeffs_flat * coeffs_flat - - # Separate left (negative) and right (positive) tail data - left_data = coeffs_squared[coeffs_flat < 0] - right_data = coeffs_squared[coeffs_flat >= 0] - - # Compute standard deviations for left and right tails - left_std = np.sqrt(np.mean(left_data)) if len(left_data) > 0 else 0 - right_std = np.sqrt(np.mean(right_data)) if len(right_data) > 0 else 0 - - # Estimate gamma (shape asymmetry parameter) - if right_std != 0: - gamma_hat = left_std / right_std - else: - gamma_hat = np.inf - - # Estimate r_hat (generalized Gaussian ratio) - mean_abs = np.mean(np.abs(coeffs_flat)) - mean_squared = np.mean(coeffs_squared) - - if mean_squared != 0: - r_hat = (mean_abs ** 2) / mean_squared - else: - r_hat = np.inf - - # Normalize r_hat using gamma - rhat_norm = r_hat * (((gamma_hat**3 + 1) * (gamma_hat + 1)) / - ((gamma_hat**2 + 1) ** 2)) - - # Find best-fitting alpha by comparing with pre-computed values - pos = np.argmin((self.prec_gammas - rhat_norm) ** 2) - alpha = self.gamma_range[pos] - - # Compute AGGD parameters - gam1 = scipy.special.gamma(1.0 / alpha) - gam2 = scipy.special.gamma(2.0 / alpha) - gam3 = scipy.special.gamma(3.0 / alpha) - - aggd_ratio = np.sqrt(gam1) / np.sqrt(gam3) - bl = aggd_ratio * left_std # Left scale parameter - br = aggd_ratio * right_std # Right scale parameter - - # Mean parameter - N = (br - bl) * (gam2 / gam1) - - return alpha, N, bl, br, left_std, right_std - - def _compute_paired_products(self, mscn_coeffs: np.ndarray) -> tuple: - """Compute products of adjacent MSCN coefficients in four orientations. - - These products capture dependencies between neighboring pixels. - - Args: - mscn_coeffs: MSCN coefficient matrix - - Returns: - Tuple of (horizontal, vertical, diagonal1, diagonal2) products - """ - # Shift in four directions and compute products - shift_h = np.roll(mscn_coeffs, 1, axis=1) # Horizontal shift - shift_v = np.roll(mscn_coeffs, 1, axis=0) # Vertical shift - shift_d1 = np.roll(shift_v, 1, axis=1) # Main diagonal shift - shift_d2 = np.roll(shift_v, -1, axis=1) # Anti-diagonal shift - - # Compute products - prod_h = mscn_coeffs * shift_h # Horizontal pairs - prod_v = mscn_coeffs * shift_v # Vertical pairs - prod_d1 = mscn_coeffs * shift_d1 # Diagonal pairs - prod_d2 = mscn_coeffs * shift_d2 # Anti-diagonal pairs - - return prod_h, prod_v, prod_d1, prod_d2 - - def _extract_subband_features(self, mscn_coeffs: np.ndarray) -> np.ndarray: - """Extract statistical features from MSCN coefficients and their products. - - Args: - mscn_coeffs: MSCN coefficient matrix - - Returns: - Feature vector of length 18 - """ - # Extract AGGD parameters from MSCN coefficients - alpha_m, N, bl, br, _, _ = self._compute_aggd_features(mscn_coeffs) - - # Compute paired products in four orientations - prod_h, prod_v, prod_d1, prod_d2 = self._compute_paired_products(mscn_coeffs) - - # Extract AGGD parameters for each product orientation - alpha1, N1, bl1, br1, _, _ = self._compute_aggd_features(prod_h) - alpha2, N2, bl2, br2, _, _ = self._compute_aggd_features(prod_v) - alpha3, N3, bl3, br3, _, _ = self._compute_aggd_features(prod_d1) - alpha4, N4, bl4, br4, _, _ = self._compute_aggd_features(prod_d2) - - # Combine all features into feature vector - # Note: For diagonal pairs in reference, bl3 is repeated twice (not br3) - features = np.array([ - alpha_m, (bl + br) / 2.0, # Shape and scale of MSCN - alpha1, N1, bl1, br1, # Vertical pairs (V) - alpha2, N2, bl2, br2, # Horizontal pairs (H) - alpha3, N3, bl3, bl3, # Diagonal pairs (D1) - note: bl3 repeated - alpha4, N4, bl4, bl4, # Anti-diagonal pairs (D2) - note: bl4 repeated - ]) - - return features - - def _extract_multiscale_features(self, image: np.ndarray) -> tuple: - """Extract features at multiple scales. - - Args: - image: Input grayscale image - - Returns: - Tuple of (all_features, mean_features, sample_covariance) - """ - h, w = image.shape - - # Check minimum size requirements - if h < self.patch_size or w < self.patch_size: - raise ValueError(f"Image too small. Minimum size: {self.patch_size}x{self.patch_size}") - - # Ensure that the patch divides evenly into img - hoffset = h % self.patch_size - woffset = w % self.patch_size - - if hoffset > 0: - image = image[:-hoffset, :] - if woffset > 0: - image = image[:, :-woffset] - - # Convert to float32 for processing - image = image.astype(np.float32) - - # Downsample image by factor of 2 using PIL (as in reference) - img_pil = Image.fromarray(image) - size = tuple((np.array(img_pil.size) * 0.5).astype(int)) - img2 = np.array(img_pil.resize(size, Image.BICUBIC)) - - # Compute MSCN transforms at two scales - mscn1, _, _ = self._compute_mscn_transform(image) - mscn1 = mscn1.astype(np.float32) - - mscn2, _, _ = self._compute_mscn_transform(img2) - mscn2 = mscn2.astype(np.float32) - - # Extract features from patches at each scale - feats_lvl1 = self._extract_patches_test_features(mscn1, self.patch_size) - feats_lvl2 = self._extract_patches_test_features(mscn2, self.patch_size // 2) - - # Concatenate features from both scales - feats = np.hstack((feats_lvl1, feats_lvl2)) - - # Calculate mean and covariance - sample_mu = np.mean(feats, axis=0) - sample_cov = np.cov(feats.T) - - return feats, sample_mu, sample_cov - - def _extract_patches_test_features(self, mscn: np.ndarray, patch_size: int) -> np.ndarray: - """Extract features from non-overlapping patches for test images. - - Args: - mscn: MSCN coefficient matrix - patch_size: Size of patches - - Returns: - Array of patch features - """ - h, w = mscn.shape - patch_size = int(patch_size) - - # Extract non-overlapping patches - patches = [] - for j in range(0, h - patch_size + 1, patch_size): - for i in range(0, w - patch_size + 1, patch_size): - patch = mscn[j:j + patch_size, i:i + patch_size] - patches.append(patch) - - patches = np.array(patches) - - # Extract features from each patch - patch_features = [] - for p in patches: - patch_features.append(self._extract_subband_features(p)) - - patch_features = np.array(patch_features) - - return patch_features - -
-[docs] - def analyze(self, image: Image.Image, *args, **kwargs) -> float: - """Calculate NIQE score for a single image. - - Args: - image: Input image to evaluate - - Returns: - float: NIQE score (lower is better, typical range: 2-8) - """ - - import scipy.linalg - import scipy.special - - # Convert to grayscale if needed - if image.mode != 'L': - if image.mode == 'RGB': - # Convert RGB to grayscale as in reference: using 'LA' and taking first channel - image = image.convert('LA') - img_array = np.array(image)[:,:,0].astype(np.float32) - else: - image = image.convert('L') - img_array = np.array(image, dtype=np.float32) - else: - img_array = np.array(image, dtype=np.float32) - - # Check minimum size requirements - min_size = self.patch_size * 2 + 1 - if img_array.shape[0] < min_size or img_array.shape[1] < min_size: - raise ValueError(f"Image too small. Minimum size: {min_size}x{min_size}") - - # Extract multi-scale features - all_features, sample_mu, sample_cov = self._extract_multiscale_features(img_array) - - # Compute distance from natural image statistics - X = sample_mu - self.pop_mu - - # Calculate Mahalanobis-like distance - # Use average of sample and population covariance as in reference - covmat = (self.pop_cov + sample_cov) / 2.0 - - # Compute pseudo-inverse for numerical stability - pinv_cov = scipy.linalg.pinv(covmat) - - # Calculate NIQE score - niqe_score = np.sqrt(np.dot(np.dot(X, pinv_cov), X)) - - return float(niqe_score)
-
- - - -
-[docs] -class SSIMAnalyzer(ComparedImageQualityAnalyzer): - """SSIM analyzer for image quality analysis. - - Calculates Structural Similarity Index between two images. - Higher SSIM indicates better quality/similarity. - """ - -
-[docs] - def __init__(self, max_pixel_value: float = 255.0): - """Initialize the SSIM analyzer. - - Args: - max_pixel_value: Maximum pixel value (255 for 8-bit images) - """ - super().__init__() - self.max_pixel_value = max_pixel_value - self.C1 = (0.01 * max_pixel_value) ** 2 - self.C2 = (0.03 * max_pixel_value) ** 2 - self.C3 = self.C2 / 2.0
- - -
-[docs] - def analyze(self, image: Image.Image, reference: Image.Image, *args, **kwargs) -> float: - """Calculate SSIM between two images. - - Args: - image (Image.Image): Image to evaluate - reference (Image.Image): Reference image - - Returns: - float: SSIM value (0 to 1) - """ - # Convert to RGB if necessary - if image.mode != 'RGB': - image = image.convert('RGB') - if reference.mode != 'RGB': - reference = reference.convert('RGB') - - # Resize if necessary - if image.size != reference.size: - reference = reference.resize(image.size, Image.Resampling.BILINEAR) - - # Convert to numpy arrays - img_array = np.array(image, dtype=np.float32) - ref_array = np.array(reference, dtype=np.float32) - - # Calculate means - mu_x = np.mean(img_array) - mu_y = np.mean(ref_array) - - # Calculate variances and covariance - sigma_x = np.std(img_array) - sigma_y = np.std(ref_array) - sigma_xy = np.mean((img_array - mu_x) * (ref_array - mu_y)) - - # Calculate SSIM - luminance_mean=(2 * mu_x * mu_y + self.C1) / (mu_x**2 + mu_y**2 + self.C1) - contrast=(2 * sigma_x * sigma_y + self.C2) / (sigma_x**2 + sigma_y**2 + self.C2) - structure_comparison=(sigma_xy + self.C3) / (sigma_x * sigma_y + self.C3) - ssim = luminance_mean * contrast * structure_comparison - - return float(ssim)
-
- - - -
-[docs] -class BRISQUEAnalyzer(DirectImageQualityAnalyzer): - """BRISQUE analyzer for no-reference image quality analysis. - - BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator) - evaluates perceptual quality of an image without requiring - a reference. Lower BRISQUE scores indicate better quality. - Typical range: 0 (best) ~ 100 (worst). - """ - -
-[docs] - def __init__(self, device: str = "cuda"): - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu")
- - - def _preprocess(self, image: Image.Image) -> torch.Tensor: - """Convert PIL Image to tensor in range [0,1] with shape (1,C,H,W).""" - if image.mode != 'RGB': - image = image.convert('RGB') - arr = np.array(image).astype(np.float32) / 255.0 - tensor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0) # BCHW - return tensor.to(self.device) - -
-[docs] - def analyze(self, image: Image.Image, *args, **kwargs) -> float: - """Calculate BRISQUE score for a single image. - - Args: - image: PIL Image - - Returns: - float: BRISQUE score (lower is better) - """ - x = self._preprocess(image) - with torch.no_grad(): - score = piq.brisque(x, data_range=1.0) # piq expects [0,1] - return float(score.item())
-
- - - -
-[docs] -class VIFAnalyzer(ComparedImageQualityAnalyzer): - """VIF (Visual Information Fidelity) analyzer using piq. - - VIF compares a distorted image with a reference image to - quantify the amount of visual information preserved. - Higher VIF indicates better quality/similarity. - Typical range: 0 ~ 1 (sometimes higher for good quality). - """ - -
-[docs] - def __init__(self, device: str = "cuda"): - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu")
- - - def _preprocess(self, image: Image.Image) -> torch.Tensor: - """Convert PIL Image to tensor in range [0,1] with shape (1,C,H,W).""" - if image.mode != 'RGB': - image = image.convert('RGB') - arr = np.array(image).astype(np.float32) / 255.0 - tensor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0) # BCHW - return tensor.to(self.device) - -
-[docs] - def analyze(self, image: Image.Image, reference: Image.Image, *args, **kwargs) -> float: - """Calculate VIF score between image and reference. - - Args: - image: Distorted/test image (PIL) - reference: Reference image (PIL) - - Returns: - float: VIF score (higher is better) - """ - x = self._preprocess(image) - y = self._preprocess(reference) - - # Ensure same size (piq expects matching shapes) - if x.shape != y.shape: - _, _, h, w = x.shape - y = torch.nn.functional.interpolate(y, size=(h, w), mode='bilinear', align_corners=False) - - with torch.no_grad(): - score = piq.vif_p(x, y, data_range=1.0) - return float(score.item())
-
- - - -
-[docs] -class FSIMAnalyzer(ComparedImageQualityAnalyzer): - """FSIM (Feature Similarity Index) analyzer using piq. - - FSIM compares structural similarity between two images - based on phase congruency and gradient magnitude. - Higher FSIM indicates better quality/similarity. - Typical range: 0 ~ 1. - """ - -
-[docs] - def __init__(self, device: str = "cuda"): - super().__init__() - self.device = torch.device(device if torch.cuda.is_available() else "cpu")
- - - def _preprocess(self, image: Image.Image) -> torch.Tensor: - """Convert PIL Image to tensor in range [0,1] with shape (1,C,H,W).""" - if image.mode != 'RGB': - image = image.convert('RGB') - arr = np.array(image).astype(np.float32) / 255.0 - tensor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0) # BCHW - return tensor.to(self.device) - -
-[docs] - def analyze(self, image: Image.Image, reference: Image.Image, *args, **kwargs) -> float: - """Calculate FSIM score between image and reference. - - Args: - image: Distorted/test image (PIL) - reference: Reference image (PIL) - - Returns: - float: FSIM score (higher is better) - """ - x = self._preprocess(image) - y = self._preprocess(reference) - - # Ensure same size - if x.shape != y.shape: - _, _, h, w = x.shape - y = torch.nn.functional.interpolate(y, size=(h, w), mode='bilinear', align_corners=False) - - with torch.no_grad(): - score = piq.fsim(x, y, data_range=1.0) - return float(score.item())
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/tools/success_rate_calculator.html b/docs/_build/html/_modules/evaluation/tools/success_rate_calculator.html deleted file mode 100644 index b88d00e..0000000 --- a/docs/_build/html/_modules/evaluation/tools/success_rate_calculator.html +++ /dev/null @@ -1,1144 +0,0 @@ - - - - - - - - evaluation.tools.success_rate_calculator — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.tools.success_rate_calculator

-from typing import List, Dict, Union
-from exceptions.exceptions import TypeMismatchException, ConfigurationError
-from sklearn.metrics import roc_auc_score, roc_curve
-
-
-[docs] -class DetectionResult: - -
-[docs] - def __init__(self, - gold_label: bool, - detection_result: float, - ): - - self.gold_label = gold_label - self.detection_result = detection_result
-
- - - -
-[docs] -class BaseSuccessRateCalculator: - -
-[docs] - def __init__(self, - labels: List[str] = ['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'], - ): - self.labels = labels
- - - def _check_instance(self, - data: List[Union[bool, float]], - expected_type: type): - for item in data: - if not isinstance(item, expected_type): - raise TypeMismatchException(expected_type, type(item)) - - def _filter_metrics(self, - metrics: Dict[str, float]) -> Dict[str, float]: - return {label: metrics[label] for label in self.labels if label in metrics} - -
-[docs] - def calculate(self, - watermarked_results: List[DetectionResult], - non_watermarked_results: List[DetectionResult]) -> Dict[str, float]: - pass
-
- - -
-[docs] -class FundamentalSuccessRateCalculator(BaseSuccessRateCalculator): - """ - Calculator for fundamental success rates of watermark detection. - - This class specifically handles the calculation of success rates for scenarios involving - watermark detection after fixed thresholding. It provides metrics based on comparisons - between expected watermarked results and actual detection outputs. - - Use this class when you need to evaluate the effectiveness of watermark detection algorithms - under fixed thresholding conditions. - """ - -
-[docs] - def __init__(self, labels: List[str] = ['TPR', 'TNR', 'FPR', 'FNR', 'P', 'R', 'F1', 'ACC']) -> None: - """ - Initialize the fundamental success rate calculator. - - Parameters: - labels (List[str]): The list of metric labels to include in the output. - """ - super().__init__(labels)
- - - def _compute_metrics(self, inputs: List[DetectionResult]) -> Dict[str, float]: - """Compute metrics based on the provided inputs.""" - TP = sum(1 for d in inputs if d.detection_result and d.gold_label) - TN = sum(1 for d in inputs if not d.detection_result and not d.gold_label) - FP = sum(1 for d in inputs if d.detection_result and not d.gold_label) - FN = sum(1 for d in inputs if not d.detection_result and d.gold_label) - - TPR = TP / (TP + FN) if TP + FN else 0.0 - FPR = FP / (FP + TN) if FP + TN else 0.0 - TNR = TN / (TN + FP) if TN + FP else 0.0 - FNR = FN / (FN + TP) if FN + TP else 0.0 - P = TP / (TP + FP) if TP + FP else 0.0 - R = TP / (TP + FN) if TP + FN else 0.0 - F1 = 2 * (P * R) / (P + R) if P + R else 0.0 - ACC = (TP + TN) / (len(inputs)) if inputs else 0.0 - - # Calculate AUC - y_true = [x.gold_label for x in inputs] - y_score = [x.detection_result for x in inputs] - auc = roc_auc_score(y_true=y_true, y_score=y_score) - - # Calculate FPR and TPR for ROC curve - fpr, tpr, _ = roc_curve(y_true=y_true, y_score=y_score) - - return { - 'TPR': TPR, 'TNR': TNR, 'FPR': FPR, 'FNR': FNR, - 'P': P, 'R': R, 'F1': F1, 'ACC': ACC, - 'AUC': auc, - 'FPR_ROC': fpr, 'TPR_ROC': tpr - } - -
-[docs] - def calculate(self, watermarked_result: List[Union[bool, DetectionResult]], non_watermarked_result: List[Union[bool, DetectionResult]]) -> Dict[str, float]: - """calculate success rates of watermark detection based on provided results.""" - - # Convert input to DetectionResult objects if needed - if watermarked_result and isinstance(watermarked_result[0], bool): - self._check_instance(watermarked_result, bool) - inputs = [DetectionResult(True, x) for x in watermarked_result] - else: - # Assume they are DetectionResult objects - inputs = list(watermarked_result) - - if non_watermarked_result and isinstance(non_watermarked_result[0], bool): - self._check_instance(non_watermarked_result, bool) - inputs.extend([DetectionResult(False, x) for x in non_watermarked_result]) - else: - # Assume they are DetectionResult objects - inputs.extend(list(non_watermarked_result)) - - metrics = self._compute_metrics(inputs) - return self._filter_metrics(metrics)
-
- - - -
-[docs] -class DynamicThresholdSuccessRateCalculator(BaseSuccessRateCalculator): - -
-[docs] - def __init__(self, - labels: List[str] = ['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'], - rule: str = 'best', - target_fpr: float = None, - reverse: bool = False, - ): - super().__init__(labels) - self.rule = rule - self.target_fpr = target_fpr - self.reverse = reverse - - if self.rule not in ['best', 'target_fpr']: - raise ConfigurationError(f"Invalid rule: {self.rule}") - - if self.target_fpr is not None and not (0 <= self.target_fpr <= 1): - raise ConfigurationError(f"Invalid target_fpr: {self.target_fpr}")
- - - def _compute_metrics(self, - inputs: List[DetectionResult], - threshold: float) -> Dict[str, float]: - if not self.reverse: - TP = sum(1 for x in inputs if x.gold_label and x.detection_result >= threshold) - FP = sum(1 for x in inputs if x.detection_result >= threshold and not x.gold_label) - TN = sum(1 for x in inputs if x.detection_result < threshold and not x.gold_label) - FN = sum(1 for x in inputs if x.detection_result < threshold and x.gold_label) - else: - TP = sum(1 for x in inputs if x.gold_label and x.detection_result <= threshold) - FP = sum(1 for x in inputs if x.detection_result <= threshold and not x.gold_label) - TN = sum(1 for x in inputs if x.detection_result > threshold and not x.gold_label) - FN = sum(1 for x in inputs if x.detection_result > threshold and x.gold_label) - - # Calculate AUC - y_true = [1 if x.gold_label else 0 for x in inputs] - # print(inputs) - if not self.reverse: - y_score = [x.detection_result for x in inputs] - else: - y_score = [-x.detection_result for x in inputs] - auc = roc_auc_score(y_true=y_true, y_score=y_score) - - # Get ROC curve - fpr, tpr, _ = roc_curve(y_true=y_true, y_score=y_score) - - metrics = { - 'TPR': TP / (TP + FN), - 'TNR': TN / (TN + FP), - 'FPR': FP / (TN + FP), - 'FNR': FN / (TP + FN), - 'P': TP / (TP + FP), - 'R': TP / (TP + FN), - 'F1': 2 * TP / (2 * TP + FP + FN), - 'ACC': (TP + TN) / (TP + TN + FP + FN), - 'AUC': auc, - 'FPR_ROC': fpr, - 'TPR_ROC': tpr, - } - return metrics - - - def _find_best_threshold(self, inputs: List[DetectionResult]) -> float: - best_threshold = 0 - best_metrics = None - for i in range(len(inputs) - 1): - threshold = (inputs[i].detection_result + inputs[i + 1].detection_result) / 2 - metrics = self._compute_metrics(inputs, threshold) - if best_metrics is None or metrics['F1'] > best_metrics['F1']: - best_threshold = threshold - best_metrics = metrics - return best_threshold - - def _find_threshold_by_fpr(self, inputs: List[DetectionResult]) -> float: - - threshold = 0 - for i in range(len(inputs) - 1): - threshold = (inputs[i].detection_result + inputs[i + 1].detection_result) / 2 - metrics = self._compute_metrics(inputs, threshold) - if metrics['FPR'] <= self.target_fpr: - break - return threshold - - def _find_threshold(self, inputs: List[DetectionResult]) -> float: - - sorted_inputs = sorted(inputs, key=lambda x: x.detection_result, reverse=self.reverse) - - if self.rule == 'best': - return self._find_best_threshold(sorted_inputs) - elif self.rule == 'target_fpr': - return self._find_threshold_by_fpr(sorted_inputs) - -
-[docs] - def calculate(self, - watermarked_results: List[float], - non_watermarked_results: List[float]) -> Dict[str, float]: - # Check if inputs are boolean values (which suggests PRC or similar fixed-threshold algorithms) - if (watermarked_results and isinstance(watermarked_results[0], bool)) or \ - (non_watermarked_results and isinstance(non_watermarked_results[0], bool)): - raise ValueError( - "DynamicThresholdSuccessRateCalculator received boolean values. " - "For algorithms like PRC that use fixed thresholds, please use " - "FundamentalSuccessRateCalculator instead." - ) - - self._check_instance(watermarked_results, float) - self._check_instance(non_watermarked_results, float) - - inputs = [DetectionResult(True, d) for d in watermarked_results] + [DetectionResult(False, d) for d in non_watermarked_results] - - threshold = self._find_threshold(inputs) - metrics = self._compute_metrics(inputs, threshold) - return self._filter_metrics(metrics)
-
- - - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/tools/video_editor.html b/docs/_build/html/_modules/evaluation/tools/video_editor.html deleted file mode 100644 index 320decb..0000000 --- a/docs/_build/html/_modules/evaluation/tools/video_editor.html +++ /dev/null @@ -1,1281 +0,0 @@ - - - - - - - - evaluation.tools.video_editor — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.tools.video_editor

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from PIL import Image
-from typing import List
-import cv2
-import numpy as np
-import tempfile
-import os
-import random
-import subprocess
-import shutil
-
-
-[docs] -class VideoEditor: - """Base class for video editors.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - pass
-
- - -
-[docs] -class MPEG4Compression(VideoEditor): - """MPEG-4 compression video editor.""" - -
-[docs] - def __init__(self, fps: float = 24.0): - """Initialize the MPEG-4 compression video editor. - - Args: - fps (float, optional): The frames per second of the compressed video. Defaults to 24.0. - """ - self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') - self.fps = fps
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Compress the video using MPEG-4 compression. - - Args: - frames (List[Image.Image]): The frames to compress. - prompt (str, optional): The prompt for video editing. Defaults to None. - - Returns: - List[Image.Image]: The compressed frames. - """ - # Transform PIL images to numpy arrays and convert to BGR format - frame_arrays = [cv2.cvtColor(np.array(f), cv2.COLOR_RGB2BGR) for f in frames] - - # Get frame size - height, width, _ = frame_arrays[0].shape - - # Use a temporary file to save the mp4 video - with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp: - video_path = tmp.name - - # Write mp4 video (MPEG-4 encoding) - out = cv2.VideoWriter(video_path, self.fourcc, self.fps, (width, height)) - - for frame in frame_arrays: - out.write(frame) - out.release() - - # Read mp4 video and decode back to frames - cap = cv2.VideoCapture(video_path) - compressed_frames = [] - while True: - ret, frame = cap.read() - if not ret: - break - # Transform back to PIL.Image - pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) - compressed_frames.append(pil_img) - cap.release() - - # Clean up temporary file - os.remove(video_path) - - return compressed_frames
-
- - - -
-[docs] -class VideoCodecAttack(VideoEditor): - """Re-encode videos with specific codecs and bitrates to simulate platform processing.""" - - _CODEC_MAP = { - "h264": ("libx264", ".mp4"), - "h265": ("libx265", ".mp4"), - "hevc": ("libx265", ".mp4"), - "vp9": ("libvpx-vp9", ".webm"), - "av1": ("libaom-av1", ".mkv"), - } - -
-[docs] - def __init__(self, codec: str = "h264", bitrate: str = "2M", fps: float = 24.0, ffmpeg_path: str = None): - """Initialize the codec attack editor. - - Args: - codec (str, optional): Target codec (h264, h265/hevc, vp9, av1). Defaults to "h264". - bitrate (str, optional): Target bitrate passed to ffmpeg (e.g., "2M"). Defaults to "2M". - fps (float, optional): Frames per second used for intermediate encoding. Defaults to 24.0. - ffmpeg_path (str, optional): Path to ffmpeg binary. If None, resolved via PATH. - """ - self.codec = codec.lower() - if self.codec == "hevc": - self.codec = "h265" - if self.codec not in self._CODEC_MAP: - raise ValueError(f"Unsupported codec '{codec}'. Supported: {', '.join(self._CODEC_MAP.keys())}") - self.bitrate = bitrate - self.fps = fps - self.ffmpeg_path = ffmpeg_path or shutil.which("ffmpeg") - if self.ffmpeg_path is None: - raise EnvironmentError("ffmpeg executable not found. Install ffmpeg or provide ffmpeg_path.")
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Re-encode the video using the configured codec and bitrate.""" - if not frames: - return frames - - frame_arrays = [cv2.cvtColor(np.array(f), cv2.COLOR_RGB2BGR) for f in frames] - height, width, _ = frame_arrays[0].shape - - # Write frames to an intermediate mp4 file - with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_in: - input_path = tmp_in.name - with tempfile.NamedTemporaryFile(suffix=self._CODEC_MAP[self.codec][1], delete=False) as tmp_out: - output_path = tmp_out.name - - writer = cv2.VideoWriter( - input_path, - cv2.VideoWriter_fourcc(*"mp4v"), - self.fps, - (width, height), - ) - for frame in frame_arrays: - writer.write(frame) - writer.release() - - codec_name, _ = self._CODEC_MAP[self.codec] - ffmpeg_cmd = [ - self.ffmpeg_path, - "-y", - "-i", - input_path, - "-c:v", - codec_name, - "-b:v", - self.bitrate, - ] - if self.codec in {"h264", "h265"}: - ffmpeg_cmd.extend(["-pix_fmt", "yuv420p"]) - ffmpeg_cmd.append(output_path) - - try: - subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except subprocess.CalledProcessError as exc: - os.remove(input_path) - os.remove(output_path) - raise RuntimeError(f"ffmpeg re-encoding failed: {exc}") from exc - - cap = None - compressed_frames: List[Image.Image] = [] - try: - cap = cv2.VideoCapture(output_path) - while True: - ret, frame = cap.read() - if not ret: - break - pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) - compressed_frames.append(pil_img) - finally: - if cap is not None: - cap.release() - os.remove(input_path) - os.remove(output_path) - - return compressed_frames
-
- - -
-[docs] -class FrameAverage(VideoEditor): - """Frame average video editor.""" - -
-[docs] - def __init__(self, n_frames: int = 3): - """Initialize the frame average video editor. - - Args: - n_frames (int, optional): The number of frames to average. Defaults to 3. - """ - self.n_frames = n_frames
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Average frames in a window of size n_frames. - - Args: - frames (List[Image.Image]): The frames to average. - prompt (str, optional): The prompt for video editing. Defaults to None. - - Returns: - List[Image.Image]: The averaged frames. - """ - n = self.n_frames - num_frames = len(frames) - # Transform all PIL images to numpy arrays and convert to float32 for averaging - arrays = [np.asarray(img).astype(np.float32) for img in frames] - result = [] - for i in range(num_frames): - # Determine current window - start = max(0, i - n // 2) - end = min(num_frames, start + n) - # If the end exceeds, move the window to the left - start = max(0, end - n) - window = arrays[start:end] - avg = np.mean(window, axis=0).astype(np.uint8) - result.append(Image.fromarray(avg)) - return result
-
- - - -
-[docs] -class FrameRateAdapter(VideoEditor): - """Resample videos to a target frame rate using linear interpolation.""" - -
-[docs] - def __init__(self, source_fps: float = 30.0, target_fps: float = 24.0): - """Initialize the frame rate adapter. - - Args: - source_fps (float, optional): Original frames per second. Defaults to 30.0. - target_fps (float, optional): Desired frames per second. Defaults to 24.0. - """ - if source_fps <= 0 or target_fps <= 0: - raise ValueError("source_fps and target_fps must be positive numbers") - self.source_fps = source_fps - self.target_fps = target_fps
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Resample frames to match the target frame rate while preserving duration.""" - if not frames or self.source_fps == self.target_fps: - return [frame.copy() for frame in frames] - - arrays = [np.asarray(frame).astype(np.float32) for frame in frames] - num_frames = len(arrays) - if num_frames == 1: - return [Image.fromarray(arrays[0].astype(np.uint8))] - - duration = (num_frames - 1) / self.source_fps - if duration <= 0: - return [Image.fromarray(arr.astype(np.uint8)) for arr in arrays] - - target_count = max(1, int(round(duration * self.target_fps)) + 1) - indices = np.linspace(0, num_frames - 1, target_count) - - resampled_frames: List[Image.Image] = [] - for idx in indices: - lower = int(np.floor(idx)) - upper = min(int(np.ceil(idx)), num_frames - 1) - if lower == upper: - interp = arrays[lower] - else: - alpha = idx - lower - interp = (1 - alpha) * arrays[lower] + alpha * arrays[upper] - resampled_frames.append(Image.fromarray(np.clip(interp, 0, 255).astype(np.uint8))) - return resampled_frames
-
- - - -
-[docs] -class FrameSwap(VideoEditor): - """Frame swap video editor.""" - -
-[docs] - def __init__(self, p: float = 0.25): - """Initialize the frame swap video editor. - - Args: - p (float, optional): The probability of swapping neighbor frames. Defaults to 0.25. - """ - self.p = p
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Swap adjacent frames with probability p. - - Args: - frames (List[Image.Image]): The frames to swap. - prompt (str, optional): The prompt for video editing. Defaults to None. - - Returns: - List[Image.Image]: The swapped frames. - """ - for i, frame in enumerate(frames): - if i == 0: - continue - if random.random() >= self.p: - frames[i - 1], frames[i] = frames[i], frames[i - 1] - return frames
-
- - - -
-[docs] -class FrameInterpolationAttack(VideoEditor): - """Insert interpolated frames to alter temporal sampling density.""" - -
-[docs] - def __init__(self, interpolated_frames: int = 1): - """Initialize the interpolation attack editor. - - Args: - interpolated_frames (int, optional): Number of synthetic frames added between consecutive original frames. Defaults to 1. - """ - if interpolated_frames < 0: - raise ValueError("interpolated_frames must be non-negative") - self.interpolated_frames = interpolated_frames
- - -
-[docs] - def edit(self, frames: List[Image.Image], prompt: str = None) -> List[Image.Image]: - """Insert interpolated frames between originals using linear blending.""" - if not frames or self.interpolated_frames == 0: - return [frame.copy() for frame in frames] - if len(frames) == 1: - return [frames[0].copy()] - - arrays = [np.asarray(frame).astype(np.float32) for frame in frames] - result: List[Image.Image] = [] - last_index = len(frames) - 1 - for idx in range(last_index): - start = arrays[idx] - end = arrays[idx + 1] - result.append(frames[idx].copy()) - for insert_idx in range(1, self.interpolated_frames + 1): - alpha = insert_idx / (self.interpolated_frames + 1) - interp = (1 - alpha) * start + alpha * end - result.append(Image.fromarray(np.clip(interp, 0, 255).astype(np.uint8))) - result.append(frames[-1].copy()) - return result
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/evaluation/tools/video_quality_analyzer.html b/docs/_build/html/_modules/evaluation/tools/video_quality_analyzer.html deleted file mode 100644 index ded7e62..0000000 --- a/docs/_build/html/_modules/evaluation/tools/video_quality_analyzer.html +++ /dev/null @@ -1,1788 +0,0 @@ - - - - - - - - evaluation.tools.video_quality_analyzer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for evaluation.tools.video_quality_analyzer

-from typing import List
-from PIL import Image
-import torch
-import torch.nn as nn
-import torch.nn.functional as F
-from torchvision import transforms
-from torchvision.transforms import Compose, Resize, ToTensor, Normalize, CenterCrop
-import numpy as np
-from tqdm import tqdm
-import cv2
-import os
-import subprocess
-from utils.media_utils import pil_to_torch
-
-try:
-    from torchvision.transforms import InterpolationMode
-    BICUBIC = InterpolationMode.BICUBIC
-except ImportError:
-    BICUBIC = Image.BICUBIC
-
-
-
-[docs] -def dino_transform_Image(n_px): - """DINO transform for PIL Images.""" - return Compose([ - Resize(size=n_px, antialias=False), - ToTensor(), - Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) - ])
- - - -
-[docs] -class VideoQualityAnalyzer: - """Video quality analyzer base class.""" - -
-[docs] - def __init__(self): - pass
- - -
-[docs] - def analyze(self, frames: List[Image.Image]): - """Analyze video quality. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Quality score(s) - """ - raise NotImplementedError("Subclasses must implement analyze method")
-
- - - -
-[docs] -class SubjectConsistencyAnalyzer(VideoQualityAnalyzer): - """Analyzer for evaluating subject consistency across video frames using DINO features. - - This analyzer measures how consistently the main subject appears across frames by: - 1. Extracting DINO features from each frame - 2. Computing cosine similarity between consecutive frames and with the first frame - 3. Averaging these similarities to get a consistency score - """ -
-[docs] - def __init__( - self, - model_url: str = "https://dl.fbaipublicfiles.com/dino/dino_vitbase16_pretrain/dino_vitbase16_pretrain_full_checkpoint.pth", - model_path: str = "dino_vitb16_full.pth", - device: str = "cuda" - ): - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.model_path = model_path - self.model_url = model_url - - # ensure weights exist / download automatically - self._download_weights() - - # load model via timm - self.model = self._load_dino_model() - self.model.eval() - self.model.to(self.device)
- - - def _download_weights(self): - if not os.path.exists(self.model_path): - import urllib - print("Downloading DINO ViT-B/16 weights...") - urllib.request.urlretrieve(self.model_url, self.model_path) - print("Download complete:", self.model_path) - else: - print("Weights already exist:", self.model_path) - - def _load_dino_model(self): - import timm - # timm vit-base-p16 structure - model = timm.create_model( - "vit_base_patch16_224", - pretrained=False, - num_classes=0 - ) - - # load full checkpoint - ckpt = torch.load(self.model_path, map_location="cpu") - - # for full checkpoint the state dict is nested - if "teacher" in ckpt: - state_dict = ckpt["teacher"] - elif "student" in ckpt: - state_dict = ckpt["student"] - else: - state_dict = ckpt - - # remove classifier head keys - state_dict = {k: v for k, v in state_dict.items() if "head" not in k} - - model.load_state_dict(state_dict, strict=False) - return model - -
-[docs] - def transform(self, img: Image.Image) -> torch.Tensor: - """Transform PIL Image to tensor for DINO model.""" - transform = dino_transform_Image(224) - return transform(img)
- - -
-[docs] - def analyze(self, frames: List[Image.Image]) -> float: - """Analyze subject consistency across video frames. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Subject consistency score (higher is better, range [0, 1]) - """ - if len(frames) < 2: - return 1.0 # Single frame is perfectly consistent with itself - - video_sim = 0.0 - frame_count = 0 - - # Process frames and extract features - with torch.no_grad(): - for i, frame in enumerate(frames): - # Transform and prepare frame - frame_tensor = self.transform(frame).unsqueeze(0).to(self.device) - - # Extract features - features = self.model(frame_tensor) - features = F.normalize(features, dim=-1, p=2) - - if i == 0: - # Store first frame features - first_frame_features = features - else: - # Compute similarity with previous frame - sim_prev = max(0.0, F.cosine_similarity(prev_features, features).item()) - - # Compute similarity with first frame - sim_first = max(0.0, F.cosine_similarity(first_frame_features, features).item()) - - # Average the two similarities - frame_sim = (sim_prev + sim_first) / 2.0 - video_sim += frame_sim - frame_count += 1 - - # Store current features as previous for next iteration - prev_features = features - - # Return average similarity across all frame pairs - if frame_count > 0: - return video_sim / frame_count - else: - return 1.0
-
- - - -
-[docs] -class MotionSmoothnessAnalyzer(VideoQualityAnalyzer): - """Analyzer for evaluating motion smoothness in videos using AMT-S model. - - This analyzer measures motion smoothness by: - 1. Extracting frames at even indices from the video - 2. Using AMT-S model to interpolate between consecutive frames - 3. Comparing interpolated frames with actual frames to compute smoothness score - - The score represents how well the motion can be predicted/interpolated, - with smoother motion resulting in higher scores. - """ - -
-[docs] - def __init__(self, model_path: str = "model/amt/amt-s.pth", - device: str = "cuda", niters: int = 1): - """Initialize the MotionSmoothnessAnalyzer. - - Args: - model_path: Path to the AMT-S model checkpoint - device: Device to run the model on ('cuda' or 'cpu') - niters: Number of interpolation iterations (default: 1) - """ - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.niters = niters - - # Initialize model parameters - self._initialize_params() - - # Load AMT-S model - self.model = self._load_amt_model(model_path) - self.model.eval() - self.model.to(self.device)
- - - def _initialize_params(self): - """Initialize parameters for video processing.""" - if self.device.type == 'cuda': - self.anchor_resolution = 1024 * 512 - self.anchor_memory = 1500 * 1024**2 - self.anchor_memory_bias = 2500 * 1024**2 - self.vram_avail = torch.cuda.get_device_properties(self.device).total_memory - else: - # Do not resize in cpu mode - self.anchor_resolution = 8192 * 8192 - self.anchor_memory = 1 - self.anchor_memory_bias = 0 - self.vram_avail = 1 - - # Time embedding for interpolation (t=0.5) - self.embt = torch.tensor(1/2).float().view(1, 1, 1, 1).to(self.device) - - def _load_amt_model(self, model_path: str): - """Load AMT-S model. - - Args: - model_path: Path to the model checkpoint - - Returns: - Loaded AMT-S model - """ - # Import AMT-S model (note the hyphen in filename) - import sys - import importlib.util - - # Load the module with hyphen in filename - spec = importlib.util.spec_from_file_location("amt_s", "model/amt/networks/AMT-S.py") - amt_s_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(amt_s_module) - Model = amt_s_module.Model - - # Create model with default parameters - model = Model( - corr_radius=3, - corr_lvls=4, - num_flows=3 - ) - - # Load checkpoint - if os.path.exists(model_path): - ckpt = torch.load(model_path, map_location="cpu", weights_only=False) - model.load_state_dict(ckpt['state_dict']) - - return model - - def _extract_frames(self, frames: List[Image.Image], start_from: int = 0) -> List[np.ndarray]: - """Extract frames at even indices starting from start_from. - - Args: - frames: List of PIL Image frames - start_from: Starting index (default: 0) - - Returns: - List of extracted frames as numpy arrays - """ - extracted = [] - for i in range(start_from, len(frames), 2): - # Convert PIL Image to numpy array - frame_np = np.array(frames[i]) - extracted.append(frame_np) - return extracted - - def _img2tensor(self, img: np.ndarray) -> torch.Tensor: - """Convert numpy image to tensor. - - Args: - img: Image as numpy array (H, W, C) - - Returns: - Image tensor (1, C, H, W) - """ - from model.amt.utils.utils import img2tensor - return img2tensor(img) - - def _tensor2img(self, tensor: torch.Tensor) -> np.ndarray: - """Convert tensor to numpy image. - - Args: - tensor: Image tensor (1, C, H, W) - - Returns: - Image as numpy array (H, W, C) - """ - from model.amt.utils.utils import tensor2img - return tensor2img(tensor) - - def _check_dim_and_resize(self, tensor_list: List[torch.Tensor]) -> List[torch.Tensor]: - """Check dimensions and resize tensors if needed. - - Args: - tensor_list: List of image tensors - - Returns: - List of resized tensors - """ - from model.amt.utils.utils import check_dim_and_resize - return check_dim_and_resize(tensor_list) - - def _calculate_scale(self, h: int, w: int) -> float: - """Calculate scaling factor based on available VRAM. - - Args: - h: Height of the image - w: Width of the image - - Returns: - Scaling factor - """ - scale = self.anchor_resolution / (h * w) * np.sqrt((self.vram_avail - self.anchor_memory_bias) / self.anchor_memory) - scale = 1 if scale > 1 else scale - scale = 1 / np.floor(1 / np.sqrt(scale) * 16) * 16 - return scale - - def _interpolate_frames(self, inputs: List[torch.Tensor], scale: float) -> List[torch.Tensor]: - """Interpolate frames using AMT-S model. - - Args: - inputs: List of input frame tensors - scale: Scaling factor for processing - - Returns: - List of interpolated frame tensors - """ - from model.amt.utils.utils import InputPadder - - # Pad inputs - padding = int(16 / scale) - padder = InputPadder(inputs[0].shape, padding) - inputs = padder.pad(*inputs) - - # Perform interpolation for specified iterations - for i in range(self.niters): - outputs = [inputs[0]] - for in_0, in_1 in zip(inputs[:-1], inputs[1:]): - in_0 = in_0.to(self.device) - in_1 = in_1.to(self.device) - with torch.no_grad(): - imgt_pred = self.model(in_0, in_1, self.embt, scale_factor=scale, eval=True)['imgt_pred'] - outputs += [imgt_pred.cpu(), in_1.cpu()] - inputs = outputs - - # Unpad outputs - outputs = padder.unpad(*outputs) - return outputs - - def _compute_frame_difference(self, img1: np.ndarray, img2: np.ndarray) -> float: - """Compute average absolute difference between two images. - - Args: - img1: First image - img2: Second image - - Returns: - Average pixel difference - """ - diff = cv2.absdiff(img1, img2) - return np.mean(diff) - - def _compute_vfi_score(self, original_frames: List[np.ndarray], interpolated_frames: List[np.ndarray]) -> float: - """Compute video frame interpolation score. - - Args: - original_frames: Original video frames - interpolated_frames: Interpolated frames - - Returns: - VFI score (lower difference means better interpolation) - """ - # Extract frames at odd indices for comparison - ori_compare = self._extract_frames([Image.fromarray(f) for f in original_frames], start_from=1) - interp_compare = self._extract_frames([Image.fromarray(f) for f in interpolated_frames], start_from=1) - - scores = [] - for ori, interp in zip(ori_compare, interp_compare): - score = self._compute_frame_difference(ori, interp) - scores.append(score) - - return np.mean(scores) if scores else 0.0 - -
-[docs] - def analyze(self, frames: List[Image.Image]) -> float: - """Analyze motion smoothness in video frames. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Motion smoothness score (higher is better, range [0, 1]) - """ - if len(frames) < 2: - return 1.0 # Single frame has perfect smoothness - - # Convert PIL Images to numpy arrays - np_frames = [np.array(frame) for frame in frames] - - # Extract frames at even indices - frame_list = self._extract_frames(frames, start_from=0) - - # Convert to tensors - inputs = [self._img2tensor(frame).to(self.device) for frame in frame_list] - - if len(inputs) <= 1: - return 1.0 # Not enough frames for interpolation - - # Check dimensions and resize if needed - inputs = self._check_dim_and_resize(inputs) - h, w = inputs[0].shape[-2:] - - # Calculate scale based on available memory - scale = self._calculate_scale(h, w) - - # Perform frame interpolation - outputs = self._interpolate_frames(inputs, scale) - - # Convert outputs back to images - output_images = [self._tensor2img(out) for out in outputs] - - # Compute VFI score - vfi_score = self._compute_vfi_score(np_frames, output_images) - - # Normalize score to [0, 1] range (higher is better) - # Original score is average pixel difference [0, 255], we normalize and invert - normalized_score = (255.0 - vfi_score) / 255.0 - - return normalized_score
-
- - - -
-[docs] -class DynamicDegreeAnalyzer(VideoQualityAnalyzer): - """Analyzer for evaluating dynamic degree (motion intensity) in videos using RAFT optical flow. - - This analyzer measures the amount and intensity of motion in videos by: - 1. Computing optical flow between consecutive frames using RAFT - 2. Calculating flow magnitude for each pixel - 3. Extracting top 5% highest flow magnitudes - 4. Determining if video has sufficient dynamic motion based on thresholds - - The score represents whether the video contains dynamic motion (1.0) or is mostly static (0.0). - """ - -
-[docs] - def __init__(self, model_path: str = "model/raft/raft-things.pth", - device: str = "cuda", sample_fps: int = 8): - """Initialize the DynamicDegreeAnalyzer. - - Args: - model_path: Path to the RAFT model checkpoint - device: Device to run the model on ('cuda' or 'cpu') - sample_fps: Target FPS for frame sampling (default: 8) - """ - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.sample_fps = sample_fps - - # Load RAFT model - self.model = self._load_raft_model(model_path) - self.model.eval() - self.model.to(self.device)
- - - def _load_raft_model(self, model_path: str): - """Load RAFT optical flow model. - - Args: - model_path: Path to the model checkpoint - - Returns: - Loaded RAFT model - """ - from model.raft.core.raft import RAFT - from easydict import EasyDict as edict - - # Configure RAFT arguments - args = edict({ - "model": model_path, - "small": False, - "mixed_precision": False, - "alternate_corr": False - }) - - # Create and load model - model = RAFT(args) - - if os.path.exists(model_path): - ckpt = torch.load(model_path, map_location="cpu") - # Remove 'module.' prefix if present (from DataParallel) - new_ckpt = {k.replace('module.', ''): v for k, v in ckpt.items()} - model.load_state_dict(new_ckpt) - - return model - - def _extract_frames_for_flow(self, frames: List[Image.Image], target_fps: int = 8) -> List[torch.Tensor]: - """Extract and prepare frames for optical flow computation. - - Args: - frames: List of PIL Image frames - target_fps: Target sampling rate (default: 8 fps) - - Returns: - List of prepared frame tensors - """ - # Estimate original FPS and calculate sampling interval - # Assuming 30fps original video, adjust sampling to get ~8fps - total_frames = len(frames) - assumed_fps = 30 # Common video fps - interval = max(1, round(assumed_fps / target_fps)) - - # Sample frames at interval - sampled_frames = [] - for i in range(0, total_frames, interval): - frame = frames[i] - # Convert PIL to numpy array - frame_np = np.array(frame) - # Convert to tensor and normalize - frame_tensor = torch.from_numpy(frame_np.astype(np.uint8)).permute(2, 0, 1).float() - frame_tensor = frame_tensor[None].to(self.device) - sampled_frames.append(frame_tensor) - - return sampled_frames - - def _compute_flow_magnitude(self, flow: torch.Tensor) -> float: - """Compute flow magnitude score from optical flow. - - Args: - flow: Optical flow tensor (B, 2, H, W) - - Returns: - Flow magnitude score - """ - # Extract flow components - flow_np = flow[0].permute(1, 2, 0).cpu().numpy() - u = flow_np[:, :, 0] - v = flow_np[:, :, 1] - - # Compute flow magnitude - magnitude = np.sqrt(np.square(u) + np.square(v)) - - # Get top 5% highest magnitudes - h, w = magnitude.shape - magnitude_flat = magnitude.flatten() - cut_index = int(h * w * 0.05) - - # Sort in descending order and take mean of top 5% - top_magnitudes = np.sort(-magnitude_flat)[:cut_index] - mean_magnitude = np.mean(np.abs(top_magnitudes)) - - return mean_magnitude.item() - - def _determine_dynamic_threshold(self, frame_shape: tuple, num_frames: int) -> dict: - """Determine thresholds for dynamic motion detection. - - Args: - frame_shape: Shape of the frame tensor - num_frames: Number of frames in the video - - Returns: - Dictionary with threshold parameters - """ - # Scale threshold based on image resolution - scale = min(frame_shape[-2:]) # min of height and width - magnitude_threshold = 6.0 * (scale / 256.0) - - # Scale count threshold based on number of frames - count_threshold = round(4 * (num_frames / 16.0)) - - return { - "magnitude_threshold": magnitude_threshold, - "count_threshold": count_threshold - } - - def _check_dynamic_motion(self, flow_scores: List[float], thresholds: dict) -> bool: - """Check if video has dynamic motion based on flow scores. - - Args: - flow_scores: List of optical flow magnitude scores - thresholds: Threshold parameters - - Returns: - True if video has dynamic motion, False otherwise - """ - magnitude_threshold = thresholds["magnitude_threshold"] - count_threshold = thresholds["count_threshold"] - - # Count frames with significant motion - motion_count = 0 - for score in flow_scores: - if score > magnitude_threshold: - motion_count += 1 - if motion_count >= count_threshold: - return True - - return False - -
-[docs] - def analyze(self, frames: List[Image.Image]) -> float: - """Analyze dynamic degree (motion intensity) in video frames. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Dynamic degree score: 1.0 if video has dynamic motion, 0.0 if mostly static - """ - if len(frames) < 2: - return 0.0 # Cannot compute optical flow with less than 2 frames - - # Extract and prepare frames for optical flow - prepared_frames = self._extract_frames_for_flow(frames, self.sample_fps) - - if len(prepared_frames) < 2: - return 0.0 - - # Determine thresholds based on video characteristics - thresholds = self._determine_dynamic_threshold( - prepared_frames[0].shape, - len(prepared_frames) - ) - - # Compute optical flow between consecutive frames - flow_scores = [] - - with torch.no_grad(): - for frame1, frame2 in zip(prepared_frames[:-1], prepared_frames[1:]): - # Pad frames if necessary - from model.raft.core.utils_core.utils import InputPadder - padder = InputPadder(frame1.shape) - frame1_padded, frame2_padded = padder.pad(frame1, frame2) - - # Compute optical flow - _, flow_up = self.model(frame1_padded, frame2_padded, iters=20, test_mode=True) - - # Calculate flow magnitude score - magnitude_score = self._compute_flow_magnitude(flow_up) - flow_scores.append(magnitude_score) - - # Check if video has dynamic motion - has_dynamic_motion = self._check_dynamic_motion(flow_scores, thresholds) - - # Return binary score: 1.0 for dynamic, 0.0 for static - return 1.0 if has_dynamic_motion else 0.0
-
- - - -
-[docs] -class BackgroundConsistencyAnalyzer(VideoQualityAnalyzer): - """Analyzer for evaluating background consistency across video frames using CLIP features. - - This analyzer measures how consistently the background appears across frames by: - 1. Extracting CLIP visual features from each frame - 2. Computing cosine similarity between consecutive frames and with the first frame - 3. Averaging these similarities to get a consistency score - - Similar to SubjectConsistencyAnalyzer but focuses on overall visual consistency - including background elements, making it suitable for detecting background stability. - """ - -
-[docs] - def __init__(self, model_name: str = "ViT-B/32", device: str = "cuda"): - """Initialize the BackgroundConsistencyAnalyzer. - - Args: - model_name: CLIP model name (default: "ViT-B/32") - device: Device to run the model on ('cuda' or 'cpu') - """ - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - - # Load CLIP model - self.model, self.preprocess = self._load_clip_model(model_name) - self.model.eval() - self.model.to(self.device) - - # Image transform for CLIP (when processing tensor inputs) - self.tensor_transform = self._get_clip_tensor_transform(224)
- - - def _load_clip_model(self, model_name: str): - """Load CLIP model. - - Args: - model_name: Name of the CLIP model to load - - Returns: - Tuple of (model, preprocess_function) - """ - import clip - - model, preprocess = clip.load(model_name, device=self.device) - return model, preprocess - - def _get_clip_tensor_transform(self, n_px: int): - """Get CLIP transform for tensor inputs. - - Args: - n_px: Target image size - - Returns: - Transform composition for tensor inputs - """ - try: - from torchvision.transforms import InterpolationMode - BICUBIC = InterpolationMode.BICUBIC - except ImportError: - BICUBIC = Image.BICUBIC - - return Compose([ - Resize(n_px, interpolation=BICUBIC, antialias=False), - CenterCrop(n_px), - transforms.Lambda(lambda x: x.float().div(255.0)), - Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711)), - ]) - - def _prepare_images_for_clip(self, frames: List[Image.Image]) -> torch.Tensor: - """Prepare PIL images for CLIP processing. - - Args: - frames: List of PIL Image frames - - Returns: - Batch tensor of preprocessed images - """ - # Use CLIP's built-in preprocess for PIL images - images = [] - for frame in frames: - processed = self.preprocess(frame) - images.append(processed) - - # Stack into batch tensor - return torch.stack(images).to(self.device) - -
-[docs] - def analyze(self, frames: List[Image.Image]) -> float: - """Analyze background consistency across video frames. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Background consistency score (higher is better, range [0, 1]) - """ - if len(frames) < 2: - return 1.0 # Single frame is perfectly consistent with itself - - # Prepare images for CLIP - images = self._prepare_images_for_clip(frames) - - # Extract CLIP features - with torch.no_grad(): - image_features = self.model.encode_image(images) - image_features = F.normalize(image_features, dim=-1, p=2) - - video_sim = 0.0 - frame_count = 0 - - # Compute similarity between frames - for i in range(len(image_features)): - image_feature = image_features[i].unsqueeze(0) - - if i == 0: - # Store first frame features - first_image_feature = image_feature - else: - # Compute similarity with previous frame - sim_prev = max(0.0, F.cosine_similarity(former_image_feature, image_feature).item()) - - # Compute similarity with first frame - sim_first = max(0.0, F.cosine_similarity(first_image_feature, image_feature).item()) - - # Average the two similarities - frame_sim = (sim_prev + sim_first) / 2.0 - video_sim += frame_sim - frame_count += 1 - - # Store current features as previous for next iteration - former_image_feature = image_feature - - # Return average similarity across all frame pairs - if frame_count > 0: - return video_sim / frame_count - else: - return 1.0
-
- - -
-[docs] -class ImagingQualityAnalyzer(VideoQualityAnalyzer): - """Analyzer for evaluating imaging quality of videos. - - This analyzer measures the quality of videos by: - 1. Inputting frames into MUSIQ image quality predictor - 2. Determining if the video is blurry or has artifacts - - The score represents the quality of the video (higher is better). - """ -
-[docs] - def __init__(self, model_path: str = "model/musiq/musiq_spaq_ckpt-358bb6af.pth", device: str = "cuda"): - self.device = torch.device(device if torch.cuda.is_available() else "cpu") - self.model = self._load_musiq(model_path) - self.model.to(self.device) - self.model.eval()
- - - def _load_musiq(self, model_path: str): - """Load MUSIQ model. - - Args: - model_path: Path to the MUSIQ model checkpoint - - Returns: - MUSIQ model - """ - # if the model_path not exists - # then makedir and wget - if not os.path.exists(model_path): - os.makedirs(os.path.dirname(model_path), exist_ok=True) - wget_command = ['wget', 'https://github.com/chaofengc/IQA-PyTorch/releases/download/v0.1-weights/musiq_spaq_ckpt-358bb6af.pth', '-P', os.path.dirname(model_path)] - subprocess.run(wget_command, check=True) - - from pyiqa.archs.musiq_arch import MUSIQ - model = MUSIQ(pretrained_model_path=model_path) - - return model - - def _preprocess_frames(self, frames: List[Image.Image]) -> torch.Tensor: - """Preprocess frames for MUSIQ model. - - Args: - frames: List of PIL Image frames - - Returns: - Preprocessed frames as tensor - """ - frames = [pil_to_torch(frame, normalize=False) for frame in frames] # [(C, H, W)] - frames = torch.stack(frames) # (T, C, H, W) - - _, _, h, w = frames.size() - if max(h, w) > 512: - scale = 512./max(h, w) - frames = F.interpolate(frames, size=(int(scale * h), int(scale * w)), mode='bilinear', align_corners=False) - - return frames - -
-[docs] - def analyze(self, frames: List[Image.Image]) -> float: - """Analyze imaging quality of video frames. - - Args: - frames: List of PIL Image frames representing the video - - Returns: - Imaging quality score (higher is better, range [0, 1]) - """ - frame_tensor = self._preprocess_frames(frames) - acc_score_video = 0.0 - for i in range(len(frame_tensor)): - frame = frame_tensor[i].unsqueeze(0).to(self.device) - score = self.model(frame) - acc_score_video += float(score) - return acc_score_video / (100 * len(frame_tensor))
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html deleted file mode 100644 index 36ce7fa..0000000 --- a/docs/_build/html/_modules/index.html +++ /dev/null @@ -1,987 +0,0 @@ - - - - - - - - Overview: module code — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- -

All modules for which code is available

- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/inversions/base_inversion.html b/docs/_build/html/_modules/inversions/base_inversion.html deleted file mode 100644 index 3bdf95b..0000000 --- a/docs/_build/html/_modules/inversions/base_inversion.html +++ /dev/null @@ -1,1021 +0,0 @@ - - - - - - - - inversions.base_inversion — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for inversions.base_inversion

-import torch
-from typing import Optional, Callable
-
-
-[docs] -class BaseInversion(): -
-[docs] - def __init__(self, - scheduler, - unet, - device, - ): - self.scheduler = scheduler - self.unet = unet - self.device = device
- - - def _prepare_latent_for_unet(self, latents, do_cfg, unet): - """ - Inputs: - latents: [B,C,H,W] or [B,F,C,H,W] - do_cfg: bool - Outputs: - latent_model_input: Tensor ready for UNet input - info: dict containing shape info - """ - - is_video_unet = any(isinstance(m, torch.nn.Conv3d) for m in unet.modules()) - - info = { - "do_cfg": do_cfg, - "is_video_unet": is_video_unet - } - - # ------------------------------ - # Case 1: image latent (4D) - # ------------------------------ - if latents.ndim == 4: - # [B, C, H, W] - info["shp"] = latents.shape - if do_cfg: - latents = torch.cat([latents, latents], dim=0) - return latents, info - - # ------------------------------ - # Case 2: video latent (5D) - # ------------------------------ - assert latents.ndim == 5, "Video input must be 4D or 5D latent." - B, F, C, H, W = latents.shape - info["shp"] = (B, F, C, H, W) - - if is_video_unet: - # video UNet (Conv3d): [B, C, F, H, W] - latents = latents.permute(0, 2, 1, 3, 4).contiguous() - if do_cfg: - latents = torch.cat([latents, latents], dim=0) - return latents, info - - else: - # image UNet but input video → flatten frames - latents = latents.reshape(B * F, C, H, W) - info["flatten"] = (B, F) - if do_cfg: - latents = torch.cat([latents, latents], dim=0) - return latents, info - - def _restore_latent_from_unet(self, noise_pred, info, guidance_scale): - """ - Inputs: - noise_pred: UNet Input - info: prepare 阶段保存的结构信息 - guidance_scale: CFG scale - 输出: - 与原输入匹配格式的噪声预测 - 图像: [B,C,H,W] - 视频: [B,F,C,H,W] - """ - - do_cfg = info["do_cfg"] - is_video_unet = info["is_video_unet"] - shp = info["shp"] - - # 1. CFG 合并 - if do_cfg: - noise_pred_cond, noise_pred_uncond = noise_pred.chunk(2, dim=0) - noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond) - - # -------------------------- - # Case 1: 输入是图像 latent - # -------------------------- - if len(shp) == 4: - # [B, C, H, W] — no reshape needed - return noise_pred - - # -------------------------- - # Case 2: 输入是视频 latent - # -------------------------- - B, F, C, H, W = shp - - if is_video_unet: - # video UNet 输出格式: [B, C, F, H, W] - noise_pred = noise_pred.permute(0, 2, 1, 3, 4).contiguous() - return noise_pred - - else: - # 图像 UNet 输出格式: [B*F, C, H, W] - noise_pred = noise_pred.reshape(B, F, C, H, W) - return noise_pred - - @torch.inference_mode() - def forward_diffusion(self, - use_old_emb_i=25, - text_embeddings=None, - old_text_embeddings=None, - new_text_embeddings=None, - latents: Optional[torch.FloatTensor] = None, - num_inference_steps: int = 10, - guidance_scale: float = 7.5, - callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, - callback_steps: Optional[int] = 1, - inverse_opt=True, - inv_order=None, - **kwargs, - ): - pass - - def _apply_guidance_scale(self, model_output, guidance_scale): - if guidance_scale > 1.0: - noise_pred_uncond, noise_pred_text = model_output.chunk(2) - noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) - return noise_pred - else: - return model_output
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/inversions/ddim_inversion.html b/docs/_build/html/_modules/inversions/ddim_inversion.html deleted file mode 100644 index 88840d4..0000000 --- a/docs/_build/html/_modules/inversions/ddim_inversion.html +++ /dev/null @@ -1,1014 +0,0 @@ - - - - - - - - inversions.ddim_inversion — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for inversions.ddim_inversion

-from functools import partial
-import torch
-from typing import Optional, Callable
-from tqdm import tqdm
-from .base_inversion import BaseInversion
-import warnings
-
-
-[docs] -class DDIMInversion(BaseInversion): -
-[docs] - def __init__(self, - scheduler, - unet, - device, - ): - super(DDIMInversion, self).__init__(scheduler, unet, device) - self.forward_diffusion = partial(self.backward_diffusion, reverse_process=True)
- - - def _backward_ddim(self, x_t, alpha_t, alpha_tm1, eps_xt): - """ from noise to image""" - return ( - alpha_tm1**0.5 - * ( - (alpha_t**-0.5 - alpha_tm1**-0.5) * x_t - + ((1 / alpha_tm1 - 1) ** 0.5 - (1 / alpha_t - 1) ** 0.5) * eps_xt - ) - + x_t - ) - - @torch.inference_mode() - def backward_diffusion( - self, - use_old_emb_i=25, - text_embeddings=None, - old_text_embeddings=None, - new_text_embeddings=None, - latents: Optional[torch.FloatTensor] = None, - num_inference_steps: int = 50, - guidance_scale: float = 7.5, - callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, - callback_steps: Optional[int] = 1, - reverse_process: True = False, - **kwargs, - ): - """ Generate image from text prompt and latents - """ - ## If kwargs has inv_order, warn that it is ignored for DDIM Inversion - if "inv_order" in kwargs: - warnings.warn("inv_order is ignored for DDIM Inversion") - if "inverse_opt" in kwargs: - warnings.warn("inverse_opt is ignored for DDIM Inversion") - - # Keep a list of inverted latents as the process goes on - intermediate_latents = [] - # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) - # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` - # corresponds to doing no classifier free guidance. - do_classifier_free_guidance = guidance_scale > 1.0 - # set timesteps - self.scheduler.set_timesteps(num_inference_steps) - # Some schedulers like PNDM have timesteps as arrays - # It's more optimized to move all timesteps to correct device beforehand - timesteps_tensor = self.scheduler.timesteps.to(self.device) - # scale the initial noise by the standard deviation required by the scheduler - latents = latents * self.scheduler.init_noise_sigma - - if old_text_embeddings is not None and new_text_embeddings is not None: - prompt_to_prompt = True - else: - prompt_to_prompt = False - - - for i, t in enumerate(tqdm(timesteps_tensor if not reverse_process else reversed(timesteps_tensor))): - if prompt_to_prompt: - if i < use_old_emb_i: - text_embeddings = old_text_embeddings - else: - text_embeddings = new_text_embeddings - - # expand the latents if we are doing classifier free guidance - # latent_model_input = ( - # torch.cat([latents] * 2) if do_classifier_free_guidance else latents - # ) - latent_model_input, info = self._prepare_latent_for_unet(latents, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - - # predict the noise residual - noise_pred_raw = self.unet( - latent_model_input, t, encoder_hidden_states=text_embeddings - ).sample - - # reshape back if needed - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - - # # perform guidance - # noise_pred = self._apply_guidance_scale(noise_pred, guidance_scale) - - prev_timestep = ( - t - - self.scheduler.config.num_train_timesteps - // self.scheduler.num_inference_steps - ) - # call the callback, if provided - if callback is not None and i % callback_steps == 0: - callback(i, t, latents) - - # ddim - alpha_prod_t = self.scheduler.alphas_cumprod[t] - alpha_prod_t_prev = ( - self.scheduler.alphas_cumprod[prev_timestep] - if prev_timestep >= 0 - else self.scheduler.final_alpha_cumprod - ) - if reverse_process: - alpha_prod_t, alpha_prod_t_prev = alpha_prod_t_prev, alpha_prod_t - latents = self._backward_ddim( - x_t=latents, - alpha_t=alpha_prod_t, - alpha_tm1=alpha_prod_t_prev, - eps_xt=noise_pred, - ) - # Save intermediate latents - intermediate_latents.append(latents.clone()) - return intermediate_latents
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/inversions/exact_inversion.html b/docs/_build/html/_modules/inversions/exact_inversion.html deleted file mode 100644 index c4f6edc..0000000 --- a/docs/_build/html/_modules/inversions/exact_inversion.html +++ /dev/null @@ -1,1436 +0,0 @@ - - - - - - - - inversions.exact_inversion — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for inversions.exact_inversion

-from .base_inversion import BaseInversion
-import torch
-from typing import Optional, Callable
-from tqdm import tqdm
-from torch.optim.lr_scheduler import ReduceLROnPlateau
-# from utils.DPMSolverPatch import convert_model_output
-from diffusers import DPMSolverMultistepInverseScheduler
-
-
-[docs] -class ExactInversion(BaseInversion): -
-[docs] - def __init__(self, - scheduler, - unet, - device, - ): - scheduler = DPMSolverMultistepInverseScheduler.from_config(scheduler.config) - super(ExactInversion, self).__init__(scheduler, unet, device)
- - - @torch.inference_mode() - def _fixedpoint_correction(self, x, s, t, x_t, r=None, order=1, n_iter=500, step_size=0.1, th=1e-3, - model_s_output=None, model_r_output=None, text_embeddings=None, guidance_scale=3.0, - scheduler=False, factor=0.5, patience=20, anchor=False, warmup=True, warmup_time=20): - do_classifier_free_guidance = guidance_scale > 1.0 - if order==1: - input = x.clone() - original_step_size = step_size - - # step size scheduler, reduce when not improved - if scheduler: - step_scheduler = StepScheduler(current_lr=step_size, factor=factor, patience=patience) - - lambda_s, lambda_t = self.scheduler.lambda_t[s], self.scheduler.lambda_t[t] - alpha_s, alpha_t = self.scheduler.alpha_t[s], self.scheduler.alpha_t[t] - sigma_s, sigma_t = self.scheduler.sigma_t[s], self.scheduler.sigma_t[t] - h = lambda_t - lambda_s - phi_1 = torch.expm1(-h) - - for i in range(n_iter): - # step size warmup - if warmup: - if i < warmup_time: - step_size = original_step_size * (i+1)/(warmup_time) - - latent_model_input, info = self._prepare_latent_for_unet(input, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - - noise_pred_raw = self.unet(latent_model_input , s, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - - model_output = self.scheduler.convert_model_output(model_output=noise_pred, sample=input) - - x_t_pred = (sigma_t / sigma_s) * input - (alpha_t * phi_1 ) * model_output - - loss = torch.nn.functional.mse_loss(x_t_pred, x_t, reduction='sum') - - if loss.item() < th: - break - - # forward step method - input = input - step_size * (x_t_pred- x_t) - - if scheduler: - step_size = step_scheduler.step(loss) - - return input - - elif order==2: - assert r is not None - input = x.clone() - original_step_size = step_size - - # step size scheduler, reduce when not improved - if scheduler: - step_scheduler = StepScheduler(current_lr=step_size, factor=factor, patience=patience) - - lambda_r, lambda_s, lambda_t = self.scheduler.lambda_t[r], self.scheduler.lambda_t[s], self.scheduler.lambda_t[t] - sigma_r, sigma_s, sigma_t = self.scheduler.sigma_t[r], self.scheduler.sigma_t[s], self.scheduler.sigma_t[t] - alpha_s, alpha_t = self.scheduler.alpha_t[s], self.scheduler.alpha_t[t] - h_0 = lambda_s - lambda_r - h = lambda_t - lambda_s - r0 = h_0 / h - phi_1 = torch.expm1(-h) - - for i in range(n_iter): - # step size warmup - if warmup: - if i < warmup_time: - step_size = original_step_size * (i+1)/(warmup_time) - - latent_model_input, info = self._prepare_latent_for_unet(input, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - - noise_pred_raw = self.unet(latent_model_input, s, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_output = self.scheduler.convert_model_output(model_output=noise_pred, sample=input) - - x_t_pred = (sigma_t / sigma_s) * input - (alpha_t * phi_1) * model_output - - # high-order term approximation - if i==0: - d = (1./ r0) * (model_s_output - model_r_output) - diff_term = 0.5 * alpha_t * phi_1 * d - - x_t_pred = x_t_pred - diff_term - - loss = torch.nn.functional.mse_loss(x_t_pred, x_t, reduction='sum') - - if loss.item() < th: - break - - # forward step method - input = input - step_size * (x_t_pred- x_t) - - if scheduler: - step_size = step_scheduler.step(loss) - if anchor: - input = (1 - 1/(i+2)) * input + (1/(i+2))*x - return input - else: - raise NotImplementedError - - @torch.inference_mode() - def forward_diffusion( - self, - use_old_emb_i=25, - text_embeddings=None, - old_text_embeddings=None, - new_text_embeddings=None, - latents: Optional[torch.FloatTensor] = None, - num_inference_steps: int = 10, - guidance_scale: float = 7.5, - callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, - callback_steps: Optional[int] = 1, - inverse_opt=False, - inv_order=0, - **kwargs, - ): - with torch.no_grad(): - # Keep a list of inverted latents as the process goes on - intermediate_latents = [] - # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) - # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` - # corresponds to doing no classifier free guidance. - do_classifier_free_guidance = guidance_scale > 1.0 - - self.scheduler.set_timesteps(num_inference_steps) - timesteps_tensor = self.scheduler.timesteps.to(self.device) - latents = latents * self.scheduler.init_noise_sigma - - if old_text_embeddings is not None and new_text_embeddings is not None: - prompt_to_prompt = True - else: - prompt_to_prompt = False - - if inv_order is None: - inv_order = self.scheduler.solver_order - inverse_opt = (inv_order != 0) - - # timesteps_tensor = reversed(timesteps_tensor) # inversion process - - self.unet = self.unet.float() - latents = latents.float() - text_embeddings = text_embeddings.float() - - for i, t in enumerate(tqdm(timesteps_tensor)): - if self.scheduler.step_index is None: - self.scheduler._init_step_index(t) - - if prompt_to_prompt: - if i < use_old_emb_i: - text_embeddings = old_text_embeddings - else: - text_embeddings = new_text_embeddings - - if i+1 < len(timesteps_tensor): - next_timestep = timesteps_tensor[i+1] - else: - next_timestep = ( - t - + self.scheduler.config.num_train_timesteps - // self.scheduler.num_inference_steps - ) - - - # call the callback, if provided - if callback is not None and i % callback_steps == 0: - callback(i, t, latents) - - - # Our Algorithm - - # Algorithm 1 - if inv_order < 2 or (inv_order == 2 and i == 0): - # s = t - # t = prev_timestep - s = next_timestep - t = ( - next_timestep - - self.scheduler.config.num_train_timesteps - // self.scheduler.num_inference_steps - ) - - lambda_s, lambda_t = self.scheduler.lambda_t[s], self.scheduler.lambda_t[t] - sigma_s, sigma_t = self.scheduler.sigma_t[s], self.scheduler.sigma_t[t] - h = lambda_t - lambda_s - alpha_s, alpha_t = self.scheduler.alpha_t[s], self.scheduler.alpha_t[t] - phi_1 = torch.expm1(-h) - - # expand the latents if classifier free guidance is used - latent_model_input, info = self._prepare_latent_for_unet(latents, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - # predict the noise residual - noise_pred_raw = self.unet(latent_model_input, s, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - - model_s = self.scheduler.convert_model_output(model_output=noise_pred, sample=latents) - x_t = latents - - # Line 5 - latents = (sigma_s / sigma_t) * (latents + alpha_t * phi_1 * model_s) - - # Line 7 : Update - if (inverse_opt): - # Alg.2 Line 11 - if (inv_order == 2 and i == 0): - latents = self._fixedpoint_correction(latents, s, t, x_t, order=1, text_embeddings=text_embeddings, guidance_scale=guidance_scale, - step_size=1, scheduler=True) - else: - latents = self._fixedpoint_correction(latents, s, t, x_t, order=1, text_embeddings=text_embeddings, guidance_scale=guidance_scale, - step_size=0.5, scheduler=True) - - # Save intermediate latents - intermediate_latents.append(latents.clone()) - - self.scheduler._step_index += 1 - - # Algorithm 2 - elif inv_order == 2: - with torch.no_grad(): - # Line 3 ~ 13 - if (i + 1 < len(timesteps_tensor)): - y = latents.clone() - - # s = t - # t = prev_timestep - s = next_timestep - if i+2 < len(timesteps_tensor): - r = timesteps_tensor[i + 2] - elif i+1 < len(timesteps_tensor): ## i == len(timesteps_tensor) - 2 - r = s + self.scheduler.config.num_train_timesteps // self.scheduler.num_inference_steps - else: ## i == len(timesteps_tensor) - 1 - r = 0 - - # r = timesteps_tensor[i + 1] if i+1 < len(timesteps_tensor) else 0 - - # Line 3 ~ 6 : fine-grained naive DDIM inversion - for tt in range(t,s,10): - ss = tt + 10 - lambda_s, lambda_t = self.scheduler.lambda_t[ss], self.scheduler.lambda_t[tt] - sigma_s, sigma_t = self.scheduler.sigma_t[ss], self.scheduler.sigma_t[tt] - h = lambda_t - lambda_s - alpha_s, alpha_t = self.scheduler.alpha_t[ss], self.scheduler.alpha_t[tt] - phi_1 = torch.expm1(-h) - - y_input, info = self._prepare_latent_for_unet(y, do_classifier_free_guidance, self.unet) - y_input = self.scheduler.scale_model_input(y_input, tt) - - noise_pred_raw = self.unet(y_input, ss, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_s = self.scheduler.convert_model_output(model_output=noise_pred, sample=y) - y = (sigma_s / sigma_t) * (y + alpha_t * phi_1 * model_s) # Line 5 - y_t = y.clone() - for tt in range(s, r,10): - ss = tt + 10 - lambda_s, lambda_t = self.scheduler.lambda_t[ss], self.scheduler.lambda_t[tt] - sigma_s, sigma_t = self.scheduler.sigma_t[ss], self.scheduler.sigma_t[tt] - h = lambda_t - lambda_s - alpha_s, alpha_t = self.scheduler.alpha_t[ss], self.scheduler.alpha_t[tt] - phi_1 = torch.expm1(-h) - - y_input, info = self._prepare_latent_for_unet(y, do_classifier_free_guidance, self.unet) - y_input = self.scheduler.scale_model_input(y_input, tt) - - model_s = self.unet(y_input, ss, encoder_hidden_states=text_embeddings).sample - noise_pred_raw = self._apply_guidance_scale(model_s, guidance_scale) - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_s = self.scheduler.convert_model_output(model_output=noise_pred, sample=y) - y = (sigma_s / sigma_t) * (y + alpha_t * phi_1 * model_s) # Line 5 - - - # Line 8 ~ 12 : backward Euler - # t = prev_timestep - s = next_timestep - if i+2 < len(timesteps_tensor): - r = timesteps_tensor[i + 2] - elif i+1 < len(timesteps_tensor): ## i == len(timesteps_tensor) - 2 - r = s + self.scheduler.config.num_train_timesteps // self.scheduler.num_inference_steps - else: ## i == len(timesteps_tensor) - 1 - r = 0 - - lambda_s, lambda_t = self.scheduler.lambda_t[s], self.scheduler.lambda_t[t] - sigma_s, sigma_t = self.scheduler.sigma_t[s], self.scheduler.sigma_t[t] - h = lambda_t - lambda_s - alpha_s, alpha_t = self.scheduler.alpha_t[s], self.scheduler.alpha_t[t] - phi_1 = torch.expm1(-h) - - x_t = latents - - # y_t_model_input = torch.cat([y_t] * 2) if do_classifier_free_guidance else y_t - y_t_model_input, info = self._prepare_latent_for_unet(y_t, do_classifier_free_guidance, self.unet) - y_t_model_input = self.scheduler.scale_model_input(y_t_model_input, s) - - noise_pred_raw = self.unet(y_t_model_input, s, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_s_output = self.scheduler.convert_model_output(model_output=noise_pred, sample=y_t) - - # y_model_input = torch.cat([y] * 2) if do_classifier_free_guidance else y - y_model_input, info = self._prepare_latent_for_unet(y, do_classifier_free_guidance, self.unet) - y_model_input = self.scheduler.scale_model_input(y_model_input, r) - - noise_pred_raw = self.unet(y_model_input, r, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_r_output = self.scheduler.convert_model_output(model_output=noise_pred, sample=y) - - latents = y_t.clone() # Line 7 - - # Line 11 : Update - if inverse_opt: - latents = self._fixedpoint_correction(latents, s, t, x_t, order=2, r=r, - model_s_output=model_s_output, model_r_output=model_r_output, text_embeddings=text_embeddings, guidance_scale=guidance_scale, - step_size=10/t, scheduler=False) - - - # Line 14 ~ 17 - elif (i + 1 == len(timesteps_tensor)): - # s = t - # t = prev_timestep - s = next_timestep - - lambda_s, lambda_t = self.scheduler.lambda_t[s], self.scheduler.lambda_t[t] - sigma_s, sigma_t = self.scheduler.sigma_t[s], self.scheduler.sigma_t[t] - h = lambda_t - lambda_s - alpha_s, alpha_t = self.scheduler.alpha_t[s], self.scheduler.alpha_t[t] - phi_1 = torch.expm1(-h) - - # latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents - latent_model_input, info = self._prepare_latent_for_unet(latents, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - - noise_pred_raw = self.unet(latent_model_input, s, encoder_hidden_states=text_embeddings).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - model_s = self.scheduler.convert_model_output(model_output=noise_pred, sample=latents) - - x_t = latents - - # Line 16 - latents = (sigma_s / sigma_t) * (latents + alpha_t * phi_1 * model_s) - - # Line 17 : Update - if (inverse_opt): - latents = self._fixedpoint_correction(latents, s, t, x_t, order=1, text_embeddings=text_embeddings, guidance_scale=guidance_scale, - step_size=10/t, scheduler=True) - else: - raise Exception("Index Error!") - - self.scheduler._step_index += 1 - # Save intermediate latents - intermediate_latents.append(latents.clone()) - else: - pass - - return intermediate_latents - - @torch.inference_mode() - def backward_diffusion( - self, - latents: Optional[torch.FloatTensor] = None, - num_inference_steps: int = 10, - guidance_scale: float = 7.5, - callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, - callback_steps: Optional[int] = 1, - inv_order=None, - **kwargs, - ): - """ - Reconstruct z_0 from z_T via the forward diffusion process - - Sampling (Explicit Method): - Order 1: Forward Euler (DDIM) - Eq. (5) - Order 2: DPM-Solver++(2M) - Eq. (6) - """ - with torch.no_grad(): - # 1. Setup - do_classifier_free_guidance = guidance_scale > 1.0 - self.scheduler.set_timesteps(num_inference_steps) - timesteps_tensor = self.scheduler.timesteps.to(self.device) - - # If no inv_order provided, default to scheduler's configuration - if inv_order is None: - inv_order = self.scheduler.solver_order - - self.unet = self.unet.float() - latents = latents.float() - - # last output from the model to be used in higher order methods - old_model_output = None - - # 2. Denoising Loop (T -> 0) - for i, t in enumerate(tqdm(timesteps_tensor)): - if self.scheduler.step_index is None: - self.scheduler._init_step_index(t) - - # s (prev_timestep in diffusion terms, lower noise) - if i + 1 < len(timesteps_tensor): - s = timesteps_tensor[i + 1] - else: - s = torch.tensor(0, device=self.device) - - # 3. Prepare Model Input - latent_model_input, info = self._prepare_latent_for_unet(latents, do_classifier_free_guidance, self.unet) - latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) - - # 4. Predict Noise/Data - noise_pred_raw = self.unet(latent_model_input, t, encoder_hidden_states=kwargs.get("text_embeddings")).sample - noise_pred = self._restore_latent_from_unet(noise_pred_raw, info, guidance_scale) - - # Transform prediction according to the type of prediction required by the scheduler - model_output = self.scheduler.convert_model_output(model_output=noise_pred, sample=latents) - - # 5. Calculate Solver Parameters - # Aquire alpha, sigma, lambda - lambda_t, lambda_s = self.scheduler.lambda_t[t], self.scheduler.lambda_t[s] - alpha_t, alpha_s = self.scheduler.alpha_t[t], self.scheduler.alpha_t[s] - sigma_t, sigma_s = self.scheduler.sigma_t[t], self.scheduler.sigma_t[s] - - h = lambda_s - lambda_t # step size - phi_1 = torch.expm1(-h) # e^{-h} - 1 - - # 6. Sampling Step (Explicit) - - # Case 1: First Order (DDIM) or First Step of Second Order - if inv_order == 1 or i == 0: - # Eq. (5): Forward Euler - # x_{t_i} = (sigma_{t_i} / sigma_{t_{i-1}}) * x_{t_{i-1}} - alpha_{t_i} * (e^{-h} - 1) * x_theta - # x_s = (sigma_s/sigma_t) * latents - alpha_s * phi_1 * model_output - latents = (sigma_s / sigma_t) * latents - (alpha_s * phi_1) * model_output - - # Case 2: Second Order (DPM-Solver++ 2M) - elif inv_order == 2: - # t_prev (old t) -> t (current) -> s (next) - t_prev = timesteps_tensor[i - 1] - lambda_prev = self.scheduler.lambda_t[t_prev] - h_0 = lambda_t - lambda_prev - r = h_0 / h - - # Eq. (6) - # D = (1 + 1/(2r)) * x_theta(t) - (1/(2r)) * x_theta(t_prev) - D = (1 + 1 / (2 * r)) * model_output - (1 / (2 * r)) * old_model_output - - latents = (sigma_s / sigma_t) * latents - (alpha_s * phi_1) * D - - # Update history - old_model_output = model_output - self.scheduler._step_index += 1 - - if callback is not None and i % callback_steps == 0: - callback(i, t, latents) - - return latents
- - - -
-[docs] -class StepScheduler(ReduceLROnPlateau): -
-[docs] - def __init__(self, mode='min', current_lr=0, factor=0.1, patience=10, - threshold=1e-4, threshold_mode='rel', cooldown=0, - min_lr=0, eps=1e-8, verbose=False): - if factor >= 1.0: - raise ValueError('Factor should be < 1.0.') - self.factor = factor - if current_lr == 0: - raise ValueError('Step size cannot be 0') - - self.min_lr = min_lr - self.current_lr = current_lr - self.patience = patience - self.verbose = verbose - self.cooldown = cooldown - self.cooldown_counter = 0 - self.mode = mode - self.threshold = threshold - self.threshold_mode = threshold_mode - self.best = None - self.num_bad_epochs = None - self.mode_worse = None # the worse value for the chosen mode - self.eps = eps - self.last_epoch = 0 - self._init_is_better(mode=mode, threshold=threshold, - threshold_mode=threshold_mode) - self._reset()
- - -
-[docs] - def step(self, metrics, epoch=None): - # convert `metrics` to float, in case it's a zero-dim Tensor - current = float(metrics) - if epoch is None: - epoch = self.last_epoch + 1 - else: - import warnings - warnings.warn("EPOCH_DEPRECATION_WARNING", UserWarning) - self.last_epoch = epoch - - if self.is_better(current, self.best): - self.best = current - self.num_bad_epochs = 0 - else: - self.num_bad_epochs += 1 - - if self.in_cooldown: - self.cooldown_counter -= 1 - self.num_bad_epochs = 0 # ignore any bad epochs in cooldown - - if self.num_bad_epochs > self.patience: - self._reduce_lr(epoch) - self.cooldown_counter = self.cooldown - self.num_bad_epochs = 0 - - return self.current_lr
- - - def _reduce_lr(self, epoch): - old_lr = self.current_lr - new_lr = max(self.current_lr * self.factor, self.min_lr) - if old_lr - new_lr > self.eps: - self.current_lr = new_lr - if self.verbose: - epoch_str = ("%.2f" if isinstance(epoch, float) else - "%.5d") % epoch - print('Epoch {}: reducing learning rate' - ' to {:.4e}.'.format(epoch_str,new_lr))
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/utils/callbacks.html b/docs/_build/html/_modules/utils/callbacks.html deleted file mode 100644 index 5fbe32a..0000000 --- a/docs/_build/html/_modules/utils/callbacks.html +++ /dev/null @@ -1,951 +0,0 @@ - - - - - - - - utils.callbacks — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for utils.callbacks

-import torch
-from typing import List
-
-
-[docs] -class DenoisingLatentsCollector: -
-[docs] - def __init__(self, save_every_n_steps: int = 1, to_cpu: bool = True): - """Initialize the latents collector. - - Args: - save_every_n_steps (int, optional): Save latents every n steps. Defaults to 1. - to_cpu (bool, optional): Whether to move latents to CPU. Defaults to True. - """ - - self.save_every_n_steps = save_every_n_steps - self.to_cpu = to_cpu - self.data = [] - self._call_count = 0
- - - def __call__(self, step: int, timestep: int, latents: torch.Tensor): - self._call_count += 1 - - if self._call_count % self.save_every_n_steps == 0: - latents_to_save = latents.clone() - if self.to_cpu: - latents_to_save = latents_to_save.cpu() - - self.data.append({ - 'step': step, - 'timestep': timestep, - 'latents': latents_to_save, - 'call_count': self._call_count - }) - - @property - def latents_list(self) -> List[torch.Tensor]: - """Return the list of latents.""" - return [item['latents'] for item in self.data] - - @property - def timesteps_list(self) -> List[int]: - """Return the list of timesteps.""" - return [item['timestep'] for item in self.data] - -
-[docs] - def get_latents_at_step(self, step: int) -> torch.Tensor: - """Get the latents at a specific step.""" - for item in self.data: - if item['step'] == step: - return item['latents'] - raise ValueError(f"No latents found for step {step}")
- - -
-[docs] - def clear(self): - """Clear the collected data.""" - self.data.clear() - self._call_count = 0
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/utils/diffusion_config.html b/docs/_build/html/_modules/utils/diffusion_config.html deleted file mode 100644 index 8183eaf..0000000 --- a/docs/_build/html/_modules/utils/diffusion_config.html +++ /dev/null @@ -1,1012 +0,0 @@ - - - - - - - - utils.diffusion_config — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for utils.diffusion_config

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from dataclasses import dataclass
-from typing import Optional, Union, Any, Dict
-import torch
-from diffusers import DPMSolverMultistepScheduler, StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline
-from utils.pipeline_utils import (
-    get_pipeline_type, 
-    PIPELINE_TYPE_IMAGE, 
-    PIPELINE_TYPE_TEXT_TO_VIDEO, 
-    PIPELINE_TYPE_IMAGE_TO_VIDEO
-)
-
-
-[docs] -@dataclass -class DiffusionConfig: - """Configuration class for diffusion models and parameters.""" - -
-[docs] - def __init__( - self, - scheduler: DPMSolverMultistepScheduler, - pipe: Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline], - device: str, - guidance_scale: float = 7.5, - num_images: int = 1, - num_inference_steps: int = 50, - num_inversion_steps: Optional[int] = None, - image_size: tuple = (512, 512), - dtype: torch.dtype = torch.float16, - gen_seed: int = 0, - init_latents_seed: int = 0, - inversion_type: str = "ddim", - num_frames: int = -1, # -1 means image generation; >=1 means video generation - **kwargs - ): - self.device = device - self.scheduler = scheduler - self.pipe = pipe - self.guidance_scale = guidance_scale - self.num_images = num_images - self.num_inference_steps = num_inference_steps - self.num_inversion_steps = num_inversion_steps or num_inference_steps - self.image_size = image_size - self.dtype = dtype - self.gen_seed = gen_seed - self.init_latents_seed = init_latents_seed - self.inversion_type = inversion_type - self.num_frames = num_frames - # Store additional kwargs - self.gen_kwargs = kwargs - - ## Assertions - assert self.inversion_type in ["ddim", "exact"], f"Invalid inversion type: {self.inversion_type}" - - ## Validate pipeline type and parameter compatibility - self._validate_pipeline_config()
- - - def _validate_pipeline_config(self) -> None: - """Validate pipeline type and parameter compatibility.""" - pipeline_type = get_pipeline_type(self.pipe) - - if pipeline_type is None: - raise ValueError(f"Unsupported pipeline type: {type(self.pipe)}") - - # Validate num_frames setting based on pipeline type - if pipeline_type == PIPELINE_TYPE_IMAGE: - if self.num_frames >= 1: - # Auto-correct for image pipelines - self.num_frames = -1 - elif pipeline_type in [PIPELINE_TYPE_TEXT_TO_VIDEO, PIPELINE_TYPE_IMAGE_TO_VIDEO]: - if self.num_frames < 1: - raise ValueError(f"For {pipeline_type} pipelines, num_frames must be >= 1, got {self.num_frames}") - - @property - def pipeline_type(self) -> str: - """Get the pipeline type.""" - return get_pipeline_type(self.pipe) - - @property - def is_video_pipeline(self) -> bool: - """Check if this is a video pipeline.""" - return self.pipeline_type in [PIPELINE_TYPE_TEXT_TO_VIDEO, PIPELINE_TYPE_IMAGE_TO_VIDEO] - - @property - def is_image_pipeline(self) -> bool: - """Check if this is an image pipeline.""" - return self.pipeline_type == PIPELINE_TYPE_IMAGE - - @property - def pipeline_requirements(self) -> Dict[str, Any]: - """Get the requirements for this pipeline type.""" - if self.pipeline_type == PIPELINE_TYPE_IMAGE: - return { - "required_params": [], - "optional_params": ["height", "width", "num_images_per_prompt"] - } - elif self.pipeline_type == PIPELINE_TYPE_TEXT_TO_VIDEO: - return { - "required_params": ["num_frames"], - "optional_params": ["height", "width", "fps"] - } - elif self.pipeline_type == PIPELINE_TYPE_IMAGE_TO_VIDEO: - return { - "required_params": ["input_image", "num_frames"], - "optional_params": ["height", "width", "fps"] - } - else: - return {"required_params": [], "optional_params": []}
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/utils/media_utils.html b/docs/_build/html/_modules/utils/media_utils.html deleted file mode 100644 index 86ac09c..0000000 --- a/docs/_build/html/_modules/utils/media_utils.html +++ /dev/null @@ -1,1312 +0,0 @@ - - - - - - - - utils.media_utils — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for utils.media_utils

-import torch
-import numpy as np
-from torchvision import transforms
-from torchvision.transforms.functional import pil_to_tensor
-from PIL import Image
-import cv2
-from diffusers import StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline
-from transformers import get_cosine_schedule_with_warmup
-from typing import Optional, Callable, Union, List, Tuple, Dict, Any
-from tqdm import tqdm
-import copy
-from utils.pipeline_utils import (
-    get_pipeline_type, 
-    PIPELINE_TYPE_IMAGE, 
-    PIPELINE_TYPE_TEXT_TO_VIDEO, 
-    PIPELINE_TYPE_IMAGE_TO_VIDEO
-)
-
-# ===== Common Utility Functions =====
-
-
-[docs] -def torch_to_numpy(tensor) -> np.ndarray: - """Convert tensor to numpy array with proper scaling.""" - tensor = (tensor / 2 + 0.5).clamp(0, 1) - if tensor.dim() == 4: # Image: B, C, H, W - return tensor.cpu().permute(0, 2, 3, 1).numpy() - elif tensor.dim() == 5: # Video: B, C, F, H, W - return tensor.cpu().permute(0, 2, 3, 4, 1).numpy() - else: - raise ValueError(f"Unsupported tensor dimension: {tensor.dim()}")
- - -
-[docs] -def pil_to_torch(image: Image.Image, normalize: bool = True) -> torch.Tensor: - """Convert PIL image to torch tensor.""" - tensor = pil_to_tensor(image) / 255.0 - if normalize: - tensor = 2.0 * tensor - 1.0 # Normalize to [-1, 1] - return tensor
- - -
-[docs] -def numpy_to_pil(img: np.ndarray) -> Image.Image: - """Convert numpy array to PIL image.""" - if img.dtype != np.uint8: - img = (img * 255).clip(0, 255).astype(np.uint8) - return Image.fromarray(img)
- - -
-[docs] -def cv2_to_pil(img: np.ndarray) -> Image.Image: - """Convert cv2 image (numpy array) to PIL image.""" - if img.dtype != np.uint8: - img = (img * 255).clip(0, 255).astype(np.uint8) - return Image.fromarray(img)
- - -
-[docs] -def pil_to_cv2(pil_img: Image.Image) -> np.ndarray: - """Convert PIL image to cv2 format (numpy array).""" - return np.asarray(pil_img) / 255.0
- - -
-[docs] -def transform_to_model_format(media: Union[Image.Image, List[Image.Image], np.ndarray, torch.Tensor], - target_size: Optional[int] = None) -> torch.Tensor: - """ - Transform image or video frames to model input format. - For image, `media` is a PIL image that will be resized to `target_size`(if provided) and then normalized to [-1, 1] and permuted to [C, H, W] from [H, W, C]. - For video, `media` is a list of frames (PIL images or numpy arrays) that will be normalized to [-1, 1] and permuted to [F, C, H, W] from [F, H, W, C]. - - Args: - media: PIL image or list of frames or video tensor - target_size: Target size for resize operations (for images) - - Returns: - torch.Tensor: Normalized tensor ready for model input - """ - if isinstance(media, Image.Image): - # Single image - if target_size is not None: - transform = transforms.Compose([ - transforms.Resize(target_size), - transforms.CenterCrop(target_size), - transforms.ToTensor(), - ]) - else: - transform = transforms.ToTensor() - return 2.0 * transform(media) - 1.0 - - elif isinstance(media, list): - # List of frames (PIL images or numpy arrays) - if all(isinstance(frame, Image.Image) for frame in media): - return torch.stack([2.0 * transforms.ToTensor()(frame) - 1.0 for frame in media]) - elif all(isinstance(frame, np.ndarray) for frame in media): - return torch.stack([2.0 * transforms.ToTensor()(numpy_to_pil(frame)) - 1.0 for frame in media]) - else: - raise ValueError("All frames must be either PIL images or numpy arrays") - - elif isinstance(media, np.ndarray) and media.ndim >= 3: - # Video numpy array - if media.ndim == 3: # Single frame: H, W, C - return 2.0 * transforms.ToTensor()(media) - 1.0 - elif media.ndim == 4: # Multiple frames: F, H, W, C - return torch.stack([2.0 * transforms.ToTensor()(frame) - 1.0 for frame in media]) - else: - raise ValueError(f"Unsupported numpy array shape: {media.shape}") - - else: - raise ValueError(f"Unsupported media type: {type(media)}")
- - -# ===== Latent Processing Functions ===== - -
-[docs] -def set_inversion(pipe: Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline], inversion_type: str): - """Set the inversion for the given pipe.""" - from inversions import DDIMInversion, ExactInversion - - if inversion_type == "ddim": - return DDIMInversion(pipe.scheduler, pipe.unet, pipe.device) - elif inversion_type == "exact": - return ExactInversion(pipe.scheduler, pipe.unet, pipe.device) - else: - raise ValueError(f"Invalid inversion type: {inversion_type}")
- - -
-[docs] -def get_random_latents(pipe: Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline], - latents=None, num_frames=None, height=512, width=512, generator=None) -> torch.Tensor: - """Get random latents for the given pipe.""" - pipeline_type = get_pipeline_type(pipe) - height = height or pipe.unet.config.sample_size * pipe.vae_scale_factor - width = width or pipe.unet.config.sample_size * pipe.vae_scale_factor - - batch_size = 1 - device = pipe._execution_device - num_channels_latents = pipe.unet.config.in_channels - - # Handle different pipeline types - if pipeline_type == PIPELINE_TYPE_IMAGE or num_frames is None: - latents = pipe.prepare_latents( - batch_size, - num_channels_latents, - height, - width, - pipe.text_encoder.dtype, - device, - generator, - latents, - ) - else: - # Video pipelines with frames - latents = pipe.prepare_latents( - batch_size, - num_channels_latents, - num_frames, - height, - width, - pipe.text_encoder.dtype, - device, - generator, - latents, - ) - - return latents
- - -# ===== Image-Specific Functions ===== - -def _get_image_latents(pipe: StableDiffusionPipeline, image: torch.Tensor, - sample: bool = True, rng_generator: Optional[torch.Generator] = None, - decoder_inv: bool = False) -> torch.Tensor: - """Get the image latents for the given image.""" - encoding_dist = pipe.vae.encode(image).latent_dist - if sample: - encoding = encoding_dist.sample(generator=rng_generator) - else: - encoding = encoding_dist.mode() - latents = encoding * 0.18215 - if decoder_inv: - latents = decoder_inv_optimization(pipe, latents, image) - return latents - -def _decode_image_latents(pipe: StableDiffusionPipeline, latents: torch.FloatTensor) -> torch.Tensor: - """Decode the image from the given latents.""" - scaled_latents = 1 / 0.18215 * latents - image = pipe.vae.decode(scaled_latents, return_dict=False)[0] - image = (image / 2 + 0.5).clamp(0, 1) - return image - -
-[docs] -def decoder_inv_optimization(pipe: StableDiffusionPipeline, latents: torch.FloatTensor, - image: torch.FloatTensor, num_steps: int = 100) -> torch.Tensor: - """ - Optimize latents to better reconstruct the input image by minimizing the error between - decoded latents and original image. - - Args: - pipe: The diffusion pipeline - latents: Initial latents - image: Target image - num_steps: Number of optimization steps - - Returns: - torch.Tensor: Optimized latents - """ - input_image = image.clone().float() - z = latents.clone().float().detach() - z.requires_grad_(True) - - loss_function = torch.nn.MSELoss(reduction='sum') - optimizer = torch.optim.Adam([z], lr=0.1) - lr_scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=10, num_training_steps=num_steps) - - for i in tqdm(range(num_steps)): - # Decode without normalization to match original implementation - scaled_latents = 1 / 0.18215 * z - x_pred = pipe.vae.decode(scaled_latents, return_dict=False)[0] - - loss = loss_function(x_pred, input_image) - - optimizer.zero_grad() - loss.backward() - optimizer.step() - lr_scheduler.step() - - return z.detach()
- - -# ===== Video-Specific Functions ===== - -def _get_video_latents(pipe: Union[TextToVideoSDPipeline, StableVideoDiffusionPipeline], - video_frames: torch.Tensor, sample: bool = True, - rng_generator: Optional[torch.Generator] = None, - permute: bool = True, - decoder_inv: bool = False) -> torch.Tensor: - """ - Encode video frames to latents. - - Args: - pipe: Video diffusion pipeline - video_frames: Tensor of video frames [F, C, H, W] - sample: Whether to sample from the latent distribution - rng_generator: Random generator for sampling - permute: Whether to permute the latents to [B, C, F, H, W] format - decoder_inv: Whether to decode the latents - - Returns: - torch.Tensor: Video latents - """ - encoding_dist = pipe.vae.encode(video_frames).latent_dist - if sample: - encoding = encoding_dist.sample(generator=rng_generator) - else: - encoding = encoding_dist.mode() - latents = (encoding * 0.18215).unsqueeze(0) - if permute: - latents = latents.permute(0, 2, 1, 3, 4) - if decoder_inv: # TODO: Implement decoder inversion for video latents - raise NotImplementedError("Decoder inversion is not implemented for video latents") - return latents - -
-[docs] -def tensor2vid(video: torch.Tensor, processor, output_type: str = "np"): - """ - Convert video tensor to desired output format. - - Args: - video: Video tensor [B, C, F, H, W] - processor: Video processor from the diffusion pipeline - output_type: Output type - 'np', 'pt', or 'pil' - - Returns: - Video in requested format - """ - batch_size, channels, num_frames, height, width = video.shape - outputs = [] - for batch_idx in range(batch_size): - batch_vid = video[batch_idx].permute(1, 0, 2, 3) - batch_output = processor.postprocess(batch_vid, output_type) - outputs.append(batch_output) - - if output_type == "np": - outputs = np.stack(outputs) - elif output_type == "pt": - outputs = torch.stack(outputs) - elif not output_type == "pil": - raise ValueError(f"{output_type} does not exist. Please choose one of ['np', 'pt', 'pil']") - - return outputs
- - -def _decode_video_latents(pipe: Union[TextToVideoSDPipeline, StableVideoDiffusionPipeline], - latents: torch.Tensor, - num_frames: Optional[int] = None) -> np.ndarray: - """ - Decode latents to video frames. - - Args: - pipe: Video diffusion pipeline - latents: Video latents - num_frames: Number of frames to decode - - Returns: - np.ndarray: Video frames - """ - if num_frames is None: - video_tensor = pipe.decode_latents(latents) - else: - video_tensor = pipe.decode_latents(latents, num_frames) - video = tensor2vid(video_tensor, pipe.video_processor) - return video - -
-[docs] -def convert_video_frames_to_images(frames: List[Union[np.ndarray, Image.Image]]) -> List[Image.Image]: - """ - Convert video frames to a list of PIL.Image objects. - - Args: - frames: List of video frames (numpy arrays or PIL images) - - Returns: - List[Image.Image]: List of PIL images - """ - pil_frames = [] - for frame in frames: - if isinstance(frame, np.ndarray): - # Convert numpy array to PIL - pil_frames.append(numpy_to_pil(frame)) - elif isinstance(frame, Image.Image): - # Already a PIL image - pil_frames.append(frame) - else: - raise ValueError(f"Unsupported frame type: {type(frame)}") - return pil_frames
- - -
-[docs] -def save_video_frames(frames: List[Union[np.ndarray, Image.Image]], save_dir: str) -> None: - """ - Save video frames to a directory. - - Args: - frames: List of video frames (numpy arrays or PIL images) - save_dir: Directory to save frames - """ - if isinstance(frames[0], np.ndarray): - frames = [(frame * 255).astype(np.uint8) if frame.dtype != np.uint8 else frame for frame in frames] - elif isinstance(frames[0], Image.Image): - frames = [np.array(frame) for frame in frames] - - for i, frame in enumerate(frames): - img = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) - cv2.imwrite(f'{save_dir}/{i:02d}.png', img)
- - -# ===== Utility Functions for Working with Different Pipeline Types ===== - -
-[docs] -def get_media_latents(pipe: Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline], - media: Union[torch.Tensor, List[torch.Tensor]], - sample: bool = True, - rng_generator: Optional[torch.Generator] = None, - decoder_inv: bool = False) -> torch.Tensor: - """ - Get latents from media (either image or video) based on pipeline type. - - Args: - pipe: Diffusion pipeline - media: Image tensor or video frames tensor - sample: Whether to sample from the latent distribution - rng_generator: Random generator for sampling - decoder_inv: Whether to use decoder inversion optimization - Returns: - torch.Tensor: Media latents - """ - pipeline_type = get_pipeline_type(pipe) - - if pipeline_type == PIPELINE_TYPE_IMAGE: - return _get_image_latents(pipe, media, sample, rng_generator, decoder_inv) - elif pipeline_type in [PIPELINE_TYPE_TEXT_TO_VIDEO, PIPELINE_TYPE_IMAGE_TO_VIDEO]: - permute = pipeline_type == PIPELINE_TYPE_TEXT_TO_VIDEO - return _get_video_latents(pipe, media, sample, rng_generator, permute, decoder_inv) - else: - raise ValueError(f"Unsupported pipeline type: {pipeline_type}")
- - -
-[docs] -def decode_media_latents(pipe: Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline], - latents: torch.Tensor, - num_frames: Optional[int] = None) -> Union[torch.Tensor, np.ndarray]: - """ - Decode latents to media (either image or video) based on pipeline type. - - Args: - pipe: Diffusion pipeline - latents: Media latents - num_frames: Number of frames (for video) - - Returns: - Union[torch.Tensor, np.ndarray]: Decoded media - """ - pipeline_type = get_pipeline_type(pipe) - - if pipeline_type == PIPELINE_TYPE_IMAGE: - return _decode_image_latents(pipe, latents) - elif pipeline_type in [PIPELINE_TYPE_TEXT_TO_VIDEO, PIPELINE_TYPE_IMAGE_TO_VIDEO]: - return _decode_video_latents(pipe, latents, num_frames) - else: - raise ValueError(f"Unsupported pipeline type: {pipeline_type}")
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/utils/pipeline_utils.html b/docs/_build/html/_modules/utils/pipeline_utils.html deleted file mode 100644 index 75c7ad8..0000000 --- a/docs/_build/html/_modules/utils/pipeline_utils.html +++ /dev/null @@ -1,1007 +0,0 @@ - - - - - - - - utils.pipeline_utils — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for utils.pipeline_utils

-from diffusers import StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline
-from typing import Any, Dict, List, Optional, Union
-
-# Pipeline type constants
-PIPELINE_TYPE_IMAGE = "image"
-PIPELINE_TYPE_TEXT_TO_VIDEO = "t2v"
-PIPELINE_TYPE_IMAGE_TO_VIDEO = "i2v"
-
-
-[docs] -def get_pipeline_type(pipeline) -> Optional[str]: - """ - Determine the type of diffusion pipeline. - - Args: - pipeline: The diffusion pipeline object - - Returns: - str: One of the pipeline type constants or None if not recognized - """ - if isinstance(pipeline, StableDiffusionPipeline): - return PIPELINE_TYPE_IMAGE - elif isinstance(pipeline, TextToVideoSDPipeline): - return PIPELINE_TYPE_TEXT_TO_VIDEO - elif isinstance(pipeline, StableVideoDiffusionPipeline): - return PIPELINE_TYPE_IMAGE_TO_VIDEO - else: - return None
- - -
-[docs] -def is_video_pipeline(pipeline) -> bool: - """ - Check if the pipeline is a video generation pipeline. - - Args: - pipeline: The diffusion pipeline object - - Returns: - bool: True if the pipeline is a video generation pipeline, False otherwise - """ - pipeline_type = get_pipeline_type(pipeline) - return pipeline_type in [PIPELINE_TYPE_TEXT_TO_VIDEO, PIPELINE_TYPE_IMAGE_TO_VIDEO]
- - -
-[docs] -def is_image_pipeline(pipeline) -> bool: - """ - Check if the pipeline is an image generation pipeline. - - Args: - pipeline: The diffusion pipeline object - - Returns: - bool: True if the pipeline is an image generation pipeline, False otherwise - """ - return get_pipeline_type(pipeline) == PIPELINE_TYPE_IMAGE
- - -
-[docs] -def is_t2v_pipeline(pipeline) -> bool: - """ - Check if the pipeline is a text-to-video pipeline. - - Args: - pipeline: The diffusion pipeline object - - Returns: - bool: True if the pipeline is a text-to-video pipeline, False otherwise - """ - return get_pipeline_type(pipeline) == PIPELINE_TYPE_TEXT_TO_VIDEO
- - -
-[docs] -def is_i2v_pipeline(pipeline) -> bool: - """ - Check if the pipeline is an image-to-video pipeline. - - Args: - pipeline: The diffusion pipeline object - - Returns: - bool: True if the pipeline is an image-to-video pipeline, False otherwise - """ - return get_pipeline_type(pipeline) == PIPELINE_TYPE_IMAGE_TO_VIDEO
- - -
-[docs] -def get_pipeline_requirements(pipeline_type: str) -> Dict[str, Any]: - """ - Get the requirements for a specific pipeline type (required parameters, etc.) - - Args: - pipeline_type: The pipeline type string - - Returns: - Dict: A dictionary containing the pipeline requirements - """ - if pipeline_type == PIPELINE_TYPE_IMAGE: - return { - "required_params": [], - "optional_params": ["height", "width", "num_images_per_prompt"] - } - elif pipeline_type == PIPELINE_TYPE_TEXT_TO_VIDEO: - return { - "required_params": ["num_frames"], - "optional_params": ["height", "width", "fps"] - } - elif pipeline_type == PIPELINE_TYPE_IMAGE_TO_VIDEO: - return { - "required_params": ["input_image", "num_frames"], - "optional_params": ["height", "width", "fps"] - } - else: - return {"required_params": [], "optional_params": []}
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/utils/utils.html b/docs/_build/html/_modules/utils/utils.html deleted file mode 100644 index b86d7e7..0000000 --- a/docs/_build/html/_modules/utils/utils.html +++ /dev/null @@ -1,981 +0,0 @@ - - - - - - - - utils.utils — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for utils.utils

-import os
-import json
-import torch
-import numpy as np
-import random
-
-
-[docs] -def inherit_docstring(cls): - """ - Inherit docstrings from base classes to methods without docstrings. - - This decorator automatically applies the docstring from a base class method - to a derived class method if the derived method doesn't have its own docstring. - - Args: - cls: The class to enhance with inherited docstrings - - Returns: - cls: The enhanced class - """ - for name, func in vars(cls).items(): - if not callable(func) or func.__doc__ is not None: - continue - - # Look for same method in base classes - for base in cls.__bases__: - base_func = getattr(base, name, None) - if base_func and getattr(base_func, "__doc__", None): - func.__doc__ = base_func.__doc__ - break - - return cls
- - - -
-[docs] -def load_config_file(path: str) -> dict: - """Load a JSON configuration file from the specified path and return it as a dictionary.""" - try: - with open(path, 'r') as f: - config_dict = json.load(f) - return config_dict - - except FileNotFoundError: - print(f"Error: The file '{path}' does not exist.") - return None - except json.JSONDecodeError as e: - print(f"Error decoding JSON in '{path}': {e}") - # Handle other potential JSON decoding errors here - return None - except Exception as e: - print(f"An unexpected error occurred: {e}") - # Handle other unexpected errors here - return None
- - - -
-[docs] -def load_json_as_list(input_file: str) -> list: - """Load a JSON file as a list of dictionaries.""" - res = [] - with open(input_file, 'r') as f: - lines = f.readlines() - for line in lines: - d = json.loads(line) - res.append(d) - return res
- - - -
-[docs] -def create_directory_for_file(file_path) -> None: - """Create the directory for the specified file path if it does not already exist.""" - directory = os.path.dirname(file_path) - if not os.path.exists(directory): - os.makedirs(directory)
- - - -
-[docs] -def set_random_seed(seed: int): - """Set random seeds for reproducibility.""" - - torch.manual_seed(seed + 0) - torch.cuda.manual_seed(seed + 1) - torch.cuda.manual_seed_all(seed + 2) - np.random.seed((seed + 3) % 2**32) - torch.cuda.manual_seed_all(seed + 4) - random.seed(seed + 5)
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/auto_visualization.html b/docs/_build/html/_modules/visualize/auto_visualization.html deleted file mode 100644 index bcfdf50..0000000 --- a/docs/_build/html/_modules/visualize/auto_visualization.html +++ /dev/null @@ -1,1034 +0,0 @@ - - - - - - - - visualize.auto_visualization — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.auto_visualization

-import torch
-from PIL import Image
-from typing import List, Optional, Dict, Any, Union
-from abc import ABC, abstractmethod
-from visualize.data_for_visualization import DataForVisualization
-import importlib
-from visualize.base import BaseVisualizer
-
-# Mapping of algorithm names to visualization data classes
-VISUALIZATION_DATA_MAPPING = {
-    'TR': 'visualize.tr.TreeRingVisualizer',
-    'GS': 'visualize.gs.GaussianShadingVisualizer', 
-    'PRC': 'visualize.prc.PRCVisualizer',
-    'RI': 'visualize.ri.RingIDVisualizer',
-    'WIND': 'visualize.wind.WINDVisualizer',
-    'SEAL': 'visualize.seal.SEALVisualizer',
-    'ROBIN': 'visualize.robin.ROBINVisualizer',
-    'VideoShield': 'visualize.videoshield.VideoShieldVisualizer',
-    'SFW': 'visualize.sfw.SFWVisualizer',
-    'VideoMark': 'visualize.videomark.VideoMarkVisualizer',
-    'GM': 'visualize.gm.GaussMarkerVisualizer',
-}
-
-
-[docs] -class AutoVisualizer: - """ - Factory class for creating visualization data instances. - - This is a generic visualization data factory that will instantiate the appropriate - visualization data class based on the algorithm name. - - This class cannot be instantiated directly using __init__() (throws an error). - """ - -
-[docs] - def __init__(self): - raise EnvironmentError( - "AutoVisualizer is designed to be instantiated " - "using the `AutoVisualizer.load(algorithm_name, **kwargs)` method." - )
- - - @staticmethod - def _get_visualization_class_name(algorithm_name: str) -> Optional[str]: - """Get the visualization data class name from the algorithm name.""" - for alg_name, class_path in VISUALIZATION_DATA_MAPPING.items(): - if algorithm_name.lower() == alg_name.lower(): - return class_path - return None - -
-[docs] - @classmethod - def load(cls, algorithm_name: str, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1) -> BaseVisualizer: - """ - Load the visualization data instance based on the algorithm name. - - Args: - algorithm_name: Name of the watermarking algorithm (e.g., 'TR', 'GS', 'PRC') - data_for_visualization: DataForVisualization instance - - Returns: - BaseVisualizer: Instance of the appropriate visualization data class - - Raises: - ValueError: If the algorithm name is not supported - """ - # Check if the algorithm exists - class_path = cls._get_visualization_class_name(algorithm_name) - - if algorithm_name != data_for_visualization.algorithm_name: - raise ValueError(f"Algorithm name mismatch: {algorithm_name} != {data_for_visualization.algorithm_name}") - - if class_path is None: - supported_algs = list(VISUALIZATION_DATA_MAPPING.keys()) - raise ValueError( - f"Invalid algorithm name: {algorithm_name}. " - f"Supported algorithms: {', '.join(supported_algs)}" - ) - - # Load the visualization data module and class - module_name, class_name = class_path.rsplit('.', 1) - try: - module = importlib.import_module(module_name) - visualization_class = getattr(module, class_name) - except (ImportError, AttributeError) as e: - raise ImportError( - f"Failed to load visualization data class '{class_name}' " - f"from module '{module_name}': {e}" - ) - - # Create and validate the instance - instance = visualization_class(data_for_visualization=data_for_visualization, dpi=dpi, watermarking_step=watermarking_step) - return instance
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/base.html b/docs/_build/html/_modules/visualize/base.html deleted file mode 100644 index d0c5ab5..0000000 --- a/docs/_build/html/_modules/visualize/base.html +++ /dev/null @@ -1,1702 +0,0 @@ - - - - - - - - visualize.base — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.base

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from abc import ABC, abstractmethod
-from typing import Optional, Dict, Any, List
-import torch
-from PIL import Image
-from visualize.data_for_visualization import DataForVisualization
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-import numpy as np
-from typing import Tuple
-from numpy.fft import fft2, fftshift, ifft2, ifftshift
-from PIL import Image
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-
-
-[docs] -class BaseVisualizer(ABC): - """Base class for watermark visualization data""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1, is_video: bool = False): - """Initialize with common attributes""" - self.data = data_for_visualization - self.dpi = dpi - self.watermarking_step = -1 # The step for inserting the watermark, defaults to -1 for the last step - self.is_video = is_video # Whether this is for T2V (video) or T2I (image) model
- - - def _fft_transform(self, latent: torch.Tensor) -> np.ndarray: - """ - Apply FFT transform to the latent tensor of the watermarked image. - """ - return fftshift(fft2(latent.cpu().numpy())) - - def _ifft_transform(self, fft_data: np.ndarray) -> np.ndarray: - """ - Apply inverse FFT transform to the fft data. - """ - return ifft2(ifftshift(fft_data)) - - def _get_latent_data(self, latents: torch.Tensor, channel: Optional[int] = None, frame: Optional[int] = None) -> torch.Tensor: - """ - Extract latent data with proper indexing for both T2I and T2V models. - - Parameters: - latents: The latent tensor [B, C, H, W] for T2I or [B, C, F, H, W] for T2V - channel: The channel index to extract - frame: The frame index to extract (only for T2V models) - - Returns: - The extracted latent tensor - """ - if self.is_video: - # T2V model: [B, C, F, H, W] - if frame is not None: - if channel is not None: - return latents[0, channel, frame] # [H, W] - else: - return latents[0, :, frame] # [C, H, W] - else: - # If no frame specified, use the middle frame - mid_frame = latents.shape[2] // 2 - if channel is not None: - return latents[0, channel, mid_frame] # [H, W] - else: - return latents[0, :, mid_frame] # [C, H, W] - else: - # T2I model: [B, C, H, W] - if channel is not None: - return latents[0, channel] # [H, W] - else: - return latents[0] # [C, H, W] - -
-[docs] - def draw_orig_latents(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - title: str = "Original Latents", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the original latents of the watermarked image. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - frame (Optional[int]): The frame index for T2V models. If None, uses middle frame for videos. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - latent_data = self._get_latent_data(self.data.orig_watermarked_latents, channel, frame).cpu().numpy() - im = ax.imshow(latent_data, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the latent channel - latent_data = self._get_latent_data(self.data.orig_watermarked_latents, i, frame).cpu().numpy() - im = sub_ax.imshow(latent_data, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_orig_latents_fft(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - title: str = "Original Latents in Fourier Domain", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the original latents of the watermarked image in the Fourier domain. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - frame (Optional[int]): The frame index for T2V models. If None, uses middle frame for videos. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - latent_data = self._get_latent_data(self.data.orig_watermarked_latents, channel, frame) - fft_data = self._fft_transform(latent_data) - - im = ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the FFT of latent channel - latent_data = self._get_latent_data(self.data.orig_watermarked_latents, i, frame) - fft_data = self._fft_transform(latent_data) - im = sub_ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_inverted_latents(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - step: Optional[int] = None, - title: str = "Inverted Latents", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the inverted latents of the watermarked image. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - frame (Optional[int]): The frame index for T2V models. If None, uses middle frame for videos. - step (Optional[int]): The timestep of the inverted latents. If None, the last timestep is used. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - # Get inverted latents data - if step is None: - reversed_latents = self.data.reversed_latents[self.watermarking_step] - else: - reversed_latents = self.data.reversed_latents[step] - - latent_data = self._get_latent_data(reversed_latents, channel, frame).cpu().numpy() - im = ax.imshow(latent_data, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Get inverted latents data - if step is None: - reversed_latents = self.data.reversed_latents[self.watermarking_step] - else: - reversed_latents = self.data.reversed_latents[step] - - latent_data = self._get_latent_data(reversed_latents, i, frame).cpu().numpy() - - # Draw the latent channel - im = sub_ax.imshow(latent_data, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_inverted_latents_fft(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - step: int = -1, - title: str = "Inverted Latents in Fourier Domain", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the inverted latents of the watermarked image in the Fourier domain. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - frame (Optional[int]): The frame index for T2V models. If None, uses middle frame for videos. - step (Optional[int]): The timestep of the inverted latents. If None, the last timestep is used. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - reversed_latents = self.data.reversed_latents[step] - latent_data = self._get_latent_data(reversed_latents, channel, frame) - fft_data = self._fft_transform(latent_data) - - im = ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the FFT of inverted latent channel - reversed_latents = self.data.reversed_latents[step] - latent_data = self._get_latent_data(reversed_latents, i, frame) - fft_data = self._fft_transform(latent_data) - im = sub_ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_diff_latents_fft(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - title: str = "Difference between Original and Inverted Latents in Fourier Domain", - cmap: str = "coolwarm", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the difference between the original and inverted initial latents of the watermarked image in the Fourier domain. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - frame (Optional[int]): The frame index for T2V models. If None, uses middle frame for videos. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - # Get original and inverted latents - orig_data = self._get_latent_data(self.data.orig_watermarked_latents, channel, frame).cpu().numpy() - - reversed_latents = self.data.reversed_latents[self.watermarking_step] - inv_data = self._get_latent_data(reversed_latents, channel, frame).cpu().numpy() - - # Compute difference - diff_data = orig_data - inv_data - - # Convert to tensor for FFT transform - diff_tensor = torch.tensor(diff_data) - fft_data = self._fft_transform(diff_tensor) - - im = ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Get original and inverted latents - orig_data = self._get_latent_data(self.data.orig_watermarked_latents, i, frame).cpu().numpy() - - reversed_latents = self.data.reversed_latents[self.watermarking_step] - inv_data = self._get_latent_data(reversed_latents, i, frame).cpu().numpy() - - # Compute difference and FFT - diff_data = orig_data - inv_data - diff_tensor = torch.tensor(diff_data) - fft_data = self._fft_transform(diff_tensor) - - # Draw the FFT of difference - im = sub_ax.imshow(np.abs(fft_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_watermarked_image(self, - title: str = "Watermarked Image", - num_frames: int = 4, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the watermarked image or video frames. - - For images (is_video=False), displays a single image. - For videos (is_video=True), displays a grid of video frames. - - Parameters: - title (str): The title of the plot. - num_frames (int): Number of frames to display for videos (default: 4). - vmin (Optional[float]): Minimum value for colormap. - vmax (Optional[float]): Maximum value for colormap. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if self.is_video: - # Video visualization: display multiple frames - return self._draw_video_frames(title=title, num_frames=num_frames, ax=ax, **kwargs) - else: - # Image visualization: display single image - return self._draw_single_image(title=title, vmin=vmin, vmax=vmax, ax=ax, **kwargs)
- - - def _draw_single_image(self, - title: str = "Watermarked Image", - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw a single watermarked image. - - Parameters: - title (str): The title of the plot. - vmin (Optional[float]): Minimum value for colormap. - vmax (Optional[float]): Maximum value for colormap. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # Convert image data to numpy array - if torch.is_tensor(self.data.image): - # Handle tensor format (like in RI watermark) - if self.data.image.dim() == 4: # [B, C, H, W] - image_array = self.data.image[0].permute(1, 2, 0).cpu().numpy() - elif self.data.image.dim() == 3: # [C, H, W] - image_array = self.data.image.permute(1, 2, 0).cpu().numpy() - else: - image_array = self.data.image.cpu().numpy() - - # Normalize to 0-1 if needed - if image_array.max() > 1.0: - image_array = image_array / 255.0 - - # Normalize [-1, 1] range to [0, 1] for imshow - if image_array.min() < 0: - image_array = (image_array + 1.0) / 2.0 - - # Clip to valid range - image_array = np.clip(image_array, 0, 1) - else: - # Handle PIL Image format - image_array = np.array(self.data.image) - - im = ax.imshow(image_array, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title, fontsize=12) - ax.axis('off') - - # Hidden colorbar for nice visualization - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - - return ax - - def _draw_video_frames(self, - title: str = "Watermarked Video Frames", - num_frames: int = 4, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw multiple frames from the watermarked video. - - This method displays a grid of video frames to show the temporal - consistency of the watermarked video. - - Parameters: - title (str): The title of the plot. - num_frames (int): Number of frames to display (default: 4). - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if not hasattr(self.data, 'video_frames') or self.data.video_frames is None: - raise ValueError("No video frames available for visualization. Please ensure video_frames is provided in data_for_visualization.") - - video_frames = self.data.video_frames - total_frames = len(video_frames) - - # Limit num_frames to available frames - num_frames = min(num_frames, total_frames) - - # Calculate which frames to show (evenly distributed) - if num_frames == 1: - frame_indices = [total_frames // 2] # Middle frame - else: - frame_indices = [int(i * (total_frames - 1) / (num_frames - 1)) for i in range(num_frames)] - - # Calculate grid layout - rows = int(np.ceil(np.sqrt(num_frames))) - cols = int(np.ceil(num_frames / rows)) - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20, fontsize=12) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.1, hspace=0.4) - - # Create subplots for each frame - for i, frame_idx in enumerate(frame_indices): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Get the frame - frame = video_frames[frame_idx] - - # Convert frame to displayable format - try: - # First, convert tensor to numpy if needed - if hasattr(frame, 'cpu'): # PyTorch tensor - frame = frame.cpu().numpy() - elif hasattr(frame, 'numpy'): # Other tensor types - frame = frame.numpy() - elif hasattr(frame, 'convert'): # PIL Image - frame = np.array(frame) - - # Handle channels-first format (C, H, W) -> (H, W, C) for numpy arrays - if isinstance(frame, np.ndarray) and len(frame.shape) == 3: - if frame.shape[0] in [1, 3, 4]: # Channels first - frame = np.transpose(frame, (1, 2, 0)) - - # Ensure proper data type for matplotlib - if isinstance(frame, np.ndarray): - if frame.dtype == np.float64: - frame = frame.astype(np.float32) - elif frame.dtype not in [np.uint8, np.float32]: - # Convert to float32 and normalize if needed - frame = frame.astype(np.float32) - if frame.max() > 1.0: - frame = frame / 255.0 - - # Normalize [-1, 1] range to [0, 1] for imshow - if frame.min() < 0: - frame = (frame + 1.0) / 2.0 - - # Clip to valid range [0, 1] - frame = np.clip(frame, 0, 1) - - im = sub_ax.imshow(frame) - - except Exception as e: - print(f"Error displaying frame {frame_idx}: {e}") - - sub_ax.set_title(f'Frame {frame_idx}', fontsize=10, pad=5) - sub_ax.axis('off') - - # Hide unused subplots - for i in range(num_frames, rows * cols): - row_idx = i // cols - col_idx = i % cols - if row_idx < rows and col_idx < cols: - empty_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - empty_ax.axis('off') - - return ax - -
-[docs] - def visualize(self, - rows: int, - cols: int, - methods: List[str], - figsize: Optional[Tuple[int, int]] = None, - method_kwargs: Optional[List[Dict[str, Any]]] = None, - save_path: Optional[str] = None) -> plt.Figure: - """ - Comprehensive visualization of watermark analysis. - - Parameters: - rows (int): The number of rows of the subplots. - cols (int): The number of columns of the subplots. - methods (List[str]): List of methods to call. - method_kwargs (Optional[List[Dict[str, Any]]]): List of keyword arguments for each method. - figsize (Tuple[int, int]): The size of the figure. - save_path (Optional[str]): The path to save the figure. - - Returns: - plt.Figure: The matplotlib figure object. - """ - # Check if the rows and cols are compatible with the number of methods - if len(methods) != rows * cols: - raise ValueError(f"The number of methods ({len(methods)}) is not compatible with the layout ({rows}x{cols})") - - # Initialize the figure size if not provided - if figsize is None: - figsize = (cols * 5, rows * 5) - - # Create figure and subplots - fig, axes = plt.subplots(rows, cols, figsize=figsize) - - # Ensure axes is always a 2D array for consistent indexing - if rows == 1 and cols == 1: - axes = np.array([[axes]]) - elif rows == 1: - axes = axes.reshape(1, -1) - elif cols == 1: - axes = axes.reshape(-1, 1) - - if method_kwargs is None: - method_kwargs = [{} for _ in methods] - - # Plot each method - for i, method_name in enumerate(methods): - row = i // cols - col = i % cols - ax = axes[row, col] - - try: - method = getattr(self, method_name) - except AttributeError: - raise ValueError(f"Method '{method_name}' not found in {self.__class__.__name__}") - - try: - # print(method_kwargs[i]) - method(ax=ax, **method_kwargs[i]) - except TypeError: - raise ValueError(f"Method '{method_name}' does not accept the given arguments: {method_kwargs[i]}") - - # if the number of methods is less than the number of axes, hide the unused axes - for i in range(len(methods), rows * cols): - row = i // cols - col = i % cols - axes[row, col].axis('off') - - plt.tight_layout(pad=2.0, w_pad=3.0, h_pad=2.0) - - if save_path is not None: - plt.savefig(save_path, bbox_inches='tight', dpi=self.dpi) - - return fig
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/gm/gm_visualizer.html b/docs/_build/html/_modules/visualize/gm/gm_visualizer.html deleted file mode 100644 index 576d329..0000000 --- a/docs/_build/html/_modules/visualize/gm/gm_visualizer.html +++ /dev/null @@ -1,1333 +0,0 @@ - - - - - - - - visualize.gm.gm_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.gm.gm_visualizer

-from typing import Optional, List
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-
-[docs] -class GaussMarkerVisualizer(BaseVisualizer): - """GaussMarker watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step)
- - - # ------------------------------------------------------------------ - # Helper utilities - # ------------------------------------------------------------------ - def _get_boolean_mask(self) -> torch.Tensor: - mask = self.data.watermarking_mask - if mask.dtype == torch.bool: - return mask - return mask != 0 - - def _resolve_channels(self, mask: torch.Tensor, requested_channel: Optional[int] = None) -> List[int]: - if mask.dim() == 3: - mask = mask.unsqueeze(0) - num_channels = mask.shape[1] - - if requested_channel is not None: - if requested_channel < 0 or requested_channel >= num_channels: - raise ValueError(f"Channel {requested_channel} is out of range. Max channel: {num_channels - 1}") - return [requested_channel] - - default_channel = getattr(self.data, "w_channel", -1) - if default_channel not in (-1, None): - if default_channel < 0 or default_channel >= num_channels: - raise ValueError(f"w_channel {default_channel} is out of range. Max channel: {num_channels - 1}") - return [int(default_channel)] - - mask_flat = mask.view(mask.shape[0], mask.shape[1], -1).any(dim=-1) - active_channels = [idx for idx, flag in enumerate(mask_flat[0].tolist()) if flag] - if not active_channels: - return list(range(num_channels)) - return active_channels - - def _grid_shape(self, count: int) -> tuple[int, int]: - rows = int(np.ceil(np.sqrt(count))) - cols = int(np.ceil(count / rows)) - return rows, cols - - def _get_reversed_latent(self, step: Optional[int] = None) -> torch.Tensor: - reversed_series = self.data.reversed_latents - if isinstance(reversed_series, (list, tuple)): - total = len(reversed_series) - index = self.watermarking_step if step is None else step - if index < 0: - index = total + index - index = max(0, min(total - 1, index)) - return reversed_series[index] - return reversed_series - - def _prepare_watermark_tensor(self) -> torch.Tensor: - generator = self.data.watermark_generator - watermark = generator.watermark_tensor().to(torch.float32) - if watermark.dim() == 3: - watermark = watermark.unsqueeze(0) - return watermark.cpu() - - # ------------------------------------------------------------------ - # Bit-level visualizations - # ------------------------------------------------------------------ -
-[docs] - def draw_watermark_bits( - self, - channel: Optional[int] = None, - title: str = "Original Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None, - ) -> Axes: - """Visualize the original watermark bits generated by GaussMarker.""" - - watermark = self._prepare_watermark_tensor() - num_channels = watermark.shape[1] - - if channel is not None: - if channel < 0 or channel >= num_channels: - raise ValueError(f"Channel {channel} is out of range. Max channel: {num_channels - 1}") - channels = [channel] - else: - channels = list(range(num_channels)) - - if len(channels) == 1: - ch = channels[0] - if ch >= num_channels: - raise ValueError(f"Channel {ch} is out of range. Max channel: {num_channels - 1}") - data = watermark[0, ch].cpu().numpy() - im = ax.imshow(data, cmap=cmap, vmin=0, vmax=1, interpolation="nearest") - if title != "": - ax.set_title(f"{title} - Channel {ch}", fontsize=10) - ax.axis('off') - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - rows, cols = self._grid_shape(len(channels)) - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), wspace=0.3, hspace=0.4) - for i, ch in enumerate(channels): - if ch >= num_channels: - raise ValueError(f"Channel {ch} is out of range. Max channel: {num_channels - 1}") - row_idx = i // cols - col_idx = i % cols - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - data = watermark[0, ch].cpu().numpy() - im = sub_ax.imshow(data, cmap=cmap, vmin=0, vmax=1, interpolation="nearest") - sub_ax.set_title(f'Channel {ch}', fontsize=8, pad=3) - sub_ax.axis('off') - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_reconstructed_watermark_bits( - self, - channel: Optional[int] = None, - step: Optional[int] = None, - title: str = "Reconstructed Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None, - ) -> Axes: - """Visualize watermark bits reconstructed from inverted latents.""" - - reversed_latent = self._get_reversed_latent(step) - generator = self.data.watermark_generator - reconstructed = generator.pred_w_from_latent(reversed_latent).to(torch.float32) - reference = generator.watermark_tensor(reconstructed.device) - bit_acc = (reconstructed == reference).float().mean().item() - - if reconstructed.dim() == 3: - reconstructed = reconstructed.unsqueeze(0) - - num_channels = reconstructed.shape[1] - - if channel is not None: - if channel < 0 or channel >= num_channels: - raise ValueError(f"Channel {channel} is out of range. Max channel: {num_channels - 1}") - channels = [channel] - else: - channels = list(range(num_channels)) - - if len(channels) == 1: - ch = channels[0] - if ch >= num_channels: - raise ValueError(f"Channel {ch} is out of range. Max channel: {num_channels - 1}") - data = reconstructed[0, ch].cpu().numpy() - im = ax.imshow(data, cmap=cmap, vmin=0, vmax=1, interpolation="nearest") - title_text = f"{title} - Channel {ch} (Bit Acc: {bit_acc:.3f})" if title != "" else f"Channel {ch} (Bit Acc: {bit_acc:.3f})" - ax.set_title(title_text, fontsize=10) - ax.axis('off') - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - ax.clear() - header = f"{title} (Bit Acc: {bit_acc:.3f})" if title != "" else f"(Bit Acc: {bit_acc:.3f})" - ax.set_title(header, pad=20) - ax.axis('off') - - rows, cols = self._grid_shape(len(channels)) - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), wspace=0.3, hspace=0.4) - - for i, ch in enumerate(channels): - if ch >= num_channels: - raise ValueError(f"Channel {ch} is out of range. Max channel: {num_channels - 1}") - row_idx = i // cols - col_idx = i % cols - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - data = reconstructed[0, ch].cpu().numpy() - im = sub_ax.imshow(data, cmap=cmap, vmin=0, vmax=1, interpolation="nearest") - sub_ax.set_title(f'Channel {ch}', fontsize=8, pad=3) - sub_ax.axis('off') - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - - # ------------------------------------------------------------------ - # Frequency-domain visualizations - # ------------------------------------------------------------------ -
-[docs] - def draw_pattern_fft( - self, - channel: Optional[int] = None, - title: str = "GaussMarker Target Pattern (FFT)", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs, - ) -> Axes: - """Visualize the intended watermark pattern in the Fourier domain.""" - - pattern = self.data.gt_patch - if pattern.dim() == 3: - pattern = pattern.unsqueeze(0) - mask_bool = self._get_boolean_mask() - if mask_bool.dim() == 3: - mask_bool = mask_bool.unsqueeze(0) - - channels = self._resolve_channels(mask_bool, channel) - pattern_abs = torch.abs(pattern).detach().cpu() - mask_cpu = mask_bool.cpu() - - if len(channels) == 1: - ch = channels[0] - amplitude = torch.zeros_like(pattern_abs[0, ch], dtype=torch.float32) - selection = mask_cpu[0, ch] - if selection.any(): - amplitude[selection] = pattern_abs[0, ch][selection] - im = ax.imshow(amplitude.numpy(), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(f"{title} - Channel {ch}") - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=ax) - cbar.ax.tick_params(labelsize=8) - ax.axis('off') - else: - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - rows, cols = self._grid_shape(len(channels)) - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), wspace=0.3, hspace=0.4) - - for i, ch in enumerate(channels): - row_idx = i // cols - col_idx = i % cols - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - amplitude = torch.zeros_like(pattern_abs[0, ch], dtype=torch.float32) - selection = mask_cpu[0, ch] - if selection.any(): - amplitude[selection] = pattern_abs[0, ch][selection] - im = sub_ax.imshow(amplitude.numpy(), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {ch}', fontsize=8, pad=3) - sub_ax.axis('off') - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_inverted_pattern_fft( - self, - channel: Optional[int] = None, - step: Optional[int] = None, - title: str = "Recovered Watermark Pattern (FFT)", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs, - ) -> Axes: - """Visualize the recovered watermark region in the Fourier domain.""" - - reversed_latent = self._get_reversed_latent(step) - fft_latents = torch.fft.fftshift(torch.fft.fft2(reversed_latent), dim=(-1, -2)) - mask_bool = self._get_boolean_mask() - if mask_bool.dim() == 3: - mask_bool = mask_bool.unsqueeze(0) - channels = self._resolve_channels(mask_bool, channel) - - target_patch = self.data.gt_patch.to(fft_latents.device) - selection = mask_bool.to(fft_latents.device) - if selection.sum() > 0: - complex_l1 = torch.abs(fft_latents - target_patch)[selection].mean().item() - else: - complex_l1 = 0.0 - - fft_abs = torch.abs(fft_latents).detach().cpu() - mask_cpu = mask_bool.cpu() - - title_suffix = f" (L1: {complex_l1:.3e})" - - if len(channels) == 1: - ch = channels[0] - amplitude = torch.zeros_like(fft_abs[0, ch], dtype=torch.float32) - selection = mask_cpu[0, ch] - if selection.any(): - amplitude[selection] = fft_abs[0, ch][selection] - im = ax.imshow(amplitude.numpy(), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(f"{title} - Channel {ch}{title_suffix}") - elif title_suffix: - ax.set_title(title_suffix.strip(), fontsize=10) - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=ax) - cbar.ax.tick_params(labelsize=8) - ax.axis('off') - else: - ax.clear() - if title != "": - ax.set_title(f"{title}{title_suffix}", pad=20) - else: - ax.set_title(title_suffix.strip(), pad=20) - ax.axis('off') - - rows, cols = self._grid_shape(len(channels)) - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), wspace=0.3, hspace=0.4) - - for i, ch in enumerate(channels): - row_idx = i // cols - col_idx = i % cols - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - amplitude = torch.zeros_like(fft_abs[0, ch], dtype=torch.float32) - selection = mask_cpu[0, ch] - if selection.any(): - amplitude[selection] = fft_abs[0, ch][selection] - im = sub_ax.imshow(amplitude.numpy(), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {ch}', fontsize=8, pad=3) - sub_ax.axis('off') - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_watermark_mask( - self, - channel: Optional[int] = None, - title: str = "Watermark Mask", - cmap: str = "viridis", - ax: Optional[Axes] = None, - ) -> Axes: - """Visualize the spatial region where the watermark is applied.""" - - mask_bool = self._get_boolean_mask() - if mask_bool.dim() == 3: - mask_bool = mask_bool.unsqueeze(0) - channels = self._resolve_channels(mask_bool, channel) - mask_cpu = mask_bool.float().cpu() - - if len(channels) == 1: - ch = channels[0] - data = mask_cpu[0, ch].numpy() - im = ax.imshow(data, cmap=cmap, vmin=0, vmax=1) - if title != "": - ax.set_title(f"{title} - Channel {ch}") - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - ax.axis('off') - else: - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - rows, cols = self._grid_shape(len(channels)) - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), wspace=0.3, hspace=0.4) - - for i, ch in enumerate(channels): - row_idx = i // cols - col_idx = i % cols - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - data = mask_cpu[0, ch].numpy() - im = sub_ax.imshow(data, cmap=cmap, vmin=0, vmax=1) - sub_ax.set_title(f'Channel {ch}', fontsize=8, pad=3) - sub_ax.axis('off') - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/gs/gs_visualizer.html b/docs/_build/html/_modules/visualize/gs/gs_visualizer.html deleted file mode 100644 index 95c9252..0000000 --- a/docs/_build/html/_modules/visualize/gs/gs_visualizer.html +++ /dev/null @@ -1,1135 +0,0 @@ - - - - - - - - visualize.gs.gs_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.gs.gs_visualizer

-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-from typing import Optional
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-from Crypto.Cipher import ChaCha20
-
-
-[docs] -class GaussianShadingVisualizer(BaseVisualizer): - """Gaussian Shading watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step)
- - - def _stream_key_decrypt(self, reversed_m): - """Decrypt the watermark using ChaCha20 cipher.""" - cipher = ChaCha20.new(key=self.data.chacha_key, nonce=self.data.chacha_nonce) - sd_byte = cipher.decrypt(np.packbits(reversed_m).tobytes()) - sd_bit = np.unpackbits(np.frombuffer(sd_byte, dtype=np.uint8)) - sd_tensor = torch.from_numpy(sd_bit).reshape(1, 4, 64, 64).to(torch.uint8) - return sd_tensor.cuda() - - def _diffusion_inverse(self, reversed_sd): - """Inverse the diffusion process to extract the watermark.""" - ch_stride = 4 // self.data.channel_copy - hw_stride = 64 // self.data.hw_copy - ch_list = [ch_stride] * self.data.channel_copy - hw_list = [hw_stride] * self.data.hw_copy - split_dim1 = torch.cat(torch.split(reversed_sd, tuple(ch_list), dim=1), dim=0) - split_dim2 = torch.cat(torch.split(split_dim1, tuple(hw_list), dim=2), dim=0) - split_dim3 = torch.cat(torch.split(split_dim2, tuple(hw_list), dim=3), dim=0) - vote = torch.sum(split_dim3, dim=0).clone() - vote[vote <= self.data.vote_threshold] = 0 - vote[vote > self.data.vote_threshold] = 1 - return vote - -
-[docs] - def draw_watermark_bits(self, - channel: Optional[int] = None, - title: str = "Original Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None) -> Axes: - """ - Draw the original watermark bits.(sd in GS class). draw ch // channel_copy images in one ax. - - Parameters: - channel (Optional[int]): The channel to visualize. If None, all channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # Step 1: reshape self.data.watermark to [1, 4 // self.data.channel_copy, 64 // self.data.hw_copy, 64 // self.data.hw_copy] - watermark = self.data.watermark.reshape(1, 4 // self.data.channel_copy, 64 // self.data.hw_copy, 64 // self.data.hw_copy) - - if channel is not None: - # Single channel visualization - if channel >= 4 // self.data.channel_copy: - raise ValueError(f"Channel {channel} is out of range. Max channel: {4 // self.data.channel_copy - 1}") - - watermark_data = watermark[0, channel].cpu().numpy() - im = ax.imshow(watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - if title != "": - ax.set_title(f"{title} - Channel {channel}", fontsize=10) - ax.axis('off') - - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - # Multi-channel visualization - # Step 2: draw watermark for (4 // self.data.channel_copy) images in this ax - num_channels = 4 // self.data.channel_copy - - # Calculate grid layout - rows = int(np.ceil(np.sqrt(num_channels))) - cols = int(np.ceil(num_channels / rows)) - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the watermark channel - watermark_data = watermark[0, i].cpu().numpy() - sub_ax.imshow(watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - - return ax
- - -
-[docs] - def draw_reconstructed_watermark_bits(self, - channel: Optional[int] = None, - title: str = "Reconstructed Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None) -> Axes: - """ - Draw the reconstructed watermark bits.(reversed_latents in GS class). draw ch // channel_copy images in one ax. - - Parameters: - channel (Optional[int]): The channel to visualize. If None, all channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # Step 1: reconstruct the watermark bits - reversed_latent = self.data.reversed_latents[self.watermarking_step] - - reversed_m = (reversed_latent > 0).int() - if self.data.chacha: - reversed_sd = self._stream_key_decrypt(reversed_m.flatten().cpu().numpy()) - else: - reversed_sd = (reversed_m + self.data.key) % 2 - - reversed_watermark = self._diffusion_inverse(reversed_sd) - bit_acc = (reversed_watermark == self.data.watermark).float().mean().item() - reconstructed_watermark = reversed_watermark.reshape(1, 4 // self.data.channel_copy, 64 // self.data.hw_copy, 64 // self.data.hw_copy) - - if channel is not None: - # Single channel visualization - if channel >= 4 // self.data.channel_copy: - raise ValueError(f"Channel {channel} is out of range. Max channel: {4 // self.data.channel_copy - 1}") - - reconstructed_watermark_data = reconstructed_watermark[0, channel].cpu().numpy() - im = ax.imshow(reconstructed_watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - if title != "": - ax.set_title(f"{title} - Channel {channel} (Bit Acc: {bit_acc:.3f})", fontsize=10) - else: - ax.set_title(f"Channel {channel} (Bit Acc: {bit_acc:.3f})", fontsize=10) - ax.axis('off') - - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - # Multi-channel visualization - # Step 2: draw reconstructed_watermark for (4 // self.data.channel_copy) images in this ax(add Bit_acc to the title) - num_channels = 4 // self.data.channel_copy - - # Calculate grid layout - rows = int(np.ceil(np.sqrt(num_channels))) - cols = int(np.ceil(num_channels / rows)) - - # Clear the axis and set title with bit accuracy - ax.clear() - if title != "": - ax.set_title(f'{title} (Bit Acc: {bit_acc:.3f})', pad=20) - else: - ax.set_title(f'(Bit Acc: {bit_acc:.3f})', pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the reconstructed watermark channel - reconstructed_watermark_data = reconstructed_watermark[0, i].cpu().numpy() - sub_ax.imshow(reconstructed_watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/prc/prc_visualizer.html b/docs/_build/html/_modules/visualize/prc/prc_visualizer.html deleted file mode 100644 index 6bf54f2..0000000 --- a/docs/_build/html/_modules/visualize/prc/prc_visualizer.html +++ /dev/null @@ -1,1180 +0,0 @@ - - - - - - - - visualize.prc.prc_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.prc.prc_visualizer

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from typing import Optional
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpec
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-[docs] -class PRCVisualizer(BaseVisualizer): - """PRC (Pseudorandom Codes) watermark visualizer""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - """ - Initialize PRC visualizer - - Args: - data_for_visualization: DataForVisualization object containing visualization data - dpi: DPI for visualization (default: 300) - watermarking_step: The step for inserting the watermark (default: -1) - """ - super().__init__(data_for_visualization, dpi, watermarking_step) - - # Pre-detach all tensors while maintaining device compatibility - if hasattr(self.data, 'watermarked_latents') and self.data.watermarked_latents is not None: - self.data.watermarked_latents = self.data.watermarked_latents.detach() - if hasattr(self.data, 'orig_latents') and self.data.orig_latents is not None: - self.data.orig_latents = self.data.orig_latents.detach() - if hasattr(self.data, 'inverted_latents') and self.data.inverted_latents is not None: - self.data.inverted_latents = self.data.inverted_latents.detach() - if hasattr(self.data, 'prc_codeword') and self.data.prc_codeword is not None: - self.data.prc_codeword = self.data.prc_codeword.detach() - if hasattr(self.data, 'generator_matrix') and self.data.generator_matrix is not None: - self.data.generator_matrix = self.data.generator_matrix.detach()
- - -
-[docs] - def draw_generator_matrix(self, - title: str = "Generator Matrix G", - cmap: str = "Blues", - use_color_bar: bool = True, - max_display_size: int = 50, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the generator matrix visualization - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - max_display_size (int): Maximum size to display (for large matrices) - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if hasattr(self.data, 'generator_matrix') and self.data.generator_matrix is not None: - gen_matrix = self.data.generator_matrix.cpu().numpy() - - # Show a sample of the matrix if it's too large - if gen_matrix.shape[0] > max_display_size or gen_matrix.shape[1] > max_display_size: - sample_size = min(max_display_size, min(gen_matrix.shape)) - matrix_sample = gen_matrix[:sample_size, :sample_size] - title += f" (Sample {sample_size}x{sample_size})" - else: - matrix_sample = gen_matrix - - im = ax.imshow(matrix_sample, cmap=cmap, aspect='auto', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'Generator Matrix\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=10) - ax.set_xlabel('Columns') - ax.set_ylabel('Rows') - return ax
- - -
-[docs] - def draw_codeword(self, - title: str = "PRC Codeword", - cmap: str = "viridis", - use_color_bar: bool = True, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the PRC codeword visualization - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if hasattr(self.data, 'prc_codeword') and self.data.prc_codeword is not None: - codeword = self.data.prc_codeword.cpu().numpy() - - # If 1D, reshape for visualization - if len(codeword.shape) == 1: - # Create a reasonable 2D shape - length = len(codeword) - height = int(np.sqrt(length)) - width = length // height - if height * width < length: - width += 1 - # Pad if necessary - padded_codeword = np.zeros(height * width) - padded_codeword[:length] = codeword - codeword = padded_codeword.reshape(height, width) - - im = ax.imshow(codeword, cmap=cmap, aspect='equal', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'PRC Codeword\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=12) - return ax
- - -
-[docs] - def draw_recovered_codeword(self, - title: str = "Recovered Codeword (c̃)", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: float = -1.0, - vmax: float = 1.0, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the recovered codeword (c̃) from PRC detection - - This visualizes the recovered codeword from prc_detection.recovered_prc - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - vmin (float): Minimum value for colormap (-1.0) - vmax (float): Maximum value for colormap (1.0) - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if hasattr(self.data, 'recovered_prc') and self.data.recovered_prc is not None: - recovered_codeword = self.data.recovered_prc.cpu().numpy().flatten() - - # Ensure it's the expected length - if len(recovered_codeword) == 16384: - # Reshape to 2D for visualization (128x128 = 16384) - codeword_2d = recovered_codeword.reshape((128, 128)) - - im = ax.imshow(codeword_2d, cmap=cmap, vmin=vmin, vmax=vmax, aspect='equal', **kwargs) - - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=ax, shrink=0.8) - cbar.set_label('Codeword Value', fontsize=8) - else: - ax.text(0.5, 0.5, f'Recovered Codeword\nUnexpected Length: {len(recovered_codeword)}\n(Expected: 16384)', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - else: - ax.text(0.5, 0.5, 'Recovered Codeword (c̃)\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=10) - ax.axis('off') - return ax
- - -
-[docs] - def draw_difference_map(self, - title: str = "Difference Map", - cmap: str = "hot", - use_color_bar: bool = True, - channel: int = 0, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw difference map between watermarked and inverted latents - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - channel (int): The channel to visualize - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if (hasattr(self.data, 'watermarked_latents') and self.data.watermarked_latents is not None and - hasattr(self.data, 'inverted_latents') and self.data.inverted_latents is not None): - - wm_latents = self._get_latent_data(self.data.watermarked_latents, channel=channel).cpu().numpy() - inv_latents = self._get_latent_data(self.data.inverted_latents, channel=channel).cpu().numpy() - - diff_map = np.abs(wm_latents - inv_latents) - im = ax.imshow(diff_map, cmap=cmap, aspect='equal', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'Difference Map\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=12) - ax.set_xlabel('Width') - ax.set_ylabel('Height') - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/robin/robin_visualizer.html b/docs/_build/html/_modules/visualize/robin/robin_visualizer.html deleted file mode 100644 index dce68fd..0000000 --- a/docs/_build/html/_modules/visualize/robin/robin_visualizer.html +++ /dev/null @@ -1,1095 +0,0 @@ - - - - - - - - visualize.robin.robin_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for visualize.robin.robin_visualizer

-from typing import Optional
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-[docs] -class ROBINVisualizer(BaseVisualizer): - """ROBIN watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step) - # ROBIN uses a specific watermarking step - if hasattr(self.data, 'watermarking_step'): - self.watermarking_step = self.data.watermarking_step - else: - raise ValueError("watermarking_step is required for ROBIN visualization")
- - -
-[docs] - def draw_pattern_fft(self, - title: str = None, - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with original watermark pattern, with all 0 background. - - Parameters: - title (str): The title of the plot. If None, includes watermarking step info. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # Use custom title with watermarking step if not provided - if title is None: - title = f"ROBIN FFT with Watermark Area (Step {self.watermarking_step})" - - orig_latent = self.data.optimized_watermark[0, self.data.w_channel].cpu() - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(orig_latent)) - fft_vis = torch.zeros_like(fft_data) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
- - -
-[docs] - def draw_inverted_pattern_fft(self, - step: Optional[int] = None, - title: str = None, - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with inverted pattern, with all 0 background. - - Parameters: - step (Optional[int]): The timestep of the inverted latents. If None, uses ROBIN's specific step. - title (str): The title of the plot. If None, includes watermarking step info. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # For ROBIN, we need to use the specific watermarking step - if step is None: - # Calculate the actual step index for ROBIN - # ROBIN uses: num_steps_to_use - 1 - self.config.watermarking_step - num_steps = len(self.data.reversed_latents) - actual_step = num_steps - 1 - self.watermarking_step - inverted_latent = self.data.reversed_latents[actual_step][0, self.data.w_channel] - else: - inverted_latent = self.data.reversed_latents[step][0, self.data.w_channel] - - # Use custom title with watermarking step if not provided - if title is None: - title = f"ROBIN FFT with Inverted Watermark Area (Step {self.watermarking_step})" - - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(inverted_latent)) - fft_vis = torch.zeros_like(fft_data).to(fft_data.device) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
- - -
-[docs] - def draw_optimized_watermark(self, - title: str = None, - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the optimized watermark pattern (ROBIN-specific). - - Parameters: - title (str): The title of the plot. If None, includes watermarking step info. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - # Use custom title with watermarking step if not provided - if title is None: - title = f"ROBIN Optimized Watermark (Step {self.watermarking_step})" - - optimized_watermark = self.data.optimized_watermark[0, self.data.w_channel].cpu() - - im = ax.imshow(np.abs(optimized_watermark.numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/seal/seal_visualizer.html b/docs/_build/html/_modules/visualize/seal/seal_visualizer.html deleted file mode 100644 index e38a0ef..0000000 --- a/docs/_build/html/_modules/visualize/seal/seal_visualizer.html +++ /dev/null @@ -1,1095 +0,0 @@ - - - - - - - - visualize.seal.seal_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for visualize.seal.seal_visualizer

-from typing import Optional
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-import numpy as np
-import math
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-[docs] -class SEALVisualizer(BaseVisualizer): - """SEAL watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step)
- - -
-[docs] - def draw_embedding_distributions(self, - title: str = "Embedding Distributions", - ax: Optional[Axes] = None, - show_legend: bool = True, - show_label: bool = True, - show_axis: bool = True) -> Axes: - """ - Draw histogram of embedding distributions comparison(original_embedding vs inspected_embedding). - - Parameters: - title (str): The title of the plot. - ax (plt.Axes): The axes to plot on. - show_legend (bool): Whether to show the legend. Default: True. - show_label (bool): Whether to show axis labels. Default: True. - show_axis (bool): Whether to show axis ticks and labels. Default: True. - - Returns: - Axes: The plotted axes. - """ - original_embedding = self.data.original_embedding - inspected_embedding = self.data.inspected_embedding - - # Convert to numpy arrays and flatten if necessary - original_data = original_embedding.cpu().numpy().flatten() - inspected_data = inspected_embedding.cpu().numpy().flatten() - - # Create overlapping histograms with transparency - ax.hist(original_data, bins=50, alpha=0.7, color='blue', - label='Original Embedding', density=True, edgecolor='darkblue', linewidth=0.5) - ax.hist(inspected_data, bins=50, alpha=0.7, color='red', - label='Inspected Embedding', density=True, edgecolor='darkred', linewidth=0.5) - - # Set labels and title - if title != "": - ax.set_title(title, fontsize=12) - if show_label: - ax.set_xlabel('Embedding Values') - ax.set_ylabel('Density') - if show_legend: - ax.legend() - if not show_axis: - ax.set_xticks([]) - ax.set_yticks([]) - ax.grid(True, alpha=0.3) - - # Create a hidden colorbar for nice visualization - im = ax.scatter([], [], c=[], cmap='viridis') - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - - return ax
- - - -
-[docs] - def draw_patch_diff(self, - title: str = "Patch Difference", - cmap: str = 'RdBu', - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - show_number: bool = False, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the difference between the reference_noise and reversed_latents in patch. - - Parameters: - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - vmin (Optional[float]): Minimum value for colormap normalization. - vmax (Optional[float]): Maximum value for colormap normalization. - show_number (bool): Whether to display numerical values on each patch. Default: False. - ax (plt.Axes): The axes to plot on. - - Returns: - plt.axes.Axes: The plotted axes. - """ - reversed_latent = self.data.reversed_latents[self.watermarking_step] # shape: [1, 4, 64, 64] - reference_noise = self.data.reference_noise # shape: [1, 4, 64, 64] - k = self.data.k_value - - patch_per_side_h = int(math.ceil(math.sqrt(k))) - patch_per_side_w = int(math.ceil(k / patch_per_side_h)) - patch_height = 64 // patch_per_side_h - patch_width = 64 // patch_per_side_w - diff_map = torch.zeros((patch_per_side_h, patch_per_side_w)) - - patch_count = 0 # Initialize patch counter - for i in range(patch_per_side_h): - for j in range(patch_per_side_w): - if patch_count >= k: - break - y_start = i * patch_height - x_start = j * patch_width - y_end = min(y_start + patch_height, 64) - x_end = min(x_start + patch_width, 64) - patch1 = reversed_latent[:, :, y_start:y_end, x_start:x_end] - patch2 = reference_noise[:, :, y_start:y_end, x_start:x_end] - l2_val = torch.norm(patch1 - patch2).item() - diff_map[i, j] = l2_val - patch_count += 1 # Increment patch counter - - im = ax.imshow(diff_map.cpu().numpy(), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - if show_number: - # Calculate appropriate font size based on patch size - patch_size = min(patch_per_side_h, patch_per_side_w) - if patch_size >= 8: - fontsize = 8 - format_str = '{:.2f}' - elif patch_size >= 4: - fontsize = 6 - format_str = '{:.1f}' - else: - fontsize = 4 - format_str = '{:.0f}' - fontsize = 4 - format_str = '{:.0f}' - for i in range(patch_per_side_h): - for j in range(patch_per_side_w): - if i * patch_per_side_w + j < k: # Only show numbers for valid patches - value = diff_map[i, j].item() - ax.text(j, i, format_str.format(value), - ha='center', va='center', color='white', - fontsize=fontsize, fontweight='bold') - ax.axis('off') - - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/sfw/sfw_visualizer.html b/docs/_build/html/_modules/visualize/sfw/sfw_visualizer.html deleted file mode 100644 index 70b8122..0000000 --- a/docs/_build/html/_modules/visualize/sfw/sfw_visualizer.html +++ /dev/null @@ -1,1095 +0,0 @@ - - - - - - - - visualize.sfw.sfw_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.sfw.sfw_visualizer

-from typing import Optional
-import torch
-from matplotlib.axes import Axes
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-[docs] -class SFWVisualizer(BaseVisualizer): - """SFW watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step)
- - -
-[docs] - def draw_pattern_fft(self, - title: str = "SFW FFT with Watermark Area", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with original watermark pattern, with all 0 background. - - Parameters: - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - orig_latent = self.data.orig_watermarked_latents[0, self.data.w_channel].cpu() - if self.data.wm_type=="HSQR": - gt_patch = self.data.gt_patch - # extract patch for channel (may be complex) - #patt = gt_patch[0, self.data.w_channel] if torch.is_tensor(gt_patch) and gt_patch.dim() == 4 else gt_patch - patt_np = gt_patch.detach().cpu().numpy() if torch.is_tensor(gt_patch) else np.array(gt_patch) - if patt_np.ndim ==4: - patt_np = patt_np[0] - # if multi-channel, try to squeeze to 2D - if patt_np.ndim == 3: - # (c, H, W) => choose first channel - patt_np = patt_np[0] - # use magnitude if complex - if np.iscomplexobj(patt_np): - patt_vis = np.abs(patt_np) - else: - patt_vis = np.abs(patt_np) - fft_np = self._fft_transform(orig_latent) - H, W = fft_np.shape - ph, pw = patt_vis.shape - sh = (H - ph) // 2 - sw = (W - pw) // 2 - fft_data = torch.from_numpy(fft_np) - fft_vis = torch.zeros_like(fft_data) - patt_t = torch.from_numpy(patt_vis.astype(np.float32)).to(dtype=fft_vis.dtype, device=fft_vis.device) - if patt_t.shape != (ph, pw): - raise ValueError(f"Pattern shape mismatch after conversion: {patt_t.shape} vs {(ph,pw)}") - fft_vis[sh:sh+ph, sw:sw+pw] = patt_t - else: - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(orig_latent)) - fft_vis = torch.zeros_like(fft_data) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
- - -
-[docs] - def draw_inverted_pattern_fft(self, - step: Optional[int] = None, - title: str = "SFW FFT with Inverted Watermark Area", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with inverted pattern, with all 0 background. - - Parameters: - step (Optional[int]): The timestep of the inverted latents. If None, the last timestep is used. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if step is None: - inverted_latent = self.data.reversed_latents[self.watermarking_step][0, self.data.w_channel] - else: - inverted_latent = self.data.reversed_latents[step][0, self.data.w_channel] - - if self.data.wm_type=="HSQR": - gt_patch = self.data.gt_patch - # extract patch for channel (may be complex) - #patt = gt_patch[0, self.data.w_channel] if torch.is_tensor(gt_patch) and gt_patch.dim() == 4 else gt_patch - patt_np = gt_patch.detach().cpu().numpy() if torch.is_tensor(gt_patch) else np.array(gt_patch) - if patt_np.ndim ==4: - patt_np = patt_np[0] - # if multi-channel, try to squeeze to 2D - if patt_np.ndim == 3: - # (c, H, W) => choose first channel - patt_np = patt_np[0] - # use magnitude if complex - if np.iscomplexobj(patt_np): - patt_vis = np.abs(patt_np) - else: - patt_vis = np.abs(patt_np) - fft_np = self._fft_transform(inverted_latent) - H, W = fft_np.shape - ph, pw = patt_vis.shape - sh = (H - ph) // 2 - sw = (W - pw) // 2 - fft_data = torch.from_numpy(fft_np) - fft_vis = torch.zeros_like(fft_data) - patt_t = torch.from_numpy(patt_vis.astype(np.float32)).to(dtype=fft_vis.dtype, device=fft_vis.device) - if patt_t.shape != (ph, pw): - raise ValueError(f"Pattern shape mismatch after conversion: {patt_t.shape} vs {(ph,pw)}") - fft_vis[sh:sh+ph, sw:sw+pw] = patt_t - else: - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(inverted_latent)) - fft_vis = torch.zeros_like(fft_data).to(fft_data.device) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/tr/tr_visualizer.html b/docs/_build/html/_modules/visualize/tr/tr_visualizer.html deleted file mode 100644 index aec1bed..0000000 --- a/docs/_build/html/_modules/visualize/tr/tr_visualizer.html +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - - - visualize.tr.tr_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for visualize.tr.tr_visualizer

-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from typing import Optional
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-[docs] -class TreeRingVisualizer(BaseVisualizer): - """Tree-Ring watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step)
- - -
-[docs] - def draw_pattern_fft(self, - title: str = "Tree-Ring FFT with Watermark Area", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with original watermark pattern, with all 0 background. - - Parameters: - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - orig_latent = self.data.orig_watermarked_latents[0, self.data.w_channel].cpu() - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(orig_latent)) - fft_vis = torch.zeros_like(fft_data) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
- - -
-[docs] - def draw_inverted_pattern_fft(self, - step: Optional[int] = None, - title: str = "Tree-Ring FFT with Inverted Watermark Area", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw FFT visualization with inverted pattern, with all 0 background. - - Parameters: - step (Optional[int]): The timestep of the inverted latents. If None, the last timestep is used. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if step is None: - inverted_latent = self.data.reversed_latents[self.watermarking_step][0, self.data.w_channel] - else: - inverted_latent = self.data.reversed_latents[step][0, self.data.w_channel] - - watermarking_mask = self.data.watermarking_mask[0, self.data.w_channel].cpu() - - fft_data = torch.from_numpy(self._fft_transform(inverted_latent)) - fft_vis = torch.zeros_like(fft_data).to(fft_data.device) - fft_vis[watermarking_mask] = fft_data[watermarking_mask] - - im = ax.imshow(np.abs(fft_vis.cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - - return ax
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/videomark/video_mark_visualizer.html b/docs/_build/html/_modules/visualize/videomark/video_mark_visualizer.html deleted file mode 100644 index f85c4b7..0000000 --- a/docs/_build/html/_modules/visualize/videomark/video_mark_visualizer.html +++ /dev/null @@ -1,1202 +0,0 @@ - - - - - - - - visualize.videomark.video_mark_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for visualize.videomark.video_mark_visualizer

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-import numpy as np
-from typing import Optional
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-
-
-
-[docs] -class VideoMarkVisualizer(BaseVisualizer): - """VideoMark watermark visualization class. - - This visualizer handles watermark visualization for VideoShield algorithm, - which extends Gaussian Shading to the video domain by adding frame dimensions. - - Key Members for VideoMarkVisualizer: - - self.data.orig_watermarked_latents: [B, C, F, H, W] - - self.data.reversed_latents: List[[B, C, F, H, W]] - """ - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1, is_video: bool = True): - super().__init__(data_for_visualization, dpi, watermarking_step, is_video) - # Pre-detach all tensors while maintaining device compatibility - if hasattr(self.data, 'watermarked_latents') and self.data.watermarked_latents is not None: - self.data.watermarked_latents = self.data.watermarked_latents.detach() - if hasattr(self.data, 'orig_latents') and self.data.orig_latents is not None: - self.data.orig_latents = self.data.orig_latents.detach() - if hasattr(self.data, 'inverted_latents') and self.data.inverted_latents is not None: - self.data.inverted_latents = self.data.inverted_latents.detach() - if hasattr(self.data, 'prc_codeword') and self.data.prc_codeword is not None: - self.data.prc_codeword = self.data.prc_codeword.detach() - if hasattr(self.data, 'generator_matrix') and self.data.generator_matrix is not None: - self.data.generator_matrix = self.data.generator_matrix.detach()
- - -
-[docs] - def draw_watermarked_video_frames(self, - num_frames: int = 4, - title: str = "Watermarked Video Frames", - ax: Optional[Axes] = None) -> Axes: - """ - Draw multiple frames from the watermarked video. - - DEPRECATED: - This method is deprecated and will be removed in a future version. - Please use `draw_watermarked_image` instead. - - This method displays a grid of video frames to show the temporal - consistency of the watermarked video. - - Args: - num_frames: Number of frames to display (default: 4) - title: The title of the plot - ax: The axes to plot on - - Returns: - The plotted axes - """ - return self._draw_video_frames( - title=title, - num_frames=num_frames, - ax=ax - )
- - -
-[docs] - def draw_generator_matrix(self, - title: str = "Generator Matrix G", - cmap: str = "Blues", - use_color_bar: bool = True, - max_display_size: int = 50, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the generator matrix visualization - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - max_display_size (int): Maximum size to display (for large matrices) - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if hasattr(self.data, 'generator_matrix') and self.data.generator_matrix is not None: - gen_matrix = self.data.generator_matrix.cpu().numpy() - - # Show a sample of the matrix if it's too large - if gen_matrix.shape[0] > max_display_size or gen_matrix.shape[1] > max_display_size: - sample_size = min(max_display_size, min(gen_matrix.shape)) - matrix_sample = gen_matrix[:sample_size, :sample_size] - title += f" (Sample {sample_size}x{sample_size})" - else: - matrix_sample = gen_matrix - - im = ax.imshow(matrix_sample, cmap=cmap, aspect='auto', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'Generator Matrix\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=10) - ax.set_xlabel('Columns') - ax.set_ylabel('Rows') - return ax
- - -
-[docs] - def draw_codeword(self, - title: str = "VideoMark Codeword", - cmap: str = "viridis", - use_color_bar: bool = True, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the PRC codeword visualization - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if hasattr(self.data, 'prc_codeword') and self.data.prc_codeword is not None: - codeword = self.data.prc_codeword[0].cpu().numpy()#Get the first-frame codeword for visualization - - # If 1D, reshape for visualization - if len(codeword.shape) == 1: - # Create a reasonable 2D shape - length = len(codeword) - height = int(np.sqrt(length)) - width = length // height - if height * width < length: - width += 1 - # Pad if necessary - padded_codeword = np.zeros(height * width) - padded_codeword[:length] = codeword - codeword = padded_codeword.reshape(height, width) - - im = ax.imshow(codeword, cmap=cmap, aspect='equal', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'PRC Codeword\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=12) - return ax
- - -
-[docs] - def draw_recovered_codeword(self, - title: str = "Recovered Codeword (c̃)", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: float = -1.0, - vmax: float = 1.0, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - - if hasattr(self.data, 'recovered_prc') and self.data.recovered_prc is not None: - recovered_codeword = self.data.recovered_prc.cpu().numpy().flatten() - length = len(recovered_codeword) - - side = int(length ** 0.5) - - if side * side == length: - codeword_2d = recovered_codeword.reshape((side, side)) - - im = ax.imshow(codeword_2d, cmap=cmap, vmin=vmin, vmax=vmax, - aspect='equal', **kwargs) - - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=ax, shrink=0.8) - cbar.set_label('Codeword Value', fontsize=8) - else: - ax.text(0.5, 0.5, - f'Recovered Codeword\nLength = {length}\nCannot reshape to square', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - else: - ax.text(0.5, 0.5, 'Recovered Codeword (c̃)\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=10) - ax.axis('off') - return ax
- - - -
-[docs] - def draw_difference_map(self, - title: str = "Difference Map", - cmap: str = "hot", - use_color_bar: bool = True, - channel: int = 0, - frame: int =0, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw difference map between watermarked and inverted latents - - Parameters: - title (str): The title of the plot - cmap (str): The colormap to use - use_color_bar (bool): Whether to display the colorbar - channel (int): The channel to visualize - ax (Axes): The axes to plot on - - Returns: - Axes: The plotted axes - """ - if (hasattr(self.data, 'watermarked_latents') and self.data.watermarked_latents is not None and - hasattr(self.data, 'inverted_latents') and self.data.inverted_latents is not None): - - wm_latents = self._get_latent_data(self.data.watermarked_latents, channel=channel, frame=frame).cpu().numpy() - inv_latents = self._get_latent_data(self.data.inverted_latents, channel=channel, frame=frame).cpu().numpy() - - diff_map = np.abs(wm_latents - inv_latents) - im = ax.imshow(diff_map, cmap=cmap, aspect='equal', **kwargs) - - if use_color_bar: - plt.colorbar(im, ax=ax, shrink=0.8) - else: - ax.text(0.5, 0.5, 'Difference Map\nNot Available', - ha='center', va='center', fontsize=12, transform=ax.transAxes) - - ax.set_title(title, fontsize=12) - ax.set_xlabel('Width') - ax.set_ylabel('Height') - return ax
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/videoshield/video_shield_visualizer.html b/docs/_build/html/_modules/visualize/videoshield/video_shield_visualizer.html deleted file mode 100644 index 8aa8b5b..0000000 --- a/docs/_build/html/_modules/visualize/videoshield/video_shield_visualizer.html +++ /dev/null @@ -1,1286 +0,0 @@ - - - - - - - - visualize.videoshield.video_shield_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for visualize.videoshield.video_shield_visualizer

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-import numpy as np
-from typing import Optional
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-from Crypto.Cipher import ChaCha20
-
-
-
-[docs] -class VideoShieldVisualizer(BaseVisualizer): - """VideoShield watermark visualization class. - - This visualizer handles watermark visualization for VideoShield algorithm, - which extends Gaussian Shading to the video domain by adding frame dimensions. - - Key Members for VideoShieldVisualizer: - - self.data.orig_watermarked_latents: [B, C, F, H, W] - - self.data.reversed_latents: List[[B, C, F, H, W]] - """ - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1, is_video: bool = True): - super().__init__(data_for_visualization, dpi, watermarking_step, is_video)
- - - def _stream_key_decrypt(self, reversed_m: np.ndarray) -> torch.Tensor: - """Decrypt the watermark using ChaCha20 cipher. - - Args: - reversed_m: Encrypted binary message array - - Returns: - Decrypted watermark tensor - """ - cipher = ChaCha20.new(key=self.data.chacha_key, nonce=self.data.chacha_nonce) - - sd_byte = cipher.decrypt(np.packbits(reversed_m).tobytes()) - sd_bit = np.unpackbits(np.frombuffer(sd_byte, dtype=np.uint8)) - - return sd_bit - - def _diffusion_inverse(self, reversed_sd: torch.Tensor) -> torch.Tensor: - """Video-specific diffusion inverse with frame dimension handling. - - Args: - reversed_sd: Video watermark tensor with shape (B, C, F, H, W) - - Returns: - Extracted watermark pattern - """ - ch_stride = 4 // self.data.k_c - frame_stride = self.data.num_frames // self.data.k_f - h_stride = self.data.latents_height // self.data.k_h - w_stride = self.data.latents_width // self.data.k_w - - ch_list = [ch_stride] * self.data.k_c - frame_list = [frame_stride] * self.data.k_f - h_list = [h_stride] * self.data.k_h - w_list = [w_stride] * self.data.k_w - - # Split and reorganize dimensions for voting - split_dim1 = torch.cat(torch.split(reversed_sd, tuple(ch_list), dim=1), dim=0) - split_dim2 = torch.cat(torch.split(split_dim1, tuple(frame_list), dim=2), dim=0) - split_dim3 = torch.cat(torch.split(split_dim2, tuple(h_list), dim=3), dim=0) - split_dim4 = torch.cat(torch.split(split_dim3, tuple(w_list), dim=4), dim=0) - - # Voting - vote = torch.sum(split_dim4, dim=0).clone() - vote[vote <= self.data.vote_threshold] = 0 - vote[vote > self.data.vote_threshold] = 1 - - return vote - -
-[docs] - def draw_watermark_bits(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - title: str = "Original Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None) -> Axes: - """Draw the original watermark bits for VideoShield. - - For video watermarks, this method can visualize specific frames or average - across frames to create a 2D visualization. - - Args: - channel: The channel to visualize. If None, all channels are shown. - frame: The frame to visualize. If None, uses middle frame for videos. - title: The title of the plot. - cmap: The colormap to use. - ax: The axes to plot on. - - Returns: - The plotted axes. - """ - # Reshape watermark to video dimensions based on repetition factors - # VideoShield watermark shape: [1, C//k_c, F//k_f, H//k_h, W//k_w] - ch_stride = 4 // self.data.k_c - frame_stride = self.data.num_frames // self.data.k_f - h_stride = self.data.latents_height // self.data.k_h - w_stride = self.data.latents_width // self.data.k_w - - watermark = self.data.watermark.reshape(1, ch_stride, frame_stride, h_stride, w_stride) - - if channel is not None: - # Single channel visualization - if channel >= ch_stride: - raise ValueError(f"Channel {channel} is out of range. Max channel: {ch_stride - 1}") - - # Select specific frame or use middle frame - if frame is not None: - if frame >= frame_stride: - raise ValueError(f"Frame {frame} is out of range. Max frame: {frame_stride - 1}") - watermark_data = watermark[0, channel, frame].cpu().numpy() - frame_info = f" - Frame {frame}" - else: - # Use middle frame - mid_frame = frame_stride // 2 - watermark_data = watermark[0, channel, mid_frame].cpu().numpy() - frame_info = f" - Frame {mid_frame} (middle)" - - im=ax.imshow(watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - if title != "": - ax.set_title(f"{title} - Channel {channel}{frame_info}", fontsize=10) - ax.axis('off') - - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - # Multi-channel visualization - num_channels = ch_stride - - # Calculate grid layout - rows = int(np.ceil(np.sqrt(num_channels))) - cols = int(np.ceil(num_channels / rows)) - - # Clear the axis and set title - ax.clear() - if title != "": - if frame is not None: - ax.set_title(f"{title} - Frame {frame}", pad=20, fontsize=10) - else: - mid_frame = frame_stride // 2 - ax.set_title(f"{title} - Frame {mid_frame} (middle)", pad=20, fontsize=10) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Select specific frame or use middle frame - if frame is not None: - if frame >= frame_stride: - raise ValueError(f"Frame {frame} is out of range. Max frame: {frame_stride - 1}") - watermark_data = watermark[0, i, frame].cpu().numpy() - else: - mid_frame = frame_stride // 2 - watermark_data = watermark[0, i, mid_frame].cpu().numpy() - - # Draw the watermark channel - sub_ax.imshow(watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - - return ax
- - -
-[docs] - def draw_reconstructed_watermark_bits(self, - channel: Optional[int] = None, - frame: Optional[int] = None, - title: str = "Reconstructed Watermark Bits", - cmap: str = "binary", - ax: Optional[Axes] = None) -> Axes: - """Draw the reconstructed watermark bits for VideoShield. - - Args: - channel: The channel to visualize. If None, all channels are shown. - frame: The frame to visualize. If None, uses middle frame for videos. - title: The title of the plot. - cmap: The colormap to use. - ax: The axes to plot on. - - Returns: - The plotted axes. - """ - # Step 1: Get reversed latents and reconstruct the watermark bits - reversed_latent = self.data.reversed_latents[self.watermarking_step] - - # Convert to binary bits - reversed_m = (reversed_latent > 0).int() - - # Decrypt - reversed_sd_flat = self._stream_key_decrypt(reversed_m.flatten().cpu().numpy()) - # Reshape back to video tensor - reversed_sd = torch.from_numpy(reversed_sd_flat).reshape(reversed_latent.shape).to(torch.uint8) - - # Extract watermark through voting mechanism - reversed_watermark = self._diffusion_inverse(reversed_sd.cuda()) - - # Calculate bit accuracy - bit_acc = (reversed_watermark == self.data.watermark).float().mean().item() - - # Reshape to video dimensions for visualization - ch_stride = 4 // self.data.k_c - frame_stride = self.data.num_frames // self.data.k_f - h_stride = self.data.latents_height // self.data.k_h - w_stride = self.data.latents_width // self.data.k_w - - reconstructed_watermark = reversed_watermark.reshape(1, ch_stride, frame_stride, h_stride, w_stride) - - if channel is not None: - # Single channel visualization - if channel >= ch_stride: - raise ValueError(f"Channel {channel} is out of range. Max channel: {ch_stride - 1}") - - # Select specific frame or use middle frame - if frame is not None: - if frame >= frame_stride: - raise ValueError(f"Frame {frame} is out of range. Max frame: {frame_stride - 1}") - reconstructed_watermark_data = reconstructed_watermark[0, channel, frame].cpu().numpy() - frame_info = f" - Frame {frame}" - else: - # Use middle frame - mid_frame = frame_stride // 2 - reconstructed_watermark_data = reconstructed_watermark[0, channel, mid_frame].cpu().numpy() - frame_info = f" - Frame {mid_frame} (middle)" - - im=ax.imshow(reconstructed_watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - if title != "": - ax.set_title(f"{title} - Channel {channel}{frame_info} (Bit Acc: {bit_acc:.3f})", fontsize=10) - else: - ax.set_title(f"Channel {channel}{frame_info} (Bit Acc: {bit_acc:.3f})", fontsize=10) - ax.axis('off') - cbar = ax.figure.colorbar(im, ax=ax, alpha=0.0) - cbar.ax.set_visible(False) - else: - # Multi-channel visualization - num_channels = ch_stride - - # Calculate grid layout - rows = int(np.ceil(np.sqrt(num_channels))) - cols = int(np.ceil(num_channels / rows)) - - # Clear the axis and set title with bit accuracy - ax.clear() - if title != "": - if frame is not None: - ax.set_title(f'{title} - Frame {frame} (Bit Acc: {bit_acc:.3f})', pad=20, fontsize=10) - else: - mid_frame = frame_stride // 2 - ax.set_title(f'{title} - Frame {mid_frame} (middle) (Bit Acc: {bit_acc:.3f})', pad=20, fontsize=10) - else: - if frame is not None: - ax.set_title(f'Frame {frame} (Bit Acc: {bit_acc:.3f})', pad=20, fontsize=10) - else: - mid_frame = frame_stride // 2 - ax.set_title(f'Frame {mid_frame} (middle) (Bit Acc: {bit_acc:.3f})', pad=20, fontsize=10) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Select specific frame or use middle frame - if frame is not None: - if frame >= frame_stride: - raise ValueError(f"Frame {frame} is out of range. Max frame: {frame_stride - 1}") - reconstructed_watermark_data = reconstructed_watermark[0, i, frame].cpu().numpy() - else: - mid_frame = frame_stride // 2 - reconstructed_watermark_data = reconstructed_watermark[0, i, mid_frame].cpu().numpy() - - # Draw the reconstructed watermark channel - sub_ax.imshow(reconstructed_watermark_data, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - - return ax
- - -
-[docs] - def draw_watermarked_video_frames(self, - num_frames: int = 4, - title: str = "Watermarked Video Frames", - ax: Optional[Axes] = None) -> Axes: - """ - Draw multiple frames from the watermarked video. - - DEPRECATED: - This method is deprecated and will be removed in a future version. - Please use `draw_watermarked_image` instead. - - This method displays a grid of video frames to show the temporal - consistency of the watermarked video. - - Args: - num_frames: Number of frames to display (default: 4) - title: The title of the plot - ax: The axes to plot on - - Returns: - The plotted axes - """ - return self._draw_video_frames( - title=title, - num_frames=num_frames, - ax=ax - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/visualize/wind/wind_visualizer.html b/docs/_build/html/_modules/visualize/wind/wind_visualizer.html deleted file mode 100644 index edbf397..0000000 --- a/docs/_build/html/_modules/visualize/wind/wind_visualizer.html +++ /dev/null @@ -1,1344 +0,0 @@ - - - - - - - - visualize.wind.wind_visualizer — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for visualize.wind.wind_visualizer

-from typing import Optional
-import torch
-import matplotlib.pyplot as plt
-from matplotlib.axes import Axes
-import numpy as np
-from visualize.base import BaseVisualizer
-from visualize.data_for_visualization import DataForVisualization
-from matplotlib.gridspec import GridSpecFromSubplotSpec
-
-
-[docs] -class WINDVisualizer(BaseVisualizer): - """WIND watermark visualization class""" - -
-[docs] - def __init__(self, data_for_visualization: DataForVisualization, dpi: int = 300, watermarking_step: int = -1): - super().__init__(data_for_visualization, dpi, watermarking_step) - index = self.data.current_index % self.data.M - self.group_pattern = self.data.group_patterns[index] # shape: [4, 64, 64]
- - -
-[docs] - def draw_group_pattern_fft(self, - channel: Optional[int] = None, - title: str = "Group Pattern in Fourier Domain", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the group pattern in Fourier Domain. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (Axes): The axes to plot on. - - Returns: - Axes: The plotted axes. - """ - if channel is not None: - im = ax.imshow(np.abs(self.group_pattern[channel].cpu().numpy()), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Draw the latent channel - latent_data = self.group_pattern[i].cpu().numpy() - im = sub_ax.imshow(np.abs(latent_data), cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - - -
-[docs] - def draw_orig_noise_wo_group_pattern(self, - channel: Optional[int] = None, - title: str = "Original Noise without Group Pattern", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the original noise without group pattern. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (plt.Axes): The axes to plot on. - - Returns: - plt.axes.Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - orig_noise_fft = self._fft_transform(self.data.orig_watermarked_latents[0, channel]) - z_fft = orig_noise_fft - self.group_pattern[channel].cpu().numpy() - z_cleaned = self._ifft_transform(z_fft).real - im = ax.imshow(z_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Compute original noise without group pattern for this channel - orig_noise_fft = self._fft_transform(self.data.orig_watermarked_latents[0, i]) - z_fft = orig_noise_fft - self.group_pattern[i].cpu().numpy() - z_cleaned = self._ifft_transform(z_fft).real - - # Draw the cleaned noise channel - im = sub_ax.imshow(z_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_inverted_noise_wo_group_pattern(self, - channel: Optional[int] = None, - title: str = "Inverted Noise without Group Pattern", - cmap: str = "viridis", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the inverted noise without group pattern. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (plt.Axes): The axes to plot on. - - Returns: - plt.axes.Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - reversed_latent = self.data.reversed_latents[self.watermarking_step] - reversed_latent_fft = self._fft_transform(reversed_latent[0, channel]) - z_fft = reversed_latent_fft - self.group_pattern[channel].cpu().numpy() - z_cleaned = self._ifft_transform(z_fft).real - im = ax.imshow(z_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Compute inverted noise without group pattern for this channel - reversed_latent = self.data.reversed_latents[self.watermarking_step] - reversed_latent_fft = self._fft_transform(reversed_latent[0, i]) - z_fft = reversed_latent_fft - self.group_pattern[i].cpu().numpy() - z_cleaned = self._ifft_transform(z_fft).real - - # Draw the cleaned inverted noise channel - im = sub_ax.imshow(z_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_diff_noise_wo_group_pattern(self, - channel: Optional[int] = None, - title: str = "Difference map without Group Pattern", - cmap: str = "coolwarm", - use_color_bar: bool = True, - vmin: Optional[float] = None, - vmax: Optional[float] = None, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - """ - Draw the difference between original and inverted noise after removing group pattern. - - Parameters: - channel (Optional[int]): The channel of the latent tensor to visualize. If None, all 4 channels are shown. - title (str): The title of the plot. - cmap (str): The colormap to use. - use_color_bar (bool): Whether to display the colorbar. - ax (plt.Axes): The axes to plot on. - - Returns: - plt.axes.Axes: The plotted axes. - """ - if channel is not None: - # Single channel visualization - # Process original latents - orig_latent_channel = self.data.orig_watermarked_latents[0, channel] - orig_noise_fft = self._fft_transform(orig_latent_channel) - orig_z_fft = orig_noise_fft - self.group_pattern[channel].cpu().numpy() - orig_z_cleaned = self._ifft_transform(orig_z_fft).real - - # Process inverted latents - reversed_latent = self.data.reversed_latents[self.watermarking_step] - reversed_latent_fft = self._fft_transform(reversed_latent[0, channel]) - inv_z_fft = reversed_latent_fft - self.group_pattern[channel].cpu().numpy() - inv_z_cleaned = self._ifft_transform(inv_z_fft).real - - # Compute difference - diff_cleaned = orig_z_cleaned - inv_z_cleaned - - im = ax.imshow(diff_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - if title != "": - ax.set_title(title) - if use_color_bar: - ax.figure.colorbar(im, ax=ax) - ax.axis('off') - else: - # Multi-channel visualization - num_channels = 4 - rows = 2 - cols = 2 - - # Clear the axis and set title - ax.clear() - if title != "": - ax.set_title(title, pad=20) - ax.axis('off') - - # Use gridspec for better control - gs = GridSpecFromSubplotSpec(rows, cols, subplot_spec=ax.get_subplotspec(), - wspace=0.3, hspace=0.4) - - # Create subplots for each channel - for i in range(num_channels): - row_idx = i // cols - col_idx = i % cols - - # Create subplot using gridspec - sub_ax = ax.figure.add_subplot(gs[row_idx, col_idx]) - - # Process original latents for this channel - orig_latent_channel = self.data.orig_watermarked_latents[0, i] - orig_noise_fft = self._fft_transform(orig_latent_channel) - orig_z_fft = orig_noise_fft - self.group_pattern[i].cpu().numpy() - orig_z_cleaned = self._ifft_transform(orig_z_fft).real - - # Process inverted latents for this channel - reversed_latent = self.data.reversed_latents[self.watermarking_step] - reversed_latent_fft = self._fft_transform(reversed_latent[0, i]) - inv_z_fft = reversed_latent_fft - self.group_pattern[i].cpu().numpy() - inv_z_cleaned = self._ifft_transform(inv_z_fft).real - - # Compute difference - diff_cleaned = orig_z_cleaned - inv_z_cleaned - - # Draw the difference channel - im = sub_ax.imshow(diff_cleaned, cmap=cmap, vmin=vmin, vmax=vmax, **kwargs) - sub_ax.set_title(f'Channel {i}', fontsize=8, pad=3) - sub_ax.axis('off') - # Add small colorbar for each subplot - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=sub_ax, fraction=0.046, pad=0.04) - cbar.ax.tick_params(labelsize=6) - - return ax
- - -
-[docs] - def draw_inverted_group_pattern_fft(self, - channel: Optional[int] = None, - title: str = "WIND Two-Stage Detection Visualization", - cmap: str = "viridis", - use_color_bar: bool = True, - ax: Optional[Axes] = None, - **kwargs) -> Axes: - - # Get inverted latents - reversed_latents = self.data.reversed_latents[self.watermarking_step] - - if channel is not None: - # Single channel visualization - latent_channel = reversed_latents[0, channel] - else: - # Average across all channels for clearer visualization - latent_channel = reversed_latents[0].mean(dim=0) - - # Convert to frequency domain - z_fft = torch.fft.fftshift(torch.fft.fft2(latent_channel), dim=(-1, -2)) - - # Get the group pattern that would be detected - index = self.data.current_index % self.data.M - if channel is not None: - group_pattern = self.group_pattern[channel] - else: - group_pattern = self.group_pattern.mean(dim=0) - - # Create circular mask - mask = self._create_circle_mask(64, self.data.group_radius) - - # Remove group pattern - z_fft_cleaned = z_fft - group_pattern * mask - - detection_signal = torch.abs(z_fft_cleaned) - - # Apply same mask that detector uses to focus on watermark region - detection_signal = detection_signal * mask - - # Plot the detection signal - im = ax.imshow(detection_signal.cpu().numpy(), cmap=cmap, **kwargs) - - if title != "": - detection_info = f" (Group {index}, Radius {self.data.group_radius})" - ax.set_title(title + detection_info, fontsize=10) - - ax.axis('off') - - # Add colorbar - if use_color_bar: - cbar = ax.figure.colorbar(im, ax=ax) - cbar.set_label('Detection Signal Magnitude', fontsize=8) - - return ax
- - - def _create_circle_mask(self, size: int, r: int) -> torch.Tensor: - """Create circular mask for watermark region (same as in detector)""" - y, x = torch.meshgrid(torch.arange(size), torch.arange(size), indexing='ij') - center = size // 2 - dist = (x - center)**2 + (y - center)**2 - return ((dist >= (r-2)**2) & (dist <= r**2)).float().to(self.data.orig_watermarked_latents.device)
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/auto_watermark.html b/docs/_build/html/_modules/watermark/auto_watermark.html deleted file mode 100644 index ab72f3a..0000000 --- a/docs/_build/html/_modules/watermark/auto_watermark.html +++ /dev/null @@ -1,999 +0,0 @@ - - - - - - - - watermark.auto_watermark — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.auto_watermark

-import importlib
-from watermark.base import BaseWatermark
-from typing import Union, Optional
-from utils.pipeline_utils import (
-    get_pipeline_type, 
-    PIPELINE_TYPE_IMAGE, 
-    PIPELINE_TYPE_TEXT_TO_VIDEO, 
-    PIPELINE_TYPE_IMAGE_TO_VIDEO
-)
-from watermark.auto_config import AutoConfig
-
-WATERMARK_MAPPING_NAMES={
-    'TR': 'watermark.tr.TR',
-    'GS': 'watermark.gs.GS',
-    'PRC': 'watermark.prc.PRC',
-    'VideoShield': 'watermark.videoshield.VideoShieldWatermark',
-    "VideoMark": 'watermark.videomark.VideoMarkWatermark',
-    'RI': 'watermark.ri.RI',
-    'SEAL': 'watermark.seal.SEAL',
-    'ROBIN': 'watermark.robin.ROBIN',
-    'WIND': 'watermark.wind.WIND',
-    'SFW': 'watermark.sfw.SFW',
-    'GM': 'watermark.gm.GM'
-}
-
-# Dictionary mapping pipeline types to supported watermarking algorithms
-PIPELINE_SUPPORTED_WATERMARKS = {
-    PIPELINE_TYPE_IMAGE: ["TR", "GS", "PRC", "RI", "SEAL", "ROBIN", "WIND", "GM", "SFW"],
-    PIPELINE_TYPE_TEXT_TO_VIDEO: ["VideoShield", "VideoMark"],
-    PIPELINE_TYPE_IMAGE_TO_VIDEO: ["VideoShield", "VideoMark"]
-}
-
-def watermark_name_from_alg_name(name: str) -> Optional[str]:
-    """Get the watermark class name from the algorithm name."""
-    for algorithm_name, watermark_name in WATERMARK_MAPPING_NAMES.items():
-        if name.lower() == algorithm_name.lower():
-            return watermark_name
-    return None
-
-
-[docs] -class AutoWatermark: - """ - This is a generic watermark class that will be instantiated as one of the watermark classes of the library when - created with the [`AutoWatermark.load`] class method. - - This class cannot be instantiated directly using `__init__()` (throws an error). - """ - -
-[docs] - def __init__(self): - raise EnvironmentError( - "AutoWatermark is designed to be instantiated " - "using the `AutoWatermark.load(algorithm_name, algorithm_config, diffusion_config)` method." - )
- - - @staticmethod - def _check_pipeline_compatibility(pipeline_type: str, algorithm_name: str) -> bool: - """Check if the pipeline type is compatible with the watermarking algorithm.""" - if pipeline_type is None: - return False - - if algorithm_name not in WATERMARK_MAPPING_NAMES: - return False - - return algorithm_name in PIPELINE_SUPPORTED_WATERMARKS.get(pipeline_type, []) - -
-[docs] - @classmethod - def load(cls, algorithm_name, algorithm_config=None, diffusion_config=None, *args, **kwargs) -> BaseWatermark: - """Load the watermark algorithm instance based on the algorithm name.""" - # Check if the algorithm exists - watermark_name = watermark_name_from_alg_name(algorithm_name) - if watermark_name is None: - supported_algs = list(WATERMARK_MAPPING_NAMES.keys()) - raise ValueError(f"Invalid algorithm name: {algorithm_name}. Please use one of the supported algorithms: {', '.join(supported_algs)}") - - # Check pipeline compatibility - if diffusion_config and diffusion_config.pipe: - pipeline_type = get_pipeline_type(diffusion_config.pipe) - if not cls._check_pipeline_compatibility(pipeline_type, algorithm_name): - supported_algs = PIPELINE_SUPPORTED_WATERMARKS.get(pipeline_type, []) - raise ValueError( - f"The algorithm '{algorithm_name}' is not compatible with the {pipeline_type} pipeline type. " - f"Supported algorithms for this pipeline type are: {', '.join(supported_algs)}" - ) - - # Load the watermark module - module_name, class_name = watermark_name.rsplit('.', 1) - module = importlib.import_module(module_name) - watermark_class = getattr(module, class_name) - watermark_config = AutoConfig.load(algorithm_name, diffusion_config, algorithm_config_path=algorithm_config, **kwargs) - watermark_instance = watermark_class(watermark_config) - return watermark_instance
- - -
-[docs] - @classmethod - def list_supported_algorithms(cls, pipeline_type: Optional[str] = None): - """List all supported watermarking algorithms, optionally filtered by pipeline type.""" - if pipeline_type is None: - return list(WATERMARK_MAPPING_NAMES.keys()) - else: - if pipeline_type not in PIPELINE_SUPPORTED_WATERMARKS: - raise ValueError(f"Unknown pipeline type: {pipeline_type}. Supported types are: {', '.join(PIPELINE_SUPPORTED_WATERMARKS.keys())}") - return PIPELINE_SUPPORTED_WATERMARKS[pipeline_type]
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/base.html b/docs/_build/html/_modules/watermark/base.html deleted file mode 100644 index ddb5494..0000000 --- a/docs/_build/html/_modules/watermark/base.html +++ /dev/null @@ -1,1510 +0,0 @@ - - - - - - - - watermark.base — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.base

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from abc import ABC, abstractmethod
-import torch
-from typing import Dict, List, Union, Optional, Any, Tuple
-from utils.diffusion_config import DiffusionConfig
-from utils.utils import load_config_file, set_random_seed
-from utils.media_utils import *
-from utils.pipeline_utils import (
-    get_pipeline_type,
-    is_image_pipeline, 
-    is_video_pipeline,
-    is_t2v_pipeline,
-    is_i2v_pipeline,
-    PIPELINE_TYPE_IMAGE, 
-    PIPELINE_TYPE_TEXT_TO_VIDEO, 
-    PIPELINE_TYPE_IMAGE_TO_VIDEO
-)
-from PIL import Image
-from diffusers import (
-    StableDiffusionPipeline, 
-    TextToVideoSDPipeline, 
-    StableVideoDiffusionPipeline,
-    DDIMInverseScheduler
-)
-
-class BaseConfig(ABC):
-    """Base configuration class for diffusion watermarking methods."""
-    
-    def __init__(self, algorithm_config: str, diffusion_config: DiffusionConfig, *args, **kwargs) -> None:
-        """Initialize base configuration with common parameters."""
-        
-        # Load config file
-        self.config_dict = load_config_file(f'config/{self.algorithm_name()}.json') if algorithm_config is None else load_config_file(algorithm_config)
-        
-        # Diffusion model parameters
-        if diffusion_config is None:
-            raise ValueError("diffusion_config cannot be None for BaseConfig initialization")
-        
-        if kwargs:
-            self.config_dict.update(kwargs)
-        
-        self.pipe = diffusion_config.pipe
-        self.scheduler = diffusion_config.scheduler
-        self.device = diffusion_config.device
-        self.guidance_scale = diffusion_config.guidance_scale
-        self.num_images = diffusion_config.num_images
-        self.num_inference_steps = diffusion_config.num_inference_steps
-        self.num_inversion_steps = diffusion_config.num_inversion_steps
-        self.image_size = diffusion_config.image_size
-        self.dtype = diffusion_config.dtype
-        self.gen_seed = diffusion_config.gen_seed
-        self.init_latents_seed = diffusion_config.init_latents_seed
-        self.inversion_type = diffusion_config.inversion_type
-        self.num_frames = diffusion_config.num_frames
-        
-        # Set inversion module
-        self.inversion = set_inversion(self.pipe, self.inversion_type)
-        # Set generation kwargs
-        self.gen_kwargs = diffusion_config.gen_kwargs
-        
-        # Get initial latents
-        init_latents_rng = torch.Generator(device=self.device)
-        init_latents_rng.manual_seed(self.init_latents_seed)
-        if self.num_frames < 1:
-            self.init_latents = get_random_latents(self.pipe, height=self.image_size[0], width=self.image_size[1], generator=init_latents_rng)
-        else:
-            self.init_latents = get_random_latents(self.pipe, num_frames=self.num_frames, height=self.image_size[0], width=self.image_size[1], generator=init_latents_rng)
-
-        # Initialize algorithm-specific parameters
-        self.initialize_parameters()
-
-    @abstractmethod
-    def initialize_parameters(self) -> None:
-        """Initialize algorithm-specific parameters. Should be overridden by subclasses."""
-        raise NotImplementedError
-
-    @property
-    def algorithm_name(self) -> str:
-        """Return the algorithm name."""
-        raise NotImplementedError
-
-
-[docs] -class BaseWatermark(ABC): - """Base class for diffusion watermarking methods.""" - -
-[docs] - def __init__(self, - config: BaseConfig, - *args, **kwargs) -> None: - """Initialize the watermarking algorithm.""" - self.config = config - self.orig_watermarked_latents = None - - # Determine pipeline type - self.pipeline_type = self._detect_pipeline_type() - - # Validate pipeline configuration - self._validate_pipeline_config()
- - - def _detect_pipeline_type(self) -> str: - """Detect the type of pipeline being used.""" - pipeline_type = get_pipeline_type(self.config.pipe) - if pipeline_type is None: - raise ValueError(f"Unsupported pipeline type: {type(self.config.pipe)}") - return pipeline_type - - def _validate_pipeline_config(self) -> None: - """Validate that the pipeline configuration is correct for the pipeline type.""" - # For image-to-video pipelines, ensure num_frames is set correctly - if self.pipeline_type == PIPELINE_TYPE_IMAGE_TO_VIDEO or self.pipeline_type == PIPELINE_TYPE_TEXT_TO_VIDEO: - if self.config.num_frames < 1: - raise ValueError(f"For {self.pipeline_type} pipelines, num_frames must be >= 1, got {self.config.num_frames}") - # For image pipelines, ensure num_frames is -1 - elif self.pipeline_type == PIPELINE_TYPE_IMAGE: - if self.config.num_frames >= 1: - raise ValueError(f"For {self.pipeline_type} pipelines, num_frames should be -1, got {self.config.num_frames}") - -
-[docs] - def get_orig_watermarked_latents(self) -> torch.Tensor: - """Get the original watermarked latents.""" - return self.orig_watermarked_latents
- - -
-[docs] - def set_orig_watermarked_latents(self, value: torch.Tensor) -> None: - """Set the original watermarked latents.""" - self.orig_watermarked_latents = value
- - -
-[docs] - def generate_watermarked_media(self, - input_data: Union[str, Image.Image], - *args, - **kwargs) -> Union[Image.Image, List[Image.Image]]: - """ - Generate watermarked media (image or video) based on pipeline type. - - This is the main interface for generating watermarked content with any - watermarking algorithm. It automatically routes to the appropriate generation - method based on the pipeline type (image or video). - - Args: - input_data: Text prompt (for T2I or T2V) or input image (for I2V) - *args: Additional positional arguments - **kwargs: Additional keyword arguments, including: - - guidance_scale: Guidance scale for generation - - num_inference_steps: Number of inference steps - - height, width: Dimensions of generated media - - seed: Random seed for generation - - Returns: - Union[Image.Image, List[Image.Image]]: Generated watermarked media - - For image pipelines: Returns a single PIL Image - - For video pipelines: Returns a list of PIL Images (frames) - - Examples: - ```python - # Image watermarking - watermark = AutoWatermark.load('TR', diffusion_config=config) - image = watermark.generate_watermarked_media( - input_data="A beautiful landscape", - guidance_scale=7.5, - num_inference_steps=50 - ) - - # Video watermarking (T2V) - watermark = AutoWatermark.load('VideoShield', diffusion_config=config) - frames = watermark.generate_watermarked_media( - input_data="A dog running in a park", - num_frames=16 - ) - - # Video watermarking (I2V) - watermark = AutoWatermark.load('VideoShield', diffusion_config=config) - frames = watermark.generate_watermarked_media( - input_data=reference_image, - num_frames=16 - ) - ``` - """ - # Route to the appropriate generation method based on pipeline type - if is_image_pipeline(self.config.pipe): - if not isinstance(input_data, str): - raise ValueError("For image generation, input_data must be a text prompt (string)") - return self._generate_watermarked_image(input_data, *args, **kwargs) - elif is_video_pipeline(self.config.pipe): - return self._generate_watermarked_video(input_data, *args, **kwargs)
- - -
-[docs] - def generate_unwatermarked_media(self, - input_data: Union[str, Image.Image], - *args, - **kwargs) -> Union[Image.Image, List[Image.Image]]: - """ - Generate unwatermarked media (image or video) based on pipeline type. - - Args: - input_data: Text prompt (for T2I or T2V) or input image (for I2V) - *args: Additional positional arguments - **kwargs: Additional keyword arguments, including: - - save_path: Path to save the generated media - - Returns: - Union[Image.Image, List[Image.Image]]: Generated unwatermarked media - """ - # Route to the appropriate generation method based on pipeline type - if is_image_pipeline(self.config.pipe): - if not isinstance(input_data, str): - raise ValueError("For image generation, input_data must be a text prompt (string)") - return self._generate_unwatermarked_image(input_data, *args, **kwargs) - elif is_video_pipeline(self.config.pipe): - return self._generate_unwatermarked_video(input_data, *args, **kwargs)
- - -
-[docs] - def detect_watermark_in_media(self, - media: Union[Image.Image, List[Image.Image], np.ndarray, torch.Tensor], - *args, - **kwargs) -> Dict[str, Any]: - """ - Detect watermark in media (image or video). - - Args: - media: The media to detect watermark in (can be PIL image, list of frames, numpy array, or tensor) - *args: Additional positional arguments - **kwargs: Additional keyword arguments, including: - - prompt: Optional text prompt used to generate the media (for some algorithms) - - num_inference_steps: Optional number of inference steps - - guidance_scale: Optional guidance scale - - num_frames: Optional number of frames - - decoder_inv: Optional decoder inversion - - inv_order: Inverse order for Exact Inversion - - detector_type: Type of detector to use - - Returns: - Dict[str, Any]: Detection results with metrics and possibly visualizations - """ - # Process the input media into the right format based on pipeline type - processed_media = self._preprocess_media_for_detection(media) - - # Route to the appropriate detection method - if is_image_pipeline(self.config.pipe): - return self._detect_watermark_in_image( - processed_media, - *args, - **kwargs - ) - else: - return self._detect_watermark_in_video( - processed_media, - *args, - **kwargs - )
- - - def _preprocess_media_for_detection(self, - media: Union[Image.Image, List[Image.Image], np.ndarray, torch.Tensor] - ) -> Union[Image.Image, List[Image.Image], torch.Tensor]: - """ - Preprocess media for detection based on its type and the pipeline type. - - Args: - media: The media to preprocess - - Returns: - Union[Image.Image, List[Image.Image], torch.Tensor]: Preprocessed media - """ - if is_image_pipeline(self.config.pipe): - if isinstance(media, Image.Image): - return media - elif isinstance(media, np.ndarray): - return cv2_to_pil(media) - elif isinstance(media, torch.Tensor): - # Convert tensor to PIL image - if media.dim() == 3: # C, H, W - media = media.unsqueeze(0) # Add batch dimension - img_np = torch_to_numpy(media)[0] # Take first image - return cv2_to_pil(img_np) - elif isinstance(media, list): # Compatible for detection pipeline - return media[0] - else: - raise ValueError(f"Unsupported media type for image pipeline: {type(media)}") - else: - # Video pipeline - if isinstance(media, list): - # List of frames - if all(isinstance(frame, Image.Image) for frame in media): - return media - elif all(isinstance(frame, np.ndarray) for frame in media): - return [cv2_to_pil(frame) for frame in media] - else: - raise ValueError("All frames must be either PIL images or numpy arrays") - elif isinstance(media, np.ndarray): - # Convert numpy video to list of PIL images - if media.ndim == 4: # F, H, W, C - return [cv2_to_pil(frame) for frame in media] - else: - raise ValueError(f"Unsupported numpy array shape for video: {media.shape}") - elif isinstance(media, torch.Tensor): - # Convert tensor to list of PIL images - if media.dim() == 5: # B, C, F, H, W - video_np = torch_to_numpy(media)[0] # Take first batch - return [cv2_to_pil(frame) for frame in video_np] - elif media.dim() == 4 and media.shape[0] > 3: # F, C, H, W (assuming F > 3) - frames = [] - for i in range(media.shape[0]): - frame_np = torch_to_numpy(media[i].unsqueeze(0))[0] - frames.append(cv2_to_pil(frame_np)) - return frames - else: - raise ValueError(f"Unsupported tensor shape for video: {media.shape}") - else: - raise ValueError(f"Unsupported media type for video pipeline: {type(media)}") - - def _generate_watermarked_image(self, - prompt: str, - *args, - **kwargs) -> Image.Image: - """ - Generate watermarked image from text prompt. - - Parameters: - prompt (str): The input prompt. - - Returns: - Image.Image: The generated watermarked image. - - Raises: - ValueError: If the pipeline doesn't support image generation. - """ - if self.pipeline_type != PIPELINE_TYPE_IMAGE: - raise ValueError(f"This pipeline ({self.pipeline_type}) does not support image generation. Use generate_watermarked_video instead.") - - # The implementation depends on the specific watermarking algorithm - # This method should be implemented by subclasses - raise NotImplementedError("This method is not implemented for this watermarking algorithm.") - - def _generate_watermarked_video(self, - input_data: Union[str, Image.Image], - *args, - **kwargs) -> Union[List[Image.Image], Image.Image]: - """ - Generate watermarked video based on text prompt or input image. - - Parameters: - input_data (Union[str, Image.Image]): Either a text prompt (for T2V) or an input image (for I2V). - - If the pipeline is T2V, input_data should be a string prompt. - - If the pipeline is I2V, input_data should be an Image object or can be passed as kwargs['input_image']. - kwargs: - - 'input_image': The input image for I2V pipelines. - - 'prompt': The text prompt for T2V pipelines. - - 'image_path': The path to the input image for I2V pipelines. - - Returns: - Union[List[Image.Image], Image.Image]: The generated watermarked video frames. - - Raises: - ValueError: If the pipeline doesn't support video generation or if input type is incompatible. - """ - if not is_video_pipeline(self.config.pipe): - raise ValueError(f"This pipeline ({self.pipeline_type}) does not support video generation. Use generate_watermarked_image instead.") - - # The implementation depends on the specific watermarking algorithm - # This method should be implemented by subclasses - raise NotImplementedError("This method is not implemented for this watermarking algorithm.") - - def _generate_unwatermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """ - Generate unwatermarked image from text prompt. - - Parameters: - prompt (str): The input prompt. - - Returns: - Image.Image: The generated unwatermarked image. - - Raises: - ValueError: If the pipeline doesn't support image generation. - """ - if not is_image_pipeline(self.config.pipe): - raise ValueError(f"This pipeline ({self.pipeline_type}) does not support image generation. Use generate_unwatermarked_video instead.") - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": self.config.init_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - set_random_seed(self.config.gen_seed) - return self.config.pipe( - prompt, - **generation_params - ).images[0] - - def _generate_unwatermarked_video(self, input_data: Union[str, Image.Image], *args, **kwargs) -> List[Image.Image]: - """ - Generate unwatermarked video based on text prompt or input image. - - Parameters: - input_data (Union[str, Image.Image]): Either a text prompt (for T2V) or an input image (for I2V). - - If the pipeline is T2V, input_data should be a string prompt. - - If the pipeline is I2V, input_data should be an Image object or can be passed as kwargs['input_image']. - kwargs: - - 'input_image': The input image for I2V pipelines. - - 'prompt': The text prompt for T2V pipelines. - - 'image_path': The path to the input image for I2V pipelines. - - Returns: - List[Image.Image]: The generated unwatermarked video frames. - - Raises: - ValueError: If the pipeline doesn't support video generation or if input type is incompatible. - """ - if not is_video_pipeline(self.config.pipe): - raise ValueError(f"This pipeline ({self.pipeline_type}) does not support video generation. Use generate_unwatermarked_image instead.") - - # Handle Text-to-Video pipeline - if is_t2v_pipeline(self.config.pipe): - # For T2V, input should be a text prompt - if not isinstance(input_data, str): - raise ValueError("Text-to-Video pipeline requires a text prompt as input_data") - - # Construct generation parameters - generation_params = { - "latents": self.config.init_latents, - "num_frames": self.config.num_frames, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "num_inference_steps": self.config.num_inference_steps, - "guidance_scale": self.config.guidance_scale, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Generate the video - set_random_seed(self.config.gen_seed) - output = self.config.pipe( - input_data, # Use prompt - **generation_params - ) - - # 根据测试结果,我们知道 TextToVideoSDPipeline 的输出有 frames 属性 - if hasattr(output, 'frames'): - frames = output.frames[0] - elif hasattr(output, 'videos'): - frames = output.videos[0] - else: - frames = output[0] if isinstance(output, tuple) else output - - # Convert frames to PIL images - frame_list = [cv2_to_pil(frame) for frame in frames] - return frame_list - - # Handle Image-to-Video pipeline - elif is_i2v_pipeline(self.config.pipe): - # For I2V, input should be an image, text prompt is optional - input_image = None - text_prompt = None - - # Check if input_data is an image passed via kwargs - if "input_image" in kwargs and isinstance(kwargs["input_image"], Image.Image): - input_image = kwargs["input_image"] - - # Check if input_data is an image - elif isinstance(input_data, Image.Image): - input_image = input_data - - # If input_data is a string but we need an image, check if an image path was provided - elif isinstance(input_data, str): - import os - from PIL import Image as PILImage - - if os.path.exists(input_data): - try: - input_image = PILImage.open(input_data).convert("RGB") - except Exception as e: - raise ValueError(f"Input data is neither an Image object nor a valid image path. Failed to load image from path: {e}") - else: - # Treat as text prompt if no valid image path - text_prompt = input_data - if input_image is None: - raise ValueError("Input image is required for Image-to-Video pipeline") - - # Construct generation parameters - generation_params = { - "image": input_image, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "num_frames": self.config.num_frames, - "latents": self.config.init_latents, - "num_inference_steps": self.config.num_inference_steps, - "max_guidance_scale": self.config.guidance_scale, - "output_type": "np", - } - # In I2VGen-XL, the text prompt is needed - if text_prompt is not None: - generation_params["prompt"] = text_prompt - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Generate the video - set_random_seed(self.config.gen_seed) - video = self.config.pipe( - **generation_params - ).frames[0] - - # Convert frames to PIL images - frame_list = [cv2_to_pil(frame) for frame in video] - return frame_list - - # This should never happen since we already checked pipeline type - raise NotImplementedError(f"Unsupported video pipeline type: {self.pipeline_type}") - - def _detect_watermark_in_video(self, - video_frames: List[Image.Image], - *args, - **kwargs) -> Dict[str, Any]: - """ - Detect watermark in video frames. - - Args: - video_frames: List of video frames as PIL images - kwargs: - - 'prompt': Optional text prompt used for generation (for T2V pipelines) - - 'reference_image': Optional reference image (for I2V pipelines) - - 'guidance_scale': The guidance scale for the detector (optional) - - 'detector_type': The type of detector to use (optional) - - 'num_inference_steps': Number of inference steps for inversion (optional) - - 'num_frames': Number of frames to use for detection (optional for I2V pipelines) - - 'decoder_inv': Whether to use decoder inversion (optional) - - 'inv_order': Inverse order for Exact Inversion (optional) - - Returns: - Dict[str, Any]: Detection results - - Raises: - NotImplementedError: If the watermarking algorithm doesn't support video watermark detection - """ - raise NotImplementedError("Video watermark detection is not implemented for this algorithm") - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs) -> Dict[str, float]: - """ - Detect watermark in image. - - Args: - image (Image.Image): The input image. - prompt (str): The prompt used for generation. - kwargs: - - 'guidance_scale': The guidance scale for the detector. - - 'detector_type': The type of detector to use. - - 'num_inference_steps': Number of inference steps for inversion. - - 'decoder_inv': Whether to use decoder inversion. - - 'inv_order': Inverse order for Exact Inversion. - - Returns: - Dict[str, float]: The detection result. - """ - raise NotImplementedError("Watermark detection in image is not implemented for this algorithm") - -
-[docs] - @abstractmethod - def get_data_for_visualize(self, media, *args, **kwargs): - """Get data for visualization.""" - pass
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/gm/gm.html b/docs/_build/html/_modules/watermark/gm/gm.html deleted file mode 100644 index 7eb34d7..0000000 --- a/docs/_build/html/_modules/watermark/gm/gm.html +++ /dev/null @@ -1,1720 +0,0 @@ - - - - - - - - watermark.gm.gm — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.gm.gm

-from __future__ import annotations
-
-import copy
-import random
-from dataclasses import dataclass
-from functools import reduce
-from pathlib import Path
-from typing import Dict, Optional, Tuple, Union
-
-import numpy as np
-import torch
-from PIL import Image
-from Crypto.Cipher import ChaCha20
-from Crypto.Random import get_random_bytes
-from scipy.special import betainc
-from scipy.stats import norm, truncnorm
-from huggingface_hub import hf_hub_download
-
-from ..base import BaseConfig, BaseWatermark
-from utils.media_utils import get_random_latents, get_media_latents, transform_to_model_format
-from utils.utils import set_random_seed
-from visualize.data_for_visualization import DataForVisualization
-from .gnr import GNRRestorer
-
-import joblib
-
-
-# -----------------------------------------------------------------------------
-# Helper utilities adapted from the official GaussMarker implementation
-# -----------------------------------------------------------------------------
-
-
-def _bytes_from_seed(seed: Optional[int], length: int) -> bytes:
-	"""Generate deterministic bytes using a Python PRNG seed."""
-	if seed is None:
-		return get_random_bytes(length)
-	rng = random.Random(seed)
-	return bytes(rng.getrandbits(8) for _ in range(length))
-
-
-
-[docs] -def circle_mask(size: int, radius: int, x_offset: int = 0, y_offset: int = 0) -> np.ndarray: - """Create a binary circle mask with optional offset.""" - x0 = y0 = size // 2 - x0 += x_offset - y0 += y_offset - grid_y, grid_x = np.ogrid[:size, :size] - grid_y = grid_y[::-1] - return ((grid_x - x0) ** 2 + (grid_y - y0) ** 2) <= radius ** 2
- - - -
-[docs] -def set_complex_sign(original: torch.Tensor, sign_tensor: torch.Tensor) -> torch.Tensor: - """Apply complex-valued sign encoding (4-way) to a complex tensor.""" - real = original.real.abs() - imag = original.imag.abs() - - sign_map_real = 1 - 2 * (sign_tensor >= 2).float() - sign_map_imag = 1 - 2 * ((sign_tensor % 2) == 1).float() - - signed_real = real * sign_map_real - signed_imag = imag * sign_map_imag - - return torch.complex(signed_real, signed_imag).to(original.dtype)
- - - -
-[docs] -def extract_complex_sign(complex_tensor: torch.Tensor) -> torch.Tensor: - """Extract complex-valued sign encoding (4-way) from a complex tensor.""" - real = complex_tensor.real - imag = complex_tensor.imag - - sign_map_real = (real <= 0).long() - sign_map_imag = (imag <= 0).long() - return 2 * sign_map_real + sign_map_imag
- - - -# ----------------------------------------------------------------------------- -# Gaussian Shading watermark with ChaCha20 encryption (generalised dimensions) -# ----------------------------------------------------------------------------- - - -
-[docs] -@dataclass -class GaussianShadingChaCha: - channel_copy: int - width_copy: int - height_copy: int - fpr: float - user_number: int - latent_channels: int - latent_height: int - latent_width: int - dtype: torch.dtype - device: torch.device - watermark_seed: Optional[int] = None - key_seed: Optional[int] = None - nonce_seed: Optional[int] = None - watermark: Optional[torch.Tensor] = None - key: Optional[bytes] = None - nonce: Optional[bytes] = None - message_bits: Optional[np.ndarray] = None - - def __post_init__(self) -> None: - self.latentlength = self.latent_channels * self.latent_height * self.latent_width - divisor = self.channel_copy * self.width_copy * self.height_copy - if self.latentlength % divisor != 0: - raise ValueError( - "Latent volume is not divisible by channel/width/height copies. " - "Please adjust w_copy/h_copy/channel_copy." - ) - self.marklength = self.latentlength // divisor - - # Voting thresholds identical to official implementation - if self.channel_copy == 1 and self.width_copy == 1 and self.height_copy == 1: - self.threshold = 1 - else: - self.threshold = self.channel_copy * self.width_copy * self.height_copy // 2 - - self.tau_onebit: Optional[float] = None - self.tau_bits: Optional[float] = None - for i in range(self.marklength): - fpr_onebit = betainc(i + 1, self.marklength - i, 0.5) - fpr_bits = fpr_onebit * self.user_number - if fpr_onebit <= self.fpr and self.tau_onebit is None: - self.tau_onebit = i / self.marklength - if fpr_bits <= self.fpr and self.tau_bits is None: - self.tau_bits = i / self.marklength - - # ------------------------------------------------------------------ - # Key/nonce helpers - # ------------------------------------------------------------------ - def _ensure_key_nonce(self) -> None: - if self.key is None: - self.key = _bytes_from_seed(self.key_seed, 32) - if self.nonce is None: - self.nonce = _bytes_from_seed(self.nonce_seed, 12) - - # ------------------------------------------------------------------ - # Sampling helpers - # ------------------------------------------------------------------ - def _truncated_sampling(self, message_bits: np.ndarray) -> torch.Tensor: - z = np.zeros(self.latentlength, dtype=np.float32) - denominator = 2.0 - ppf = [norm.ppf(j / denominator) for j in range(int(denominator) + 1)] - for idx in range(self.latentlength): - dec_mes = reduce(lambda a, b: 2 * a + b, message_bits[idx : idx + 1]) - dec_mes = int(dec_mes) - z[idx] = truncnorm.rvs(ppf[dec_mes], ppf[dec_mes + 1]) - tensor = torch.from_numpy(z).reshape(1, self.latent_channels, self.latent_height, self.latent_width) - return tensor.to(self.device, dtype=torch.float32) - - def _generate_watermark(self) -> None: - generator = torch.Generator(device="cpu") - if self.watermark_seed is not None: - generator.manual_seed(self.watermark_seed) - - watermark = torch.randint( - low=0, - high=2, - size=( - 1, - self.latent_channels // self.channel_copy, - self.latent_height // self.width_copy, - self.latent_width // self.height_copy, - ), - generator=generator, - dtype=torch.int64, - ) - self.watermark = watermark.to(self.device) - - tiled = self.watermark.repeat(1, self.channel_copy, self.width_copy, self.height_copy) - self.message_bits = self._stream_key_encrypt(tiled.flatten().cpu().numpy()) - - # ------------------------------------------------------------------ - # Encryption helpers - # ------------------------------------------------------------------ - def _stream_key_encrypt(self, spread_bits: np.ndarray) -> np.ndarray: - self._ensure_key_nonce() - cipher = ChaCha20.new(key=self.key, nonce=self.nonce) - packed = np.packbits(spread_bits).tobytes() - encrypted = cipher.encrypt(packed) - unpacked = np.unpackbits(np.frombuffer(encrypted, dtype=np.uint8)) - return unpacked[: self.latentlength] - - def _stream_key_decrypt(self, encrypted_bits: np.ndarray) -> torch.Tensor: - self._ensure_key_nonce() - cipher = ChaCha20.new(key=self.key, nonce=self.nonce) - packed = np.packbits(encrypted_bits).tobytes() - decrypted = cipher.decrypt(packed) - bits = np.unpackbits(np.frombuffer(decrypted, dtype=np.uint8)) - bits = bits[: self.latentlength] - tensor = torch.from_numpy(bits.astype(np.uint8)).reshape( - 1, self.latent_channels, self.latent_height, self.latent_width - ) - return tensor.to(self.device) - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ -
-[docs] - def create_watermark_and_return_w_m(self) -> Tuple[torch.Tensor, torch.Tensor]: - if self.watermark is None or self.message_bits is None: - self._generate_watermark() - message_bits = self.message_bits - sampled = self._truncated_sampling(message_bits) - sampled = sampled.to(self.device, dtype=torch.float32) - m_tensor = torch.from_numpy(message_bits.astype(np.float32)).reshape( - 1, self.latent_channels, self.latent_height, self.latent_width - ).to(self.device) - return sampled, m_tensor
- - -
-[docs] - def diffusion_inverse(self, spread_tensor: torch.Tensor) -> torch.Tensor: - tensor = spread_tensor.to(self.device).reshape( - 1, - self.channel_copy, - self.latent_channels // self.channel_copy, - self.width_copy, - self.latent_height // self.width_copy, - self.height_copy, - self.latent_width // self.height_copy, - ) - # Move channel copy to front, height/width copies accordingly - tensor = tensor.sum(dim=(1, 3, 5)) - vote = tensor.clone() - vote[vote <= self.threshold] = 0 - vote[vote > self.threshold] = 1 - return vote.to(torch.int64)
- - -
-[docs] - def pred_m_from_latent(self, reversed_latents: torch.Tensor) -> torch.Tensor: - return (reversed_latents > 0).int().to(self.device)
- - -
-[docs] - def pred_w_from_latent(self, reversed_latents: torch.Tensor) -> torch.Tensor: - reversed_m = self.pred_m_from_latent(reversed_latents) - spread_bits = reversed_m.flatten().detach().cpu().numpy().astype(np.uint8) - decrypted = self._stream_key_decrypt(spread_bits) - return self.diffusion_inverse(decrypted)
- - -
-[docs] - def pred_w_from_m(self, reversed_m: torch.Tensor) -> torch.Tensor: - spread_bits = reversed_m.flatten().detach().cpu().numpy().astype(np.uint8) - decrypted = self._stream_key_decrypt(spread_bits) - return self.diffusion_inverse(decrypted)
- - -
-[docs] - def watermark_tensor(self, device: Optional[torch.device] = None) -> torch.Tensor: - if self.watermark is None: - self._generate_watermark() - device = device or self.device - return self.watermark.to(device)
-
- - - -# ----------------------------------------------------------------------------- -# Utility helpers for GaussMarker -# ----------------------------------------------------------------------------- - - -
-[docs] -class GMUtils: -
-[docs] - def __init__(self, config: "GMConfig") -> None: - self.config = config - self.device = config.device - self.latent_shape = ( - 1, - config.latent_channels, - config.latent_height, - config.latent_width, - ) - try: - self.pipeline_dtype = next(config.pipe.unet.parameters()).dtype - except StopIteration: - self.pipeline_dtype = config.dtype - - watermark_cls = GaussianShadingChaCha - self.watermark_generator = watermark_cls( - channel_copy=config.channel_copy, - width_copy=config.w_copy, - height_copy=config.h_copy, - fpr=config.fpr, - user_number=config.user_number, - latent_channels=config.latent_channels, - latent_height=config.latent_height, - latent_width=config.latent_width, - dtype=torch.float32, - device=torch.device(config.device), - watermark_seed=config.watermark_seed, - key_seed=config.chacha_key_seed, - nonce_seed=config.chacha_nonce_seed, - ) - - # Pre-initialize watermark to keep deterministic behaviour - set_random_seed(config.watermark_seed) - self.base_watermark_latents, self.base_message = self.watermark_generator.create_watermark_and_return_w_m() - self.base_message = self.base_message.to(self.device, dtype=torch.float32) - - self.radius_list = list(range(config.w_radius, 0, -1)) - self.gt_patch = self._build_watermarking_pattern() - self.watermarking_mask = self._build_watermarking_mask() - self.gnr_restorer = self._build_gnr_restorer() - self.fuser = self._build_fuser() - self.fuser_threshold = float(self.config.fuser_threshold) if self.config.fuser_threshold is not None else 0.5 - self.fuser_frequency_scale = float(self.config.fuser_frequency_scale)
- - - # ------------------------------------------------------------------ - # Pattern / mask construction - # ------------------------------------------------------------------ - def _build_watermarking_pattern(self) -> torch.Tensor: - set_random_seed(self.config.w_seed) - base_latents = get_random_latents( - pipe=self.config.pipe, - height=self.config.image_size[0], - width=self.config.image_size[1], - ).to(self.device, dtype=torch.float32) - - pattern = self.config.w_pattern.lower() - if "seed_ring" in pattern: - gt_patch = base_latents.clone() - tmp = copy.deepcopy(gt_patch) - for radius in self.radius_list: - mask = torch.tensor(circle_mask(gt_patch.shape[-1], radius), device=self.device, dtype=torch.bool) - for ch in range(gt_patch.shape[1]): - gt_patch[:, ch, mask] = tmp[0, ch, 0, radius].item() - elif "seed_zeros" in pattern: - gt_patch = torch.zeros_like(base_latents) - elif "seed_rand" in pattern: - gt_patch = base_latents.clone() - elif "rand" in pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(base_latents), dim=(-1, -2)) - gt_patch[:] = gt_patch[0] - elif "zeros" in pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(base_latents), dim=(-1, -2)) * 0 - elif "const" in pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(base_latents), dim=(-1, -2)) * 0 - gt_patch += self.config.w_pattern_const - elif "signal_ring" in pattern: - gt_patch = torch.randint_like(base_latents, low=0, high=2, dtype=torch.int64) - if self.config.w_length is None: - self.config.w_length = len(self.radius_list) * base_latents.shape[1] - watermark_signal = torch.randint(low=0, high=4, size=(self.config.w_length,)) - idx = 0 - for radius in self.radius_list: - mask = torch.tensor(circle_mask(base_latents.shape[-1], radius), device=self.device) - for ch in range(gt_patch.shape[1]): - signal = watermark_signal[idx % len(watermark_signal)].item() - gt_patch[:, ch, mask] = signal - idx += 1 - else: # default ring - gt_patch = torch.fft.fftshift(torch.fft.fft2(base_latents), dim=(-1, -2)) - tmp = gt_patch.clone() - for radius in self.radius_list: - mask = torch.tensor(circle_mask(gt_patch.shape[-1], radius), device=self.device, dtype=torch.bool) - for ch in range(gt_patch.shape[1]): - gt_patch[:, ch, mask] = tmp[0, ch, 0, radius].item() - return gt_patch.to(self.device) - - def _build_watermarking_mask(self) -> torch.Tensor: - mask = torch.zeros(self.latent_shape, dtype=torch.bool, device=self.device) - shape = self.config.w_mask_shape.lower() - - if shape == "circle": - base_mask = torch.tensor(circle_mask(self.latent_shape[-1], self.config.w_radius), device=self.device) - if self.config.w_channel == -1: - mask[:, :, base_mask] = True - else: - mask[:, self.config.w_channel, base_mask] = True - elif shape == "square": - anchor = self.latent_shape[-1] // 2 - sl = slice(anchor - self.config.w_radius, anchor + self.config.w_radius) - if self.config.w_channel == -1: - mask[:, :, sl, sl] = True - else: - mask[:, self.config.w_channel, sl, sl] = True - elif shape == "signal_circle": - mask = torch.zeros(self.latent_shape, dtype=torch.long, device=self.device) - label = 1 - for radius in self.radius_list: - base_mask = torch.tensor(circle_mask(self.latent_shape[-1], radius), device=self.device) - mask[:, :, base_mask] = label - label += 1 - elif shape == "no": - return mask - else: - raise NotImplementedError(f"Unsupported watermark mask shape: {shape}") - - return mask - - def _build_gnr_restorer(self) -> Optional[GNRRestorer]: - checkpoint = self.config.gnr_checkpoint - if not checkpoint: - return None - checkpoint_path = Path(checkpoint) - repo = getattr(self.config, "huggingface_repo", None) - hf_dir=getattr(self.config, "hf_dir", None) - if repo: - try: - hf_path = hf_hub_download(repo_id=repo, filename=Path(checkpoint).name,cache_dir=hf_dir) - print(f"Downloaded GNR checkpoint from Huggingface Hub: {hf_path}") - checkpoint_path = Path(hf_path) - except Exception as e: - raise FileNotFoundError(f"GNR checkpoint not found on ({repo}). error: {e}") - in_channels = self.config.latent_channels * (2 if self.config.gnr_classifier_type == 1 else 1) - return GNRRestorer( - checkpoint_path=checkpoint_path, - in_channels=in_channels, - out_channels=self.config.latent_channels, - nf=self.config.gnr_model_nf, - device=torch.device(self.config.device), - classifier_type=self.config.gnr_classifier_type, - base_message=self.base_message if self.config.gnr_classifier_type == 1 else None, - ) - - def _build_fuser(self): - checkpoint = self.config.fuser_checkpoint - if not checkpoint: - return None - if joblib is None: - raise ImportError( - "joblib is required to load the GaussMarker fuser. Install joblib or disable the fuser." - ) - repo = getattr(self.config, "huggingface_repo", None) - hf_dir=getattr(self.config, "hf_dir", None) - if repo: - try: - hf_path = hf_hub_download(repo_id=repo, filename=Path(checkpoint).name,cache_dir=hf_dir) - candidates = [Path(hf_path)] - except Exception as e: - raise FileNotFoundError(f"Fuser checkpoint not found on ({repo}). error: {e}") - base_dir = Path(__file__).resolve().parent - candidates.append(base_dir / checkpoint) - candidates.append(base_dir.parent.parent / checkpoint) - for candidate in candidates: - if not candidate.is_file(): - from huggingface_hub import snapshot_download - import os - snapshot_download( - repo_id="Generative-Watermark-Toolkits/MarkDiffusion-gm", - local_dir=checkpoint.split("/")[0], - repo_type="model", - local_dir_use_symlinks=False, - endpoint=os.getenv("HF_ENDPOINT", "https://huggingface.co"), - ) - return joblib.load(candidate) - raise FileNotFoundError(f"Fuser checkpoint not found at '{checkpoint}'") - - # ------------------------------------------------------------------ - # Watermark injection / detection helpers - # ------------------------------------------------------------------ - def _inject_complex(self, latents: torch.Tensor) -> torch.Tensor: - fft_latents = torch.fft.fftshift(torch.fft.fft2(latents), dim=(-1, -2)) - target_patch = self.gt_patch - mask = self.watermarking_mask - if mask.dtype != torch.bool: - fft_latents[mask != 0] = target_patch[mask != 0].clone() - else: - fft_latents[mask] = target_patch[mask].clone() - injected = torch.fft.ifft2(torch.fft.ifftshift(fft_latents, dim=(-1, -2))).real - return injected - - def _inject_seed(self, latents: torch.Tensor) -> torch.Tensor: - mask = self.watermarking_mask - injected = latents.clone() - injected[mask] = self.gt_patch[mask].clone() - return injected - - def _inject_signal(self, latents: torch.Tensor) -> torch.Tensor: - fft_latents = torch.fft.fftshift(torch.fft.fft2(latents), dim=(-1, -2)) - mask = self.watermarking_mask - signals = extract_complex_sign(self.gt_patch) - fft_latents_signal = set_complex_sign(fft_latents, signals) - fft_latents[mask != 0] = fft_latents_signal[mask != 0] - injected = torch.fft.ifft2(torch.fft.ifftshift(fft_latents, dim=(-1, -2))).real - return injected - -
-[docs] - def inject_watermark(self, base_latents: torch.Tensor) -> torch.Tensor: - base_latents = base_latents.to(self.device, dtype=torch.float32) - injection = self.config.w_injection.lower() - if "complex" in injection: - watermarked = self._inject_complex(base_latents) - elif "seed" in injection: - watermarked = self._inject_seed(base_latents) - elif "signal" in injection: - watermarked = self._inject_signal(base_latents) - else: - raise NotImplementedError(f"Unsupported injection mode: {self.config.w_injection}") - return watermarked.to(self.config.dtype)
- - -
-[docs] - def generate_watermarked_latents(self, seed: Optional[int] = None) -> torch.Tensor: - if seed is None: - seed = self.config.gen_seed - set_random_seed(seed) - sampled_latents, _ = self.watermark_generator.create_watermark_and_return_w_m() - sampled_latents = sampled_latents.to(self.device, dtype=torch.float32) - watermarked = self.inject_watermark(sampled_latents) - target_dtype = self.pipeline_dtype or self.config.dtype - return watermarked.to(target_dtype)
- - - def _compute_complex_l1(self, reversed_latents: torch.Tensor) -> float: - fft_latents = torch.fft.fftshift(torch.fft.fft2(reversed_latents), dim=(-1, -2)) - target_patch = self.gt_patch - mask = self.watermarking_mask - if mask.dtype != torch.bool: - selection = mask != 0 - else: - selection = mask - if selection.sum() == 0: - return 0.0 - diff = torch.abs(fft_latents[selection] - target_patch[selection]) - return float(diff.mean().item()) - -
-[docs] - def detect_from_latents(self, reversed_latents: torch.Tensor, detector_type: Optional[str] = None) -> Dict[str, Union[float, bool]]: - reversed_latents = reversed_latents.to(self.device, dtype=torch.float32) - metrics: Dict[str, Union[float, bool]] = {} - - bit_watermark = self.watermark_generator.pred_w_from_latent(reversed_latents) - reference_bits = self.watermark_generator.watermark_tensor(bit_watermark.device) - bit_accuracy = (bit_watermark == reference_bits).float().mean().item() - metrics["bit_accuracy"] = float(bit_accuracy) - metrics["tau_bits"] = float(self.watermark_generator.tau_bits or 0.0) - metrics["tau_onebit"] = float(self.watermark_generator.tau_onebit or 0.0) - - reversed_m = self.watermark_generator.pred_m_from_latent(reversed_latents) - message_bits = torch.from_numpy(self.watermark_generator.message_bits.astype(np.float32)).to(reversed_m.device) - m_accuracy = (reversed_m.flatten() == message_bits).float().mean().item() - metrics["message_accuracy"] = float(m_accuracy) - - gnr_bit_accuracy = None - gnr_message_accuracy = None - if self.gnr_restorer is not None: - restored_probs = self.gnr_restorer.restore(reversed_m) - restored_binary = (restored_probs > self.config.gnr_binary_threshold).float() - restored_w = self.watermark_generator.pred_w_from_m(restored_binary) - gnr_bit_accuracy = (restored_w == reference_bits).float().mean().item() - restored_message = restored_binary.flatten() - gnr_message_accuracy = (restored_message == message_bits).float().mean().item() - metrics["gnr_bit_accuracy"] = float(gnr_bit_accuracy) - metrics["gnr_message_accuracy"] = float(gnr_message_accuracy) - - metrics["complex_l1"] = self._compute_complex_l1(reversed_latents) - frequency_score = -metrics["complex_l1"] * self.fuser_frequency_scale - metrics["frequency_score"] = float(frequency_score) - - threshold = self.watermark_generator.tau_bits or 0.5 - decision_threshold = threshold - decision_bit_accuracy = bit_accuracy - if self.gnr_restorer is not None and self.config.gnr_use_for_decision and gnr_bit_accuracy is not None: - decision_bit_accuracy = max(decision_bit_accuracy, gnr_bit_accuracy) - if self.config.gnr_threshold is not None: - decision_threshold = float(self.config.gnr_threshold) - metrics["decision_bit_accuracy"] = float(decision_bit_accuracy) - metrics["decision_threshold"] = float(decision_threshold) - - fused_score = None - if self.fuser is not None: - spatial_score = gnr_bit_accuracy if (gnr_bit_accuracy is not None and self.config.gnr_use_for_decision) else bit_accuracy - frequency_score = metrics["frequency_score"] - features = np.array([[spatial_score, frequency_score]], dtype=np.float32) - if hasattr(self.fuser, "predict_proba"): - fused_score = float(self.fuser.predict_proba(features)[0, 1]) - elif hasattr(self.fuser, "decision_function"): - fused_score = float(self.fuser.decision_function(features)[0]) - else: - raise AttributeError("Unsupported fuser model: missing predict_proba/decision_function") - metrics["fused_score"] = fused_score - metrics["fused_threshold"] = float(self.fuser_threshold) - metrics["is_watermarked"] = bool(fused_score >= self.fuser_threshold) - else: - metrics["is_watermarked"] = bool(decision_bit_accuracy >= decision_threshold) - if gnr_bit_accuracy is not None: - metrics["gnr_threshold"] = float(decision_threshold if self.config.gnr_use_for_decision and self.config.gnr_threshold is not None else threshold) - - if detector_type == "is_watermarked": - return {"is_watermarked": metrics["is_watermarked"]} - if detector_type == "gnr_bit_acc" and gnr_bit_accuracy is not None: - selected_threshold = decision_threshold if self.config.gnr_use_for_decision and self.config.gnr_threshold is not None else threshold - return { - "gnr_bit_accuracy": float(gnr_bit_accuracy), - "threshold": float(selected_threshold), - "is_watermarked": bool(gnr_bit_accuracy >= selected_threshold), - } - if detector_type == "fused" and fused_score is not None: - return { - "fused_score": float(fused_score), - "threshold": float(self.fuser_threshold), - "is_watermarked": bool(fused_score >= self.fuser_threshold), - } - return metrics
-
- - - -# ----------------------------------------------------------------------------- -# Configuration for GaussMarker -# ----------------------------------------------------------------------------- - - -
-[docs] -class GMConfig(BaseConfig): -
-[docs] - def initialize_parameters(self) -> None: - cfg = self.config_dict - self.channel_copy = cfg.get("channel_copy", 1) - self.w_copy = cfg.get("w_copy", 8) - self.h_copy = cfg.get("h_copy", 8) - self.user_number = cfg.get("user_number", 1_000_000) - self.fpr = cfg.get("fpr", 1e-6) - self.chacha_key_seed = cfg.get("chacha_key_seed") - self.chacha_nonce_seed = cfg.get("chacha_nonce_seed") - self.watermark_seed = cfg.get("watermark_seed", self.gen_seed) - - self.w_seed = cfg.get("w_seed", 999_999) - self.w_channel = cfg.get("w_channel", -1) - self.w_pattern = cfg.get("w_pattern", "ring") - self.w_mask_shape = cfg.get("w_mask_shape", "circle") - self.w_radius = cfg.get("w_radius", 4) - self.w_measurement = cfg.get("w_measurement", "l1_complex") - self.w_injection = cfg.get("w_injection", "complex") - self.w_pattern_const = cfg.get("w_pattern_const", 0.0) - self.w_length = cfg.get("w_length") - - self.gnr_checkpoint = cfg.get("gnr_checkpoint") - self.gnr_classifier_type = cfg.get("gnr_classifier_type", 0) - self.gnr_model_nf = cfg.get("gnr_model_nf", 128) - self.gnr_binary_threshold = cfg.get("gnr_binary_threshold", 0.5) - self.gnr_use_for_decision = cfg.get("gnr_use_for_decision", True) - self.gnr_threshold = cfg.get("gnr_threshold") - self.huggingface_repo = cfg.get("huggingface_repo") - self.fuser_checkpoint = cfg.get("fuser_checkpoint") - self.fuser_threshold = cfg.get("fuser_threshold") - self.fuser_frequency_scale = cfg.get("fuser_frequency_scale", 0.01) - self.hf_dir = cfg.get("hf_dir") - - self.latent_channels = self.pipe.unet.config.in_channels - self.latent_height = self.image_size[0] // self.pipe.vae_scale_factor - self.latent_width = self.image_size[1] // self.pipe.vae_scale_factor - - if self.latent_channels % self.channel_copy != 0: - raise ValueError("channel_copy must divide latent channels") - if self.latent_height % self.w_copy != 0 or self.latent_width % self.h_copy != 0: - raise ValueError("w_copy and h_copy must divide latent spatial dimensions")
- - - @property - def algorithm_name(self) -> str: - return "GM"
- - - -# ----------------------------------------------------------------------------- -# Main GaussMarker watermark class -# ----------------------------------------------------------------------------- - - -
-[docs] -class GM(BaseWatermark): -
-[docs] - def __init__(self, watermark_config: GMConfig, *args, **kwargs) -> None: - self.config = watermark_config - self.utils = GMUtils(self.config) - super().__init__(self.config)
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - seed = kwargs.pop("seed", self.config.gen_seed) - watermarked_latents = self.utils.generate_watermarked_latents(seed=seed) - self.set_orig_watermarked_latents(watermarked_latents) - - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": kwargs.pop("guidance_scale", self.config.guidance_scale), - "num_inference_steps": kwargs.pop("num_inference_steps", self.config.num_inference_steps), - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - for key, value in self.config.gen_kwargs.items(): - generation_params.setdefault(key, value) - generation_params.update(kwargs) - generation_params["latents"] = watermarked_latents - - images = self.config.pipe(prompt, **generation_params).images - return images[0] - - def _detect_watermark_in_image( - self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs, - ) -> Dict[str, Union[float, bool]]: - guidance_scale = kwargs.get("guidance_scale", self.config.guidance_scale) - num_steps = kwargs.get("num_inference_steps", self.config.num_inference_steps) - - do_cfg = guidance_scale > 1.0 - prompt_embeds, negative_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_cfg, - num_images_per_prompt=1, - ) - if do_cfg: - text_embeddings = torch.cat([negative_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - processed = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to( - text_embeddings.dtype - ).to(self.config.device) - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed, - sample=False, - decoder_inv=kwargs.get("decoder_inv", False), - ) - - inversion_kwargs = { - key: val - for key, val in kwargs.items() - if key not in {"decoder_inv", "guidance_scale", "num_inference_steps", "detector_type"} - } - - reversed_series = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale, - num_inference_steps=num_steps, - **inversion_kwargs, - ) - reversed_latents = reversed_series[-1] - - return self.utils.detect_from_latents(reversed_latents, detector_type=kwargs.get("detector_type")) - -
-[docs] - def get_data_for_visualize( - self, - image: Image.Image, - prompt: str = "", - guidance_scale: Optional[float] = None, - decoder_inv: bool = False, - *args, - **kwargs, - ) -> DataForVisualization: - guidance = guidance_scale if guidance_scale is not None else self.config.guidance_scale - set_random_seed(self.config.gen_seed) - watermarked_latents = self.utils.generate_watermarked_latents(seed=self.config.gen_seed) - - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": guidance, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - for key, value in self.config.gen_kwargs.items(): - generation_params.setdefault(key, value) - - watermarked_image = self.config.pipe(prompt, **generation_params).images[0] - - do_cfg = guidance > 1.0 - prompt_embeds, negative_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_cfg, - num_images_per_prompt=1, - ) - text_embeddings = torch.cat([negative_embeds, prompt_embeds]) if do_cfg else prompt_embeds - - processed = transform_to_model_format(watermarked_image, target_size=self.config.image_size[0]).unsqueeze(0) - processed = processed.to(text_embeddings.dtype).to(self.config.device) - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed, - sample=False, - decoder_inv=decoder_inv, - ) - - reversed_series = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance, - num_inference_steps=self.config.num_inversion_steps, - ) - - return DataForVisualization( - config=self.config, - utils=self.utils, - orig_watermarked_latents=self.get_orig_watermarked_latents(), - reversed_latents=reversed_series, - image=image, - )
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/gs/gs.html b/docs/_build/html/_modules/watermark/gs/gs.html deleted file mode 100644 index 4636f4e..0000000 --- a/docs/_build/html/_modules/watermark/gs/gs.html +++ /dev/null @@ -1,1213 +0,0 @@ - - - - - - - - watermark.gs.gs — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.gs.gs

-from ..base import BaseWatermark, BaseConfig
-import torch
-from typing import Dict
-from PIL import Image
-from utils.diffusion_config import DiffusionConfig
-import numpy as np
-from Crypto.Cipher import ChaCha20
-import random
-from scipy.stats import norm,truncnorm
-from functools import reduce
-from visualize.data_for_visualization import DataForVisualization
-from detection.gs.gs_detection import GSDetector
-from utils.media_utils import *
-from utils.utils import set_random_seed
-
-
-[docs] -class GSConfig(BaseConfig): - """Config class for Gaussian Shading algorithm.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - self.channel_copy = self.config_dict['channel_copy'] - self.hw_copy = self.config_dict['hw_copy'] - self.chacha = self.config_dict['chacha'] - self.wm_key = self.config_dict['wm_key'] - self.chacha_key_seed = self.config_dict['chacha_key_seed'] - self.chacha_nonce_seed = self.config_dict['chacha_nonce_seed'] - self.threshold = self.config_dict['threshold'] - self.vote_threshold = 1 if self.hw_copy == 1 and self.channel_copy == 1 else self.channel_copy * self.hw_copy * self.hw_copy // 2 - - self.latents_height = self.image_size[0] // self.pipe.vae_scale_factor - self.latents_width = self.image_size[1] // self.pipe.vae_scale_factor - generator = torch.Generator(device=self.device) - generator.manual_seed(self.wm_key) - self.watermark = torch.randint(0, 2, [1, 4 // self.channel_copy, self.latents_height // self.hw_copy, self.latents_width // self.hw_copy], generator=generator, device=self.device) - if not self.chacha: - self.key = torch.randint(0, 2, [1, 4, self.latents_height, self.latents_width], generator=generator, device=self.device)
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'GS'
- - -
-[docs] -class GSUtils: - """Utility class for Gaussian Shading algorithm.""" - -
-[docs] - def __init__(self, config: GSConfig, *args, **kwargs) -> None: - """ - Initialize the Gaussian Shading watermarking utility. - - Parameters: - config (GSConfig): Configuration for the Gaussian Shading watermarking algorithm. - """ - self.config = config - self.chacha_key = self._get_bytes_with_seed(self.config.chacha_key_seed, 32) - self.chacha_nonce = self._get_bytes_with_seed(self.config.chacha_nonce_seed, 12) - self.latentlength = 4 * 64 * 64 - self.marklength = self.latentlength//(self.config.channel_copy * self.config.hw_copy * self.config.hw_copy)
- - - def _get_bytes_with_seed(self, seed: int, n: int) -> bytes: - random.seed(seed) - return bytes(random.getrandbits(8) for _ in range(n)) - - def _stream_key_encrypt(self, sd): - """Encrypt the watermark using ChaCha20 cipher.""" - cipher = ChaCha20.new(key=self.chacha_key, nonce=self.chacha_nonce) - m_byte = cipher.encrypt(np.packbits(sd).tobytes()) - m_bit = np.unpackbits(np.frombuffer(m_byte, dtype=np.uint8)) - return m_bit - - def _truncSampling(self, message): - """Truncated Gaussian sampling for watermarking.""" - z = np.zeros(self.latentlength) - denominator = 2.0 - ppf = [norm.ppf(j / denominator) for j in range(int(denominator) + 1)] - for i in range(self.latentlength): - dec_mes = reduce(lambda a, b: 2 * a + b, message[i : i + 1]) - dec_mes = int(dec_mes) - z[i] = truncnorm.rvs(ppf[dec_mes], ppf[dec_mes + 1]) - z = torch.from_numpy(z).reshape(1, 4, 64, 64).float() - return z.cuda() - - def _create_watermark(self) -> torch.Tensor: - """Create watermark pattern without encryption.""" - sd = self.config.watermark.repeat(1,self.config.channel_copy,self.config.hw_copy,self.config.hw_copy) - m = ((sd + self.config.key) % 2).flatten().cpu().numpy() - w = self._truncSampling(m) - return w - - def _create_watermark_chacha(self) -> torch.Tensor: - """Create watermark pattern using ChaCha20 cipher.""" - sd = self.config.watermark.repeat(1,self.config.channel_copy,self.config.hw_copy,self.config.hw_copy) - m = self._stream_key_encrypt(sd.flatten().cpu().numpy()) - w = self._truncSampling(m) - return w - -
-[docs] - def inject_watermark(self) -> torch.Tensor: - """Inject watermark into latent space.""" - if self.config.chacha: - watermarked = self._create_watermark_chacha() - else: - watermarked = self._create_watermark() - return watermarked
-
- - -
-[docs] -class GS(BaseWatermark): - """Main class for Gaussian Shading watermarking algorithm.""" - -
-[docs] - def __init__(self, - watermark_config: GSConfig, - *args, **kwargs): - """ - Initialize the Gaussian Shading watermarking algorithm. - - Parameters: - watermark_config (GSConfig): Configuration instance of the GS algorithm. - """ - self.config = watermark_config - self.utils = GSUtils(self.config) - - self.detector = GSDetector( - watermarking_mask=self.config.watermark, - chacha=self.config.chacha, - wm_key=(self.utils.chacha_key, self.utils.chacha_nonce) if self.config.chacha else self.config.key, - channel_copy=self.config.channel_copy, - hw_copy=self.config.hw_copy, - vote_threshold=self.config.vote_threshold, - threshold=self.config.threshold, - device=self.config.device - )
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Generate image with Gaussian Shading watermark.""" - set_random_seed(self.config.gen_seed) - watermarked_latents = self.utils.inject_watermark() - - # save watermarked latents - self.set_orig_watermarked_latents(watermarked_latents) - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_latents - - return self.config.pipe( - prompt, - **generation_params - ).images[0] - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str="", - *args, - **kwargs) -> Dict[str, float]: - """Detect Gaussian Shading watermark.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image, sample=False, decoder_inv=kwargs.get("decoder_inv", False)) - - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - # Step 4: Reverse Image Latents - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: float=1, - decoder_inv: bool=False, - *args, - **kwargs) -> DataForVisualization: - """Get Gaussian Shading visualization data""" - # 1. Generate watermarked latents - set_random_seed(self.config.gen_seed) - watermarked_latents = self.utils.inject_watermark() - - # 2. Generate actual watermarked image using the same process as _generate_watermarked_image - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Generate the actual watermarked image - watermarked_image = self.config.pipe( - prompt, - **generation_params - ).images[0] - - # 3. Perform watermark detection to get inverted latents (for comparison) - inverted_latents = None - try: - # Use the same detection process as _detect_watermark_in_image - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess watermarked image for detection - processed_image = transform_to_model_format( - watermarked_image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=decoder_inv - ) - - # Reverse Image Latents to get inverted noise - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - ) - - except Exception as e: - raise ValueError(f"Warning: Could not perform inversion for visualization: {e}") - - # 4. Prepare visualization data - return DataForVisualization( - config=self.config, - utils=self.utils, - orig_watermarked_latents=self.orig_watermarked_latents, - reversed_latents=reversed_latents, - image=image - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/prc/prc.html b/docs/_build/html/_modules/watermark/prc/prc.html deleted file mode 100644 index 20a2224..0000000 --- a/docs/_build/html/_modules/watermark/prc/prc.html +++ /dev/null @@ -1,1319 +0,0 @@ - - - - - - - - watermark.prc.prc — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.prc.prc

-from ..base import BaseWatermark, BaseConfig
-import torch
-from typing import Dict, Tuple
-from utils.diffusion_config import DiffusionConfig
-import numpy as np
-import galois
-from scipy.sparse import csr_matrix
-from scipy.special import binom
-from visualize.data_for_visualization import DataForVisualization
-from detection.prc.prc_detection import PRCDetector
-from utils.media_utils import *
-from utils.utils import set_random_seed
-from PIL import Image
-
-
-[docs] -class PRCConfig(BaseConfig): - """Config class for PRC algorithm.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - self.fpr = self.config_dict['fpr'] - self.t = self.config_dict['prc_t'] - self.var = self.config_dict['var'] - self.threshold = self.config_dict['threshold'] - self.message = self._str_to_binary_array(self.config_dict['message']) - self.message_length = len(self.message) # 8-bit for each character, <= 512 bits for robustness - self.latents_height = self.image_size[0] // self.pipe.vae_scale_factor - self.latents_width = self.image_size[1] // self.pipe.vae_scale_factor - self.latents_channel = self.pipe.unet.config.in_channels - self.n = self.latents_height * self.latents_width * self.latents_channel # Dimension of the latent space - self.GF = galois.GF(2) - - # Seeds for key generation - self.gen_matrix_seed = self.config_dict['keygen']['gen_matrix_seed'] - self.indice_seed = self.config_dict['keygen']['indice_seed'] - self.one_time_pad_seed = self.config_dict['keygen']['one_time_pad_seed'] - self.test_bits_seed = self.config_dict['keygen']['test_bits_seed'] - self.permute_bits_seed = self.config_dict['keygen']['permute_bits_seed'] - - # Seeds for encoding - self.payload_seed = self.config_dict['encode']['payload_seed'] - self.error_seed = self.config_dict['encode']['error_seed'] - self.pseudogaussian_seed = self.config_dict['encode']['pseudogaussian_seed']
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'PRC' - - def _str_to_binary_array(self, s: str) -> np.ndarray: - """Convert string to binary array.""" - # Convert string to binary string - binary_str = ''.join(format(ord(c), '08b') for c in s) - - # Convert binary string to NumPy array - binary_array = np.array([int(bit) for bit in binary_str]) - - return binary_array
- - - -
-[docs] -class PRCUtils: - """Utility class for PRC algorithm.""" - -
-[docs] - def __init__(self, config: PRCConfig, *args, **kwargs) -> None: - """Initialize PRC utility.""" - self.config = config - self.encoding_key, self.decoding_key = self._generate_encoding_key(self.config.message_length)
- - - def _generate_encoding_key(self, message_length: int) -> Tuple[tuple, tuple]: - """Generate encoding key for PRC algorithm.""" - # Set basic scheme parameters - num_test_bits = int(np.ceil(np.log2(1 / self.config.fpr))) - secpar = int(np.log2(binom(self.config.n, self.config.t))) - g = secpar - k = message_length + g + num_test_bits - r = self.config.n - k - secpar - noise_rate = 1 - 2 ** (-secpar / g ** 2) - - # Sample n by k generator matrix (all but the first n-r of these will be over-written) - generator_matrix = self.config.GF.Random(shape=(self.config.n, k), seed=self.config.gen_matrix_seed) - - # Sample scipy.sparse parity-check matrix together with the last n-r rows of the generator matrix - row_indices = [] - col_indices = [] - data = [] - for i, row in enumerate(range(r)): - np.random.seed(self.config.indice_seed + i) - chosen_indices = np.random.choice(self.config.n - r + row, self.config.t - 1, replace=False) - chosen_indices = np.append(chosen_indices, self.config.n - r + row) - row_indices.extend([row] * self.config.t) - col_indices.extend(chosen_indices) - data.extend([1] * self.config.t) - generator_matrix[self.config.n - r + row] = generator_matrix[chosen_indices[:-1]].sum(axis=0) - parity_check_matrix = csr_matrix((data, (row_indices, col_indices))) - - # Compute scheme parameters - max_bp_iter = int(np.log(self.config.n) / np.log(self.config.t)) - - # Sample one-time pad and test bits - one_time_pad = self.config.GF.Random(self.config.n, seed=self.config.one_time_pad_seed) - test_bits = self.config.GF.Random(num_test_bits, seed=self.config.test_bits_seed) - - # Permute bits - np.random.seed(self.config.permute_bits_seed) - permutation = np.random.permutation(self.config.n) - generator_matrix = generator_matrix[permutation] - one_time_pad = one_time_pad[permutation] - parity_check_matrix = parity_check_matrix[:, permutation] - - return (generator_matrix, one_time_pad, test_bits, g, noise_rate), (generator_matrix, parity_check_matrix, one_time_pad, self.config.fpr, noise_rate, test_bits, g, max_bp_iter, self.config.t) - - def _encode_message(self, encoding_key: tuple, message: str = None) -> np.ndarray: - """Encode a message using PRC algorithm.""" - generator_matrix, one_time_pad, test_bits, g, noise_rate = encoding_key - n, k = generator_matrix.shape - - if message is None: - payload = np.concatenate((test_bits, self.config.GF.Random(k - len(test_bits), seed=self.config.payload_seed))) - else: - assert len(message) <= k-len(test_bits)-g, "Message is too long" - payload = np.concatenate((test_bits, self.config.GF.Random(g, seed=self.config.payload_seed), self.config.GF(message), self.config.GF.Zeros(k-len(test_bits)-g-len(message)))) - - np.random.seed(self.config.error_seed) - error = self.config.GF(np.random.binomial(1, noise_rate, n)) - - return 1 - 2 * torch.tensor(payload @ generator_matrix.T + one_time_pad + error, dtype=float) - - def _sample_prc_codeword(self, codeword: torch.Tensor, basis: torch.Tensor = None) -> torch.Tensor: - """Sample a PRC codeword.""" - codeword_np = codeword.numpy() - np.random.seed(self.config.pseudogaussian_seed) - pseudogaussian_np = codeword_np * np.abs(np.random.randn(*codeword_np.shape)) - pseudogaussian = torch.from_numpy(pseudogaussian_np).to(dtype=torch.float32) - if basis is None: - return pseudogaussian - return pseudogaussian @ basis.T - -
-[docs] - def inject_watermark(self) -> torch.Tensor: - """Generate watermarked latents from PRC codeword.""" - # Step 1: Encode message - prc_codeword = self._encode_message(self.encoding_key, self.config.message) - # Step 2: Sample PRC codeword and get watermarked latents - watermarked_latents = self._sample_prc_codeword(prc_codeword).reshape(1, self.config.latents_channel, self.config.latents_height, self.config.latents_width).to(self.config.device) - - return watermarked_latents
-
- - -
-[docs] -class PRC(BaseWatermark): - """PRC watermark class.""" - -
-[docs] - def __init__(self, - watermark_config: PRCConfig, - *args, **kwargs): - """ - Initialize PRC watermarking algorithm. - - Parameters: - watermark_config (PRCConfig): Configuration instance of the PRC algorithm. - """ - self.config = watermark_config - self.utils = PRCUtils(self.config) - - self.detector = PRCDetector( - var=self.config.var, - decoding_key=self.utils.decoding_key, - GF=self.config.GF, - threshold=self.config.threshold, - device=self.config.device - )
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> torch.Tensor: - """Generate watermarked image.""" - watermarked_latents = self.utils.inject_watermark() - - # save watermarked latents - self.set_orig_watermarked_latents(watermarked_latents) - - # Set gen seed - set_random_seed(self.config.gen_seed) - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_latents - - return self.config.pipe( - prompt, - **generation_params - ).images[0] - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str="", - *args, - **kwargs) -> Dict[str, float]: - """Detect watermark in image.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image, sample=False, decoder_inv=kwargs.get('decoder_inv', False)) - - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - # Step 4: Reverse Image Latents - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: float=1, - decoder_inv: bool=False, - *args, - **kwargs) -> DataForVisualization: - # 1. Generate watermarked latents and collect intermediate data - set_random_seed(self.config.gen_seed) - - # Step 1: Encode message - prc_codeword = self.utils._encode_message(self.utils.encoding_key, self.config.message) - - # Step 2: Sample PRC codeword - pseudogaussian_noise = self.utils._sample_prc_codeword(prc_codeword) - - # Step 3: Generate watermarked latents - watermarked_latents = pseudogaussian_noise.reshape(1, self.config.latents_channel, self.config.latents_height, self.config.latents_width).to(self.config.device) - - # 2. Generate actual watermarked image using the same process as _generate_watermarked_image - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Generate the actual watermarked image - watermarked_image = self.config.pipe( - prompt, - **generation_params - ).images[0] - - # 3. Perform watermark detection to get inverted latents (for comparison) - inverted_latents = None - try: - # Use the same detection process as _detect_watermark_in_image - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess watermarked image for detection - processed_image = transform_to_model_format( - watermarked_image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=decoder_inv - ) - - # Reverse Image Latents to get inverted noise - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - inverted_latents = reversed_latents - - except Exception as e: - print(f"Warning: Could not perform inversion for visualization: {e}") - inverted_latents = None - - # 3.5. Run actual detection to get recovered PRC codeword - recovered_prc = None - try: - if inverted_latents is not None: - # Use the detector to recover the PRC codeword - detection_result = self.detector.eval_watermark(inverted_latents) - # The detector should have recovered_prc attribute or return it - if hasattr(self.detector, 'recovered_prc') and self.detector.recovered_prc is not None: - recovered_prc = self.detector.recovered_prc - elif 'recovered_prc' in detection_result: - recovered_prc = detection_result['recovered_prc'] - else: - print("Warning: Detector did not provide recovered_prc") - except Exception as e: - print(f"Warning: Could not recover PRC codeword for visualization: {e}") - recovered_prc = None - - # 4. Prepare PRC-specific data - # Convert message to binary - message_bits = torch.tensor(self.config._str_to_binary_array(self.config.config_dict['message']), dtype=torch.float32) - - # Get generator matrix - generator_matrix = torch.tensor(np.array(self.utils.encoding_key[0], dtype=float), dtype=torch.float32) - - # Get parity check matrix - parity_check_matrix = self.utils.decoding_key[1] - - # PRC parameters for visualization - prc_params = { - 'FPR': self.config.fpr, - 'Parameter t': self.config.t, - 'Variance': self.config.var, - 'Threshold': self.config.threshold, - 'Message Length': self.config.message_length, - 'Latent Dimension': self.config.n - } - - # 5. Prepare visualization data - # Convert inverted_latents to list format to match base class expectations - reversed_latents_list = [inverted_latents] if inverted_latents is not None else [None] - - return DataForVisualization( - config=self.config, - utils=self.utils, - latent_lists=[watermarked_latents], - orig_latents=watermarked_latents, - orig_watermarked_latents=watermarked_latents, - watermarked_latents=watermarked_latents, - watermarked_image=watermarked_image, - image=image, - reversed_latents=reversed_latents_list, - inverted_latents=inverted_latents, - # PRC-specific data - message_bits=message_bits, - prc_codeword=torch.tensor(prc_codeword, dtype=torch.float32), - pseudogaussian_noise=torch.tensor(pseudogaussian_noise, dtype=torch.float32), - generator_matrix=generator_matrix, - parity_check_matrix=parity_check_matrix, - prc_params=prc_params, - threshold=self.config.threshold, - recovered_prc=torch.tensor(recovered_prc, dtype=torch.float32) if recovered_prc is not None else None - )
-
- - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/robin/robin.html b/docs/_build/html/_modules/watermark/robin/robin.html deleted file mode 100644 index 06b9af0..0000000 --- a/docs/_build/html/_modules/watermark/robin/robin.html +++ /dev/null @@ -1,1453 +0,0 @@ - - - - - - - - watermark.robin.robin — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.robin.robin

-from ..base import BaseWatermark, BaseConfig
-from utils.media_utils import *
-import os
-import types
-import torch
-from typing import Dict, Union, List, Optional
-from utils.utils import set_random_seed, inherit_docstring
-from utils.diffusion_config import DiffusionConfig
-import copy
-import numpy as np
-from PIL import Image
-from huggingface_hub import hf_hub_download
-from visualize.data_for_visualization import DataForVisualization
-from evaluation.dataset import StableDiffusionPromptsDataset
-from utils.media_utils import get_random_latents
-from .watermark_generator import OptimizedDataset, get_watermarking_mask, get_watermarking_pattern, inject_watermark, optimizer_wm_prompt, ROBINWatermarkedImageGeneration
-from detection.robin.robin_detection import ROBINDetector
-
-
-[docs] -class ROBINConfig(BaseConfig): - """Config class for ROBIN algorithm, load config file and initialize parameters.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - ## Watermarking-Specific Parameters - self.w_seed = self.config_dict['w_seed'] - self.w_channel = self.config_dict['w_channel'] - self.w_pattern = self.config_dict['w_pattern'] - self.w_mask_shape = self.config_dict['w_mask_shape'] - self.w_up_radius = self.config_dict['w_up_radius'] - self.w_low_radius = self.config_dict['w_low_radius'] - self.w_injection = self.config_dict['w_injection'] - self.w_pattern_const = self.config_dict['w_pattern_const'] - self.threshold = self.config_dict['threshold'] - - self.watermarking_step = self.config_dict['watermarking_step'] - - self.is_training_from_scratch = self.config_dict.get('training_from_scratch', False) - ## Training-Specific Parameters - self.learning_rate = self.config_dict['learning_rate'] # learning rate for watermark optimization - self.scale_lr = self.config_dict['scale_lr'] # if True, learning_rate will be multiplied by gradient_accumulation_steps * train_batch_size * num_processes - self.max_train_steps = self.config_dict['max_train_steps'] # maximum number of training steps for watermark optimization - self.save_steps = self.config_dict['save_steps'] # save steps for watermark optimization - self.train_batch_size = self.config_dict['train_batch_size'] # batch size for watermark optimization - self.gradient_accumulation_steps = self.config_dict['gradient_accumulation_steps'] # gradient accumulation steps for watermark optimization - self.gradient_checkpointing = self.config_dict['gradient_checkpointing'] # if True, use gradient checkpointing for watermark optimization - self.mixed_precision = self.config_dict['mixed_precision'] # fp16, fp32, bf16 - self.train_seed = self.config_dict['train_seed'] # seed for watermark optimization - - self.optimized_guidance_scale = self.config_dict['optimized_guidance_scale'] # guidance scale for optimized prompt signal - self.data_guidance_scale = self.config_dict['data_guidance_scale'] # guidance scale for data prompt signal - self.train_guidance_scale = self.config_dict['train_guidance_scale'] # guidance scale for training prompt signal - self.hf_dir = self.config_dict['hf_dir'] - # self.output_img_dir = 'watermark/robin/generated_images' - self.output_img_dir = "watermark/robin/generated_images" - self.ckpt_dir = 'watermark/robin/ckpts'
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'ROBIN'
- - -
-[docs] -class ROBINUtils: - """Utility class for ROBIN algorithm, contains helper functions.""" - -
-[docs] - def __init__(self, config: ROBINConfig, *args, **kwargs) -> None: - """ - Initialize the ROBIN watermarking algorithm. - - Parameters: - config (ROBINConfig): Configuration for the ROBIN algorithm. - """ - self.config = config
- - -
-[docs] - def build_generation_params(self, **kwargs) -> Dict: - """Build generation parameters from config and kwargs.""" - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": self.config.init_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - return generation_params
- - -
-[docs] - def generate_clean_images(self, dataset: StableDiffusionPromptsDataset, **kwargs) -> List[Image.Image]: - """Generate clean images for optimization.""" - generation_params = self.build_generation_params(**kwargs, guidance_scale=self.config.data_guidance_scale) - - clean_images = [] - for i, prompt in enumerate(dataset): - formatted_img_filename = f"ori-lg{generation_params['guidance_scale']}-{i}.jpg" - if os.path.exists(os.path.join(self.config.output_img_dir, formatted_img_filename)): - clean_images.append(Image.open(os.path.join(self.config.output_img_dir, formatted_img_filename))) - else: - no_watermarked_image = self.config.pipe( - prompt, - **generation_params, - ).images[0] - clean_images.append(no_watermarked_image) - - os.makedirs(self.config.output_img_dir, exist_ok=True) - no_watermarked_image.save(os.path.join(self.config.output_img_dir, f"ori-lg{generation_params['guidance_scale']}-{i}.jpg")) - - return clean_images
- - -
-[docs] - def build_watermarking_args(self) -> types.SimpleNamespace: - """Build watermarking arguments from config.""" - watermarking_args = { - "w_seed": self.config.w_seed, - "w_channel": self.config.w_channel, - "w_pattern": self.config.w_pattern, - "w_mask_shape": self.config.w_mask_shape, - "w_up_radius": self.config.w_up_radius, - "w_low_radius": self.config.w_low_radius, - "w_pattern_const": self.config.w_pattern_const, - "w_injection": self.config.w_injection, - } - return types.SimpleNamespace(**watermarking_args)
- - -
-[docs] - def build_hyperparameters(self) -> Dict: - """Build hyperparameters for optimization from config.""" - return { - "learning_rate": self.config.learning_rate, - "scale_lr": self.config.scale_lr, - "max_train_steps": self.config.max_train_steps, - "save_steps": self.config.save_steps, - "train_batch_size": self.config.train_batch_size, - "gradient_accumulation_steps": self.config.gradient_accumulation_steps, - "gradient_checkpointing": self.config.gradient_checkpointing, - "guidance_scale": self.config.train_guidance_scale, - "optimized_guidance_scale": self.config.optimized_guidance_scale, - "mixed_precision": self.config.mixed_precision, - "seed": self.config.train_seed, - "output_dir": self.config.ckpt_dir, - }
- - -
-[docs] - def optimize_watermark(self, dataset: StableDiffusionPromptsDataset, watermarking_args: types.SimpleNamespace) -> tuple: - """Optimize watermark and watermarking signal.""" - init_latents_w = get_random_latents(pipe=self.config.pipe) - watermarking_mask = get_watermarking_mask(init_latents_w, self.config, self.config.device).detach().cpu() - - # Build hyperparameters - hyperparameters = self.build_hyperparameters() - checkpoint_path=hf_hub_download(repo_id="Generative-Watermark-Toolkits/MarkDiffusion-robin", filename=f"optimized_wm5-30_embedding-step-{hyperparameters['max_train_steps']}.pt", cache_dir=self.config.hf_dir) - - # if os.path.exists(checkpoint_path): - if (not self.config.is_training_from_scratch): - if not os.path.exists(checkpoint_path): - os.makedirs(self.config.ckpt_dir, exist_ok=True) - from huggingface_hub import snapshot_download - snapshot_download( - repo_id="Generative-Watermark-Toolkits/MarkDiffusion-robin", - local_dir=self.config.ckpt_dir, - repo_type="model", - local_dir_use_symlinks=False, - endpoint=os.getenv("HF_ENDPOINT", "https://huggingface.co"), - ) - - print(f"Loading checkpoint from {checkpoint_path}") - checkpoint = torch.load(checkpoint_path) - optimized_watermark = checkpoint['opt_wm'].to(self.config.device) - optimized_watermarking_signal = checkpoint['opt_acond'].to(self.config.device) - - return watermarking_mask, optimized_watermark, optimized_watermarking_signal - else: - print(f"Start training from scratch") - # Generate clean images - clean_images = self.generate_clean_images(dataset) - # Create training dataset - train_dataset = OptimizedDataset( - data_root=self.config.output_img_dir, - custom_dataset=dataset, - size=512, - repeats=10, - interpolation="bicubic", - ) - - train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=self.config.train_batch_size, shuffle=True) - - opt_watermark = get_watermarking_pattern(pipe=self.config.pipe, args=watermarking_args, device=self.config.device) - - optimized_watermark, optimized_watermarking_signal = optimizer_wm_prompt( - pipe=self.config.pipe, - dataloader=train_dataloader, - hyperparameters=hyperparameters, - mask=watermarking_mask, - opt_wm=opt_watermark, - save_path=self.config.ckpt_dir, - args=watermarking_args, - ) - - return watermarking_mask, optimized_watermark, optimized_watermarking_signal
- - -
-[docs] - def initialize_detector(self, watermarking_mask, optimized_watermark) -> ROBINDetector: - """Initialize the ROBIN detector.""" - return ROBINDetector( - watermarking_mask=watermarking_mask, - gt_patch=optimized_watermark, - threshold=self.config.threshold, - device=self.config.device - )
- - -
-[docs] - def preprocess_image_for_detection(self, image: Image.Image, prompt: str, guidance_scale: float) -> tuple: - """Preprocess image and get text embeddings for detection.""" - # Get Text Embeddings - do_classifier_free_guidance = (guidance_scale > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess Image - processed_image = transform_to_model_format( - image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - return text_embeddings, processed_image
- - -
-[docs] - def extract_latents_for_detection(self, - image: Image.Image, - prompt: str, - guidance_scale: float, - num_inference_steps: int, - extract_latents_step: int, - **kwargs) -> torch.Tensor: - """Extract and reverse latents for watermark detection.""" - # Preprocess image and get text embeddings - text_embeddings, processed_image = self.preprocess_image_for_detection(image, prompt, guidance_scale) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=kwargs.get('decoder_inv', False) - ) - - # Reverse Image Latents - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale, - num_inference_steps=num_inference_steps, - **inversion_kwargs - )[extract_latents_step] - - return reversed_latents
-
- - -
-[docs] -@inherit_docstring -class ROBIN(BaseWatermark): -
-[docs] - def __init__(self, - watermarking_config: ROBINConfig, - *args, **kwargs): - """ - Initialize the ROBIN watermarking algorithm. - - Parameters: - watermarking_config (ROBINConfig): Configuration for the ROBIN algorithm. - """ - #super().__init__(algorithm_config, diffusion_config) - self.config = watermarking_config - self.utils = ROBINUtils(self.config) - - # === Get the optimized watermark & watermarking signal before generation === - self.dataset = StableDiffusionPromptsDataset() - - # Build watermarking arguments - self.watermarking_args = self.utils.build_watermarking_args() - - # Optimize watermark and get components - self.watermarking_mask, self.optimized_watermark, self.optimized_watermarking_signal = self.utils.optimize_watermark( - self.dataset, - self.watermarking_args - ) - - # Initialize detector - self.detector = self.utils.initialize_detector(self.watermarking_mask, self.optimized_watermark)
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Internal method to generate a watermarked image.""" - self.set_orig_watermarked_latents(self.config.init_latents) - - # Build generation parameters using utils - generation_params = self.utils.build_generation_params(**kwargs) - # Override guidance_scale for watermarked generation - generation_params["guidance_scale"] = self.config.guidance_scale - # Ensure latents parameter is not overridden - generation_params["latents"] = self.config.init_latents - - # Filter out parameters not supported by ROBINWatermarkedImageGeneration - supported_params = { - 'height', 'width', 'num_inference_steps', 'guidance_scale', 'optimized_guidance_scale', - 'negative_prompt', 'num_images_per_prompt', 'eta', 'generator', 'latents', - 'output_type', 'return_dict', 'callback', 'callback_steps' - } - filtered_params = {k: v for k, v in generation_params.items() if k in supported_params} - - # Ensure watermarking components are on the correct device - watermarking_mask = self.watermarking_mask.to(self.config.device) - optimized_watermark = self.optimized_watermark.to(self.config.device) - optimized_watermarking_signal = self.optimized_watermarking_signal.to(self.config.device) if self.optimized_watermarking_signal is not None else None - - # Generate watermarked image - set_random_seed(self.config.gen_seed) - result = ROBINWatermarkedImageGeneration( - pipe=self.config.pipe, - prompt=prompt, - watermarking_mask=watermarking_mask, - gt_patch=optimized_watermark, - opt_acond=optimized_watermarking_signal, - watermarking_step=self.config.watermarking_step, - args=self.watermarking_args, - **filtered_params, - ) - return result.images[0] - - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs) -> Dict[str, float]: - """Detect the watermark in the image.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image, sample=False, decoder_inv=kwargs.get('decoder_inv', False)) - - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - # Extract and reverse latents for detection using utils - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[num_steps_to_use - 1 - self.config.watermarking_step] - - # Evaluate watermark - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: Optional[float]=None, - decoder_inv: bool=False, - *args, - **kwargs) -> DataForVisualization: - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = guidance_scale if guidance_scale is not None else self.config.guidance_scale - - # Step 1: Generate watermarked latents (generation process) - set_random_seed(self.config.gen_seed) - # For ROBIN, the watermarked latents are the init_latents (watermark is applied during generation) - watermarked_latents = self.config.init_latents - - # Step 2: Generate actual watermarked image using the same process as _generate_watermarked_image - generation_params = self.utils.build_generation_params() - generation_params["guidance_scale"] = self.config.guidance_scale - generation_params["latents"] = self.config.init_latents - - # Generate the actual watermarked image with ROBIN watermarking - watermarked_image = ROBINWatermarkedImageGeneration( - pipe=self.config.pipe, - prompt=prompt, - watermarking_mask=self.watermarking_mask, - gt_patch=self.optimized_watermark, - opt_acond=self.optimized_watermarking_signal, - watermarking_step=self.config.watermarking_step, - args=self.watermarking_args, - **generation_params, - ).images[0] - - # Step 3: Perform watermark detection to get inverted latents (detection process) - reversed_latents_list = None - - # Get Text Embeddings for detection - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess watermarked image for detection - processed_image = transform_to_model_format( - watermarked_image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=decoder_inv - ) - - # Reverse Image Latents to get inverted noise - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['prompt', 'decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents_list = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=self.config.num_inference_steps, - **inversion_kwargs - ) - - # Step 4: Prepare visualization data - return DataForVisualization( - config=self.config, - utils=self.utils, - reversed_latents=reversed_latents_list, - orig_watermarked_latents=self.orig_watermarked_latents, - image=image, - # ROBIN-specific data - watermarking_mask=self.watermarking_mask, - optimized_watermark=self.optimized_watermark, - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/seal/seal.html b/docs/_build/html/_modules/watermark/seal/seal.html deleted file mode 100644 index 6006500..0000000 --- a/docs/_build/html/_modules/watermark/seal/seal.html +++ /dev/null @@ -1,1265 +0,0 @@ - - - - - - - - watermark.seal.seal — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.seal.seal

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from ..base import BaseWatermark, BaseConfig
-import torch
-from typing import List
-from utils.utils import set_random_seed
-from utils.diffusion_config import DiffusionConfig
-from visualize.data_for_visualization import DataForVisualization
-from transformers import Blip2Processor, Blip2ForConditionalGeneration
-from sentence_transformers import SentenceTransformer
-from PIL import Image
-import math
-from detection.seal.seal_detection import SEALDetector
-from utils.media_utils import *
-
-
-[docs] -class SEALConfig(BaseConfig): - """Config class for SEAL algorithm.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize parameters for SEAL algorithm.""" - self.k_value = self.config_dict['k_value'] - self.b_value = self.config_dict['b_value'] - self.patch_distance_threshold = self.config_dict['patch_distance_threshold'] - self.theta_mid = self.config_dict['theta_mid'] - self.cap_processor = Blip2Processor.from_pretrained(self.config_dict['cap_processor']) - self.cap_model = Blip2ForConditionalGeneration.from_pretrained(self.config_dict['cap_processor'], torch_dtype=torch.float16).to(self.device) - self.sentence_model = SentenceTransformer(self.config_dict['sentence_model']).to(self.device) - - self.secret_salt = self.config_dict['secret_salt']
- - - @property - def algorithm_name(self) -> str: - """Return the name of the algorithm.""" - return "SEAL"
- - -
-[docs] -class SEALUtils: - """Utility class for SEAL algorithm.""" - -
-[docs] - def __init__(self, config: SEALConfig, *args, **kwargs) -> None: - """Initialize SEAL utility.""" - self.config = config
- - - def _simhash(self, v: torch.Tensor, k: int, b: int, seed: int) -> List[int]: - """ - SimHash algorithm to generate hash keys for an embedding vector. - - Args: - v: Input embedding vector - k: Number of patches - b: Number of bits per patch - seed: Random seed - - Returns: - List of hash keys - """ - - keys = [] - set_random_seed(seed) - for j in range(k): - bits = [0] * b - for i in range(b): - r_i = torch.randn_like(v) - bits[i] = 1 if torch.dot(r_i, v) > 0 else 0 - bits[i] = (bits[i] + i + j * 1e4) - hash_value = hash(tuple(bits)) - keys.append(hash_value) - return keys - -
-[docs] - def generate_caption(self, image: Image.Image) -> str: - """ - Generate caption for an image. - - Args: - image: PIL Image object - - Returns: - Caption string - """ - raw_image = image.convert('RGB') - inputs = self.config.cap_processor(raw_image, return_tensors="pt") - inputs = {k: v.to(self.config.device) for k, v in inputs.items()} - out = self.config.cap_model.generate(**inputs) - return self.config.cap_processor.decode(out[0], skip_special_tokens=True)
- - -
-[docs] - def generate_initial_noise(self, embedding: torch.Tensor, k: int, b: int, seed: int) -> torch.Tensor: - """ - Generates initial noise using improved simhash approach. - - Args: - embedding: Input embedding vector(Nomalized) - k: k_value(Patch number) - b: b_value(Bit number per patch) - seed: Random seed(secret_salt) - Returns: - Noise tensor with shape [1, 4, 64, 64] - """ - - # Calculate patch grid dimensions - patch_per_side = int(math.ceil(math.sqrt(k))) - - # Generate hash keys for the embedding - keys = self._simhash(embedding, k, b, seed) - - # Create empty noise tensor - initial_noise = torch.zeros(1, 4, 64, 64, device=self.config.device) - - # Calculate patch dimensions - patch_height = 64 // patch_per_side - patch_width = 64 // patch_per_side - - # Fill noise tensor with random patches based on hash keys - patch_count = 0 - hash_mapping = {} - - for i in range(patch_per_side): - for j in range(patch_per_side): - if patch_count >= k: - break - - # Get hash key for this patch - hash_key = keys[patch_count] - hash_mapping[patch_count] = hash_key - - # Set random seed based on hash key - set_random_seed(hash_key) - - # Calculate patch coordinates - y_start = i * patch_height - x_start = j * patch_width - y_end = min(y_start + patch_height, 64) - x_end = min(x_start + patch_width, 64) - - # Generate random noise for this patch - initial_noise[:, :, y_start:y_end, x_start:x_end] = torch.randn( - (1, 4, y_end - y_start, x_end - x_start), - device=self.config.device - ) - - patch_count += 1 - - return initial_noise
-
- - -
-[docs] -class SEAL(BaseWatermark): - """SEAL watermarking algorithm.""" - -
-[docs] - def __init__(self, watermark_config: SEALConfig, *args, **kwargs) -> None: - """ - Initialize the SEAL algorithm. - - Parameters: - watermark_config (SEALConfig): Configuration instance of the SEAL algorithm. - """ - self.config = watermark_config - self.utils = SEALUtils(self.config) - self.original_embedding = None - - self.detector = SEALDetector(self.config.k_value, self.config.b_value, self.config.theta_mid, self.config.cap_processor, self.config.cap_model, self.config.sentence_model, self.config.patch_distance_threshold, self.config.device)
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Generate watermarked image.""" - - ## Step 1: Generate original image - image = self.config.pipe(prompt).images[0] - - ## Step 2: Caption the original image - image_caption = self.utils.generate_caption(image) - - ## Step 3: Get the embedding of the caption - embedding = self.config.sentence_model.encode(image_caption, convert_to_tensor=True).to(self.config.device) - embedding = embedding / torch.norm(embedding) - self.original_embedding = embedding - - ## Step 4: Get the watermarked initial latents - watermarked_latents = self.utils.generate_initial_noise(embedding, self.config.k_value, self.config.b_value, self.config.secret_salt) - - # save watermarked latents - self.set_orig_watermarked_latents(watermarked_latents) - - # Set gen seed - set_random_seed(self.config.gen_seed) - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_latents - - ## Step 5: Generate watermarked image - watermarked_image = self.config.pipe(prompt, **generation_params).images[0] - - return watermarked_image - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str="", - *args, - **kwargs) -> bool: - """Detect watermark in the image.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image_tensor = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - image_tensor = image_tensor.to(dtype=self.config.pipe.vae.dtype) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image_tensor, sample=False, decoder_inv=kwargs.get('decoder_inv', False)) - - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - # Step 4: Reverse Image Latents - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - # Step 5: Generate noise z(~) again from the inspected image's caption - inspected_caption = self.utils.generate_caption(image) - inspected_embedding = self.config.sentence_model.encode(inspected_caption, convert_to_tensor=True).to(self.config.device) - inspected_embedding = inspected_embedding / torch.norm(inspected_embedding) - - inspected_noise = self.utils.generate_initial_noise(inspected_embedding, self.config.k_value, self.config.b_value, self.config.secret_salt) - - # Detect the watermark - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, inspected_noise, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents, inspected_noise) - - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: float=1, - decoder_inv: bool=False, - *args, **kwargs) -> DataForVisualization: - """ - Get data for visualization of SEAL watermarking process. - - Args: - image: Input image for visualization - prompt: Text prompt used for generation - guidance_scale: Guidance scale for diffusion process - decoder_inv: Whether to use decoder inversion - - Returns: - DataForVisualization object with SEAL-specific data - """ - # Step 1: Perform detection process for comparison - inspected_caption = self.utils.generate_caption(image) - inspected_embedding = self.config.sentence_model.encode(inspected_caption, convert_to_tensor=True).to(self.config.device) - normalized_inspected_embedding = inspected_embedding / torch.norm(inspected_embedding) - - # Generate inspected noise for detection - inspected_noise = self.utils.generate_initial_noise(normalized_inspected_embedding, self.config.k_value, self.config.b_value, self.config.secret_salt) - - # Step 2: Get inverted latents for detection visualization - do_classifier_free_guidance = (guidance_scale > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - image_tensor = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - image_tensor = image_tensor.to(dtype=self.config.pipe.vae.dtype) - image_latents = get_media_latents(pipe=self.config.pipe, media=image_tensor, sample=False, decoder_inv=decoder_inv) - - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale, - num_inference_steps=self.config.num_inference_steps, - **inversion_kwargs - ) - - # Get original watermarked latents - orig_watermarked_latents = self.get_orig_watermarked_latents() - - # Get original embedding - original_embedding = self.original_embedding - - # Create DataForVisualization object with SEAL-specific data - data_for_visualization = DataForVisualization( - config=self.config, - utils=self.utils, - orig_watermarked_latents=orig_watermarked_latents, - reversed_latents=reversed_latents, - inspected_embedding=normalized_inspected_embedding, - original_embedding=original_embedding, - reference_noise=inspected_noise, - image=image, - ) - - return data_for_visualization
-
- - - -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/sfw/sfw.html b/docs/_build/html/_modules/watermark/sfw/sfw.html deleted file mode 100644 index cfddf48..0000000 --- a/docs/_build/html/_modules/watermark/sfw/sfw.html +++ /dev/null @@ -1,1431 +0,0 @@ - - - - - - - - watermark.sfw.sfw — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.sfw.sfw

-from ..base import BaseWatermark, BaseConfig
-from utils.media_utils import *
-import torch
-from typing import Dict, Optional
-from utils.utils import set_random_seed, inherit_docstring
-from utils.diffusion_config import DiffusionConfig
-import numpy as np
-from PIL import Image
-from visualize.data_for_visualization import DataForVisualization
-from detection.sfw.sfw_detection import SFWDetector
-import torchvision.transforms as tforms
-import qrcode
-import os
-
-
-[docs] -class SFWConfig(BaseConfig): - """Config class for SFW algorithm, load config file and initialize parameters.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - self.w_seed = self.config_dict['w_seed'] - self.delta=self.config_dict['delta'] - self.wm_type=self.config_dict['wm_type'] # "HSTR" or "HSQR" - self.threshold = self.config_dict['threshold'] - self.w_channel = self.config_dict['w_channel']
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'SFW'
- - -
-[docs] -class SFWUtils: - """Utility class for SFW algorithm, contains helper functions.""" - -
-[docs] - def __init__(self, config: SFWConfig, *args, **kwargs) -> None: - """ - Initialize the SFW watermarking algorithm. - - Parameters: - config (SFWConfig): Configuration for the SFW algorithm. - """ - self.config = config - self.gt_patch = self._get_watermarking_pattern() - self.watermarking_mask = self._get_watermarking_mask(self.config.init_latents)
- - - # [Fourier transforms] -
-[docs] - @staticmethod - def fft(input_tensor): - assert len(input_tensor.shape) == 4 - return torch.fft.fftshift(torch.fft.fft2(input_tensor), dim=(-1, -2))
- - -
-[docs] - @staticmethod - def ifft(input_tensor): - assert len(input_tensor.shape) == 4 - return torch.fft.ifft2(torch.fft.ifftshift(input_tensor, dim=(-1, -2)))
- - - @staticmethod - @torch.no_grad() - def rfft(input_tensor): - assert len(input_tensor.shape) == 4 - return torch.fft.fftshift(torch.fft.rfft2(input_tensor, dim=(-2,-1)), dim=-2) - - @staticmethod - @torch.no_grad() - def irfft(input_tensor): - assert len(input_tensor.shape) == 4 - return torch.fft.irfft2(torch.fft.ifftshift(input_tensor, dim=-2), dim=(-2,-1), s=(input_tensor.shape[-2],input_tensor.shape[-2])) - -
-[docs] - def circle_mask(self,size: int, r=16, x_offset=0, y_offset=0): - x0 = y0 = size // 2 - x0 += x_offset - y0 += y_offset - y, x = np.ogrid[:size, :size] - return ((x - x0)**2 + (y-y0)**2)<= r**2
- - - @torch.no_grad() - def enforce_hermitian_symmetry(self,freq_tensor): - B, C, H, W = freq_tensor.shape # fftshifted frequency (complex tensor) - center (32,32) - assert H == W, "H != W" - freq_tensor = freq_tensor.clone() - freq_tensor_tmp = freq_tensor.clone() - # DC point (no imaginary) - freq_tensor[:, :, H//2, W//2] = torch.real(freq_tensor_tmp[:, :, H//2, W//2]) - if H % 2 == 0: # Even - # Nyquist Points (no imaginary) - freq_tensor[:, :, 0, 0] = torch.real(freq_tensor_tmp[:, :, 0, 0]) - freq_tensor[:, :, H//2, 0] = torch.real(freq_tensor_tmp[:, :, H//2, 0]) # (32, 0) - freq_tensor[:, :, 0, W//2] = torch.real(freq_tensor_tmp[:, :, 0, W//2]) # (0, 32) - - # Nyquist axis - conjugate - freq_tensor[:, :, 0, 1:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, 0, W//2+1:], dims=[2])) - freq_tensor[:, :, H//2, 1:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2, W//2+1:], dims=[2])) - freq_tensor[:, :, 1:H//2, 0] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2+1:, 0], dims=[2])) - freq_tensor[:, :, 1:H//2, W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2+1:, W//2], dims=[2])) - # Square quadrants - conjugate - freq_tensor[:, :, 1:H//2, 1:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2+1:, W//2+1:], dims=[2, 3])) - freq_tensor[:, :, H//2+1:, 1:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, 1:H//2, W//2+1:], dims=[2, 3])) - else: # Odd - # Nyquist axis - conjugate - freq_tensor[:, :, H//2, 0:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2, W//2+1:], dims=[2])) - freq_tensor[:, :, 0:H//2, W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2+1:, W//2], dims=[2])) - # Square quadrants - conjugate - freq_tensor[:, :, 0:H//2, 0:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, H//2+1:, W//2+1:], dims=[2, 3])) - freq_tensor[:, :, H//2+1:, 0:W//2] = torch.conj(torch.flip(freq_tensor_tmp[:, :, 0:H//2, W//2+1:], dims=[2, 3])) - return freq_tensor - - @torch.no_grad() - def make_Fourier_treering_pattern(self,pipe, shape, w_seed=999999, resolution=512): - assert shape[-1] == shape[-2] # 64==64 - g = torch.Generator(device=self.config.device).manual_seed(w_seed) - gt_init = pipe.prepare_latents(1, pipe.unet.in_channels, resolution, resolution, pipe.unet.dtype, torch.device(self.config.device), g) # (1,4,64,64) - # [HSTR] center-aware design - watermarked_latents_fft = SFWUtils.fft(torch.zeros(shape, device=self.config.device)) # (1,4,64,64) complex64 - start = 10 - end = 54 # 64-10 = hw_latent-start - center_slice = (slice(None), slice(None), slice(start, end), slice(start, end)) - gt_patch_tmp = SFWUtils.fft(gt_init[center_slice]).clone().detach() # (1,4,44,44) complex64 - center_len = gt_patch_tmp.shape[-1] // 2 # 22 - for radius in range(center_len-1, 0, -1): # [21,20,...,1] - tmp_mask = torch.tensor(self.circle_mask(size=shape[-1], r=radius)) # (64,64) - for j in range(watermarked_latents_fft.shape[1]): # GT : all channel Tree-Ring - watermarked_latents_fft[:, j, tmp_mask] = gt_patch_tmp[0, j, center_len, center_len + radius].item() # Use (22,22+radius) element. - # Gaussian noise key (Heterogenous watermark in RingID) - watermarked_latents_fft[:,[0], start:end, start:end] = gt_patch_tmp[:, [0]] # (1,1,44,44) complex64 - # [Hermitian Symmetric Fourier] HSTR - return self.enforce_hermitian_symmetry(watermarked_latents_fft) - - # HSQR - hermitian symmetric QR pattern -
-[docs] - class QRCodeGenerator: -
-[docs] - def __init__(self, box_size=2, border=1, qr_version=1): - self.qr = qrcode.QRCode(version=qr_version, box_size=box_size, border=border, - error_correction = qrcode.constants.ERROR_CORRECT_H)
- - -
-[docs] - def make_qr_tensor(self, data, filename='qrcode.png', save_img=False): - self.qr.add_data(data) - self.qr.make(fit=True) - img = self.qr.make_image(fill_color="black", back_color="white") - if save_img: - img.save(filename) - self.clear() - img_array = np.array(img) - tensor = torch.from_numpy(img_array) - return tensor.clone().detach() # boolean (h,w)
- - -
-[docs] - def clear(self): - self.qr.clear()
-
- - - @torch.no_grad() - def make_hsqr_pattern(self,idx: int): - qr_generator = self.QRCodeGenerator(box_size=2, border=0, qr_version=1) - data = f"HSQR{idx % 10000}" - qr_tensor = qr_generator.make_qr_tensor(data=data) # (42,42) boolean tensor - qr_tensor = qr_tensor.repeat(len([self.config.w_channel]), 1, 1) # (c_wm,42,42) boolean tensor - return qr_tensor # (c_wm,42,42) boolean tensor - - def _get_watermarking_pattern(self) -> torch.Tensor: - """Get the ground truth watermarking pattern.""" - set_random_seed(self.config.w_seed) - shape = (1, 4, 64, 64) - if self.config.wm_type == "HSQR": - Fourier_watermark_pattern_list = [self.make_hsqr_pattern(idx=self.config.w_seed)] - else: - Fourier_watermark_pattern_list = [self.make_Fourier_treering_pattern(self.config.pipe, shape, self.config.w_seed)] - pattern_gt_batch = [Fourier_watermark_pattern_list[0]] - # adjust dims of pattern_gt_batch - if len(pattern_gt_batch[0].shape) == 4: - pattern_gt_batch = torch.cat(pattern_gt_batch, dim=0) # (N,4,64,64) for HSTR - elif len(pattern_gt_batch[0].shape) == 3: - pattern_gt_batch = torch.stack(pattern_gt_batch, dim=0) # (N,c_wm,42,42) for HSQR - else: - raise ValueError(f"Unexpected pattern_gt_batch shape: {pattern_gt_batch[0].shape}") - assert len(pattern_gt_batch.shape) == 4 - - return pattern_gt_batch - - # ring mask -
-[docs] - class RounderRingMask: -
-[docs] - def __init__(self, size=65, r_out=14): - assert size >= 3 - self.size = size - self.r_out = r_out - - num_rings = r_out - zero_bg_freq = torch.zeros(size, size) - center = size // 2 - center_x, center_y = center, center - - ring_vector = torch.tensor([(200 - i*4) * (-1)**i for i in range(num_rings)]) - zero_bg_freq[center_x, center_y:center_y+num_rings] = ring_vector - zero_bg_freq = zero_bg_freq[None, None, ...] - self.ring_vector_np = ring_vector.numpy() - - res = torch.zeros(360, size, size) - res[0] = zero_bg_freq - for angle in range(1, 360): - zero_bg_freq_rot = tforms.functional.rotate(zero_bg_freq, angle=angle) - res[angle] = zero_bg_freq_rot - - res = res.numpy() - self.res = res - self.pure_bg = np.zeros((size, size)) - for x in range(size): - for y in range(size): - values, count = np.unique(res[:, x, y], return_counts=True) - if len(count) > 2: - self.pure_bg[x, y] = values[count == max(count[values!=0])][0] - elif len(count) == 2: - self.pure_bg[x, y] = values[values!=0][0]
- - -
-[docs] - def get_ring_mask(self, r_out, r_in): - # get mask from pure_bg - assert r_out <= self.r_out - if r_in - 1 < 0: - right_end = 0 # None, to take the center - else: - right_end = r_in - 1 - cand_list = self.ring_vector_np[r_out-1:right_end:-1] - mask = np.isin(self.pure_bg, cand_list) - if self.size % 2: - mask = mask[:self.size-1, :self.size-1] # [64, 64] - return mask
-
- - - def _get_watermarking_mask(self, init_latents: torch.Tensor) -> torch.Tensor: - """Get the watermarking mask.""" - shape=(1,4,64,64) - tree_masks = torch.zeros(shape, dtype=torch.bool) # (1,4,64,64) - single_channel_tree_watermark_mask = torch.tensor(self.circle_mask(size=shape[-1], r=14)) # (64,64) - tree_masks[:, [self.config.w_channel]] = single_channel_tree_watermark_mask # (64,64) - masks = tree_masks - mask_obj = self.RounderRingMask(size=65, r_out=14) - single_channel_heter_watermark_mask = torch.tensor(mask_obj.get_ring_mask(r_out=14, r_in=3) ) # (64,64) - masks[:, [0]] = single_channel_heter_watermark_mask # (64,64) RounderRingMask for Hetero Watermark (noise) - return masks - - @torch.no_grad() - def inject_wm(self,init_latents: torch.Tensor): - # for HSTR - assert len(self.gt_patch.shape) == 4 - assert len(self.watermarking_mask.shape) == 4 - batch_size = init_latents.shape[0] - self.watermarking_mask = self.watermarking_mask.repeat(batch_size, 1, 1, 1) - - init_latents =init_latents.to(self.config.device) - self.gt_patch = self.gt_patch.to(self.config.device) - self.watermarking_mask = self.watermarking_mask.to(self.config.device) - - # inject watermarks in fourier space - start = 10 - end = 54 # 64-10 = hw_latent-start - center_slice = (slice(None), slice(None), slice(start, end), slice(start, end)) - assert len(init_latents[center_slice].shape) == 4 - center_latent_fft=torch.fft.fftshift(torch.fft.fft2(init_latents[center_slice]), dim=(-1, -2))# (N,4,44,44) complex64 - #injection - temp_mask = self.watermarking_mask[center_slice] # (N,4,44,44) boolean - temp_pattern = self.gt_patch[center_slice] # (N,4,44,44) complex64 - center_latent_fft[temp_mask] = temp_pattern[temp_mask].clone() # (N,4,44,44) complex64 - # IFFT - assert len(center_latent_fft.shape) == 4 - center_latent_ifft=torch.fft.ifft2(torch.fft.ifftshift(center_latent_fft, dim=(-1, -2)))# (N,4,44,44) - center_latent_ifft = center_latent_ifft.real if center_latent_ifft.imag.abs().max() < 1e-3 else center_latent_ifft - - init_latents = init_latents.clone() - init_latents[center_slice] = center_latent_ifft - init_latents[init_latents == float("Inf")] = 4 - init_latents[init_latents == float("-Inf")] = -4 - return init_latents # float32 - - @torch.no_grad() - def inject_hsqr(self,inverted_latent): # (N,4,64,64) -> (N,4,64,64) - assert len(self.gt_patch.shape) == 4 # (N,c_wm,42,42) - inverted_latent = inverted_latent.to(self.config.device) - self.gt_patch = self.gt_patch.to(self.config.device) - qr_pix_len = self.gt_patch.shape[-1] # 42 - qr_pix_half = (qr_pix_len + 1) // 2 # 21 - qr_left = self.gt_patch[:, :, :, :qr_pix_half] # (N,c_wm,42,21) boolean - qr_right = self.gt_patch[:, :, :, qr_pix_half:] # (N,c_wm,42,21) boolean - # rfft - start = 10 - end = 54 # 64-10 = hw_latent-start - center_slice = (slice(None), slice(None), slice(start, end), slice(start, end)) - center_latent_rfft = SFWUtils.rfft(inverted_latent[center_slice]) # (N,4,44,44) -> # (N,4,44,23) complex64 - center_real_batch = center_latent_rfft.real # (N,4,44,23) f32 - center_imag_batch = center_latent_rfft.imag # (N,4,44,23) f32 - real_slice = (slice(None),[self.config.w_channel], slice(1, 1+qr_pix_len), slice(1, 1+qr_pix_half)) - imag_slice = (slice(None), [self.config.w_channel], slice(1, 1+qr_pix_len), slice(1, 1+qr_pix_half)) - center_real_batch[real_slice] = torch.where(qr_left, center_real_batch[real_slice].abs() + self.config.delta, -center_real_batch[real_slice].abs() - self.config.delta) - center_imag_batch[imag_slice] = torch.where(qr_right, center_imag_batch[imag_slice].abs() + self.config.delta, -center_imag_batch[imag_slice].abs() - self.config.delta) - center_latent_ifft = SFWUtils.irfft(torch.complex(center_real_batch, center_imag_batch)) # (N,4,44,44) f32 - inverted_latent = inverted_latent.clone() - inverted_latent[center_slice] = center_latent_ifft - return inverted_latent # (N,4,64,64)
- - -
-[docs] -@inherit_docstring -class SFW(BaseWatermark): -
-[docs] - def __init__(self, - watermark_config: SFWConfig, - *args, **kwargs): - """ - Initialize the SFW watermarking algorithm. - - Parameters: - watermark_config (SFWConfig): Configuration instance of the SFW algorithm. - """ - self.config = watermark_config - self.utils = SFWUtils(self.config) - self.detector = SFWDetector( - watermarking_mask=self.utils.watermarking_mask, - gt_patch=self.utils.gt_patch, - w_channel=self.config.w_channel, - threshold=self.config.threshold, - device=self.config.device, - wm_type=self.config.wm_type - )
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Internal method to generate a watermarked image.""" - if(self.config.wm_type=="HSQR"): - watermarked_latents = self.utils.inject_hsqr(self.config.init_latents) - else: - watermarked_latents = self.utils.inject_wm(self.config.init_latents) - - # save watermarked latents - self.set_orig_watermarked_latents(watermarked_latents) - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_latents - - return self.config.pipe( - prompt, - **generation_params - ).images[0] - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs) -> Dict[str, float]: - """Detect the watermark in the image.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, # TODO: Multiple image generation to be supported - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image, sample=False, decoder_inv=kwargs.get('decoder_inv', False)) - - # Step 4: Reverse Image Latents - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - # Step 5: Evaluate Watermark - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: Optional[float]=None, - decoder_inv: bool=False, - *args, - **kwargs) -> DataForVisualization: - """Get data for visualization including detection inversion""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = guidance_scale if guidance_scale is not None else self.config.guidance_scale - - # Step 1: Generate watermarked latents (generation process) - set_random_seed(self.config.gen_seed) - if (self.config.wm_type=="HSQR"): - watermarked_latents = self.utils.inject_hsqr(self.config.init_latents) - else: - watermarked_latents = self.utils.inject_wm(self.config.init_latents) - - # Step 2: Generate actual watermarked image using the same process as _generate_watermarked_image - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Generate the actual watermarked image - watermarked_image = self.config.pipe( - prompt, - **generation_params - ).images[0] - - # Step 3: Perform watermark detection to get inverted latents (detection process) - inverted_latents = None - try: - # Get Text Embeddings for detection - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, # TODO: Multiple image generation to be supported - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess watermarked image for detection - processed_image = transform_to_model_format( - watermarked_image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=decoder_inv - ) - - # Reverse Image Latents to get inverted noise - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['prompt', 'decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents_list = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=self.config.num_inference_steps, - **inversion_kwargs - ) - - inverted_latents = reversed_latents_list[-1] - - except Exception as e: - print(f"Warning: Could not perform inversion for visualization: {e}") - inverted_latents = None - - # Step 4: Prepare visualization data - return DataForVisualization( - config=self.config, - utils=self.utils, - reversed_latents=reversed_latents_list, - orig_watermarked_latents=self.orig_watermarked_latents, - image=image - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/tr/tr.html b/docs/_build/html/_modules/watermark/tr/tr.html deleted file mode 100644 index 2cef5da..0000000 --- a/docs/_build/html/_modules/watermark/tr/tr.html +++ /dev/null @@ -1,1238 +0,0 @@ - - - - - - - - watermark.tr.tr — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.tr.tr

-from ..base import BaseWatermark, BaseConfig
-from utils.media_utils import *
-import torch
-from typing import Dict, Union, List, Optional
-from utils.utils import set_random_seed, inherit_docstring
-from utils.diffusion_config import DiffusionConfig
-import copy
-import numpy as np
-from PIL import Image
-from visualize.data_for_visualization import DataForVisualization
-from detection.tr.tr_detection import TRDetector
-
-
-[docs] -class TRConfig(BaseConfig): - """Config class for TR algorithm, load config file and initialize parameters.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - self.w_seed = self.config_dict['w_seed'] - self.w_channel = self.config_dict['w_channel'] - self.w_pattern = self.config_dict['w_pattern'] - self.w_mask_shape = self.config_dict['w_mask_shape'] - self.w_radius = self.config_dict['w_radius'] - self.w_pattern_const = self.config_dict['w_pattern_const'] - self.threshold = self.config_dict['threshold']
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'TR'
- - -
-[docs] -class TRUtils: - """Utility class for TR algorithm, contains helper functions.""" - -
-[docs] - def __init__(self, config: TRConfig, *args, **kwargs) -> None: - """ - Initialize the Tree-Ring watermarking algorithm. - - Parameters: - config (TRConfig): Configuration for the Tree-Ring algorithm. - """ - self.config = config - self.gt_patch = self._get_watermarking_pattern() - self.watermarking_mask = self._get_watermarking_mask(self.config.init_latents)
- - - def _circle_mask(self, size: int=64, r: int=10, x_offset: int=0, y_offset: int=0) -> np.ndarray: - """Generate a circular mask.""" - x0 = y0 = size // 2 - x0 += x_offset - y0 += y_offset - y, x = np.ogrid[:size, :size] - y = y[::-1] - - return ((x - x0)**2 + (y-y0)**2)<= r**2 - - def _get_watermarking_pattern(self) -> torch.Tensor: - """Get the ground truth watermarking pattern.""" - set_random_seed(self.config.w_seed) - - gt_init = get_random_latents(pipe=self.config.pipe, height=self.config.image_size[0], width=self.config.image_size[1]) - - if 'seed_ring' in self.config.w_pattern: - gt_patch = gt_init - - gt_patch_tmp = copy.deepcopy(gt_patch) - for i in range(self.config.w_radius, 0, -1): - tmp_mask = self._circle_mask(gt_init.shape[-1], r=i) - tmp_mask = torch.tensor(tmp_mask).to(self.config.device) - - for j in range(gt_patch.shape[1]): - gt_patch[:, j, tmp_mask] = gt_patch_tmp[0, j, 0, i].item() - elif 'seed_zeros' in self.config.w_pattern: - gt_patch = gt_init * 0 - elif 'seed_rand' in self.config.w_pattern: - gt_patch = gt_init - elif 'rand' in self.config.w_pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(gt_init), dim=(-1, -2)) - gt_patch[:] = gt_patch[0] - elif 'zeros' in self.config.w_pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(gt_init), dim=(-1, -2)) * 0 - elif 'const' in self.config.w_pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(gt_init), dim=(-1, -2)) * 0 - gt_patch += self.config.w_pattern_const - elif 'ring' in self.config.w_pattern: - gt_patch = torch.fft.fftshift(torch.fft.fft2(gt_init), dim=(-1, -2)) - - gt_patch_tmp = copy.deepcopy(gt_patch) - for i in range(self.config.w_radius, 0, -1): - tmp_mask = self._circle_mask(gt_init.shape[-1], r=i) - tmp_mask = torch.tensor(tmp_mask).to(self.config.device) - - for j in range(gt_patch.shape[1]): - gt_patch[:, j, tmp_mask] = gt_patch_tmp[0, j, 0, i].item() - - return gt_patch - - def _get_watermarking_mask(self, init_latents: torch.Tensor) -> torch.Tensor: - """Get the watermarking mask.""" - watermarking_mask = torch.zeros(init_latents.shape, dtype=torch.bool).to(self.config.device) - - if self.config.w_mask_shape == 'circle': - np_mask = self._circle_mask(init_latents.shape[-1], r=self.config.w_radius) - torch_mask = torch.tensor(np_mask).to(self.config.device) - - if self.config.w_channel == -1: - # all channels - watermarking_mask[:, :] = torch_mask - else: - watermarking_mask[:, self.config.w_channel] = torch_mask - elif self.config.w_mask_shape == 'square': - anchor_p = init_latents.shape[-1] // 2 - if self.config.w_channel == -1: - # all channels - watermarking_mask[:, :, anchor_p-self.config.w_radius:anchor_p+self.config.w_radius, anchor_p-self.config.w_radius:anchor_p+self.config.w_radius] = True - else: - watermarking_mask[:, self.config.w_channel, anchor_p-self.config.w_radius:anchor_p+self.config.w_radius, anchor_p-self.config.w_radius:anchor_p+self.config.w_radius] = True - elif self.config.w_mask_shape == 'no': - pass - else: - raise NotImplementedError(f'w_mask_shape: {self.config.w_mask_shape}') - - return watermarking_mask - -
-[docs] - def inject_watermark(self, init_latents: torch.Tensor) -> torch.Tensor: - init_latents_w_fft = torch.fft.fftshift(torch.fft.fft2(init_latents), dim=(-1, -2)) - - init_latents_w_fft[self.watermarking_mask] = self.gt_patch[self.watermarking_mask].clone() - - init_latents_w = torch.fft.ifft2(torch.fft.ifftshift(init_latents_w_fft, dim=(-1, -2))).real - - return init_latents_w
-
- - -
-[docs] -@inherit_docstring -class TR(BaseWatermark): -
-[docs] - def __init__(self, - watermark_config: TRConfig, - *args, **kwargs): - """ - Initialize the TR watermarking algorithm. - - Parameters: - watermark_config (TRConfig): Configuration instance of the Tree-Ring algorithm. - """ - self.config = watermark_config - self.utils = TRUtils(self.config) - - self.detector = TRDetector( - watermarking_mask=self.utils.watermarking_mask, - gt_patch=self.utils.gt_patch, - threshold=self.config.threshold, - device=self.config.device - )
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Internal method to generate a watermarked image.""" - watermarked_latents = self.utils.inject_watermark(self.config.init_latents) - - # save watermarked latents - self.set_orig_watermarked_latents(watermarked_latents) - - # Construct generation parameters - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_latents - - return self.config.pipe( - prompt, - **generation_params - ).images[0] - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs) -> Dict[str, float]: - """Detect the watermark in the image.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Step 1: Get Text Embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, # TODO: Multiple image generation to be supported - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Step 2: Preprocess Image - image = transform_to_model_format(image, target_size=self.config.image_size[0]).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Step 3: Get Image Latents - image_latents = get_media_latents(pipe=self.config.pipe, media=image, sample=False, decoder_inv=kwargs.get('decoder_inv', False)) - - # Step 4: Reverse Image Latents - # Pass only known parameters to forward_diffusion, and let kwargs handle any additional parameters - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - # Step 5: Evaluate Watermark - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str="", - guidance_scale: Optional[float]=None, - decoder_inv: bool=False, - *args, - **kwargs) -> DataForVisualization: - """Get data for visualization including detection inversion - similar to GS logic.""" - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = guidance_scale if guidance_scale is not None else self.config.guidance_scale - - # Step 1: Generate watermarked latents (generation process) - set_random_seed(self.config.gen_seed) - watermarked_latents = self.utils.inject_watermark(self.config.init_latents) - - # Step 2: Generate actual watermarked image using the same process as _generate_watermarked_image - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_latents, - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Generate the actual watermarked image - watermarked_image = self.config.pipe( - prompt, - **generation_params - ).images[0] - - # Step 3: Perform watermark detection to get inverted latents (detection process) - inverted_latents = None - try: - # Get Text Embeddings for detection - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, # TODO: Multiple image generation to be supported - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess watermarked image for detection - processed_image = transform_to_model_format( - watermarked_image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get Image Latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_image, - sample=False, - decoder_inv=decoder_inv - ) - - # Reverse Image Latents to get inverted noise - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['prompt', 'decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents_list = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=self.config.num_inference_steps, - **inversion_kwargs - ) - - inverted_latents = reversed_latents_list[-1] - - except Exception as e: - print(f"Warning: Could not perform inversion for visualization: {e}") - inverted_latents = None - - # Step 4: Prepare visualization data - return DataForVisualization( - config=self.config, - utils=self.utils, - reversed_latents=reversed_latents_list, - orig_watermarked_latents=self.orig_watermarked_latents, - image=image, - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/videomark/video_mark.html b/docs/_build/html/_modules/watermark/videomark/video_mark.html deleted file mode 100644 index 4b174e5..0000000 --- a/docs/_build/html/_modules/watermark/videomark/video_mark.html +++ /dev/null @@ -1,1439 +0,0 @@ - - - - - - - - watermark.videomark.video_mark — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for watermark.videomark.video_mark

-from ..base import BaseWatermark, BaseConfig
-import torch
-import numpy as np
-from typing import Dict, Tuple, Any, Optional, List, Union
-from PIL import Image
-import galois
-from scipy.sparse import csr_matrix
-from scipy.special import binom
-import logging
-from functools import reduce
-from visualize.data_for_visualization import DataForVisualization
-from detection.videomark.videomark_detection import VideoMarkDetector
-from utils.media_utils import *
-from utils.utils import set_random_seed
-from utils.pipeline_utils import is_video_pipeline, is_t2v_pipeline, is_i2v_pipeline
-from utils.callbacks import DenoisingLatentsCollector
-import random
-
-# Setup logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Constants
-VAE_DOWNSAMPLE_FACTOR = 8
-DEFAULT_CONFIDENCE_THRESHOLD = 0.6
-
-
-
-[docs] -class VideoMarkConfig(BaseConfig): - """Config class for VideoMark algorithm.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize algorithm-specific parameters.""" - self.fpr = self.config_dict['fpr'] - self.t = self.config_dict['prc_t'] - self.var = self.config_dict['var'] - self.threshold = self.config_dict['threshold'] - self.sequence_length = self.config_dict['sequence_length'] # Length of the watermark sequence - self.message_length = self.config_dict['message_length'] # Number of bits in each sequence - self.message_sequence = np.random.randint(0, 2, size=(self.sequence_length, self.message_length)) # <= 512 bits for robustness - self.shift = np.random.default_rng().integers(0, self.sequence_length - self.num_frames) - self.message = self.message_sequence[self.shift : self.shift + self.num_frames] - self.latents_height = self.image_size[0] // self.pipe.vae_scale_factor - self.latents_width = self.image_size[1] // self.pipe.vae_scale_factor - self.latents_channel = self.pipe.unet.config.in_channels - self.n = self.latents_height * self.latents_width * self.latents_channel # Dimension of the latent space - self.GF = galois.GF(2) - - # Seeds for key generation - self.gen_matrix_seed = self.config_dict['keygen']['gen_matrix_seed'] - self.indice_seed = self.config_dict['keygen']['indice_seed'] - self.one_time_pad_seed = self.config_dict['keygen']['one_time_pad_seed'] - self.test_bits_seed = self.config_dict['keygen']['test_bits_seed'] - self.permute_bits_seed = self.config_dict['keygen']['permute_bits_seed'] - - # Seeds for encoding - self.payload_seed = self.config_dict['encode']['payload_seed'] - self.error_seed = self.config_dict['encode']['error_seed'] - self.pseudogaussian_seed = self.config_dict['encode']['pseudogaussian_seed']
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'VideoMark' - - def _get_message(length: int, window: int, seed=None) -> int: - """Return a random start index for a subarray of size `window` in array of size `length`.""" - rng = np.random.default_rng() - return rng.integers(0, length - window)
- - -
-[docs] -class VideoMarkUtils: - """Utility class for VideoMark algorithm.""" - -
-[docs] - def __init__(self, config: VideoMarkConfig, *args, **kwargs) -> None: - """Initialize PRC utility.""" - self.config = config - self.encoding_key, self.decoding_key = self._generate_encoding_key(self.config.message_length)
- - - def _generate_encoding_key(self, message_length: int) -> Tuple[tuple, tuple]: - """Generate encoding key for PRC algorithm.""" - # Set basic scheme parameters - num_test_bits = int(np.ceil(np.log2(1 / self.config.fpr))) - secpar = int(np.log2(binom(self.config.n, self.config.t))) - g = secpar - k = message_length + g + num_test_bits - r = self.config.n - k - secpar - noise_rate = 1 - 2 ** (-secpar / g ** 2) - - # Sample n by k generator matrix (all but the first n-r of these will be over-written) - generator_matrix = self.config.GF.Random(shape=(self.config.n, k), seed=self.config.gen_matrix_seed) - - # Sample scipy.sparse parity-check matrix together with the last n-r rows of the generator matrix - row_indices = [] - col_indices = [] - data = [] - for i, row in enumerate(range(r)): - np.random.seed(self.config.indice_seed + i) - chosen_indices = np.random.choice(self.config.n - r + row, self.config.t - 1, replace=False) - chosen_indices = np.append(chosen_indices, self.config.n - r + row) - row_indices.extend([row] * self.config.t) - col_indices.extend(chosen_indices) - data.extend([1] * self.config.t) - generator_matrix[self.config.n - r + row] = generator_matrix[chosen_indices[:-1]].sum(axis=0) - parity_check_matrix = csr_matrix((data, (row_indices, col_indices))) - - # Compute scheme parameters - max_bp_iter = int(np.log(self.config.n) / np.log(self.config.t)) - - # Sample one-time pad and test bits - one_time_pad = self.config.GF.Random(self.config.n, seed=self.config.one_time_pad_seed) - test_bits = self.config.GF.Random(num_test_bits, seed=self.config.test_bits_seed) - - # Permute bits - np.random.seed(self.config.permute_bits_seed) - permutation = np.random.permutation(self.config.n) - generator_matrix = generator_matrix[permutation] - one_time_pad = one_time_pad[permutation] - parity_check_matrix = parity_check_matrix[:, permutation] - - return (generator_matrix, one_time_pad, test_bits, g, noise_rate), (generator_matrix, parity_check_matrix, one_time_pad, self.config.fpr, noise_rate, test_bits, g, max_bp_iter, self.config.t) - - def _encode_message(self, encoding_key: tuple, message: np.ndarray = None) -> np.ndarray: - """Encode a message using PRC algorithm.""" - generator_matrix, one_time_pad, test_bits, g, noise_rate = encoding_key - n, k = generator_matrix.shape - - if message is None: - payload = np.concatenate((test_bits, self.config.GF.Random(k - len(test_bits), seed=self.config.payload_seed))) - else: - assert len(message) <= k-len(test_bits)-g, "Message is too long" - payload = np.concatenate((test_bits, self.config.GF.Random(g, seed=self.config.payload_seed), self.config.GF(message), self.config.GF.Zeros(k-len(test_bits)-g-len(message)))) - - np.random.seed(self.config.error_seed) - error = self.config.GF(np.random.binomial(1, noise_rate, n)) - - return 1 - 2 * torch.tensor(payload @ generator_matrix.T + one_time_pad + error, dtype=float) - - - def _sample_prc_codeword(self, codeword: torch.Tensor, basis: torch.Tensor = None) -> torch.Tensor: - """Sample a PRC codeword.""" - codeword_np = codeword.numpy() - np.random.seed(self.config.pseudogaussian_seed) - pseudogaussian_np = codeword_np * np.abs(np.random.randn(*codeword_np.shape)) - pseudogaussian = torch.from_numpy(pseudogaussian_np).to(dtype=torch.float32) - if basis is None: - return pseudogaussian - return pseudogaussian @ basis.T - -
-[docs] - def inject_watermark(self) -> torch.Tensor: - """Generate watermarked latents from PRC codeword.""" - # Step 1: Encode message - prc_codeword = torch.stack([self._encode_message(self.encoding_key, self.config.message[frame_index]) for frame_index in range(self.config.num_frames)]) - # Step 2: Sample PRC codeword and get watermarked latents - watermarked_latents = self._sample_prc_codeword(prc_codeword).reshape(self.config.num_frames, 1, self.config.latents_channel, self.config.latents_height, self.config.latents_width).to(self.config.device) - - return watermarked_latents.permute(1, 2, 0, 3, 4) # (b, c, f, h, w)
-
- - - - -
-[docs] -class VideoMarkWatermark(BaseWatermark): - """Main class for VideoMark watermarking algorithm.""" - -
-[docs] - def __init__(self, watermark_config: VideoMarkConfig, *args, **kwargs) -> None: - """Initialize the VideoShield watermarking algorithm. - - Args: - watermark_config: Configuration instance of the VideoMark algorithm - """ - self.config = watermark_config - self.utils = VideoMarkUtils(self.config) - - # Initialize detector with encryption keys from utils - self.detector = VideoMarkDetector( - message_sequence=self.config.message_sequence, - watermark=self.config.message, - num_frames=self.config.num_frames, - var=self.config.var, - decoding_key=self.utils.decoding_key, - GF=self.config.GF, - threshold=self.config.threshold, - device=self.config.device - )
- - - def _generate_watermarked_video(self, prompt: str, num_frames: Optional[int] = None, *args, **kwargs) -> List[Image.Image]: - """Generate watermarked video using VideoMark algorithm. - - Args: - prompt: The input prompt for video generation - num_frames: Number of frames to generate (uses config value if None) - - Returns: - List of generated watermarked video frames - """ - if not is_video_pipeline(self.config.pipe): - raise ValueError(f"This pipeline ({self.config.pipe.__class__.__name__}) does not support video generation.") - - # Set random seed for reproducibility - set_random_seed(self.config.gen_seed) - - # Use config frames if not specified - frames_to_generate = num_frames if num_frames is not None else self.config.num_frames - - # Set num_frames in config for watermark generation - original_num_frames = getattr(self.config, 'num_frames', None) - self.config.num_frames = frames_to_generate - - try: - # Generate watermarked latents - watermarked_latents = self.utils.inject_watermark().to(self.config.pipe.unet.dtype) - - # Save watermarked latents for visualization - self.set_orig_watermarked_latents(watermarked_latents) - - # Construct video generation parameters - generation_params = { - "num_inference_steps": self.config.num_inference_steps, - "guidance_scale": self.config.guidance_scale, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "num_frames": frames_to_generate, - "latents": watermarked_latents - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - if key != "num_frames": # Prevent overriding processed parameters - generation_params[key] = value - - # Handle I2V pipelines that need dimension permutation (like SVD) - final_latents = watermarked_latents - if is_i2v_pipeline(self.config.pipe): - logger.info("I2V pipeline detected, permuting latent dimensions.") - final_latents = final_latents.permute(0, 2, 1, 3, 4) # (b,c,f,h,w) -> (b,f,c,h,w) - - generation_params["latents"] = final_latents - - # Generate video - output = self.config.pipe( - prompt, - **generation_params - ) - - # Extract frames from output - if hasattr(output, 'frames'): - frames = output.frames[0] - elif hasattr(output, 'videos'): - frames = output.videos[0] - else: - frames = output[0] if isinstance(output, tuple) else output - - # Convert frames to PIL Images - frame_list = [] - for frame in frames: - if not isinstance(frame, Image.Image): - if isinstance(frame, np.ndarray): - if frame.dtype == np.uint8: - frame_pil = Image.fromarray(frame) - else: - frame_scaled = (frame * 255).astype(np.uint8) - frame_pil = Image.fromarray(frame_scaled) - elif isinstance(frame, torch.Tensor): - if frame.dim() == 3 and frame.shape[-1] in [1, 3]: - if frame.max() <= 1.0: - frame = (frame * 255).byte() - frame_np = frame.cpu().numpy() - frame_pil = Image.fromarray(frame_np) - else: - raise ValueError(f"Unexpected tensor shape for frame: {frame.shape}") - else: - raise TypeError(f"Unexpected type for frame: {type(frame)}") - else: - frame_pil = frame - - frame_list.append(frame_pil) - - return frame_list - - finally: - # Restore original num_frames - if original_num_frames is not None: - self.config.num_frames = original_num_frames - elif hasattr(self.config, 'num_frames'): - delattr(self.config, 'num_frames') - - def _detect_watermark_in_image(self, image: Image.Image, prompt: str = "", - *args, **kwargs) -> Dict[str, float]: - """Detect VideoMark watermark in image. - - Args: - image: Input PIL image - prompt: Text prompt used for generation - - Returns: - Dictionary containing detection results - """ - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Get text embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess image - image_tensor = transform_to_model_format( - image, target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get image latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=image_tensor, - sample=False, - decoder_inv=kwargs.get("decoder_inv", False) - ) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - # Use detector or utils for evaluation - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.utils.eval_watermark(reversed_latents) - - def _get_video_latents(self, vae, video_frames, sample=True, rng_generator=None, permute=True): - encoding_dist = vae.encode(video_frames).latent_dist - if sample: - encoding = encoding_dist.sample(generator=rng_generator) - else: - encoding = encoding_dist.mode() - latents = (encoding * 0.18215).unsqueeze(0) - if permute: - latents = latents.permute(0, 2, 1, 3, 4) - return latents - - def _detect_watermark_in_video(self, - video_frames: Union[torch.Tensor, List[Image.Image]], - prompt: str = "", - detector_type: str = 'bit_acc', - *args, **kwargs) -> Dict[str, float]: - """Detect VideoMark watermark in video. - - Args: - video_frames: Input video frames as tensor or list of PIL images - prompt: Text prompt used for generation - - Returns: - Dictionary containing detection results - """ - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Convert frames to tensor if needed - if isinstance(video_frames, list): - from torchvision import transforms - frames_tensor = torch.stack([transforms.ToTensor()(frame) for frame in video_frames]) - video_frames = 2.0 * frames_tensor - 1.0 # Normalize to [-1, 1] - - video_frames = video_frames.to(self.config.device).to(self.config.pipe.vae.dtype) - - # Get video latents - with torch.no_grad(): - # TODO: Add support for I2V pipeline - video_latents = self._get_video_latents(self.config.pipe.vae, video_frames, sample=False) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['guidance_scale', 'num_inference_steps']} - - from diffusers import DDIMInverseScheduler - original_scheduler = self.config.pipe.scheduler - inverse_scheduler = DDIMInverseScheduler.from_config(original_scheduler.config) - self.config.pipe.scheduler = inverse_scheduler - - final_reversed_latents = self.config.pipe( - prompt=prompt, - latents=video_latents, - num_inference_steps=num_steps_to_use, - guidance_scale=guidance_scale_to_use, - output_type='latent', - **inversion_kwargs - ).frames # [B, F, H, W, C](T2V) - self.config.pipe.scheduler = original_scheduler - - # Use detector for evaluation - return self.detector.eval_watermark(final_reversed_latents, detector_type=detector_type) - - -
-[docs] - def get_data_for_visualize(self, - video_frames: List[Image.Image], - prompt: str = "", - guidance_scale: float = 1, - *args, **kwargs) -> DataForVisualization: - """Get VideoMark visualization data. - - This method generates the necessary data for visualizing VideoMark watermarks, - including original watermarked latents and reversed latents from inversion. - - Args: - image: The image to visualize watermarks for (can be None for generation only) - prompt: The text prompt used for generation - guidance_scale: Guidance scale for generation and inversion - - Returns: - DataForVisualization object containing visualization data - """ - # Prepare PRC-specific data - message_bits = torch.tensor(self.config.message, dtype=torch.float32) - - # Get generator matrix - generator_matrix = torch.tensor(np.array(self.utils.encoding_key[0], dtype=float), dtype=torch.float32) - - # Get parity check matrix - parity_check_matrix = self.utils.decoding_key[1] - - # 1. Generate watermarked latents and collect intermediate data - set_random_seed(self.config.gen_seed) - - # Step 1: Encode message - prc_codeword = torch.stack([self.utils._encode_message(self.utils.encoding_key, self.config.message[frame_index]) for frame_index in range(self.config.num_frames)]) - - # Step 2: Sample PRC codeword - pseudogaussian_noise = self.utils._sample_prc_codeword(prc_codeword) - - # Step 3: Generate watermarked latents - watermarked_latents = pseudogaussian_noise.reshape(self.config.num_frames, 1, self.config.latents_channel, self.config.latents_height, self.config.latents_width).to(self.config.device) - watermarked_latents = watermarked_latents.permute(1, 2, 0, 3, 4) - - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Convert frames to tensor if needed - if isinstance(video_frames, list): - from torchvision import transforms - frames_tensor = torch.stack([transforms.ToTensor()(frame) for frame in video_frames]) - video_frames = 2.0 * frames_tensor - 1.0 # Normalize to [-1, 1] - - video_frames = video_frames.to(self.config.device).to(self.config.pipe.vae.dtype) - - # Get video latents - with torch.no_grad(): - # TODO: Add support for I2V pipeline - video_latents = self._get_video_latents(self.config.pipe.vae, video_frames, sample=False) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['guidance_scale', 'num_inference_steps']} - - from diffusers import DDIMInverseScheduler - original_scheduler = self.config.pipe.scheduler - inverse_scheduler = DDIMInverseScheduler.from_config(original_scheduler.config) - self.config.pipe.scheduler = inverse_scheduler - collector = DenoisingLatentsCollector(save_every_n_steps=1, to_cpu=True) - - final_reversed_latents = self.config.pipe( - prompt=prompt, - latents=video_latents, - num_inference_steps=num_steps_to_use, - guidance_scale=guidance_scale_to_use, - output_type='latent', - callback=collector, - callback_steps=1, - **inversion_kwargs - ).frames # [B, F, H, W, C](T2V) - self.config.pipe.scheduler = original_scheduler - - reversed_latents = collector.latents_list # List[Tensor] - - inverted_latents = final_reversed_latents - recovered_prc = None - try: - if inverted_latents is not None: - # Use the detector to recover the PRC codeword - detection_result = self.detector.eval_watermark(inverted_latents) - # The detector should have recovered_prc attribute or return it - if hasattr(self.detector, 'recovered_prc') and self.detector.recovered_prc is not None: - recovered_prc = self.detector.recovered_prc - elif 'recovered_prc' in detection_result: - recovered_prc = detection_result['recovered_prc'] - else: - print("Warning: Detector did not provide recovered_prc") - except Exception as e: - print(f"Warning: Could not recover PRC codeword for visualization: {e}") - recovered_prc = None - - return DataForVisualization( - config=self.config, - utils=self.utils, - orig_watermarked_latents=watermarked_latents, - watermarked_latents=watermarked_latents, - reversed_latents=reversed_latents, - inverted_latents=inverted_latents, - video_frames=video_frames, - # PRC-specific data - message_bits= message_bits, - prc_codeword=torch.tensor(prc_codeword, dtype=torch.float32), - pseudogaussian_noise=torch.tensor(pseudogaussian_noise, dtype=torch.float32), - generator_matrix=generator_matrix, - parity_check_matrix=parity_check_matrix, - threshold=self.config.threshold, - recovered_prc=torch.tensor(recovered_prc, dtype=torch.float32) if recovered_prc is not None else None - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/videoshield/video_shield.html b/docs/_build/html/_modules/watermark/videoshield/video_shield.html deleted file mode 100644 index 508f738..0000000 --- a/docs/_build/html/_modules/watermark/videoshield/video_shield.html +++ /dev/null @@ -1,1414 +0,0 @@ - - - - - - - - watermark.videoshield.video_shield — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - - -
  • -
  • -
-
-
-
-
- -

Source code for watermark.videoshield.video_shield

-from ..base import BaseWatermark, BaseConfig
-import torch
-import numpy as np
-from typing import Dict, Tuple, Any, Optional, List, Union
-from PIL import Image
-from Crypto.Cipher import ChaCha20
-from Crypto.Random import get_random_bytes
-import logging
-from scipy.stats import norm, truncnorm
-from functools import reduce
-from visualize.data_for_visualization import DataForVisualization
-from detection.videoshield.videoshield_detection import VideoShieldDetector
-from utils.media_utils import *
-from utils.utils import set_random_seed
-from utils.pipeline_utils import is_video_pipeline, is_t2v_pipeline, is_i2v_pipeline
-from utils.callbacks import DenoisingLatentsCollector
-import random
-
-# Setup logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Constants
-VAE_DOWNSAMPLE_FACTOR = 8
-DEFAULT_CONFIDENCE_THRESHOLD = 0.6
-
-
-
-[docs] -class VideoShieldConfig(BaseConfig): - """Config class for VideoShield algorithm.""" - -
-[docs] - def initialize_parameters(self) -> None: - """Initialize VideoShield configuration.""" - # Repetition factors for video watermarking - self.k_f: int = self.config_dict['k_f'] # Frame repetition factor - self.k_c: int = self.config_dict['k_c'] # Channel repetition factor - self.k_h: int = self.config_dict['k_h'] # Height repetition factor - self.k_w: int = self.config_dict['k_w'] # Width repetition factor - - # Temporal threshold for localization - self.t_temp: float = self.config_dict['t_temp'] - - # HSTR (Hierarchical Spatio-Temporal Refinement) parameters - self.hstr_levels: int = self.config_dict['hstr_levels'] - self.t_wm: List[float] = self.config_dict['t_wm'] - self.t_orig: List[float] = self.config_dict['t_orig'] - - # Watermark generation parameters - self.wm_key: int = self.config_dict.get('wm_key', 42) - self.chacha_key_seed: int = self.config_dict.get('chacha_key_seed', 123456) - self.chacha_nonce_seed: int = self.config_dict.get('chacha_nonce_seed', 789012) - - # Detection threshold - self.threshold: float = self.config_dict.get('threshold', 0.6) - - # Calculate latent dimensions - self.latents_height = self.image_size[0] // VAE_DOWNSAMPLE_FACTOR - self.latents_width = self.image_size[1] // VAE_DOWNSAMPLE_FACTOR - - # Generate watermark pattern - generator = torch.Generator(device=self.device) - generator.manual_seed(self.wm_key) - - # For video, we need frame dimension - if hasattr(self, 'num_frames') and self.num_frames > 0: - self.watermark = torch.randint( - 0, 2, - [1, 4 // self.k_c, self.num_frames // self.k_f, - self.latents_height // self.k_h, self.latents_width // self.k_w], - generator=generator, device=self.device - ) - else: - # Fallback for image-only case - self.watermark = torch.randint( - 0, 2, - [1, 4 // self.k_c, self.latents_height // self.k_h, self.latents_width // self.k_w], - generator=generator, device=self.device - )
- - - @property - def algorithm_name(self) -> str: - """Return the algorithm name.""" - return 'VideoShield'
- - - -
-[docs] -class VideoShieldUtils: - """Utility class for VideoShield algorithm.""" - -
-[docs] - def __init__(self, config: VideoShieldConfig, *args, **kwargs) -> None: - """Initialize the VideoShield watermarking utility.""" - self.config = config - - # Generate deterministic cryptographic keys using seeds - self.chacha_key = self._get_bytes_with_seed(self.config.chacha_key_seed, 32) - self.chacha_nonce = self._get_bytes_with_seed(self.config.chacha_nonce_seed, 12) - - # Calculate latent space dimensions - if hasattr(self.config, 'num_frames') and self.config.num_frames > 0: - # Video case: include frame dimension - self.latentlength = 4 * self.config.num_frames * self.config.latents_height * self.config.latents_width - else: - # Image case: no frame dimension - self.latentlength = 4 * self.config.latents_height * self.config.latents_width - - # Calculate watermark length based on repetition factors - self.marklength = self.latentlength // (self.config.k_f * self.config.k_c * self.config.k_h * self.config.k_w) - - # Voting threshold for watermark extraction - if self.config.k_f == 1 and self.config.k_c == 1 and self.config.k_h == 1 and self.config.k_w == 1: - self.vote_threshold = 1 - else: - self.vote_threshold = (self.config.k_f * self.config.k_c * self.config.k_h * self.config.k_w) // 2
- - - def _get_bytes_with_seed(self, seed: int, n: int) -> bytes: - """Generate deterministic bytes using a seed.""" - random.seed(seed) - return bytes(random.getrandbits(8) for _ in range(n)) - - def _stream_key_encrypt(self, sd: np.ndarray) -> np.ndarray: - """Encrypt the watermark using ChaCha20 cipher.""" - try: - cipher = ChaCha20.new(key=self.chacha_key, nonce=self.chacha_nonce) - m_byte = cipher.encrypt(np.packbits(sd).tobytes()) - m_bit = np.unpackbits(np.frombuffer(m_byte, dtype=np.uint8)) - return m_bit[:len(sd)] # Ensure same length as input - except Exception as e: - logger.error(f"Encryption failed: {e}") - raise RuntimeError("Encryption failed") from e - - def _truncated_sampling(self, message: np.ndarray) -> torch.Tensor: - """Truncated Gaussian sampling for watermarking. - - Args: - message: Binary message as a numpy array of 0s and 1s - - Returns: - Watermarked latents tensor - """ - z = np.zeros(self.latentlength) - denominator = 2.0 - ppf = [norm.ppf(j / denominator) for j in range(int(denominator) + 1)] - - for i in range(self.latentlength): - dec_mes = reduce(lambda a, b: 2 * a + b, message[i : i + 1]) - dec_mes = int(dec_mes) - z[i] = truncnorm.rvs(ppf[dec_mes], ppf[dec_mes + 1]) - - # Reshape based on whether this is video or image - if hasattr(self.config, 'num_frames') and self.config.num_frames > 0: - # Video: (batch, channels, frames, height, width) - z = torch.from_numpy(z).reshape( - 1, 4, self.config.num_frames, self.config.latents_height, self.config.latents_width - ).float() - else: - # Image: (batch, channels, height, width) - z = torch.from_numpy(z).reshape( - 1, 4, self.config.latents_height, self.config.latents_width - ).float() - - return z.to(self.config.device) - -
-[docs] - def create_watermark_and_return_w(self) -> torch.Tensor: - """Create watermark pattern and return watermarked initial latents.""" - # Create repeated watermark pattern - if hasattr(self.config, 'num_frames') and self.config.num_frames > 0: - # Video case: repeat along all dimensions including frames - sd = self.config.watermark.repeat(1, self.config.k_c, self.config.k_f, self.config.k_h, self.config.k_w) - else: - # Image case: repeat along spatial dimensions only - sd = self.config.watermark.repeat(1, self.config.k_c, self.config.k_h, self.config.k_w) - - # Encrypt the repeated watermark - m = self._stream_key_encrypt(sd.flatten().cpu().numpy()) - - # Generate watermarked latents using truncated sampling - w = self._truncated_sampling(m) - - return w
-
- - -
-[docs] -class VideoShieldWatermark(BaseWatermark): - """Main class for VideoShield watermarking algorithm.""" - -
-[docs] - def __init__(self, watermark_config: VideoShieldConfig, *args, **kwargs) -> None: - """Initialize the VideoShield watermarking algorithm. - - Args: - watermark_config: Configuration instance of the VideoShield algorithm - """ - self.config = watermark_config - self.utils = VideoShieldUtils(self.config) - - # Initialize detector with encryption keys from utils - self.detector = VideoShieldDetector( - watermark=self.config.watermark, - threshold=self.config.threshold, - device=self.config.device, - chacha_key=self.utils.chacha_key, - chacha_nonce=self.utils.chacha_nonce, - height=self.config.image_size[0], - width=self.config.image_size[1], - num_frames=self.config.num_frames, - k_f=self.config.k_f, - k_c=self.config.k_c, - k_h=self.config.k_h, - k_w=self.config.k_w - )
- - - def _generate_watermarked_video(self, prompt: str, num_frames: Optional[int] = None, *args, **kwargs) -> List[Image.Image]: - """Generate watermarked video using VideoShield algorithm. - - Args: - prompt: The input prompt for video generation - num_frames: Number of frames to generate (uses config value if None) - - Returns: - List of generated watermarked video frames - """ - if not is_video_pipeline(self.config.pipe): - raise ValueError(f"This pipeline ({self.config.pipe.__class__.__name__}) does not support video generation.") - - # Set random seed for reproducibility - set_random_seed(self.config.gen_seed) - - # Use config frames if not specified - frames_to_generate = num_frames if num_frames is not None else self.config.num_frames - - # Set num_frames in config for watermark generation - original_num_frames = getattr(self.config, 'num_frames', None) - self.config.num_frames = frames_to_generate - - try: - # Generate watermarked latents - watermarked_latents = self.utils.create_watermark_and_return_w().to(self.config.pipe.unet.dtype) - - # Save watermarked latents for visualization - self.set_orig_watermarked_latents(watermarked_latents) - - # Construct video generation parameters - generation_params = { - "num_inference_steps": self.config.num_inference_steps, - "guidance_scale": self.config.guidance_scale, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "num_frames": frames_to_generate, - "latents": watermarked_latents - } - - # Add parameters from config.gen_kwargs - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - if key != "num_frames": # Prevent overriding processed parameters - generation_params[key] = value - - # Handle I2V pipelines that need dimension permutation (like SVD) - final_latents = watermarked_latents - if is_i2v_pipeline(self.config.pipe): - logger.info("I2V pipeline detected, permuting latent dimensions.") - final_latents = final_latents.permute(0, 2, 1, 3, 4) # (b,c,f,h,w) -> (b,f,c,h,w) - - generation_params["latents"] = final_latents - - # Generate video - output = self.config.pipe( - prompt, - **generation_params - ) - - # Extract frames from output - if hasattr(output, 'frames'): - frames = output.frames[0] - elif hasattr(output, 'videos'): - frames = output.videos[0] - else: - frames = output[0] if isinstance(output, tuple) else output - - # Convert frames to PIL Images - frame_list = [] - for frame in frames: - if not isinstance(frame, Image.Image): - if isinstance(frame, np.ndarray): - if frame.dtype == np.uint8: - frame_pil = Image.fromarray(frame) - else: - frame_scaled = (frame * 255).astype(np.uint8) - frame_pil = Image.fromarray(frame_scaled) - elif isinstance(frame, torch.Tensor): - if frame.dim() == 3 and frame.shape[-1] in [1, 3]: - if frame.max() <= 1.0: - frame = (frame * 255).byte() - frame_np = frame.cpu().numpy() - frame_pil = Image.fromarray(frame_np) - else: - raise ValueError(f"Unexpected tensor shape for frame: {frame.shape}") - else: - raise TypeError(f"Unexpected type for frame: {type(frame)}") - else: - frame_pil = frame - - frame_list.append(frame_pil) - - return frame_list - - finally: - # Restore original num_frames - if original_num_frames is not None: - self.config.num_frames = original_num_frames - elif hasattr(self.config, 'num_frames'): - delattr(self.config, 'num_frames') - - def _detect_watermark_in_image(self, image: Image.Image, prompt: str = "", - *args, **kwargs) -> Dict[str, float]: - """Detect VideoShield watermark in image. - - Args: - image: Input PIL image - prompt: Text prompt used for generation - - Returns: - Dictionary containing detection results - """ - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Get text embeddings - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - # Preprocess image - image_tensor = transform_to_model_format( - image, target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - # Get image latents - image_latents = get_media_latents( - pipe=self.config.pipe, - media=image_tensor, - sample=False, - decoder_inv=kwargs.get("decoder_inv", False) - ) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['decoder_inv', 'guidance_scale', 'num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale_to_use, - num_inference_steps=num_steps_to_use, - **inversion_kwargs - )[-1] - - # Use detector or utils for evaluation - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.utils.eval_watermark(reversed_latents) - - def _get_video_latents(self, vae, video_frames, sample=True, rng_generator=None, permute=True): - encoding_dist = vae.encode(video_frames).latent_dist - if sample: - encoding = encoding_dist.sample(generator=rng_generator) - else: - encoding = encoding_dist.mode() - latents = (encoding * 0.18215).unsqueeze(0) - if permute: - latents = latents.permute(0, 2, 1, 3, 4) - return latents - - def _detect_watermark_in_video(self, - video_frames: Union[torch.Tensor, List[Image.Image]], - prompt: str = "", - detector_type: str = 'bit_acc', - *args, **kwargs) -> Dict[str, float]: - """Detect VideoShield watermark in video. - - Args: - video_frames: Input video frames as tensor or list of PIL images - prompt: Text prompt used for generation - - Returns: - Dictionary containing detection results - """ - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Convert frames to tensor if needed - if isinstance(video_frames, list): - from torchvision import transforms - frames_tensor = torch.stack([transforms.ToTensor()(frame) for frame in video_frames]) - video_frames = 2.0 * frames_tensor - 1.0 # Normalize to [-1, 1] - - video_frames = video_frames.to(self.config.device).to(self.config.pipe.vae.dtype) - - # Get video latents - with torch.no_grad(): - # TODO: Add support for I2V pipeline - video_latents = self._get_video_latents(self.config.pipe.vae, video_frames, sample=False) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['guidance_scale', 'num_inference_steps']} - - from diffusers import DDIMInverseScheduler - original_scheduler = self.config.pipe.scheduler - inverse_scheduler = DDIMInverseScheduler.from_config(original_scheduler.config) - self.config.pipe.scheduler = inverse_scheduler - - final_reversed_latents = self.config.pipe( - prompt=prompt, - latents=video_latents, - num_inference_steps=num_steps_to_use, - guidance_scale=guidance_scale_to_use, - output_type='latent', - **inversion_kwargs - ).frames # [B, F, H, W, C](T2V) - self.config.pipe.scheduler = original_scheduler - - # Use detector for evaluation - return self.detector.eval_watermark(final_reversed_latents, detector_type=detector_type) - - -
-[docs] - def get_data_for_visualize(self, - video_frames: List[Image.Image], - prompt: str = "", - guidance_scale: float = 1, - *args, **kwargs) -> DataForVisualization: - """Get VideoShield visualization data. - - This method generates the necessary data for visualizing VideoShield watermarks, - including original watermarked latents and reversed latents from inversion. - - Args: - image: The image to visualize watermarks for (can be None for generation only) - prompt: The text prompt used for generation - guidance_scale: Guidance scale for generation and inversion - - Returns: - DataForVisualization object containing visualization data - """ - # Use config values as defaults if not explicitly provided - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_steps_to_use = kwargs.get('num_inference_steps', self.config.num_inference_steps) - - # Convert frames to tensor if needed - if isinstance(video_frames, list): - from torchvision import transforms - frames_tensor = torch.stack([transforms.ToTensor()(frame) for frame in video_frames]) - video_frames = 2.0 * frames_tensor - 1.0 # Normalize to [-1, 1] - - video_frames = video_frames.to(self.config.device).to(self.config.pipe.vae.dtype) - - # Get video latents - with torch.no_grad(): - # TODO: Add support for I2V pipeline - video_latents = self._get_video_latents(self.config.pipe.vae, video_frames, sample=False) - - # Perform DDIM inversion - inversion_kwargs = {k: v for k, v in kwargs.items() - if k not in ['guidance_scale', 'num_inference_steps']} - - from diffusers import DDIMInverseScheduler - original_scheduler = self.config.pipe.scheduler - inverse_scheduler = DDIMInverseScheduler.from_config(original_scheduler.config) - self.config.pipe.scheduler = inverse_scheduler - collector = DenoisingLatentsCollector(save_every_n_steps=1, to_cpu=True) - - final_reversed_latents = self.config.pipe( - prompt=prompt, - latents=video_latents, - num_inference_steps=num_steps_to_use, - guidance_scale=guidance_scale_to_use, - output_type='latent', - callback=collector, - callback_steps=1, - **inversion_kwargs - ).frames # [B, F, H, W, C](T2V) - self.config.pipe.scheduler = original_scheduler - - reversed_latents = collector.latents_list # List[Tensor] - - return DataForVisualization( - config=self.config, - utils=self.utils, - orig_watermarked_latents=self.get_orig_watermarked_latents(), - reversed_latents=reversed_latents, - video_frames=video_frames, - )
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_modules/watermark/wind/wind.html b/docs/_build/html/_modules/watermark/wind/wind.html deleted file mode 100644 index 51d1efd..0000000 --- a/docs/_build/html/_modules/watermark/wind/wind.html +++ /dev/null @@ -1,1178 +0,0 @@ - - - - - - - - watermark.wind.wind — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for watermark.wind.wind

-# Copyright 2025 THU-BPM MarkDiffusion.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import torch
-import hashlib
-import numpy as np
-import logging
-from typing import Dict, Any, Union, List, Optional
-from PIL import Image
-from utils.media_utils import *
-from utils.utils import load_config_file, set_random_seed
-from utils.diffusion_config import DiffusionConfig
-from utils.media_utils import transform_to_model_format, get_media_latents
-from watermark.base import BaseConfig, BaseWatermark
-from exceptions.exceptions import AlgorithmNameMismatchError
-from detection.wind.wind_detection import WINDetector
-from visualize.data_for_visualization import DataForVisualization
-
-logger = logging.getLogger(__name__)
-
-
-[docs] -class WINDConfig(BaseConfig): - -
-[docs] - def initialize_parameters(self) -> None: - - self.w_seed = self.config_dict['w_seed'] - self.N = self.config_dict['num_noises'] - self.M = self.config_dict['num_groups'] - self.secret_salt = self.config_dict['secret_salt'].encode() - self.hash_func = getattr(hashlib, self.config_dict['hash_function']) - self.group_radius = self.config_dict['group_radius'] - self.threshold = self.config_dict['threshold'] - self.current_index = self.config_dict['current_index'] - self.noise_groups = self._precompute_noise_groups()
- - - def _precompute_noise_groups(self): - groups = {} - for i in range(self.N): - g = i % self.M - if g not in groups: - groups[g] = [] - seed = self._generate_seed(i) - groups[g].append(self._generate_noise(seed)) - return groups - - def _generate_seed(self, index: int) -> bytes: - """Generate seed""" - return self.hash_func(f"{index}{self.secret_salt}".encode()).digest() - - def _generate_noise(self, seed: bytes) -> torch.Tensor: - """Generate noises from seeds""" - rng = np.random.RandomState(int.from_bytes(seed[:4], 'big')) - return torch.from_numpy(rng.randn(4, 64, 64)).float().to(self.device) - - @property - def algorithm_name(self) -> str: - return 'WIND'
- - -
-[docs] -class WINDUtils: - -
-[docs] - def __init__(self, config: WINDConfig): - self.config = config - self.group_patterns = self._generate_group_patterns() - self.original_noise = None
- - - def _generate_group_patterns(self) -> Dict[int, torch.Tensor]: - set_random_seed(self.config.w_seed) - patterns = {} - for g in range(self.config.M): - pattern = torch.fft.fftshift( - torch.fft.fft2(torch.randn(4, 64, 64).to(self.config.device)), - dim=(-1, -2) - ) - mask = self._circle_mask(64, self.config.group_radius) - pattern *= mask - patterns[g] = pattern - return patterns - - def _circle_mask(self, size: int, r: int) -> torch.Tensor: - y, x = torch.meshgrid(torch.arange(size), torch.arange(size)) - center = size // 2 - dist = (x - center)**2 + (y - center)**2 - return ((dist >= (r-2)**2) & (dist <= r**2)).float().to(self.config.device) - -
-[docs] - def inject_watermark(self, index: int) -> torch.Tensor: - seed = self.config._generate_seed(index) - z_i = self.config._generate_noise(seed) - self.original_noise = z_i - g = index % self.config.M - z_fft = torch.fft.fftshift(torch.fft.fft2(z_i), dim=(-1, -2)) - - mask = self._circle_mask(64, self.config.group_radius) - z_fft = z_fft + self.group_patterns[g] * mask - - z_combined = torch.fft.ifft2(torch.fft.ifftshift(z_fft)).real - return z_combined
-
- - -
-[docs] -class WIND(BaseWatermark): - -
-[docs] - def __init__(self, watermark_config: WINDConfig, *args, **kwargs): - """ - Initialize the WIND algorithm. - - Parameters: - watermark_config (WINDConfig): Configuration instance of the WIND algorithm. - """ - self.config = watermark_config - self.utils = WINDUtils(self.config) - - self.detector = WINDetector( - noise_groups=self.config.noise_groups, - group_patterns=self.utils.group_patterns, - threshold=self.config.threshold, - device=self.config.device, - group_radius=self.config.group_radius - )
- - - def _generate_watermarked_image(self, prompt: str, *args, **kwargs) -> Image.Image: - """Generate a watermarked image.""" - index = self.config.current_index % self.config.M - - watermarked_z = self.utils.inject_watermark(index).unsqueeze(0) # [1, 4, 64, 64] - self.set_orig_watermarked_latents(watermarked_z) - set_random_seed(self.config.gen_seed) - - generation_params = { - "num_images_per_prompt": self.config.num_images, - "guidance_scale": self.config.guidance_scale, - "num_inference_steps": self.config.num_inference_steps, - "height": self.config.image_size[0], - "width": self.config.image_size[1], - "latents": watermarked_z, - } - - if hasattr(self.config, "gen_kwargs") and self.config.gen_kwargs: - for key, value in self.config.gen_kwargs.items(): - if key not in generation_params: - generation_params[key] = value - - # Use kwargs to override default parameters - for key, value in kwargs.items(): - generation_params[key] = value - - # Ensure latents parameter is not overridden - generation_params["latents"] = watermarked_z - - result = self.config.pipe( - prompt, - **generation_params - ) - - if isinstance(result, tuple): - return result[0].images[0] - else: - return result.images[0] - - def _detect_watermark_in_image(self, - image: Image.Image, - prompt: str = "", - *args, - **kwargs) -> Dict[str, Any]: - - guidance_scale_to_use = kwargs.get('guidance_scale', self.config.guidance_scale) - num_inference_steps = kwargs.get('num_inference_steps', 50) - - do_classifier_free_guidance = (guidance_scale_to_use > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - processed_img = transform_to_model_format( - image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_img, - sample=False, - decoder_inv = kwargs.get('decoder_inv',False) - ) - inversion_kwargs = {k: v for k, v in kwargs.items() if k not in ['decoder_inv','guidance_scale','num_inference_steps']} - - reversed_latents = self.config.inversion.forward_diffusion( - num_inference_steps=num_inference_steps, - guidance_scale=guidance_scale_to_use, - latents=image_latents, - text_embeddings=text_embeddings, - **inversion_kwargs - )[-1] - if 'detector_type' in kwargs: - return self.detector.eval_watermark(reversed_latents, detector_type=kwargs['detector_type']) - else: - return self.detector.eval_watermark(reversed_latents) - -
-[docs] - def get_data_for_visualize(self, - image: Image.Image, - prompt: str = "", - guidance_scale: Optional[float] = None, - decoder_inv: bool = False, - *args, - **kwargs): - - guidance_scale = guidance_scale or self.config.guidance_scale - num_inference_steps = kwargs.get('num_inference_steps', 50) - - do_classifier_free_guidance = (guidance_scale > 1.0) - prompt_embeds, negative_prompt_embeds = self.config.pipe.encode_prompt( - prompt=prompt, - device=self.config.device, - do_classifier_free_guidance=do_classifier_free_guidance, - num_images_per_prompt=1, - ) - - if do_classifier_free_guidance: - text_embeddings = torch.cat([negative_prompt_embeds, prompt_embeds]) - else: - text_embeddings = prompt_embeds - - processed_img = transform_to_model_format( - image, - target_size=self.config.image_size[0] - ).unsqueeze(0).to(text_embeddings.dtype).to(self.config.device) - - image_latents = get_media_latents( - pipe=self.config.pipe, - media=processed_img, - sample=False, - decoder_inv=decoder_inv - ) - - reversed_latents = self.config.inversion.forward_diffusion( - latents=image_latents, - text_embeddings=text_embeddings, - guidance_scale=guidance_scale, - num_inference_steps=num_inference_steps, - reverse=True, - **kwargs - ) - - data = DataForVisualization( - config=self.config, - utils=self.utils, - image=image, - reversed_latents=reversed_latents, - orig_watermarked_latents=self.orig_watermarked_latents, - ) - - return data
-
- -
- -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/_sources/BUILD.md.txt b/docs/_build/html/_sources/BUILD.md.txt deleted file mode 100644 index e0e2b0a..0000000 --- a/docs/_build/html/_sources/BUILD.md.txt +++ /dev/null @@ -1,162 +0,0 @@ -# Building MarkDiffusion Documentation - -This guide explains how to build the documentation locally. - -## Prerequisites - -1. Python 3.10 or higher -2. Sphinx and related packages - -## Installation - -Install documentation dependencies: - -```bash -cd docs -pip install -r requirements.txt -``` - -## Building HTML Documentation - -### On Linux/Mac: - -```bash -cd docs -make html -``` - -### On Windows: - -```bash -cd docs -make.bat html -``` - -The built documentation will be in `docs/_build/html/`. Open `docs/_build/html/index.html` in your browser. - -## Building Other Formats - -### PDF - -```bash -make latexpdf -``` - -### ePub - -```bash -make epub -``` - -### All formats - -```bash -make html epub latexpdf -``` - -## Cleaning Build Files - -```bash -make clean -``` - -## Live Reload (Development) - -For development with auto-reload: - -```bash -pip install sphinx-autobuild -sphinx-autobuild docs docs/_build/html -``` - -Then open http://127.0.0.1:8000 in your browser. - -## Read the Docs - -The documentation is automatically built and hosted on Read the Docs when you push to the main branch. - -Configuration file: `.readthedocs.yaml` - -## Troubleshooting - -### Missing Dependencies - -If you get import errors: - -```bash -pip install -r requirements.txt -pip install -r docs/requirements.txt -``` - -### Build Warnings - -To see detailed warnings: - -```bash -make html SPHINXOPTS="-W" -``` - -To fail on warnings: - -```bash -make html SPHINXOPTS="-W --keep-going" -``` - -### Clear Cache - -Sometimes you need to clear the build cache: - -```bash -make clean -rm -rf docs/_build -``` - -## Documentation Structure - -``` -docs/ -├── conf.py # Sphinx configuration -├── index.rst # Main documentation page -├── installation.rst # Installation guide -├── quickstart.rst # Quick start guide -├── tutorial.rst # Tutorials -├── user_guide/ # User guides -│ ├── algorithms.rst -│ ├── watermarking.rst -│ ├── visualization.rst -│ └── evaluation.rst -├── advanced/ # Advanced topics -│ ├── custom_algorithms.rst -│ ├── evaluation_pipelines.rst -│ └── configuration.rst -├── api/ # API reference -│ ├── watermark.rst -│ ├── detection.rst -│ ├── visualization.rst -│ ├── evaluation.rst -│ └── utils.rst -├── changelog.rst # Changelog -├── contributing.rst # Contributing guide -├── citation.rst # Citation information -├── _static/ # Static files (CSS, images) -├── _templates/ # Custom templates -├── Makefile # Build script (Unix) -├── make.bat # Build script (Windows) -└── requirements.txt # Documentation dependencies -``` - -## Contributing to Documentation - -See [Contributing Guide](contributing.rst) for details on: - -- Writing documentation -- Adding new pages -- Updating API docs -- Style guidelines - -## Links - -- Documentation: https://markdiffusion.readthedocs.io/ -- GitHub: https://github.com/THU-BPM/MarkDiffusion -- Read the Docs Project: https://readthedocs.org/projects/markdiffusion/ - diff --git a/docs/_build/html/_sources/api/detection.rst.txt b/docs/_build/html/_sources/api/detection.rst.txt deleted file mode 100644 index 3e4a025..0000000 --- a/docs/_build/html/_sources/api/detection.rst.txt +++ /dev/null @@ -1,96 +0,0 @@ -Detection API -============= - -This page documents the watermark detection API. - -Base Detector -------------- - -.. autoclass:: detection.base.BaseDetector - :members: - :undoc-members: - :show-inheritance: - -Detection Methods ------------------ - -Tree-Ring Detection -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.tr.tr_detection - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Detection -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.gs.gs_detection - :members: - :undoc-members: - :show-inheritance: - -ROBIN Detection -~~~~~~~~~~~~~~~ - -.. automodule:: detection.robin.robin_detection - :members: - :undoc-members: - :show-inheritance: - -WIND Detection -~~~~~~~~~~~~~~ - -.. automodule:: detection.wind.wind_detection - :members: - :undoc-members: - :show-inheritance: - -SFW Detection -~~~~~~~~~~~~~ - -.. automodule:: detection.sfw.sfw_detection - :members: - :undoc-members: - :show-inheritance: - -GaussMarker Detection -~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.gm.gm_detection - :members: - :undoc-members: - :show-inheritance: - -PRC Detection -~~~~~~~~~~~~~ - -.. automodule:: detection.prc.prc_detection - :members: - :undoc-members: - :show-inheritance: - -SEAL Detection -~~~~~~~~~~~~~~ - -.. automodule:: detection.seal.seal_detection - :members: - :undoc-members: - :show-inheritance: - -VideoShield Detection -~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.videoshield.videoshield_detection - :members: - :undoc-members: - :show-inheritance: - -VideoMark Detection -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.videomark.videomark_detection - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/_build/html/_sources/api/evaluation.rst.txt b/docs/_build/html/_sources/api/evaluation.rst.txt deleted file mode 100644 index 43633f8..0000000 --- a/docs/_build/html/_sources/api/evaluation.rst.txt +++ /dev/null @@ -1,83 +0,0 @@ -Evaluation API -============== - -This page documents the evaluation API. - -Pipelines ---------- - -Detection Pipelines -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.pipelines.detection - :members: - :undoc-members: - :show-inheritance: - -Image Quality Analysis Pipelines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.pipelines.image_quality_analysis - :members: - :undoc-members: - :show-inheritance: - -Video Quality Analysis Pipelines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.pipelines.video_quality_analysis - :members: - :undoc-members: - :show-inheritance: - -Datasets --------- - -.. automodule:: evaluation.dataset - :members: - :undoc-members: - :show-inheritance: - -Evaluation Tools ----------------- - -Success Rate Calculators -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.tools.success_rate_calculator - :members: - :undoc-members: - :show-inheritance: - -Image Editors (Attacks) -~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.tools.image_editor - :members: - :undoc-members: - :show-inheritance: - -Video Editors (Attacks) -~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.tools.video_editor - :members: - :undoc-members: - :show-inheritance: - -Image Quality Analyzers -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.tools.image_quality_analyzer - :members: - :undoc-members: - :show-inheritance: - -Video Quality Analyzers -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: evaluation.tools.video_quality_analyzer - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/_build/html/_sources/api/utils.rst.txt b/docs/_build/html/_sources/api/utils.rst.txt deleted file mode 100644 index 874f5a4..0000000 --- a/docs/_build/html/_sources/api/utils.rst.txt +++ /dev/null @@ -1,72 +0,0 @@ -Utilities API -============= - -This page documents utility modules. - -Diffusion Configuration ------------------------ - -.. automodule:: utils.diffusion_config - :members: - :undoc-members: - :show-inheritance: - -Pipeline Utilities ------------------- - -.. automodule:: utils.pipeline_utils - :members: - :undoc-members: - :show-inheritance: - -Media Utilities ---------------- - -.. automodule:: utils.media_utils - :members: - :undoc-members: - :show-inheritance: - -General Utilities ------------------ - -.. automodule:: utils.utils - :members: - :undoc-members: - :show-inheritance: - -Callbacks ---------- - -.. automodule:: utils.callbacks - :members: - :undoc-members: - :show-inheritance: - -Inversions ----------- - -Base Inversion -~~~~~~~~~~~~~~ - -.. automodule:: inversions.base_inversion - :members: - :undoc-members: - :show-inheritance: - -DDIM Inversion -~~~~~~~~~~~~~~ - -.. automodule:: inversions.ddim_inversion - :members: - :undoc-members: - :show-inheritance: - -Exact Inversion -~~~~~~~~~~~~~~~ - -.. automodule:: inversions.exact_inversion - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/_build/html/_sources/api/visualization.rst.txt b/docs/_build/html/_sources/api/visualization.rst.txt deleted file mode 100644 index 85132bd..0000000 --- a/docs/_build/html/_sources/api/visualization.rst.txt +++ /dev/null @@ -1,104 +0,0 @@ -Visualization API -================= - -This page documents the visualization API. - -AutoVisualizer --------------- - -.. autoclass:: visualize.auto_visualization.AutoVisualizer - :members: - :undoc-members: - :show-inheritance: - -Base Visualizer ---------------- - -.. autoclass:: visualize.base.BaseVisualizer - :members: - :undoc-members: - :show-inheritance: - -Algorithm-Specific Visualizers -------------------------------- - -Tree-Ring Visualizer -~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.tr.tr_visualizer - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Visualizer -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.gs.gs_visualizer - :members: - :undoc-members: - :show-inheritance: - -ROBIN Visualizer -~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.robin.robin_visualizer - :members: - :undoc-members: - :show-inheritance: - -WIND Visualizer -~~~~~~~~~~~~~~~ - -.. automodule:: visualize.wind.wind_visualizer - :members: - :undoc-members: - :show-inheritance: - -SFW Visualizer -~~~~~~~~~~~~~~ - -.. automodule:: visualize.sfw.sfw_visualizer - :members: - :undoc-members: - :show-inheritance: - -GaussMarker Visualizer -~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.gm.gm_visualizer - :members: - :undoc-members: - :show-inheritance: - -PRC Visualizer -~~~~~~~~~~~~~~ - -.. automodule:: visualize.prc.prc_visualizer - :members: - :undoc-members: - :show-inheritance: - -SEAL Visualizer -~~~~~~~~~~~~~~~ - -.. automodule:: visualize.seal.seal_visualizer - :members: - :undoc-members: - :show-inheritance: - -VideoShield Visualizer -~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.videoshield.video_shield_visualizer - :members: - :undoc-members: - :show-inheritance: - -VideoMark Visualizer -~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.videomark.video_mark_visualizer - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/_build/html/_sources/api/watermark.rst.txt b/docs/_build/html/_sources/api/watermark.rst.txt deleted file mode 100644 index 401cd43..0000000 --- a/docs/_build/html/_sources/api/watermark.rst.txt +++ /dev/null @@ -1,101 +0,0 @@ -Watermark API -============= - -This page documents the watermarking API. - -AutoWatermark -------------- - -.. autoclass:: watermark.auto_watermark.AutoWatermark - :members: - :undoc-members: - :show-inheritance: - -Base Watermark --------------- - -.. autoclass:: watermark.base.BaseWatermark - :members: - :undoc-members: - :show-inheritance: - -Tree-Ring Watermark -------------------- - -.. automodule:: watermark.tr.tr - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Watermark --------------------------- - -.. automodule:: watermark.gs.gs - :members: - :undoc-members: - :show-inheritance: - -ROBIN Watermark ---------------- - -.. automodule:: watermark.robin.robin - :members: - :undoc-members: - :show-inheritance: - -WIND Watermark --------------- - -.. automodule:: watermark.wind.wind - :members: - :undoc-members: - :show-inheritance: - -SFW Watermark -------------- - -.. automodule:: watermark.sfw.sfw - :members: - :undoc-members: - :show-inheritance: - -GaussMarker ------------ - -.. automodule:: watermark.gm.gm - :members: - :undoc-members: - :show-inheritance: - -PRC Watermark -------------- - -.. automodule:: watermark.prc.prc - :members: - :undoc-members: - :show-inheritance: - -SEAL Watermark --------------- - -.. automodule:: watermark.seal.seal - :members: - :undoc-members: - :show-inheritance: - -VideoShield ------------ - -.. automodule:: watermark.videoshield.video_shield - :members: - :undoc-members: - :show-inheritance: - -VideoMark ---------- - -.. automodule:: watermark.videomark.video_mark - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/_build/html/_sources/changelog.rst.txt b/docs/_build/html/_sources/changelog.rst.txt deleted file mode 100644 index cc98289..0000000 --- a/docs/_build/html/_sources/changelog.rst.txt +++ /dev/null @@ -1,138 +0,0 @@ -Changelog -========= - -All notable changes to MarkDiffusion will be documented in this file. - -Version 1.0.0 (2025-01-XX) --------------------------- - -Initial Release -~~~~~~~~~~~~~~~ - -**Implemented Algorithms:** - -- Tree-Ring (TR) - Pattern-based watermarking -- Ring-ID (RI) - Multi-key identification -- ROBIN - Robust and invisible watermarking -- WIND - Two-stage robust watermarking -- SFW - Semantic Fourier watermarking -- Gaussian-Shading (GS) - Performance-lossless watermarking -- GaussMarker (GM) - Dual-domain watermarking -- PRC - Undetectable watermarking -- SEAL - Semantic-aware watermarking -- VideoShield - Video watermarking -- VideoMark - Distortion-free video watermarking - -**Features:** - -- Unified implementation framework for watermarking algorithms -- Comprehensive evaluation module with 24 tools -- 8 automated evaluation pipelines -- Custom visualization tools for all algorithms -- Support for both image and video watermarking -- Extensive documentation and tutorials - -**Evaluation Tools:** - -*Detectability:* -- FundamentalSuccessRateCalculator -- DynamicThresholdSuccessRateCalculator - -*Image Attacks:* -- JPEG Compression -- Gaussian Blur -- Gaussian Noise -- Rotation -- Crop & Scale -- Brightness Adjustment -- Masking -- Overlay -- Adaptive Noise Injection - -*Video Attacks:* -- MPEG-4 Compression -- Frame Averaging -- Frame Swapping -- Video Codec Attack (H.264/H.265/VP9/AV1) -- Frame Rate Adapter -- Frame Interpolation Attack - -*Image Quality Metrics:* -- PSNR (Peak Signal-to-Noise Ratio) -- SSIM (Structural Similarity Index) -- LPIPS (Learned Perceptual Image Patch Similarity) -- CLIP Score -- FID (Fréchet Inception Distance) -- Inception Score -- NIQE (Natural Image Quality Evaluator) -- BRISQUE -- VIF (Visual Information Fidelity) -- FSIM (Feature Similarity Index) - -*Video Quality Metrics:* -- Subject Consistency -- Background Consistency -- Motion Smoothness -- Dynamic Degree -- Imaging Quality - -Recent Updates --------------- - -**2025.10.10** - -- Added Mask, Overlay, AdaptiveNoiseInjection image attack tools -- Thanks to Zheyu Fu for the contribution - -**2025.10.09** - -- Added VideoCodecAttack, FrameRateAdapter, FrameInterpolationAttack video attack tools -- Thanks to Luyang Si for the contribution - -**2025.10.08** - -- Added SSIM, BRISQUE, VIF, FSIM image quality analyzers -- Thanks to Huan Wang for the contribution - -**2025.10.07** - -- Added SFW (Semantic Fourier Watermarking) algorithm -- Thanks to Huan Wang for the contribution - -**2025.10.07** - -- Added VideoMark watermarking algorithm -- Thanks to Hanqian Li for the contribution - -**2025.09.29** - -- Added GaussMarker watermarking algorithm -- Thanks to Luyang Si for the contribution - -Upcoming Features ------------------ - -**Planned for v1.1.0:** - -- Additional watermarking algorithms -- More evaluation metrics -- Enhanced visualization capabilities -- Performance optimizations -- Extended documentation - -**Under Consideration:** - -- Real-time watermarking support -- Web interface for demonstration -- Pre-trained model zoo -- Integration with more diffusion models -- Support for additional modalities (3D, audio) - -Contributing ------------- - -We welcome contributions! See :doc:`contributing` for guidelines. - -To report bugs or request features, please open an issue on GitHub: -https://github.com/THU-BPM/MarkDiffusion/issues - diff --git a/docs/_build/html/_sources/citation.rst.txt b/docs/_build/html/_sources/citation.rst.txt deleted file mode 100644 index 5bfd6c0..0000000 --- a/docs/_build/html/_sources/citation.rst.txt +++ /dev/null @@ -1,231 +0,0 @@ -Citation -======== - -If you use MarkDiffusion in your research, please cite our paper: - -BibTeX ------- - -.. code-block:: bibtex - - @article{pan2025markdiffusion, - title={MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models}, - author={Pan, Leyi and Guan, Sheng and Fu, Zheyu and Si, Luyang and Wang, Zian and Hu, Xuming and King, Irwin and Yu, Philip S and Liu, Aiwei and Wen, Lijie}, - journal={arXiv preprint arXiv:2509.10569}, - year={2025} - } - -APA Style ---------- - -Pan, L., Guan, S., Fu, Z., Si, L., Wang, Z., Hu, X., King, I., Yu, P. S., Liu, A., & Wen, L. (2025). -MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models. -*arXiv preprint arXiv:2509.10569*. - -MLA Style ---------- - -Pan, Leyi, et al. "MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models." -*arXiv preprint arXiv:2509.10569* (2025). - -Algorithm-Specific Citations ------------------------------ - -If you use specific algorithms, please also cite their original papers: - -Tree-Ring Watermark -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bibtex - - @misc{wen2023treeringwatermarksfingerprintsdiffusion, - title={Tree-Ring Watermarks: Fingerprints for Diffusion Images that are Invisible and Robust}, - author={Yuxin Wen and John Kirchenbauer and Jonas Geiping and Tom Goldstein}, - year={2023}, - eprint={2305.20030}, - archivePrefix={arXiv}, - primaryClass={cs.LG}, - url={https://arxiv.org/abs/2305.20030}, - } - -Ring-ID -~~~~~~~ - -.. code-block:: bibtex - - @article{ci2024ringid, - title={RingID: Rethinking Tree-Ring Watermarking for Enhanced Multi-Key Identification}, - author={Ci, Hai and Yang, Pei and Song, Yiren and Shou, Mike Zheng}, - journal={arXiv preprint arXiv:2404.14055}, - year={2024} - } - -ROBIN -~~~~~ - -.. code-block:: bibtex - - @inproceedings{huangrobin, - title={ROBIN: Robust and Invisible Watermarks for Diffusion Models with Adversarial Optimization}, - author={Huang, Huayang and Wu, Yu and Wang, Qian}, - booktitle={The Thirty-eighth Annual Conference on Neural Information Processing Systems} - } - - -WIND -~~~~ - -.. code-block:: bibtex - - @article{arabi2024hidden, - title={Hidden in the Noise: Two-Stage Robust Watermarking for Images}, - author={Arabi, Kasra and Feuer, Benjamin and Witter, R Teal and Hegde, Chinmay and Cohen, Niv}, - journal={arXiv preprint arXiv:2412.04653}, - year={2024} - } - -SFW -~~~ - -.. code-block:: bibtex - - @inproceedings{lee2025semantic, - title={Semantic Watermarking Reinvented: Enhancing Robustness and Generation Quality with Fourier Integrity}, - author={Lee, Sung Ju and Cho, Nam Ik}, - booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, - pages={18759--18769}, - year={2025} - } - -Gaussian-Shading -~~~~~~~~~~~~~~~~ - -.. code-block:: bibtex - - @article{yang2024gaussian, - title={Gaussian Shading: Provable Performance-Lossless Image Watermarking for Diffusion Models}, - author={Yang, Zijin and Zeng, Kai and Chen, Kejiang and Fang, Han and Zhang, Weiming and Yu, Nenghai}, - journal={arXiv preprint arXiv:2404.04956}, - year={2024}, - } - -GaussMarker -~~~~~~~~~~~ - -.. code-block:: bibtex - - @misc{li2025gaussmarkerrobustdualdomainwatermark, - title={GaussMarker: Robust Dual-Domain Watermark for Diffusion Models}, - author={Kecen Li and Zhicong Huang and Xinwen Hou and Cheng Hong}, - year={2025}, - eprint={2506.11444}, - archivePrefix={arXiv}, - primaryClass={cs.CR}, - url={https://arxiv.org/abs/2506.11444}, - } - -PRC -~~~ - -.. code-block:: bibtex - - @article{gunn2025undetectable, - title={An undetectable watermark for generative image models}, - author={Gunn, Sam and Zhao, Xuandong and Song, Dawn}, - journal={arXiv preprint arXiv:2410.07369}, - year={2024} - } - -SEAL -~~~~ - -.. code-block:: bibtex - - @article{arabi2025seal, - title={SEAL: Semantic Aware Image Watermarking}, - author={Arabi, Kasra and Witter, R Teal and Hegde, Chinmay and Cohen, Niv}, - journal={arXiv preprint arXiv:2503.12172}, - year={2025} - } - -VideoShield -~~~~~~~~~~~ - -.. code-block:: bibtex - - @inproceedings{hu2025videoshield, - title={VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking}, - author={Runyi Hu and Jie Zhang and Yiming Li and Jiwei Li and Qing Guo and Han Qiu and Tianwei Zhang}, - booktitle={International Conference on Learning Representations (ICLR)}, - year={2025} - } - -VideoMark -~~~~~~~~~ - -.. code-block:: bibtex - - @article{hu2025videomark, - title={VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models}, - author={Hu, Xuming and Li, Hanqian and Li, Jungang and Liu, Aiwei}, - journal={arXiv preprint arXiv:2504.16359}, - year={2025} - } - -Acknowledgments ---------------- - -We would like to thank: - -- All contributors to the MarkDiffusion project -- The authors of the watermarking algorithms implemented in this toolkit -- The open-source community for their valuable feedback and contributions -- Research institutions supporting this work - -Using MarkDiffusion in Publications ------------------------------------- - -When using MarkDiffusion in your research: - -1. **Cite the main MarkDiffusion paper** (required) -2. **Cite specific algorithm papers** you use (required) -3. **Mention the toolkit in your acknowledgments** -4. **Link to the GitHub repository** - -Example acknowledgment text: - - *"This research utilized MarkDiffusion [1], an open-source toolkit for generative - watermarking. We specifically employed the Gaussian-Shading algorithm [2] for - watermark embedding and detection."* - -License -------- - -MarkDiffusion is released under the MIT License. See the LICENSE file for details. - -When using MarkDiffusion, please ensure compliance with the licenses of: - -- Individual watermarking algorithms -- Pre-trained models -- Datasets used for evaluation - -Contact -------- - -For questions about citation or collaboration: - -- **GitHub**: https://github.com/THU-BPM/MarkDiffusion -- **Paper**: https://arxiv.org/abs/2509.10569 -- **Homepage**: https://generative-watermark.github.io/ - -Updates -------- - -This citation information was last updated: November 2025 - -For the most up-to-date citation information, please check: - -- The project README -- The paper on arXiv -- The project homepage - diff --git a/docs/_build/html/_sources/code_of_conduct.rst.txt b/docs/_build/html/_sources/code_of_conduct.rst.txt deleted file mode 100644 index d75d35c..0000000 --- a/docs/_build/html/_sources/code_of_conduct.rst.txt +++ /dev/null @@ -1,50 +0,0 @@ -Code of Conduct -================ - -Community Standards -------------------- - -We are committed to providing a welcoming and harassment-free experience for everyone in the MarkDiffusion community. - -.. important:: - For the complete Code of Conduct, please refer to the `code_of_conduct.md <../code_of_conduct.md>`_ - file in the repository root. - -The complete document includes detailed information about: - -- Our pledge to create an inclusive, diverse, and healthy community -- Standards for acceptable and unacceptable behavior -- Enforcement responsibilities of community leaders -- Scope of application -- Reporting procedures for violations -- Enforcement guidelines and consequences - -Quick Reference ---------------- - -**Expected Behavior:** - -- Demonstrate empathy and kindness toward other people -- Be respectful of differing opinions, viewpoints, and experiences -- Give and gracefully accept constructive feedback -- Focus on what is best for the overall community - -**Unacceptable Behavior:** - -- Use of sexualized language or imagery -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information without permission - -Reporting ---------- - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders. - -For full details, see `code_of_conduct.md <../code_of_conduct.md>`_. - -Attribution ------------ - -This Code of Conduct is adapted from the `Contributor Covenant `_, version 2.0. - diff --git a/docs/_build/html/_sources/contributing.rst.txt b/docs/_build/html/_sources/contributing.rst.txt deleted file mode 100644 index 48657fb..0000000 --- a/docs/_build/html/_sources/contributing.rst.txt +++ /dev/null @@ -1,283 +0,0 @@ -Contributing to MarkDiffusion -============================= - -We welcome contributions from the community! This guide will help you get started. - -.. note:: - Please read our `Code of Conduct <../code_of_conduct.md>`_ and the detailed - `Contributing Guidelines <../contributing.md>`_ in the repository root before contributing. - -Overview --------- - -This document provides technical guidelines for contributing to MarkDiffusion. For general contribution -workflow (forking, cloning, creating branches, submitting PRs), please refer to the -`Contributing Guidelines <../contributing.md>`_ in the repository root. - -Development Guidelines ----------------------- - -Code Style -~~~~~~~~~~ - -We follow PEP 8 style guidelines. Please ensure your code: - -- Uses 4 spaces for indentation -- Has descriptive variable and function names -- Includes docstrings for all public functions and classes -- Stays under 100 characters per line when practical - -Example: - -.. code-block:: python - - def generate_watermarked_media(self, input_data, **kwargs): - """ - Generate watermarked media from input. - - Args: - input_data (str): Text prompt or input data - **kwargs: Additional generation parameters - - Returns: - PIL.Image: Watermarked image - - Examples: - >>> watermark = AutoWatermark.load('GS', 'config/GS.json', config) - >>> image = watermark.generate_watermarked_media("A sunset") - """ - # Implementation - pass - -Documentation -~~~~~~~~~~~~~ - -All new features should include: - -- Docstrings following Google or NumPy style -- Type hints for function arguments and returns -- Usage examples in docstrings -- Updates to relevant documentation pages - -Example with type hints: - -.. code-block:: python - - from typing import Union, Dict, Any - from PIL import Image - - def detect_watermark_in_media( - self, - media: Union[Image.Image, list], - **kwargs: Any - ) -> Dict[str, Any]: - """ - Detect watermark in media. - - Args: - media: PIL Image or list of PIL Images (for video) - **kwargs: Additional detection parameters - - Returns: - Dictionary containing detection results with keys: - - detected (bool): Whether watermark was detected - - score (float): Detection confidence score - - threshold (float): Detection threshold used - """ - pass - -Testing -~~~~~~~ - -All new code should include tests: - -.. code-block:: python - - import unittest - from watermark.auto_watermark import AutoWatermark - - class TestMyFeature(unittest.TestCase): - def setUp(self): - """Set up test fixtures.""" - self.watermark = AutoWatermark.load('GS', 'config/GS.json', config) - - def test_generation(self): - """Test watermarked image generation.""" - image = self.watermark.generate_watermarked_media("Test prompt") - self.assertIsNotNone(image) - self.assertEqual(image.size, (512, 512)) - - def test_detection(self): - """Test watermark detection.""" - image = self.watermark.generate_watermarked_media("Test prompt") - result = self.watermark.detect_watermark_in_media(image) - self.assertTrue(result['detected']) - -Run tests: - -.. code-block:: bash - - python -m pytest test/ - # Or for specific test - python -m pytest test/test_watermark.py::TestMyFeature::test_generation - -Contribution Process --------------------- - -Adding a New Algorithm -~~~~~~~~~~~~~~~~~~~~~~ - -To add a new watermarking algorithm: - -1. **Create algorithm directory structure** - - .. code-block:: bash - - watermark/my_algorithm/ - ├── __init__.py - ├── my_algorithm.py - detection/my_algorithm/ - ├── __init__.py - ├── my_algorithm_detection.py - visualize/my_algorithm/ - ├── __init__.py - ├── my_algorithm_visualizer.py - config/ - ├── MyAlgorithm.json - test/ - ├── test_my_algorithm.py - -2. **Implement the algorithm** - - Implement the watermark generation and detection logic. - -3. **Add configuration** - - Create ``config/MyAlgorithm.json`` with algorithm parameters. - -4. **Register algorithm** - - Add to ``watermark/auto_watermark.py``: - - .. code-block:: python - - from watermark.my_algorithm.my_algorithm import MyAlgorithm - - class AutoWatermark: - ALGORITHM_MAP = { - # ... existing algorithms - 'MA': MyAlgorithm, - } - -5. **Write tests** - - Create comprehensive tests in ``test/test_my_algorithm.py``. - -6. **Update documentation** - - - Add algorithm description to ``docs/user_guide/algorithms.rst`` - - Update ``docs/index.rst`` to list the new algorithm - - Add usage examples - -7. **Submit pull request** - - See Pull Request Guidelines below. - -Adding Evaluation Tools -~~~~~~~~~~~~~~~~~~~~~~~ - -To add a new evaluation tool: - -1. **Implement the tool** - - For image attacks: - - .. code-block:: python - - from evaluation.tools.image_editor import BaseImageEditor - - class MyAttack(BaseImageEditor): - def __init__(self, param1, param2, **kwargs): - super().__init__(**kwargs) - self.param1 = param1 - self.param2 = param2 - - def edit_image(self, image): - # Implement attack - return modified_image - - For quality metrics: - - .. code-block:: python - - from evaluation.tools.image_quality_analyzer import BaseImageQualityAnalyzer - - class MyMetric(BaseImageQualityAnalyzer): - def analyze(self, image1, image2=None): - # Implement metric - return score - -2. **Add tests** - -3. **Update documentation** - -4. **Submit pull request** - -Submission Checklist --------------------- - -Before submitting your contribution, ensure: - -**Testing** - -.. code-block:: bash - - python -m pytest test/ - -**Code Style** - -.. code-block:: bash - - flake8 watermark/ detection/ evaluation/ visualize/ - black --check watermark/ detection/ evaluation/ visualize/ - -**Documentation** - -- Update relevant documentation -- Add entry to CHANGELOG.md -- Ensure docstrings are complete - -**Pull Request** - -For the complete pull request process and guidelines, please refer to `contributing.md <../contributing.md>`_ -in the repository root. - -Additional Information ----------------------- - -**Community Guidelines** - -All participants are expected to follow our `Code of Conduct <../code_of_conduct.md>`_. -Please be respectful, constructive, and help create a welcoming environment for everyone. - -**Reporting Issues** - -For bug reports and feature requests, please use the appropriate templates configured in the GitHub repository. - -**Questions?** - -If you have questions: - -- Open a discussion on GitHub -- Check existing issues and pull requests -- Review the documentation at https://markdiffusion.readthedocs.io - -**Contact** - -For major contributions or collaborations: - -- GitHub: https://github.com/THU-BPM/MarkDiffusion -- Email: panly24@mails.tsinghua.edu.cn - -Thank you for contributing to MarkDiffusion! - diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt deleted file mode 100644 index 018513d..0000000 --- a/docs/_build/html/_sources/index.rst.txt +++ /dev/null @@ -1,136 +0,0 @@ -MarkDiffusion Documentation -============================ - -.. image:: https://img.shields.io/badge/Homepage-5F259F?style=for-the-badge&logo=homepage&logoColor=white - :target: https://generative-watermark.github.io/ - :alt: Homepage - -.. image:: https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white - :target: https://arxiv.org/abs/2509.10569 - :alt: Paper - -.. image:: https://img.shields.io/badge/HF--Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black - :target: https://huggingface.co/Generative-Watermark-Toolkits - :alt: HF Models - -Welcome to MarkDiffusion -------------------------- - -**MarkDiffusion** is an open-source Python toolkit for generative watermarking of latent diffusion models. -As the use of diffusion-based generative models expands, ensuring the authenticity and origin of generated -media becomes critical. MarkDiffusion simplifies the access, understanding, and assessment of watermarking -technologies, making it accessible to both researchers and the broader community. - -.. note:: - If you are interested in LLM watermarking (text watermark), please refer to the - `MarkLLM `_ toolkit from our group. - -Key Features ------------- - -🚀 **Unified Implementation Framework** - MarkDiffusion provides a modular architecture supporting eleven state-of-the-art generative - image/video watermarking algorithms of LDMs. - -📦 **Comprehensive Algorithm Support** - Currently implements 11 watermarking algorithms from two major categories: - - - **Pattern-based methods**: Tree-Ring, Ring-ID, ROBIN, WIND, SFW - - **Key-based methods**: Gaussian-Shading, GaussMarker, PRC, SEAL, VideoShield, VideoMark - -🔍 **Visualization Solutions** - The toolkit includes custom visualization tools that enable clear and insightful views into - how different watermarking algorithms operate under various scenarios. - -📊 **Comprehensive Evaluation Module** - With 24 evaluation tools covering detectability, robustness, and impact on output quality, - MarkDiffusion provides comprehensive assessment capabilities with 8 automated evaluation pipelines. - -Quick Example -------------- - -Here's a simple example to get you started with MarkDiffusion: - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - # Device setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - - # Configure diffusion pipeline - scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load watermark algorithm - watermark = AutoWatermark.load('TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config) - - # Generate watermarked media - prompt = "A beautiful sunset over the ocean" - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Detect watermark - detection_result = watermark.detect_watermark_in_media(watermarked_image) - print(f"Watermark detected: {detection_result}") - -Documentation Contents ----------------------- - -.. toctree:: - :maxdepth: 2 - :caption: Getting Started - - installation - quickstart - tutorial - -.. toctree:: - :maxdepth: 2 - :caption: User Guide - - user_guide/algorithms - user_guide/watermarking - user_guide/visualization - user_guide/evaluation - -.. toctree:: - :maxdepth: 2 - :caption: API Reference - - api/watermark - api/detection - api/visualization - api/evaluation - api/utils - -.. toctree:: - :maxdepth: 1 - :caption: Additional Resources - - changelog - contributing - code_of_conduct - citation - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/_build/html/_sources/installation.rst.txt b/docs/_build/html/_sources/installation.rst.txt deleted file mode 100644 index 6e84a27..0000000 --- a/docs/_build/html/_sources/installation.rst.txt +++ /dev/null @@ -1,201 +0,0 @@ -Installation -============ - -This guide will help you install MarkDiffusion and its dependencies. - -Requirements ------------- - -- Python 3.10 or higher -- PyTorch (with CUDA support recommended for GPU acceleration) -- 8GB+ RAM (16GB+ recommended for video watermarking) -- CUDA-compatible GPU (optional but highly recommended) - -Basic Installation ------------------- - -1. **Clone the Repository** - - .. code-block:: bash - - git clone https://github.com/THU-BPM/MarkDiffusion.git - cd MarkDiffusion - -2. **Install Dependencies** - - .. code-block:: bash - - pip install -r requirements.txt - -3. **Download Pre-trained Models** - - MarkDiffusion uses pre-trained models stored on Hugging Face. Download the required models: - - .. code-block:: bash - - # The models will be downloaded to the ckpts/ directory - # Visit: https://huggingface.co/Generative-Watermark-Toolkits - - For each algorithm you plan to use, download the corresponding model weights from the - `Generative-Watermark-Toolkits `_ - repository and place them in the appropriate ``ckpts/`` subdirectory. - -Installation with Conda ------------------------ - -If you prefer using Conda for environment management: - -.. code-block:: bash - - # Create a new conda environment - conda create -n markdiffusion python=3.10 - conda activate markdiffusion - - # Install PyTorch with CUDA support - conda install pytorch torchvision torchaudio pytorch-cuda=12.6 -c pytorch -c nvidia - - # Install other dependencies - pip install -r requirements.txt - -GPU Support ------------ - -For GPU acceleration, make sure you have: - -1. **NVIDIA GPU** with CUDA support -2. **CUDA Toolkit** installed (version 11.8 or higher) -3. **cuDNN** library - -To verify GPU availability: - -.. code-block:: python - - import torch - print(f"CUDA available: {torch.cuda.is_available()}") - print(f"CUDA version: {torch.version.cuda}") - print(f"Device count: {torch.cuda.device_count()}") - -Algorithm-Specific Setup ------------------------- - -Some algorithms require additional model checkpoints beyond the base installation: - -GaussMarker (GM) -~~~~~~~~~~~~~~~~ - -GaussMarker requires two pre-trained models for watermark detection and restoration: - -1. **GNR Model** (Generative Noise Restoration): A UNet-based model for restoring watermark bits from noisy latents -2. **Fuser Model**: A classifier for fusion-based watermark detection decisions - -**Setup:** - -.. code-block:: bash - - # Create the checkpoint directory - mkdir -p watermark/gm/ckpts/ - - # Download models from Hugging Face - # Visit: https://huggingface.co/Generative-Watermark-Toolkits/GaussMarker - # Place the following files: - # - model_final.pth -> watermark/gm/ckpts/model_final.pth - # - sd21_cls2.pkl -> watermark/gm/ckpts/sd21_cls2.pkl - -**Configuration Path** (in ``config/GM.json``): - -- ``gnr_checkpoint``: ``"watermark/gm/ckpts/model_final.pth"`` -- ``fuser_checkpoint``: ``"watermark/gm/ckpts/sd21_cls2.pkl"`` - -SEAL -~~~~ - -SEAL uses pre-trained models from Hugging Face for caption generation and embedding: - -1. **BLIP2 Model**: For generating image captions (blip2-flan-t5-xl) -2. **Sentence Transformer**: For caption embedding - -**Setup:** - -.. code-block:: bash - - # These models will be automatically downloaded from Hugging Face on first use - # Or you can pre-download them: - - # Download BLIP2 model - python -c "from transformers import Blip2Processor, Blip2ForConditionalGeneration; \ - Blip2Processor.from_pretrained('Salesforce/blip2-flan-t5-xl'); \ - Blip2ForConditionalGeneration.from_pretrained('Salesforce/blip2-flan-t5-xl')" - - # Download sentence transformer (if using custom fine-tuned model) - # Update config/SEAL.json paths accordingly - -**Configuration** (in ``config/SEAL.json``): - -- ``cap_processor``: Path or model name for BLIP2 processor -- ``cap_model``: Path or model name for BLIP2 model -- ``sentence_model``: Path or model name for sentence transformer - -.. note:: - SEAL models are large (~15GB for BLIP2). Ensure you have sufficient disk space and memory. - -Other Algorithms -~~~~~~~~~~~~~~~~ - -The following algorithms work with the base installation and do not require additional checkpoints: - -- **Tree-Ring (TR)**, **Ring-ID (RI)**, **ROBIN**, **WIND**, **SFW**: Pattern-based methods using frequency domain manipulation -- **Gaussian-Shading (GS)**, **PRC**: Key-based methods with built-in watermark generation -- **VideoShield**, **VideoMark**: Video watermarking algorithms using temporal consistency - -Verification ------------- - -To verify your installation, run the test suite: - -.. code-block:: bash - - python -m pytest test/ - -Or try a simple example: - -.. code-block:: python - - from watermark.auto_watermark import AutoWatermark - print("MarkDiffusion successfully installed!") - -Troubleshooting ---------------- - -Common Issues -~~~~~~~~~~~~~ - -**Issue: Module not found error** - -Solution: Make sure all dependencies are installed: - -.. code-block:: bash - - pip install -r requirements.txt --upgrade - -**Issue: Model weights not found** - -Solution: Download the required models from Hugging Face and place them in the correct directory structure. - -Getting Help -~~~~~~~~~~~~ - -If you encounter issues: - -1. Check the `GitHub Issues `_ -2. Consult the `FAQ `_ -3. Open a new issue with detailed information about your problem - -Next Steps ----------- - -Now that you have installed MarkDiffusion, proceed to: - -- :doc:`quickstart` - Get started with basic examples -- :doc:`tutorial` - Learn through step-by-step tutorials -- :doc:`user_guide/algorithms` - Explore available algorithms - diff --git a/docs/_build/html/_sources/quickstart.rst.txt b/docs/_build/html/_sources/quickstart.rst.txt deleted file mode 100644 index da107f7..0000000 --- a/docs/_build/html/_sources/quickstart.rst.txt +++ /dev/null @@ -1,245 +0,0 @@ -Quick Start -=========== - -This guide will help you get started with MarkDiffusion quickly. - -Basic Workflow --------------- - -The typical workflow with MarkDiffusion consists of three main steps: - -1. **Configure** the diffusion model and watermarking algorithm -2. **Generate** watermarked images or videos -3. **Detect** watermarks in media - -Step 1: Setup and Configuration --------------------------------- - -First, import the necessary modules and configure your diffusion model: - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - # Device setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - - # Configure the diffusion model - model_id = "stabilityai/stable-diffusion-2-1" - scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device) - - # Create diffusion configuration - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - -Step 2: Load a Watermarking Algorithm --------------------------------------- - -MarkDiffusion supports multiple watermarking algorithms. Here's how to load one: - -.. code-block:: python - - # Load Tree-Ring watermarking algorithm - watermark = AutoWatermark.load( - 'TR', # Algorithm name - algorithm_config='config/TR.json', # Configuration file - diffusion_config=diffusion_config # Diffusion settings - ) - -Available algorithms: - -- **TR**: Tree-Ring -- **RI**: Ring-ID -- **ROBIN**: ROBIN -- **WIND**: WIND -- **SFW**: Semantic Fourier Watermark -- **GS**: Gaussian-Shading -- **GM**: GaussMarker -- **PRC**: PRC -- **SEAL**: SEAL -- **VideoShield**: VideoShield -- **VideoMark**: VideoMark - -Step 3: Generate Watermarked Media ------------------------------------ - -Image Generation -~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Define your prompt - prompt = "A beautiful landscape with mountains and a lake at sunset" - - # Generate watermarked image - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Save the image - watermarked_image.save("watermarked_output.png") - - # Display the image - watermarked_image.show() - -Video Generation -~~~~~~~~~~~~~~~~ - -For video watermarking algorithms (VideoShield, VideoMark): - -.. code-block:: python - - # Load video watermarking algorithm - video_watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_diffusion_config - ) - - # Generate watermarked video - prompt = "A cat walking through a garden" - video_frames = video_watermark.generate_watermarked_media(prompt) - - # Save video frames - for i, frame in enumerate(video_frames): - frame.save(f"output/frame_{i:04d}.png") - -Step 4: Detect Watermarks --------------------------- - -After generating watermarked media, you can detect the watermark: - -.. code-block:: python - - # Detect watermark in the generated image - detection_result = watermark.detect_watermark_in_media(watermarked_image) - - # Print detection results - print(f"Detection result: {detection_result}") - - # For algorithms that return a score - if 'score' in detection_result: - print(f"Confidence score: {detection_result['score']}") - print(f"Threshold: {detection_result.get('threshold', 'N/A')}") - -Complete Example ----------------- - -Here's a complete example putting it all together: - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - def main(): - # Setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - model_id = "stabilityai/stable-diffusion-2-1" - - # Load diffusion model - scheduler = DPMSolverMultistepScheduler.from_pretrained( - model_id, subfolder="scheduler" - ) - pipe = StableDiffusionPipeline.from_pretrained( - model_id, scheduler=scheduler - ).to(device) - - # Configure diffusion - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load watermarking algorithm - watermark = AutoWatermark.load( - 'GS', # Gaussian-Shading - algorithm_config='config/GS.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - prompt = "A serene Japanese garden with cherry blossoms" - print("Generating watermarked image...") - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Save image - watermarked_image.save("output.png") - print("Image saved to output.png") - - # Detect watermark - print("Detecting watermark...") - detection_result = watermark.detect_watermark_in_media(watermarked_image) - print(f"Detection result: {detection_result}") - - return watermarked_image, detection_result - - if __name__ == "__main__": - image, result = main() - -Comparing Multiple Algorithms ------------------------------- - -You can easily compare different algorithms: - -.. code-block:: python - - algorithms = ['TR', 'GS', 'ROBIN', 'SEAL'] - prompt = "A futuristic city skyline at night" - - results = {} - for algo_name in algorithms: - print(f"\nTesting {algo_name}...") - - # Load algorithm - watermark = AutoWatermark.load( - algo_name, - algorithm_config=f'config/{algo_name}.json', - diffusion_config=diffusion_config - ) - - # Generate and detect - img = watermark.generate_watermarked_media(prompt) - detection = watermark.detect_watermark_in_media(img) - - results[algo_name] = { - 'image': img, - 'detection': detection - } - - # Save image - img.save(f"output_{algo_name}.png") - - # Print comparison - print("\n=== Comparison Results ===") - for algo, data in results.items(): - print(f"{algo}: {data['detection']}") - -Next Steps ----------- - -Now that you're familiar with the basics: - -- :doc:`tutorial` - Detailed tutorials for each component -- :doc:`user_guide/algorithms` - Learn about specific algorithms -- :doc:`user_guide/visualization` - Visualize watermarking mechanisms -- :doc:`user_guide/evaluation` - Evaluate watermark quality and robustness - diff --git a/docs/_build/html/_sources/tutorial.rst.txt b/docs/_build/html/_sources/tutorial.rst.txt deleted file mode 100644 index 1eaf158..0000000 --- a/docs/_build/html/_sources/tutorial.rst.txt +++ /dev/null @@ -1,367 +0,0 @@ -Tutorial -======== - -This tutorial provides step-by-step examples for using MarkDiffusion's main features. - -Tutorial 1: Basic Image Watermarking -------------------------------------- - -Learn how to watermark images using different algorithms. - -Using Tree-Ring Watermark -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tree-Ring is a pattern-based watermarking method that embeds invisible patterns in the frequency domain. - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - # Setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - model_id = "stabilityai/stable-diffusion-2-1" - - scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device) - - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load Tree-Ring watermark - tr_watermark = AutoWatermark.load( - 'TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - prompt = "A majestic mountain landscape with snow peaks" - watermarked_img = tr_watermark.generate_watermarked_media(prompt) - watermarked_img.save("tr_watermarked.png") - - # Detect watermark - result = tr_watermark.detect_watermark_in_media(watermarked_img) - print(f"Tree-Ring Detection: {result}") - -Using Gaussian-Shading Watermark -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Gaussian-Shading is a key-based method that provides provable performance-lossless watermarking. - -.. code-block:: python - - # Load Gaussian-Shading watermark - gs_watermark = AutoWatermark.load( - 'GS', - algorithm_config='config/GS.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - watermarked_img = gs_watermark.generate_watermarked_media(prompt) - watermarked_img.save("gs_watermarked.png") - - # Detect watermark - result = gs_watermark.detect_watermark_in_media(watermarked_img) - print(f"Gaussian-Shading Detection: {result}") - -Tutorial 2: Visualizing Watermark Mechanisms ---------------------------------------------- - -Learn how to visualize watermarking mechanisms using the same approach as in ``MarkDiffusion_demo.ipynb``. - -Tree-Ring Visualization -~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - # Get visualization data - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - - # Load visualizer - visualizer = AutoVisualizer.load('TR', data_for_visualization=data_for_visualization) - - # Create visualization with specific methods and channels - method_kwargs = [{}, {"channel": 0}, {}, {"channel": 0}, {}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image', - 'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'], - method_kwargs=method_kwargs, - save_path='TR_watermark_visualization.pdf' - ) - -Gaussian-Shading Visualization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - visualizer = AutoVisualizer.load('GS', data_for_visualization=data_for_visualization) - - method_kwargs = [{"channel": 0}, {"channel": 0}, {}, {"channel": 0}, {"channel": 0}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_watermark_bits', 'draw_orig_latents', 'draw_watermarked_image', - 'draw_inverted_latents', 'draw_reconstructed_watermark_bits'], - method_kwargs=method_kwargs, - save_path='GS_watermark_visualization.pdf' - ) - -ROBIN Visualization -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - visualizer = AutoVisualizer.load('ROBIN', data_for_visualization=data_for_visualization) - - method_kwargs = [{}, {"channel": 3}, {}, {"channel": 3}, {}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image', - 'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'], - method_kwargs=method_kwargs, - save_path='ROBIN_watermark_visualization.pdf' - ) - -Tutorial 3: Evaluating Watermark Quality ------------------------------------------ - -Assess the quality and robustness of watermarks. - -Image Quality Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import StableDiffusionPromptsDataset - from evaluation.pipelines.image_quality_analysis import ComparedImageQualityAnalysisPipeline - from evaluation.tools.image_quality_analyzer import PSNRAnalyzer, SSIMAnalyzer - from evaluation.pipelines.image_quality_analysis import QualityPipelineReturnType - - # Create dataset - dataset = StableDiffusionPromptsDataset(max_samples=50) - - # Setup quality analysis pipeline - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer(), SSIMAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - # Evaluate watermark quality - results = pipeline.evaluate(gs_watermark) - print(f"PSNR: {results['PSNR']:.2f} dB") - print(f"SSIM: {results['SSIM']:.4f}") - -Robustness Evaluation -~~~~~~~~~~~~~~~~~~~~~~ - -Test watermark robustness against various attacks: - -.. code-block:: python - - from evaluation.tools.image_editor import ( - JPEGCompression, GaussianBlurring, GaussianNoise, Rotation - ) - from evaluation.pipelines.detection import WatermarkedMediaDetectionPipeline - from evaluation.pipelines.detection import DetectionPipelineReturnType - from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator - - # Test against JPEG compression - dataset = StableDiffusionPromptsDataset(max_samples=100) - - # Create detection pipeline with JPEG attack - detection_pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[JPEGCompression(quality=75)], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - # Evaluate detection after attack - detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, - } - - scores = detection_pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs) - print(f"Detection scores after JPEG compression: {scores}") - -Multiple Attacks Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Test robustness against multiple attacks - attacks = { - 'JPEG-75': JPEGCompression(quality=75), - 'JPEG-50': JPEGCompression(quality=50), - 'Blur': GaussianBlurring(kernel_size=3), - 'Noise': GaussianNoise(std=0.05), - 'Rotation': Rotation(angle=15) - } - - results = {} - for attack_name, attack_editor in attacks.items(): - pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[attack_editor], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - scores = pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs) - results[attack_name] = scores - - # Print results - print("\n=== Robustness Results ===") - for attack, scores in results.items(): - avg_score = sum(scores) / len(scores) if scores else 0 - print(f"{attack}: Average Score = {avg_score:.4f}") - -Tutorial 4: Video Watermarking -------------------------------- - -Learn how to watermark videos using VideoShield or VideoMark. - -Basic Video Watermarking -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from diffusers import DiffusionPipeline - - # Setup for video generation - video_model_id = "cerspense/zeroscope_v2_576w" - video_pipe = DiffusionPipeline.from_pretrained( - video_model_id, - torch_dtype=torch.float16 - ).to(device) - - video_diffusion_config = DiffusionConfig( - scheduler=video_pipe.scheduler, - pipe=video_pipe, - device=device, - image_size=(576, 320), - num_inference_steps=40, - guidance_scale=7.5, - gen_seed=42, - num_frames=16 - ) - - # Load VideoShield watermark - video_watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_diffusion_config - ) - - # Generate watermarked video - prompt = "A drone flying over a beach at sunset" - video_frames = video_watermark.generate_watermarked_media(prompt) - - # Save frames - import os - os.makedirs("output_video", exist_ok=True) - for i, frame in enumerate(video_frames): - frame.save(f"output_video/frame_{i:04d}.png") - - # Detect watermark in video - detection_result = video_watermark.detect_watermark_in_media(video_frames) - print(f"Video watermark detection: {detection_result}") - -Video Quality Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import VBenchDataset - from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline - from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - MotionSmoothnessAnalyzer - ) - - # Create video dataset - video_dataset = VBenchDataset(max_samples=20, dimension='subject_consistency') - - # Setup video quality pipeline - video_pipeline = DirectVideoQualityAnalysisPipeline( - dataset=video_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[SubjectConsistencyAnalyzer(device=device)], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - # Evaluate video quality - results = video_pipeline.evaluate(video_watermark) - print(f"Subject consistency: {results}") - -Tutorial 5: Custom Configuration ---------------------------------- - -Customize watermarking parameters for your specific needs. - -Batch Processing -~~~~~~~~~~~~~~~~ - -.. code-block:: python - - prompts = [ - "A serene lake at dawn", - "A bustling city street at night", - "A colorful flower garden", - "A snowy mountain peak", - "A tropical beach paradise" - ] - - output_dir = "batch_output" - os.makedirs(output_dir, exist_ok=True) - - for i, prompt in enumerate(prompts): - print(f"Processing {i+1}/{len(prompts)}: {prompt}") - - # Generate watermarked image - img = gs_watermark.generate_watermarked_media(prompt) - img.save(f"{output_dir}/watermarked_{i:03d}.png") - - # Detect watermark - result = gs_watermark.detect_watermark_in_media(img) - print(f" Detection: {result}") - -Next Steps ----------- - -Explore more advanced topics: - -- :doc:`user_guide/algorithms` - Detailed algorithm documentation -- :doc:`api/watermark` - Complete API reference - diff --git a/docs/_build/html/_sources/user_guide/algorithms.rst.txt b/docs/_build/html/_sources/user_guide/algorithms.rst.txt deleted file mode 100644 index 7891c5b..0000000 --- a/docs/_build/html/_sources/user_guide/algorithms.rst.txt +++ /dev/null @@ -1,445 +0,0 @@ -Watermarking Algorithms -======================= - -MarkDiffusion supports 11 state-of-the-art watermarking algorithms for latent diffusion models. - -Algorithm Overview ------------------- - -.. list-table:: - :header-rows: 1 - :widths: 15 15 10 60 - - * - Algorithm - - Category - - Target - - Description - * - Tree-Ring (TR) - - Pattern - - Image - - Embeds invisible ring patterns in frequency domain - * - Ring-ID (RI) - - Pattern - - Image - - Multi-key identification with tree-ring patterns - * - ROBIN - - Pattern - - Image - - Robust and invisible watermarks with adversarial optimization - * - WIND - - Pattern - - Image - - Two-stage robust watermarking hidden in noise - * - SFW - - Pattern - - Image - - Semantic watermarking with Fourier integrity - * - Gaussian-Shading (GS) - - Key - - Image - - Provable performance-lossless image watermarking - * - GaussMarker (GM) - - Key - - Image - - Robust dual-domain watermarking - * - PRC - - Key - - Image - - Undetectable watermark for generative models - * - SEAL - - Key - - Image - - Semantic-aware image watermarking - * - VideoShield - - Key - - Video - - Video diffusion model regulation via watermarking - * - VideoMark - - Key - - Video - - Distortion-free robust watermarking for video - -Pattern-Based Methods ---------------------- - -Pattern-based methods embed predefined patterns into the generation process. - -Tree-Ring (TR) -~~~~~~~~~~~~~~ - -**Reference:** `Tree-Ring Watermarks: Fingerprints for Diffusion Images that are Invisible and Robust `_ - -Tree-Ring embeds circular patterns in the Fourier domain of initial latents, making them invisible in the spatial domain but detectable through frequency analysis. - -**Key Features:** - -- Invisible watermarks in spatial domain -- Robust to common image transformations -- No need for additional neural networks - -**Usage:** - -.. code-block:: python - - from watermark.auto_watermark import AutoWatermark - - watermark = AutoWatermark.load( - 'TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - image = watermark.generate_watermarked_media(prompt) - - # Detect watermark - result = watermark.detect_watermark_in_media(image) - -**Configuration Parameters:** - -From ``config/TR.json``: - -- ``w_seed``: 999999 - Watermark seed -- ``w_channel``: 0 - Channel index to embed watermark -- ``w_pattern``: "zeros" - Pattern type -- ``w_mask_shape``: "circle" - Mask shape -- ``w_radius``: 10 - Ring radius in frequency domain -- ``w_pattern_const``: 0 - Pattern constant -- ``threshold``: 50 - Detection threshold - -Ring-ID (RI) -~~~~~~~~~~~~ - -**Reference:** `RingID: Rethinking Tree-Ring Watermarking for Enhanced Multi-Key Identification `_ - -Ring-ID extends Tree-Ring to support multiple keys, enabling multi-user identification and improved robustness. - -**Key Features:** - -- Multi-key identification -- Enhanced robustness -- Backward compatible with Tree-Ring - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'RI', - algorithm_config='config/RI.json', - diffusion_config=diffusion_config - ) - -**Configuration Parameters:** - -From ``config/RI.json``: - -- ``ring_width``: 1 - Ring width -- ``quantization_levels``: 4 - Quantization levels -- ``ring_value_range``: 64 - Ring value range -- ``assigned_keys``: 10 - Number of assigned keys -- ``radius``: 14 - Ring radius -- ``radius_cutoff``: 3 - Radius cutoff -- ``heter_watermark_channel``: [0] - Heterogeneous watermark channel -- ``ring_watermark_channel``: [3] - Ring watermark channel -- ``threshold``: 50 - Detection threshold - -ROBIN -~~~~~ - -**Reference:** `ROBIN: Robust and Invisible Watermarks for Diffusion Models with Adversarial Optimization `_ - -ROBIN uses adversarial optimization to create robust watermarks that are invisible to human eyes and resistant to attacks. - -**Key Features:** - -- Adversarial optimization for robustness -- Invisible watermarks -- Trained watermark generator - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'ROBIN', - algorithm_config='config/ROBIN.json', - diffusion_config=diffusion_config - ) - -**Configuration Parameters:** - -From ``config/ROBIN.json``: - -- ``w_seed``: 999999 - Watermark seed -- ``w_channel``: 3 - Watermark channel -- ``w_pattern``: "ring" - Pattern type -- ``w_up_radius``: 30 - Upper radius -- ``w_low_radius``: 5 - Lower radius -- ``watermarking_step``: 35 - Watermarking injection step -- ``threshold``: 45 - Detection threshold -- ``learning_rate``: 0.0005 - Training learning rate -- ``max_train_steps``: 2000 - Maximum training steps - -WIND -~~~~ - -**Reference:** `Hidden in the Noise: Two-Stage Robust Watermarking for Images `_ - -WIND implements a two-stage watermarking approach that hides watermarks in the noise initialization. - -**Key Features:** - -- Two-stage watermarking -- Hidden in initial noise -- High robustness - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'WIND', - algorithm_config='config/WIND.json', - diffusion_config=diffusion_config - ) - -SFW -~~~ - -**Reference:** `Semantic Watermarking Reinvented: Enhancing Robustness and Generation Quality with Fourier Integrity `_ - -SFW combines semantic information with Fourier domain watermarking for enhanced robustness and quality. - -**Key Features:** - -- Semantic-aware watermarking -- Fourier integrity preservation -- Minimal quality degradation - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'SFW', - algorithm_config='config/SFW.json', - diffusion_config=diffusion_config - ) - -Key-Based Methods ------------------ - -Key-based methods use secret keys to embed and extract watermarks. - -Gaussian-Shading (GS) -~~~~~~~~~~~~~~~~~~~~~ - -**Reference:** `Gaussian Shading: Provable Performance-Lossless Image Watermarking for Diffusion Models `_ - -Gaussian-Shading provides provably performance-lossless watermarking by injecting Gaussian noise into the generation process. - -**Key Features:** - -- Performance-lossless (provable) -- No quality degradation -- Simple and efficient - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'GS', - algorithm_config='config/GS.json', - diffusion_config=diffusion_config - ) - -**Configuration Parameters:** - -From ``config/GS.json``: - -- ``channel_copy``: 1 - Channel to copy watermark -- ``wm_key``: 42 - Watermark key -- ``hw_copy``: 8 - Height/width copy parameter -- ``chacha``: true - Use ChaCha encryption -- ``chacha_key_seed``: 123456 - ChaCha key seed -- ``chacha_nonce_seed``: 789012 - ChaCha nonce seed -- ``threshold``: 0.7 - Detection threshold - -GaussMarker (GM) -~~~~~~~~~~~~~~~~ - -**Reference:** `GaussMarker: Robust Dual-Domain Watermark for Diffusion Models `_ - -GaussMarker combines spatial and frequency domain watermarking for enhanced robustness. - -**Key Features:** - -- Dual-domain watermarking -- Trained GNR (Gaussian Noise Residual) network -- High robustness to attacks - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'GM', - algorithm_config='config/GM.json', - diffusion_config=diffusion_config - ) - -**Training GNR Network:** - -.. code-block:: bash - - python watermark/gm/train_GNR.py --config config/GM.json - -PRC -~~~ - -**Reference:** `An undetectable watermark for generative image models `_ - -PRC creates undetectable watermarks that are imperceptible to human observers and detection systems. - -**Key Features:** - -- Undetectable by design -- High security -- Minimal perceptual impact - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'PRC', - algorithm_config='config/PRC.json', - diffusion_config=diffusion_config - ) - -SEAL -~~~~ - -**Reference:** `SEAL: Semantic Aware Image Watermarking `_ - -SEAL leverages semantic information to embed watermarks that adapt to image content. - -**Key Features:** - -- Semantic-aware embedding -- Content-adaptive watermarking -- Preserved semantic integrity - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'SEAL', - algorithm_config='config/SEAL.json', - diffusion_config=diffusion_config - ) - -Video Watermarking Methods ---------------------------- - -VideoShield -~~~~~~~~~~~ - -**Reference:** `VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking `_ - -VideoShield embeds watermarks into video generation models for content regulation and tracking. - -**Key Features:** - -- Video-specific watermarking -- Temporal consistency -- Frame-level detection - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_diffusion_config - ) - - # Generate watermarked video frames - frames = watermark.generate_watermarked_media(prompt) - - # Detect in video - result = watermark.detect_watermark_in_media(frames) - -VideoMark -~~~~~~~~~ - -**Reference:** `VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models `_ - -VideoMark provides distortion-free watermarking specifically designed for video diffusion models. - -**Key Features:** - -- Distortion-free embedding -- Robust to video compression -- Temporal coherence preservation - -**Usage:** - -.. code-block:: python - - watermark = AutoWatermark.load( - 'VideoMark', - algorithm_config='config/VideoMark.json', - diffusion_config=video_diffusion_config - ) - -Algorithm Comparison --------------------- - -Choosing the Right Algorithm -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**For High Invisibility:** - -- Gaussian-Shading (GS) - Provably lossless -- PRC - Designed for undetectability -- Tree-Ring (TR) - Invisible in spatial domain - -**For High Robustness:** - -- ROBIN - Adversarial optimization -- GaussMarker (GM) - Dual-domain approach -- WIND - Two-stage robustness - -**For Video Content:** - -- VideoShield - Video regulation -- VideoMark - Distortion-free video watermarking - -**For Multi-User Scenarios:** - -- Ring-ID (RI) - Multi-key support -- SEAL - Semantic-aware adaptation - -Algorithm Comparison -~~~~~~~~~~~~~~~~~~~~ - -The following figure shows the performance comparison of different watermarking algorithms: - -.. image:: ../../img/img_algorithm_comparison.png - :width: 800px - :alt: Algorithm Performance Comparison - :align: center - -Next Steps ----------- - -- :doc:`../tutorial` - Hands-on tutorials for each algorithm -- :doc:`watermarking` - Detailed watermarking workflow -- :doc:`visualization` - Visualize how algorithms work -- :doc:`evaluation` - Evaluate algorithm performance - diff --git a/docs/_build/html/_sources/user_guide/evaluation.rst.txt b/docs/_build/html/_sources/user_guide/evaluation.rst.txt deleted file mode 100644 index 917e9eb..0000000 --- a/docs/_build/html/_sources/user_guide/evaluation.rst.txt +++ /dev/null @@ -1,600 +0,0 @@ -Evaluation -========== - -MarkDiffusion provides comprehensive evaluation tools to assess watermark performance across three key dimensions: detectability, robustness, and output quality. - -Overview --------- - -Evaluation Dimensions -~~~~~~~~~~~~~~~~~~~~~ - -1. **Detectability** - How reliably can watermarks be detected? -2. **Robustness** - How well do watermarks survive attacks? -3. **Quality** - How much do watermarks affect output quality? - -Evaluation Components -~~~~~~~~~~~~~~~~~~~~~ - -- **Pipelines** - Automated evaluation workflows -- **Tools** - Individual evaluation metrics and attack methods -- **Analyzers** - Quality assessment modules -- **Calculators** - Detection performance metrics - -Detectability Evaluation ------------------------- - -Basic Detection Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import StableDiffusionPromptsDataset - from evaluation.pipelines.detection import ( - WatermarkedMediaDetectionPipeline, - UnWatermarkedMediaDetectionPipeline, - DetectionPipelineReturnType - ) - from evaluation.tools.success_rate_calculator import ( - DynamicThresholdSuccessRateCalculator - ) - - # Create dataset - dataset = StableDiffusionPromptsDataset(max_samples=200) - - # Setup pipelines - watermarked_pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[], # No attacks - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - unwatermarked_pipeline = UnWatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - # Configure detection - detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, - } - - # Evaluate - watermarked_scores = watermarked_pipeline.evaluate( - watermark, detection_kwargs=detection_kwargs - ) - unwatermarked_scores = unwatermarked_pipeline.evaluate( - watermark, detection_kwargs=detection_kwargs - ) - - # Calculate metrics - calculator = DynamicThresholdSuccessRateCalculator( - labels=['watermarked'] * len(watermarked_scores) + - ['unwatermarked'] * len(unwatermarked_scores), - target_fpr=0.01 # Target false positive rate - ) - - results = calculator.calculate(watermarked_scores, unwatermarked_scores) - print(f"TPR at 1% FPR: {results['tpr']:.4f}") - print(f"AUC: {results['auc']:.4f}") - -Detection Metrics -~~~~~~~~~~~~~~~~~ - -Available detection metrics: - -- **TPR** (True Positive Rate) - Watermark detection rate -- **FPR** (False Positive Rate) - False alarm rate -- **TNR** (True Negative Rate) - Correct rejection rate -- **FNR** (False Negative Rate) - Miss rate -- **Accuracy** - Overall detection accuracy -- **Precision** - Positive predictive value -- **Recall** - Same as TPR -- **F1-Score** - Harmonic mean of precision and recall -- **AUC** (Area Under ROC Curve) - Overall performance - -Fixed Threshold Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.tools.success_rate_calculator import ( - FundamentalSuccessRateCalculator - ) - - # Use fixed threshold - calculator = FundamentalSuccessRateCalculator( - threshold=0.5 # Fixed detection threshold - ) - - results = calculator.calculate(watermarked_scores, unwatermarked_scores) - print(f"Accuracy: {results['accuracy']:.4f}") - print(f"F1-Score: {results['f1_score']:.4f}") - -Robustness Evaluation ---------------------- - -Image Attacks -~~~~~~~~~~~~~ - -Test watermark robustness against various image attacks: - -.. code-block:: python - - from evaluation.tools.image_editor import ( - JPEGCompression, - GaussianBlurring, - GaussianNoise, - Rotation, - CrSc, # Crop and Scale - Brightness, - Mask, - Overlay, - AdaptiveNoiseInjection - ) - - # Define attacks - attacks = { - 'JPEG-90': JPEGCompression(quality=90), - 'JPEG-75': JPEGCompression(quality=75), - 'JPEG-50': JPEGCompression(quality=50), - 'Blur-3': GaussianBlurring(kernel_size=3), - 'Blur-5': GaussianBlurring(kernel_size=5), - 'Noise-0.01': GaussianNoise(std=0.01), - 'Noise-0.05': GaussianNoise(std=0.05), - 'Rotate-15': Rotation(angle=15), - 'Rotate-45': Rotation(angle=45), - 'CropScale-0.75': CrSc(crop_ratio=0.75), - 'Brightness-1.2': Brightness(factor=1.2), - 'Mask': Mask(num_masks=5, mask_size=50), - 'Overlay': Overlay(num_strokes=10), - 'AdaptiveNoise': AdaptiveNoiseInjection(noise_type='gaussian') - } - - # Evaluate against each attack - robustness_results = {} - for attack_name, attack_editor in attacks.items(): - print(f"\nEvaluating: {attack_name}") - - pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[attack_editor], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - scores = pipeline.evaluate(watermark, detection_kwargs=detection_kwargs) - avg_score = sum(scores) / len(scores) if scores else 0 - robustness_results[attack_name] = avg_score - - # Print results - print("\n=== Robustness Results ===") - for attack, score in sorted(robustness_results.items(), - key=lambda x: x[1], reverse=True): - print(f"{attack:20s}: {score:.4f}") - -Video Attacks -~~~~~~~~~~~~~ - -Test video watermark robustness: - -.. code-block:: python - - from evaluation.tools.video_editor import ( - MPEG4Compression, - FrameAverage, - FrameSwap, - VideoCodecAttack, - FrameRateAdapter, - FrameInterpolationAttack - ) - - # Define video attacks - video_attacks = { - 'MPEG4': MPEG4Compression(quality=20), - 'FrameAvg': FrameAverage(window_size=3), - 'FrameSwap': FrameSwap(swap_probability=0.1), - 'H264': VideoCodecAttack(codec='h264', bitrate='2M'), - 'H265': VideoCodecAttack(codec='h265', bitrate='2M'), - 'FPS-15': FrameRateAdapter(target_fps=15), - 'Interpolate': FrameInterpolationAttack(factor=2) - } - - # Evaluate video robustness - from evaluation.dataset import VBenchDataset - - video_dataset = VBenchDataset(max_samples=50) - video_robustness_results = {} - - for attack_name, attack_editor in video_attacks.items(): - pipeline = WatermarkedMediaDetectionPipeline( - dataset=video_dataset, - media_editor_list=[attack_editor], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - scores = pipeline.evaluate(video_watermark, detection_kwargs=detection_kwargs) - video_robustness_results[attack_name] = sum(scores) / len(scores) - -Combined Attacks -~~~~~~~~~~~~~~~~ - -Test against multiple simultaneous attacks: - -.. code-block:: python - - # Combine multiple attacks - combined_attacks = [ - JPEGCompression(quality=75), - GaussianBlurring(kernel_size=3), - Rotation(angle=10) - ] - - pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=combined_attacks, # All attacks applied - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - scores = pipeline.evaluate(watermark, detection_kwargs=detection_kwargs) - print(f"Combined attack score: {sum(scores)/len(scores):.4f}") - -Quality Evaluation ------------------- - -Image Quality Metrics -~~~~~~~~~~~~~~~~~~~~~ - -Direct Quality Analysis -^^^^^^^^^^^^^^^^^^^^^^^ - -For single image quality metrics: - -.. code-block:: python - - from evaluation.pipelines.image_quality_analysis import ( - DirectImageQualityAnalysisPipeline, - QualityPipelineReturnType - ) - from evaluation.tools.image_quality_analyzer import ( - NIQECalculator, BRISQUEAnalyzer - ) - - pipeline = DirectImageQualityAnalysisPipeline( - dataset=dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[NIQECalculator(), BRISQUEAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(watermark) - print(f"NIQE (watermarked): {results['watermarked']['NIQE']:.4f}") - print(f"NIQE (unwatermarked): {results['unwatermarked']['NIQE']:.4f}") - print(f"BRISQUE (watermarked): {results['watermarked']['BRISQUE']:.4f}") - -Referenced Quality Analysis -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For metrics requiring reference images or text: - -.. code-block:: python - - from evaluation.pipelines.image_quality_analysis import ( - ReferencedImageQualityAnalysisPipeline - ) - from evaluation.tools.image_quality_analyzer import CLIPScoreCalculator - from evaluation.dataset import MSCOCODataset - - mscoco_dataset = MSCOCODataset(max_samples=100) - - pipeline = ReferencedImageQualityAnalysisPipeline( - dataset=mscoco_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[CLIPScoreCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(watermark) - print(f"CLIP Score: {results['CLIPScore']:.4f}") - -Compared Quality Analysis -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Compare watermarked vs unwatermarked images: - -.. code-block:: python - - from evaluation.pipelines.image_quality_analysis import ( - ComparedImageQualityAnalysisPipeline - ) - from evaluation.tools.image_quality_analyzer import ( - PSNRAnalyzer, SSIMAnalyzer, LPIPSAnalyzer, - VIFAnalyzer, FSIMAnalyzer - ) - - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[ - PSNRAnalyzer(), - SSIMAnalyzer(), - LPIPSAnalyzer(), - VIFAnalyzer(), - FSIMAnalyzer() - ], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(watermark) - print(f"PSNR: {results['PSNR']:.2f} dB") - print(f"SSIM: {results['SSIM']:.4f}") - print(f"LPIPS: {results['LPIPS']:.4f}") - print(f"VIF: {results['VIF']:.4f}") - print(f"FSIM: {results['FSIM']:.4f}") - -Group Quality Analysis -^^^^^^^^^^^^^^^^^^^^^^ - -Metrics requiring sets of images: - -.. code-block:: python - - from evaluation.pipelines.image_quality_analysis import ( - GroupImageQualityAnalysisPipeline - ) - from evaluation.tools.image_quality_analyzer import ( - FIDCalculator, InceptionScoreCalculator - ) - - pipeline = GroupImageQualityAnalysisPipeline( - dataset=mscoco_dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[FIDCalculator(), InceptionScoreCalculator()], - unwatermarked_image_source='generated', - reference_image_source='natural', - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(watermark) - print(f"FID: {results['FID']:.2f}") - print(f"IS: {results['InceptionScore']:.2f}") - -Repeat Quality Analysis -^^^^^^^^^^^^^^^^^^^^^^^ - -For diversity evaluation: - -.. code-block:: python - - from evaluation.pipelines.image_quality_analysis import ( - RepeatImageQualityAnalysisPipeline - ) - - pipeline = RepeatImageQualityAnalysisPipeline( - dataset=StableDiffusionPromptsDataset(max_samples=10), - prompt_per_image=20, # Generate 20 images per prompt - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[LPIPSAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(watermark) - print(f"Average LPIPS (diversity): {results['LPIPS']:.4f}") - -Video Quality Metrics -~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import VBenchDataset - from evaluation.pipelines.video_quality_analysis import ( - DirectVideoQualityAnalysisPipeline - ) - from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - BackgroundConsistencyAnalyzer, - MotionSmoothnessAnalyzer, - DynamicDegreeAnalyzer, - ImagingQualityAnalyzer - ) - - # Evaluate different video quality dimensions - dimensions = { - 'subject_consistency': SubjectConsistencyAnalyzer(device='cuda'), - 'background_consistency': BackgroundConsistencyAnalyzer(device='cuda'), - 'motion_smoothness': MotionSmoothnessAnalyzer(device='cuda'), - 'dynamic_degree': DynamicDegreeAnalyzer(device='cuda'), - 'imaging_quality': ImagingQualityAnalyzer(device='cuda') - } - - video_quality_results = {} - for dim_name, analyzer in dimensions.items(): - video_dataset = VBenchDataset(max_samples=50, dimension=dim_name) - - pipeline = DirectVideoQualityAnalysisPipeline( - dataset=video_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[analyzer], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - results = pipeline.evaluate(video_watermark) - video_quality_results[dim_name] = results - - # Print results - print("\n=== Video Quality Results ===") - for dim, score in video_quality_results.items(): - print(f"{dim}: {score}") - -Comprehensive Evaluation ------------------------- - -Full Evaluation Suite -~~~~~~~~~~~~~~~~~~~~~ - -Evaluate all aspects together: - -.. code-block:: python - - def comprehensive_evaluation(watermark_algo, dataset, attacks): - """Run comprehensive evaluation on a watermark algorithm.""" - results = { - 'detectability': {}, - 'robustness': {}, - 'quality': {} - } - - # 1. Detectability - print("=== Detectability Evaluation ===") - watermarked_pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, media_editor_list=[], show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - unwatermarked_pipeline = UnWatermarkedMediaDetectionPipeline( - dataset=dataset, media_editor_list=[], show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - wm_scores = watermarked_pipeline.evaluate(watermark_algo) - unwm_scores = unwatermarked_pipeline.evaluate(watermark_algo) - - calculator = DynamicThresholdSuccessRateCalculator(target_fpr=0.01) - det_results = calculator.calculate(wm_scores, unwm_scores) - results['detectability'] = det_results - - # 2. Robustness - print("\n=== Robustness Evaluation ===") - for attack_name, attack in attacks.items(): - pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, media_editor_list=[attack], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - scores = pipeline.evaluate(watermark_algo) - results['robustness'][attack_name] = sum(scores) / len(scores) - - # 3. Quality - print("\n=== Quality Evaluation ===") - quality_pipeline = ComparedImageQualityAnalysisPipeline( - dataset=dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer(), SSIMAnalyzer(), LPIPSAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - quality_results = quality_pipeline.evaluate(watermark_algo) - results['quality'] = quality_results - - return results - - # Run evaluation - attacks = { - 'JPEG-75': JPEGCompression(quality=75), - 'Blur': GaussianBlurring(kernel_size=3), - 'Noise': GaussianNoise(std=0.05), - 'Rotation': Rotation(angle=15) - } - - eval_results = comprehensive_evaluation(watermark, dataset, attacks) - - # Print summary - print("\n" + "="*50) - print("COMPREHENSIVE EVALUATION SUMMARY") - print("="*50) - print(f"\nDetectability:") - print(f" TPR @ 1% FPR: {eval_results['detectability']['tpr']:.4f}") - print(f" AUC: {eval_results['detectability']['auc']:.4f}") - print(f"\nRobustness:") - for attack, score in eval_results['robustness'].items(): - print(f" {attack}: {score:.4f}") - print(f"\nQuality:") - print(f" PSNR: {eval_results['quality']['PSNR']:.2f} dB") - print(f" SSIM: {eval_results['quality']['SSIM']:.4f}") - print(f" LPIPS: {eval_results['quality']['LPIPS']:.4f}") - -Compare Multiple Algorithms -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - algorithms = ['TR', 'GS', 'ROBIN', 'SEAL'] - comparison_results = {} - - for algo_name in algorithms: - print(f"\n{'='*50}") - print(f"Evaluating {algo_name}") - print('='*50) - - # Load algorithm - algo = AutoWatermark.load( - algo_name, - algorithm_config=f'config/{algo_name}.json', - diffusion_config=diffusion_config - ) - - # Evaluate - results = comprehensive_evaluation(algo, dataset, attacks) - comparison_results[algo_name] = results - - # Print comparison table - import pandas as pd - - # Create comparison dataframe - comparison_data = [] - for algo, results in comparison_results.items(): - row = { - 'Algorithm': algo, - 'TPR@1%FPR': results['detectability']['tpr'], - 'AUC': results['detectability']['auc'], - 'PSNR': results['quality']['PSNR'], - 'SSIM': results['quality']['SSIM'], - } - for attack in attacks: - row[f'Rob_{attack}'] = results['robustness'][attack] - comparison_data.append(row) - - df = pd.DataFrame(comparison_data) - print("\n" + "="*100) - print("ALGORITHM COMPARISON") - print("="*100) - print(df.to_string(index=False)) - -Best Practices --------------- - -Sample Size Selection -~~~~~~~~~~~~~~~~~~~~~ - -- **Quick test**: 50-100 samples -- **Standard evaluation**: 200-500 samples -- **Publication**: 1000+ samples - -Next Steps ----------- - -- :doc:`algorithms` - Algorithm-specific evaluation tips -- :doc:`../api/evaluation` - Evaluation API reference - diff --git a/docs/_build/html/_sources/user_guide/visualization.rst.txt b/docs/_build/html/_sources/user_guide/visualization.rst.txt deleted file mode 100644 index 31eeb75..0000000 --- a/docs/_build/html/_sources/user_guide/visualization.rst.txt +++ /dev/null @@ -1,272 +0,0 @@ -Visualization -============= - -MarkDiffusion provides powerful visualization tools to understand how watermarking algorithms work. - -Overview --------- - -Visualization helps you: - -- Understand watermarking mechanisms -- Debug watermarking issues -- Present results in papers/reports -- Educate users about watermarking - -The visualization module creates insightful plots showing: - -- Watermark patterns in frequency domain -- Latent representations -- Watermark bits and reconstruction -- Spatial and frequency domain comparisons - -Basic Usage ------------ - -Simple Visualization -~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from watermark.auto_watermark import AutoWatermark - from visualize.auto_visualization import AutoVisualizer - - # Generate watermarked image - watermark = AutoWatermark.load('GS', 'config/GS.json', diffusion_config) - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Get visualization data - viz_data = watermark.get_data_for_visualize(watermarked_image) - - # Create visualizer - visualizer = AutoVisualizer.load('GS', data_for_visualization=viz_data) - - # Generate visualization - fig = visualizer.visualize( - rows=2, - cols=2, - methods=['draw_watermark_bits', 'draw_reconstructed_watermark_bits', - 'draw_inverted_latents', 'draw_inverted_latents_fft'] - ) - - # Save figure - fig.savefig('visualization.png', dpi=300, bbox_inches='tight') - -Visualization Methods by Algorithm ------------------------------------ - -Tree-Ring (TR) -~~~~~~~~~~~~~~ - -Tree-Ring visualizations show frequency domain patterns: - -.. code-block:: python - - visualizer = AutoVisualizer.load('TR', data_for_visualization=viz_data) - - fig = visualizer.visualize( - rows=2, - cols=3, - methods=[ - 'draw_init_latents', # Initial latent representation - 'draw_init_latents_fft', # FFT of initial latents - 'draw_watermark_pattern', # Ring pattern - 'draw_watermarked_latents', # Watermarked latents - 'draw_watermarked_latents_fft',# FFT showing rings - 'draw_generated_image' # Final image - ] - ) - -**Available Methods:** - -- ``draw_init_latents`` - Initial latent vectors -- ``draw_init_latents_fft`` - Frequency spectrum of initial latents -- ``draw_watermark_pattern`` - The ring pattern overlay -- ``draw_watermarked_latents`` - Latents after watermark injection -- ``draw_watermarked_latents_fft`` - Frequency domain with rings visible -- ``draw_generated_image`` - Final generated image - -Gaussian-Shading (GS) -~~~~~~~~~~~~~~~~~~~~~ - -Gaussian-Shading visualizations show bit-level watermark information: - -.. code-block:: python - - visualizer = AutoVisualizer.load('GS', data_for_visualization=viz_data) - - fig = visualizer.visualize( - rows=2, - cols=2, - methods=[ - 'draw_watermark_bits', # Original watermark bits - 'draw_reconstructed_watermark_bits', # Extracted bits - 'draw_inverted_latents', # Inverted latent codes - 'draw_inverted_latents_fft' # Frequency analysis - ] - ) - -**Available Methods:** - -- ``draw_watermark_bits`` - Original watermark message bits -- ``draw_reconstructed_watermark_bits`` - Extracted watermark bits -- ``draw_bit_accuracy_heatmap`` - Bit-wise accuracy visualization -- ``draw_inverted_latents`` - Inverted latent representation -- ``draw_inverted_latents_fft`` - FFT of inverted latents -- ``draw_generated_image`` - Final watermarked image - -ROBIN -~~~~~ - -ROBIN visualizations show adversarially optimized patterns: - -.. code-block:: python - - visualizer = AutoVisualizer.load('ROBIN', data_for_visualization=viz_data) - - fig = visualizer.visualize( - rows=2, - cols=2, - methods=[ - 'draw_watermark_message', - 'draw_extracted_message', - 'draw_perturbation_pattern', - 'draw_frequency_analysis' - ] - ) - -**Available Methods:** - -- ``draw_watermark_message`` - Original message -- ``draw_extracted_message`` - Detected message -- ``draw_perturbation_pattern`` - Adversarial perturbation -- ``draw_frequency_analysis`` - Frequency domain analysis -- ``draw_robustness_map`` - Spatial robustness heatmap - -GaussMarker (GM) -~~~~~~~~~~~~~~~~ - -GaussMarker shows dual-domain watermarking: - -.. code-block:: python - - visualizer = AutoVisualizer.load('GM', data_for_visualization=viz_data) - - fig = visualizer.visualize( - rows=2, - cols=3, - methods=[ - 'draw_spatial_watermark', - 'draw_frequency_watermark', - 'draw_combined_watermark', - 'draw_gnr_output', - 'draw_detection_map', - 'draw_generated_image' - ] - ) - -**Available Methods:** - -- ``draw_spatial_watermark`` - Spatial domain component -- ``draw_frequency_watermark`` - Frequency domain component -- ``draw_combined_watermark`` - Combined dual-domain -- ``draw_gnr_output`` - GNR network output -- ``draw_detection_map`` - Detection confidence map - -VideoShield -~~~~~~~~~~~ - -Video visualizations show temporal patterns: - -.. code-block:: python - - visualizer = AutoVisualizer.load('VideoShield', data_for_visualization=viz_data) - - fig = visualizer.visualize( - rows=3, - cols=4, - methods=[ - 'draw_frame_sequence', # All frames - 'draw_temporal_watermark', # Watermark over time - 'draw_optical_flow', # Motion patterns - 'draw_consistency_map' # Temporal consistency - ] - ) - -**Available Methods:** - -- ``draw_frame_sequence`` - Video frame grid -- ``draw_temporal_watermark`` - Watermark evolution over frames -- ``draw_optical_flow`` - Motion flow visualization -- ``draw_consistency_map`` - Temporal consistency -- ``draw_frame_differences`` - Inter-frame differences - -Advanced Visualization ----------------------- - -Custom Layout -~~~~~~~~~~~~~ - -Create custom visualization layouts: - -.. code-block:: python - - import matplotlib.pyplot as plt - - # Create custom figure - fig, axes = plt.subplots(2, 3, figsize=(15, 10)) - - # Get visualizer - visualizer = AutoVisualizer.load('TR', data_for_visualization=viz_data) - - # Draw on specific axes - visualizer.draw_init_latents(ax=axes[0, 0]) - visualizer.draw_init_latents_fft(ax=axes[0, 1]) - visualizer.draw_watermark_pattern(ax=axes[0, 2]) - visualizer.draw_watermarked_latents(ax=axes[1, 0]) - visualizer.draw_watermarked_latents_fft(ax=axes[1, 1]) - visualizer.draw_generated_image(ax=axes[1, 2]) - - # Adjust layout - plt.tight_layout() - fig.savefig('custom_visualization.png', dpi=300) - -Comparing Methods -~~~~~~~~~~~~~~~~~ - -Visualize multiple algorithms side-by-side: - -.. code-block:: python - - import matplotlib.pyplot as plt - - algorithms = ['TR', 'GS', 'ROBIN'] - fig, axes = plt.subplots(len(algorithms), 3, figsize=(15, len(algorithms)*4)) - - for i, algo in enumerate(algorithms): - # Load watermark - wm = AutoWatermark.load(algo, f'config/{algo}.json', diffusion_config) - img = wm.generate_watermarked_media(prompt) - viz_data = wm.get_data_for_visualize(img) - - # Visualize - visualizer = AutoVisualizer.load(algo, data_for_visualization=viz_data) - visualizer.draw_generated_image(ax=axes[i, 0]) - visualizer.draw_inverted_latents_fft(ax=axes[i, 1]) - - # Detection heatmap - detection = wm.detect_watermark_in_media(img) - axes[i, 2].text(0.5, 0.5, f"{algo}\nScore: {detection.get('score', 'N/A')}", - ha='center', va='center', fontsize=14) - axes[i, 2].axis('off') - - plt.tight_layout() - fig.savefig('algorithm_comparison.png', dpi=300) - -Next Steps ----------- - -- :doc:`evaluation` - Evaluate watermark performance -- :doc:`algorithms` - Learn about algorithm internals -- :doc:`../api/visualization` - Visualization API reference - diff --git a/docs/_build/html/_sources/user_guide/watermarking.rst.txt b/docs/_build/html/_sources/user_guide/watermarking.rst.txt deleted file mode 100644 index 8ecff0a..0000000 --- a/docs/_build/html/_sources/user_guide/watermarking.rst.txt +++ /dev/null @@ -1,302 +0,0 @@ -Watermarking Workflow -===================== - -This guide explains the complete workflow for watermarking images and videos with MarkDiffusion. - -Basic Workflow --------------- - -The watermarking process consists of three main stages: - -1. **Configuration** - Set up the diffusion model and watermarking algorithm -2. **Generation** - Generate watermarked media -3. **Detection** - Detect and verify watermarks - -Configuration -------------- - -Diffusion Model Setup -~~~~~~~~~~~~~~~~~~~~~ - -First, configure your diffusion model: - -.. code-block:: python - - import torch - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - from utils.diffusion_config import DiffusionConfig - - # Device selection - device = 'cuda' if torch.cuda.is_available() else 'cpu' - - # Load model - model_id = "stabilityai/stable-diffusion-2-1" - scheduler = DPMSolverMultistepScheduler.from_pretrained( - model_id, - subfolder="scheduler" - ) - pipe = StableDiffusionPipeline.from_pretrained( - model_id, - scheduler=scheduler - ).to(device) - - # Create configuration - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - -DiffusionConfig Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. list-table:: - :header-rows: 1 - :widths: 20 15 65 - - * - Parameter - - Type - - Description - * - scheduler - - Scheduler - - Diffusion scheduler (e.g., DDPM, DDIM, DPM) - * - pipe - - Pipeline - - Diffusion pipeline object - * - device - - str - - Device to run on ('cuda' or 'cpu') - * - image_size - - tuple - - Output image size (height, width) - * - num_inference_steps - - int - - Number of denoising steps - * - guidance_scale - - float - - Classifier-free guidance scale - * - gen_seed - - int - - Random seed for reproducibility - * - inversion_type - - str - - Type of latent inversion ('ddim' or 'exact') - -Algorithm Configuration -~~~~~~~~~~~~~~~~~~~~~~~ - -Each watermarking algorithm has its own configuration file: - -.. code-block:: python - - from watermark.auto_watermark import AutoWatermark - - # Load watermark with configuration - watermark = AutoWatermark.load( - 'GS', # Algorithm name - algorithm_config='config/GS.json', # Config file path - diffusion_config=diffusion_config - ) - -Configuration File Structure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Example ``GS.json`` configuration: - -.. code-block:: json - - { - "algorithm_name": "GS", - "secret_key": 42, - "message_length": 256, - "embed_dim": 4, - "watermark_strength": 1.0, - "detection_threshold": 0.5 - } - -You can customize these parameters based on your requirements. - -Generation ----------- - -Image Generation -~~~~~~~~~~~~~~~~ - -Basic image generation with watermark: - -.. code-block:: python - - # Single image generation - prompt = "A beautiful landscape with mountains" - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Save image - watermarked_image.save("output.png") - - # Display image - watermarked_image.show() - -Video Generation -~~~~~~~~~~~~~~~~ - -For video watermarking: - -.. code-block:: python - - from diffusers import DiffusionPipeline - - # Setup video pipeline - video_pipe = DiffusionPipeline.from_pretrained( - "cerspense/zeroscope_v2_576w", - torch_dtype=torch.float16 - ).to(device) - - video_config = DiffusionConfig( - scheduler=video_pipe.scheduler, - pipe=video_pipe, - device=device, - image_size=(576, 320), - num_inference_steps=40, - guidance_scale=7.5, - gen_seed=42, - num_frames=16 - ) - - # Load video watermark - video_watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_config - ) - - # Generate watermarked video - prompt = "A cat walking in a garden" - frames = video_watermark.generate_watermarked_media(prompt) - - # Save frames - import os - os.makedirs("video_output", exist_ok=True) - for i, frame in enumerate(frames): - frame.save(f"video_output/frame_{i:04d}.png") - -Detection ---------- - -Basic Detection -~~~~~~~~~~~~~~~ - -Detect watermark in a generated image: - -.. code-block:: python - - # Detect watermark - detection_result = watermark.detect_watermark_in_media(watermarked_image) - - print(f"Detection result: {detection_result}") - -Batch Detection -~~~~~~~~~~~~~~~ - -Detect watermarks in multiple images: - -.. code-block:: python - - import os - from PIL import Image - - # Load images - image_dir = "watermarked_images" - results = {} - - for filename in os.listdir(image_dir): - if filename.endswith(('.png', '.jpg', '.jpeg')): - img_path = os.path.join(image_dir, filename) - img = Image.open(img_path) - - # Detect watermark - result = watermark.detect_watermark_in_media(img) - results[filename] = result - - # Print results - for filename, result in results.items(): - print(f"{filename}: {result}") - -Detection with Custom Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some algorithms support custom detection parameters: - -.. code-block:: python - - detection_result = watermark.detect_watermark_in_media( - watermarked_image, - num_inference_steps=50, - guidance_scale=1.0, - detection_threshold=0.6 # Custom threshold - ) - -Video Detection -~~~~~~~~~~~~~~~ - -Detect watermarks in video frames: - -.. code-block:: python - - # Detect in all frames - detection_result = video_watermark.detect_watermark_in_media(frames) - - # Frame-by-frame detection - frame_results = [] - for i, frame in enumerate(frames): - result = video_watermark.detect_watermark_in_media(frame) - frame_results.append(result) - print(f"Frame {i}: {result}") - -Watermark Removal Prevention ------------------------------ - -Testing Against Attacks -~~~~~~~~~~~~~~~~~~~~~~~ - -Verify watermark persistence after attacks: - -.. code-block:: python - - from evaluation.tools.image_editor import ( - JPEGCompression, GaussianBlurring, Rotation - ) - - # Original detection - original_result = watermark.detect_watermark_in_media(watermarked_image) - print(f"Original: {original_result}") - - # After JPEG compression - jpeg_editor = JPEGCompression(quality=75) - compressed_image = jpeg_editor.edit_image(watermarked_image) - jpeg_result = watermark.detect_watermark_in_media(compressed_image) - print(f"After JPEG: {jpeg_result}") - - # After blur - blur_editor = GaussianBlurring(kernel_size=3) - blurred_image = blur_editor.edit_image(watermarked_image) - blur_result = watermark.detect_watermark_in_media(blurred_image) - print(f"After blur: {blur_result}") - - # After rotation - rotation_editor = Rotation(angle=15) - rotated_image = rotation_editor.edit_image(watermarked_image) - rotation_result = watermark.detect_watermark_in_media(rotated_image) - print(f"After rotation: {rotation_result}") - -Next Steps ----------- - -- :doc:`visualization` - Visualize watermarking mechanisms -- :doc:`evaluation` - Evaluate watermark quality and robustness -- :doc:`algorithms` - Learn about specific algorithms - diff --git a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 8141580..0000000 --- a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css deleted file mode 100644 index 7ebbd6d..0000000 --- a/docs/_build/html/_static/basic.css +++ /dev/null @@ -1,914 +0,0 @@ -/* - * Sphinx stylesheet -- basic theme. - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin-top: 10px; -} - -ul.search li { - padding: 5px 0; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 360px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a:visited { - color: #551A8B; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -nav.contents, -aside.topic, -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -nav.contents, -aside.topic, -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -nav.contents > :last-child, -aside.topic > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -nav.contents::after, -aside.topic::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -aside.footnote > span, -div.citation > span { - float: left; -} -aside.footnote > span:last-of-type, -div.citation > span:last-of-type { - padding-right: 0.5em; -} -aside.footnote > p { - margin-left: 2em; -} -div.citation > p { - margin-left: 4em; -} -aside.footnote > p:last-of-type, -div.citation > p:last-of-type { - margin-bottom: 0em; -} -aside.footnote > p:last-of-type:after, -div.citation > p:last-of-type:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -.sig dd { - margin-top: 0px; - margin-bottom: 0px; -} - -.sig dl { - margin-top: 0px; - margin-bottom: 0px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -.translated { - background-color: rgba(207, 255, 207, 0.2) -} - -.untranslated { - background-color: rgba(255, 207, 207, 0.2) -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_build/html/_static/check-solid.svg b/docs/_build/html/_static/check-solid.svg deleted file mode 100644 index 92fad4b..0000000 --- a/docs/_build/html/_static/check-solid.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/_build/html/_static/clipboard.min.js b/docs/_build/html/_static/clipboard.min.js deleted file mode 100644 index 54b3c46..0000000 --- a/docs/_build/html/_static/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.8 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 - - - - diff --git a/docs/_build/html/_static/copybutton.css b/docs/_build/html/_static/copybutton.css deleted file mode 100644 index f1916ec..0000000 --- a/docs/_build/html/_static/copybutton.css +++ /dev/null @@ -1,94 +0,0 @@ -/* Copy buttons */ -button.copybtn { - position: absolute; - display: flex; - top: .3em; - right: .3em; - width: 1.7em; - height: 1.7em; - opacity: 0; - transition: opacity 0.3s, border .3s, background-color .3s; - user-select: none; - padding: 0; - border: none; - outline: none; - border-radius: 0.4em; - /* The colors that GitHub uses */ - border: #1b1f2426 1px solid; - background-color: #f6f8fa; - color: #57606a; -} - -button.copybtn.success { - border-color: #22863a; - color: #22863a; -} - -button.copybtn svg { - stroke: currentColor; - width: 1.5em; - height: 1.5em; - padding: 0.1em; -} - -div.highlight { - position: relative; -} - -/* Show the copybutton */ -.highlight:hover button.copybtn, button.copybtn.success { - opacity: 1; -} - -.highlight button.copybtn:hover { - background-color: rgb(235, 235, 235); -} - -.highlight button.copybtn:active { - background-color: rgb(187, 187, 187); -} - -/** - * A minimal CSS-only tooltip copied from: - * https://codepen.io/mildrenben/pen/rVBrpK - * - * To use, write HTML like the following: - * - *

Short

- */ - .o-tooltip--left { - position: relative; - } - - .o-tooltip--left:after { - opacity: 0; - visibility: hidden; - position: absolute; - content: attr(data-tooltip); - padding: .2em; - font-size: .8em; - left: -.2em; - background: grey; - color: white; - white-space: nowrap; - z-index: 2; - border-radius: 2px; - transform: translateX(-102%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); -} - -.o-tooltip--left:hover:after { - display: block; - opacity: 1; - visibility: visible; - transform: translateX(-100%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); - transition-delay: .5s; -} - -/* By default the copy button shouldn't show up when printing a page */ -@media print { - button.copybtn { - display: none; - } -} diff --git a/docs/_build/html/_static/copybutton.js b/docs/_build/html/_static/copybutton.js deleted file mode 100644 index ff4aa32..0000000 --- a/docs/_build/html/_static/copybutton.js +++ /dev/null @@ -1,248 +0,0 @@ -// Localization support -const messages = { - 'en': { - 'copy': 'Copy', - 'copy_to_clipboard': 'Copy to clipboard', - 'copy_success': 'Copied!', - 'copy_failure': 'Failed to copy', - }, - 'es' : { - 'copy': 'Copiar', - 'copy_to_clipboard': 'Copiar al portapapeles', - 'copy_success': '¡Copiado!', - 'copy_failure': 'Error al copiar', - }, - 'de' : { - 'copy': 'Kopieren', - 'copy_to_clipboard': 'In die Zwischenablage kopieren', - 'copy_success': 'Kopiert!', - 'copy_failure': 'Fehler beim Kopieren', - }, - 'fr' : { - 'copy': 'Copier', - 'copy_to_clipboard': 'Copier dans le presse-papier', - 'copy_success': 'Copié !', - 'copy_failure': 'Échec de la copie', - }, - 'ru': { - 'copy': 'Скопировать', - 'copy_to_clipboard': 'Скопировать в буфер', - 'copy_success': 'Скопировано!', - 'copy_failure': 'Не удалось скопировать', - }, - 'zh-CN': { - 'copy': '复制', - 'copy_to_clipboard': '复制到剪贴板', - 'copy_success': '复制成功!', - 'copy_failure': '复制失败', - }, - 'it' : { - 'copy': 'Copiare', - 'copy_to_clipboard': 'Copiato negli appunti', - 'copy_success': 'Copiato!', - 'copy_failure': 'Errore durante la copia', - } -} - -let locale = 'en' -if( document.documentElement.lang !== undefined - && messages[document.documentElement.lang] !== undefined ) { - locale = document.documentElement.lang -} - -let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; -if (doc_url_root == '#') { - doc_url_root = ''; -} - -/** - * SVG files for our copy buttons - */ -let iconCheck = ` - ${messages[locale]['copy_success']} - - -` - -// If the user specified their own SVG use that, otherwise use the default -let iconCopy = ``; -if (!iconCopy) { - iconCopy = ` - ${messages[locale]['copy_to_clipboard']} - - - -` -} - -/** - * Set up copy/paste for code blocks - */ - -const runWhenDOMLoaded = cb => { - if (document.readyState != 'loading') { - cb() - } else if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', cb) - } else { - document.attachEvent('onreadystatechange', function() { - if (document.readyState == 'complete') cb() - }) - } -} - -const codeCellId = index => `codecell${index}` - -// Clears selected text since ClipboardJS will select the text when copying -const clearSelection = () => { - if (window.getSelection) { - window.getSelection().removeAllRanges() - } else if (document.selection) { - document.selection.empty() - } -} - -// Changes tooltip text for a moment, then changes it back -// We want the timeout of our `success` class to be a bit shorter than the -// tooltip and icon change, so that we can hide the icon before changing back. -var timeoutIcon = 2000; -var timeoutSuccessClass = 1500; - -const temporarilyChangeTooltip = (el, oldText, newText) => { - el.setAttribute('data-tooltip', newText) - el.classList.add('success') - // Remove success a little bit sooner than we change the tooltip - // So that we can use CSS to hide the copybutton first - setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) - setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) -} - -// Changes the copy button icon for two seconds, then changes it back -const temporarilyChangeIcon = (el) => { - el.innerHTML = iconCheck; - setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) -} - -const addCopyButtonToCodeCells = () => { - // If ClipboardJS hasn't loaded, wait a bit and try again. This - // happens because we load ClipboardJS asynchronously. - if (window.ClipboardJS === undefined) { - setTimeout(addCopyButtonToCodeCells, 250) - return - } - - // Add copybuttons to all of our code cells - const COPYBUTTON_SELECTOR = 'div.highlight pre'; - const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) - codeCells.forEach((codeCell, index) => { - const id = codeCellId(index) - codeCell.setAttribute('id', id) - - const clipboardButton = id => - `` - codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) - }) - -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} - - -var copyTargetText = (trigger) => { - var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); - - // get filtered text - let exclude = '.linenos'; - - let text = filterText(target, exclude); - return formatCopyText(text, '>>> |\\.\\.\\. |\\$ ', true, true, true, true, '', '') -} - - // Initialize with a callback so we can modify the text before copy - const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) - - // Update UI with error/success messages - clipboard.on('success', event => { - clearSelection() - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) - temporarilyChangeIcon(event.trigger) - }) - - clipboard.on('error', event => { - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) - }) -} - -runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/docs/_build/html/_static/copybutton_funcs.js b/docs/_build/html/_static/copybutton_funcs.js deleted file mode 100644 index dbe1aaa..0000000 --- a/docs/_build/html/_static/copybutton_funcs.js +++ /dev/null @@ -1,73 +0,0 @@ -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -export function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} diff --git a/docs/_build/html/_static/css/badge_only.css b/docs/_build/html/_static/css/badge_only.css deleted file mode 100644 index 88ba55b..0000000 --- a/docs/_build/html/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb6000..0000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e23..0000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63..0000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5..0000000 Binary files a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca..0000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845..0000000 --- a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2..0000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a..0000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc6..0000000 Binary files a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d80..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff b/docs/_build/html/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff2 b/docs/_build/html/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb19504..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff b/docs/_build/html/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307f..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff2 b/docs/_build/html/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf9843..0000000 Binary files a/docs/_build/html/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/css/theme.css b/docs/_build/html/_static/css/theme.css deleted file mode 100644 index 0f14f10..0000000 --- a/docs/_build/html/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js deleted file mode 100644 index 0398ebb..0000000 --- a/docs/_build/html/_static/doctools.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Base JavaScript utilities for all Sphinx HTML documentation. - */ -"use strict"; - -const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ - "TEXTAREA", - "INPUT", - "SELECT", - "BUTTON", -]); - -const _ready = (callback) => { - if (document.readyState !== "loading") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } -}; - -/** - * Small JavaScript module for the documentation. - */ -const Documentation = { - init: () => { - Documentation.initDomainIndexTable(); - Documentation.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS: {}, - PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), - LOCALE: "unknown", - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext: (string) => { - const translated = Documentation.TRANSLATIONS[string]; - switch (typeof translated) { - case "undefined": - return string; // no translation - case "string": - return translated; // translation exists - default: - return translated[0]; // (singular, plural) translation tuple exists - } - }, - - ngettext: (singular, plural, n) => { - const translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated !== "undefined") - return translated[Documentation.PLURAL_EXPR(n)]; - return n === 1 ? singular : plural; - }, - - addTranslations: (catalog) => { - Object.assign(Documentation.TRANSLATIONS, catalog.messages); - Documentation.PLURAL_EXPR = new Function( - "n", - `return (${catalog.plural_expr})` - ); - Documentation.LOCALE = catalog.locale; - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar: () => { - document.querySelectorAll("input[name=q]")[0]?.focus(); - }, - - /** - * Initialise the domain index toggle buttons - */ - initDomainIndexTable: () => { - const toggler = (el) => { - const idNumber = el.id.substr(7); - const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); - if (el.src.substr(-9) === "minus.png") { - el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; - toggledRows.forEach((el) => (el.style.display = "none")); - } else { - el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; - toggledRows.forEach((el) => (el.style.display = "")); - } - }; - - const togglerElements = document.querySelectorAll("img.toggler"); - togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) - ); - togglerElements.forEach((el) => (el.style.display = "")); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); - }, - - initOnKeyListeners: () => { - // only install a listener if it is really needed - if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS - ) - return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.altKey || event.ctrlKey || event.metaKey) return; - - if (!event.shiftKey) { - switch (event.key) { - case "ArrowLeft": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const prevLink = document.querySelector('link[rel="prev"]'); - if (prevLink && prevLink.href) { - window.location.href = prevLink.href; - event.preventDefault(); - } - break; - case "ArrowRight": - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; - - const nextLink = document.querySelector('link[rel="next"]'); - if (nextLink && nextLink.href) { - window.location.href = nextLink.href; - event.preventDefault(); - } - break; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case "/": - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; - Documentation.focusSearchBar(); - event.preventDefault(); - } - }); - }, -}; - -// quick alias for translations -const _ = Documentation.gettext; - -_ready(Documentation.init); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js deleted file mode 100644 index 89435bb..0000000 --- a/docs/_build/html/_static/documentation_options.js +++ /dev/null @@ -1,13 +0,0 @@ -const DOCUMENTATION_OPTIONS = { - VERSION: '1.0.0', - LANGUAGE: 'en', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png deleted file mode 100644 index a858a41..0000000 Binary files a/docs/_build/html/_static/file.png and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.eot b/docs/_build/html/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.ttf b/docs/_build/html/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff b/docs/_build/html/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb19504..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d41549..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d80..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.eot b/docs/_build/html/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f82642..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.ttf b/docs/_build/html/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff b/docs/_build/html/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.eot b/docs/_build/html/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.ttf b/docs/_build/html/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff b/docs/_build/html/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307f..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 b/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf9843..0000000 Binary files a/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8ef..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb6000..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e23..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a79..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5..0000000 Binary files a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/docs/_build/html/_static/jquery.js b/docs/_build/html/_static/jquery.js deleted file mode 100644 index c4c6022..0000000 --- a/docs/_build/html/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); - - const languagesHTML = ` -
-
Languages
- ${languages - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} -
- `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/docs/_build/html/_static/language_data.js b/docs/_build/html/_static/language_data.js deleted file mode 100644 index c7fe6c6..0000000 --- a/docs/_build/html/_static/language_data.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. - */ - -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; - - -/* Non-minified version is copied as a separate JS file, if available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png deleted file mode 100644 index d96755f..0000000 Binary files a/docs/_build/html/_static/minus.png and /dev/null differ diff --git a/docs/_build/html/_static/nbsphinx-broken-thumbnail.svg b/docs/_build/html/_static/nbsphinx-broken-thumbnail.svg deleted file mode 100644 index 4919ca8..0000000 --- a/docs/_build/html/_static/nbsphinx-broken-thumbnail.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/docs/_build/html/_static/nbsphinx-code-cells.css b/docs/_build/html/_static/nbsphinx-code-cells.css deleted file mode 100644 index a3fb27c..0000000 --- a/docs/_build/html/_static/nbsphinx-code-cells.css +++ /dev/null @@ -1,259 +0,0 @@ -/* remove conflicting styling from Sphinx themes */ -div.nbinput.container div.prompt *, -div.nboutput.container div.prompt *, -div.nbinput.container div.input_area pre, -div.nboutput.container div.output_area pre, -div.nbinput.container div.input_area .highlight, -div.nboutput.container div.output_area .highlight { - border: none; - padding: 0; - margin: 0; - box-shadow: none; -} - -div.nbinput.container > div[class*=highlight], -div.nboutput.container > div[class*=highlight] { - margin: 0; -} - -div.nbinput.container div.prompt *, -div.nboutput.container div.prompt * { - background: none; -} - -div.nboutput.container div.output_area .highlight, -div.nboutput.container div.output_area pre { - background: unset; -} - -div.nboutput.container div.output_area div.highlight { - color: unset; /* override Pygments text color */ -} - -/* avoid gaps between output lines */ -div.nboutput.container div[class*=highlight] pre { - line-height: normal; -} - -/* input/output containers */ -div.nbinput.container, -div.nboutput.container { - display: -webkit-flex; - display: flex; - align-items: flex-start; - margin: 0; - width: 100%; -} -@media (max-width: 540px) { - div.nbinput.container, - div.nboutput.container { - flex-direction: column; - } -} - -/* input container */ -div.nbinput.container { - padding-top: 5px; -} - -/* last container */ -div.nblast.container { - padding-bottom: 5px; -} - -/* input prompt */ -div.nbinput.container div.prompt pre, -/* for sphinx_immaterial theme: */ -div.nbinput.container div.prompt pre > code { - color: #307FC1; -} - -/* output prompt */ -div.nboutput.container div.prompt pre, -/* for sphinx_immaterial theme: */ -div.nboutput.container div.prompt pre > code { - color: #BF5B3D; -} - -/* all prompts */ -div.nbinput.container div.prompt, -div.nboutput.container div.prompt { - width: 4.5ex; - padding-top: 5px; - position: relative; - user-select: none; -} - -div.nbinput.container div.prompt > div, -div.nboutput.container div.prompt > div { - position: absolute; - right: 0; - margin-right: 0.3ex; -} - -@media (max-width: 540px) { - div.nbinput.container div.prompt, - div.nboutput.container div.prompt { - width: unset; - text-align: left; - padding: 0.4em; - } - div.nboutput.container div.prompt.empty { - padding: 0; - } - - div.nbinput.container div.prompt > div, - div.nboutput.container div.prompt > div { - position: unset; - } -} - -/* disable scrollbars and line breaks on prompts */ -div.nbinput.container div.prompt pre, -div.nboutput.container div.prompt pre { - overflow: hidden; - white-space: pre; -} - -/* input/output area */ -div.nbinput.container div.input_area, -div.nboutput.container div.output_area { - -webkit-flex: 1; - flex: 1; - overflow: auto; -} -@media (max-width: 540px) { - div.nbinput.container div.input_area, - div.nboutput.container div.output_area { - width: 100%; - } -} - -/* input area */ -div.nbinput.container div.input_area { - border: 1px solid #e0e0e0; - border-radius: 2px; - /*background: #f5f5f5;*/ -} - -/* override MathJax center alignment in output cells */ -div.nboutput.container div[class*=MathJax] { - text-align: left !important; -} - -/* override sphinx.ext.imgmath center alignment in output cells */ -div.nboutput.container div.math p { - text-align: left; -} - -/* standard error */ -div.nboutput.container div.output_area.stderr { - background: #fdd; -} - -/* ANSI colors */ -.ansi-black-fg { color: #3E424D; } -.ansi-black-bg { background-color: #3E424D; } -.ansi-black-intense-fg { color: #282C36; } -.ansi-black-intense-bg { background-color: #282C36; } -.ansi-red-fg { color: #E75C58; } -.ansi-red-bg { background-color: #E75C58; } -.ansi-red-intense-fg { color: #B22B31; } -.ansi-red-intense-bg { background-color: #B22B31; } -.ansi-green-fg { color: #00A250; } -.ansi-green-bg { background-color: #00A250; } -.ansi-green-intense-fg { color: #007427; } -.ansi-green-intense-bg { background-color: #007427; } -.ansi-yellow-fg { color: #DDB62B; } -.ansi-yellow-bg { background-color: #DDB62B; } -.ansi-yellow-intense-fg { color: #B27D12; } -.ansi-yellow-intense-bg { background-color: #B27D12; } -.ansi-blue-fg { color: #208FFB; } -.ansi-blue-bg { background-color: #208FFB; } -.ansi-blue-intense-fg { color: #0065CA; } -.ansi-blue-intense-bg { background-color: #0065CA; } -.ansi-magenta-fg { color: #D160C4; } -.ansi-magenta-bg { background-color: #D160C4; } -.ansi-magenta-intense-fg { color: #A03196; } -.ansi-magenta-intense-bg { background-color: #A03196; } -.ansi-cyan-fg { color: #60C6C8; } -.ansi-cyan-bg { background-color: #60C6C8; } -.ansi-cyan-intense-fg { color: #258F8F; } -.ansi-cyan-intense-bg { background-color: #258F8F; } -.ansi-white-fg { color: #C5C1B4; } -.ansi-white-bg { background-color: #C5C1B4; } -.ansi-white-intense-fg { color: #A1A6B2; } -.ansi-white-intense-bg { background-color: #A1A6B2; } - -.ansi-default-inverse-fg { color: #FFFFFF; } -.ansi-default-inverse-bg { background-color: #000000; } - -.ansi-bold { font-weight: bold; } -.ansi-underline { text-decoration: underline; } - - -div.nbinput.container div.input_area div[class*=highlight] > pre, -div.nboutput.container div.output_area div[class*=highlight] > pre, -div.nboutput.container div.output_area div[class*=highlight].math, -div.nboutput.container div.output_area.rendered_html, -div.nboutput.container div.output_area > div.output_javascript, -div.nboutput.container div.output_area:not(.rendered_html) > img{ - padding: 5px; - margin: 0; -} - -/* fix copybtn overflow problem in chromium (needed for 'sphinx_copybutton') */ -div.nbinput.container div.input_area > div[class^='highlight'], -div.nboutput.container div.output_area > div[class^='highlight']{ - overflow-y: hidden; -} - -/* hide copy button on prompts for 'sphinx_copybutton' extension ... */ -.prompt .copybtn, -/* ... and 'sphinx_immaterial' theme */ -.prompt .md-clipboard.md-icon { - display: none; -} - -/* Some additional styling taken form the Jupyter notebook CSS */ -.jp-RenderedHTMLCommon table, -div.rendered_html table { - border: none; - border-collapse: collapse; - border-spacing: 0; - color: black; - font-size: 12px; - table-layout: fixed; -} -.jp-RenderedHTMLCommon thead, -div.rendered_html thead { - border-bottom: 1px solid black; - vertical-align: bottom; -} -.jp-RenderedHTMLCommon tr, -.jp-RenderedHTMLCommon th, -.jp-RenderedHTMLCommon td, -div.rendered_html tr, -div.rendered_html th, -div.rendered_html td { - text-align: right; - vertical-align: middle; - padding: 0.5em 0.5em; - line-height: normal; - white-space: normal; - max-width: none; - border: none; -} -.jp-RenderedHTMLCommon th, -div.rendered_html th { - font-weight: bold; -} -.jp-RenderedHTMLCommon tbody tr:nth-child(odd), -div.rendered_html tbody tr:nth-child(odd) { - background: #f5f5f5; -} -.jp-RenderedHTMLCommon tbody tr:hover, -div.rendered_html tbody tr:hover { - background: rgba(66, 165, 245, 0.2); -} - diff --git a/docs/_build/html/_static/nbsphinx-gallery.css b/docs/_build/html/_static/nbsphinx-gallery.css deleted file mode 100644 index 365c27a..0000000 --- a/docs/_build/html/_static/nbsphinx-gallery.css +++ /dev/null @@ -1,31 +0,0 @@ -.nbsphinx-gallery { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - gap: 5px; - margin-top: 1em; - margin-bottom: 1em; -} - -.nbsphinx-gallery > a { - padding: 5px; - border: 1px dotted currentColor; - border-radius: 2px; - text-align: center; -} - -.nbsphinx-gallery > a:hover { - border-style: solid; -} - -.nbsphinx-gallery img { - max-width: 100%; - max-height: 100%; -} - -.nbsphinx-gallery > a > div:first-child { - display: flex; - align-items: start; - justify-content: center; - height: 120px; - margin-bottom: 5px; -} diff --git a/docs/_build/html/_static/nbsphinx-no-thumbnail.svg b/docs/_build/html/_static/nbsphinx-no-thumbnail.svg deleted file mode 100644 index 9dca758..0000000 --- a/docs/_build/html/_static/nbsphinx-no-thumbnail.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png deleted file mode 100644 index 7107cec..0000000 Binary files a/docs/_build/html/_static/plus.png and /dev/null differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css deleted file mode 100644 index 6f8b210..0000000 --- a/docs/_build/html/_static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #F00 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #04D } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ -.highlight .no { color: #800 } /* Name.Constant */ -.highlight .nd { color: #A2F } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00F } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #BBB } /* Text.Whitespace */ -.highlight .mb { color: #666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666 } /* Literal.Number.Float */ -.highlight .mh { color: #666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #00F } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js deleted file mode 100644 index 2c774d1..0000000 --- a/docs/_build/html/_static/searchtools.js +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Sphinx JavaScript utilities for the full-text search. - */ -"use strict"; - -/** - * Simple result scoring code. - */ -if (typeof Scorer === "undefined") { - var Scorer = { - // Implement the following function to further tweak the score for each result - // The function takes a result array [docname, title, anchor, descr, score, filename] - // and returns the new score. - /* - score: result => { - const [docname, title, anchor, descr, score, filename, kind] = result - return score - }, - */ - - // query matches the full name of an object - objNameMatch: 11, - // or matches in the last dotted part of the object name - objPartialMatch: 6, - // Additive scores depending on the priority of the object - objPrio: { - 0: 15, // used to be importantResults - 1: 5, // used to be objectResults - 2: -5, // used to be unimportantResults - }, - // Used when the priority is not in the mapping. - objPrioDefault: 0, - - // query found in title - title: 15, - partialTitle: 7, - // query found in terms - term: 5, - partialTerm: 2, - }; -} - -// Global search result kind enum, used by themes to style search results. -class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } -} - -const _removeChildren = (element) => { - while (element && element.lastChild) element.removeChild(element.lastChild); -}; - -/** - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - */ -const _escapeRegExp = (string) => - string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string - -const _displayItem = (item, searchTerms, highlightTerms) => { - const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; - const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; - const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; - const contentRoot = document.documentElement.dataset.content_root; - - const [docName, title, anchor, descr, score, _filename, kind] = item; - - let listItem = document.createElement("li"); - // Add a class representing the item's type: - // can be used by a theme's CSS selector for styling - // See SearchResultKind for the class names. - listItem.classList.add(`kind-${kind}`); - let requestUrl; - let linkUrl; - if (docBuilder === "dirhtml") { - // dirhtml builder - let dirname = docName + "/"; - if (dirname.match(/\/index\/$/)) - dirname = dirname.substring(0, dirname.length - 6); - else if (dirname === "index/") dirname = ""; - requestUrl = contentRoot + dirname; - linkUrl = requestUrl; - } else { - // normal html builders - requestUrl = contentRoot + docName + docFileSuffix; - linkUrl = docName + docLinkSuffix; - } - let linkEl = listItem.appendChild(document.createElement("a")); - linkEl.href = linkUrl + anchor; - linkEl.dataset.score = score; - linkEl.innerHTML = title; - if (descr) { - listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; - // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) - fetch(requestUrl) - .then((responseData) => responseData.text()) - .then((data) => { - if (data) - listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) - ); - // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - }); - Search.output.appendChild(listItem); -}; -const _finishSearch = (resultCount) => { - Search.stopPulse(); - Search.title.innerText = _("Search Results"); - if (!resultCount) - Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." - ); - else - Search.status.innerText = Documentation.ngettext( - "Search finished, found one page matching the search query.", - "Search finished, found ${resultCount} pages matching the search query.", - resultCount, - ).replace('${resultCount}', resultCount); -}; -const _displayNextItem = ( - results, - resultCount, - searchTerms, - highlightTerms, -) => { - // results left, load the summary and display it - // this is intended to be dynamic (don't sub resultsCount) - if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); - setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 - ); - } - // search finished, update title and status message - else _finishSearch(resultCount); -}; -// Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. -// Order the results by score (in opposite order of appearance, since the -// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. -const _orderResultsByScoreThenName = (a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; -}; - -/** - * Default splitQuery function. Can be overridden in ``sphinx.search`` with a - * custom function per language. - * - * The regular expression works by splitting the string on consecutive characters - * that are not Unicode letters, numbers, underscores, or emoji characters. - * This is the same as ``\W+`` in Python, preserving the surrogate pair area. - */ -if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query - .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings -} - -/** - * Search Module - */ -const Search = { - _index: null, - _queued_query: null, - _pulse_status: -1, - - htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); - } - if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); - if (anchorContent) return anchorContent.textContent; - - console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` - ); - } - - // if anchor not specified or not found, fall back to main content - const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent) return docContent.textContent; - - console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." - ); - return ""; - }, - - init: () => { - const query = new URLSearchParams(window.location.search).get("q"); - document - .querySelectorAll('input[name="q"]') - .forEach((el) => (el.value = query)); - if (query) Search.performSearch(query); - }, - - loadIndex: (url) => - (document.body.appendChild(document.createElement("script")).src = url), - - setIndex: (index) => { - Search._index = index; - if (Search._queued_query !== null) { - const query = Search._queued_query; - Search._queued_query = null; - Search.query(query); - } - }, - - hasIndex: () => Search._index !== null, - - deferQuery: (query) => (Search._queued_query = query), - - stopPulse: () => (Search._pulse_status = -1), - - startPulse: () => { - if (Search._pulse_status >= 0) return; - - const pulse = () => { - Search._pulse_status = (Search._pulse_status + 1) % 4; - Search.dots.innerText = ".".repeat(Search._pulse_status); - if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something (or wait until index is loaded) - */ - performSearch: (query) => { - // create the required interface elements - const searchText = document.createElement("h2"); - searchText.textContent = _("Searching"); - const searchSummary = document.createElement("p"); - searchSummary.classList.add("search-summary"); - searchSummary.innerText = ""; - const searchList = document.createElement("ul"); - searchList.setAttribute("role", "list"); - searchList.classList.add("search"); - - const out = document.getElementById("search-results"); - Search.title = out.appendChild(searchText); - Search.dots = Search.title.appendChild(document.createElement("span")); - Search.status = out.appendChild(searchSummary); - Search.output = out.appendChild(searchList); - - const searchProgress = document.getElementById("search-progress"); - // Some themes don't use the search progress node - if (searchProgress) { - searchProgress.innerText = _("Preparing search..."); - } - Search.startPulse(); - - // index already loaded, the browser was quick! - if (Search.hasIndex()) Search.query(query); - else Search.deferQuery(query); - }, - - _parseQuery: (query) => { - // stem the search terms and add them to the correct list - const stemmer = new Stemmer(); - const searchTerms = new Set(); - const excludedTerms = new Set(); - const highlightTerms = new Set(); - const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); - splitQuery(query.trim()).forEach((queryTerm) => { - const queryTermLower = queryTerm.toLowerCase(); - - // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; - - // stem the word - let word = stemmer.stemWord(queryTermLower); - // select the correct list - if (word[0] === "-") excludedTerms.add(word.substr(1)); - else { - searchTerms.add(word); - highlightTerms.add(queryTermLower); - } - }); - - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) - } - - // console.debug("SEARCH: searching for:"); - // console.info("required: ", [...searchTerms]); - // console.info("excluded: ", [...excludedTerms]); - - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; - }, - - /** - * execute search (requires search index to be loaded) - */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - - // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename, kind]. - const normalResults = []; - const nonMainIndexResults = []; - - _removeChildren(document.getElementById("search-progress")); - - const queryLower = query.toLowerCase().trim(); - for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { - for (const [file, id] of foundTitles) { - const score = Math.round(Scorer.title * queryLower.length / title.length); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles - normalResults.push([ - docNames[file], - titles[file] !== title ? `${titles[file]} > ${title}` : title, - id !== null ? "#" + id : "", - null, - score + boost, - filenames[file], - SearchResultKind.title, - ]); - } - } - } - - // search for explicit entries in index directives - for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); - const result = [ - docNames[file], - titles[file], - id ? "#" + id : "", - null, - score, - filenames[file], - SearchResultKind.index, - ]; - if (isMain) { - normalResults.push(result); - } else { - nonMainIndexResults.push(result); - } - } - } - } - - // lookup as object - objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) - ); - - // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); - - // let the scorer override scores with a custom scoring function - if (Scorer.score) { - normalResults.forEach((item) => (item[4] = Scorer.score(item))); - nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); - } - - // Sort each group of results by score and then alphabetically by name. - normalResults.sort(_orderResultsByScoreThenName); - nonMainIndexResults.sort(_orderResultsByScoreThenName); - - // Combine the result groups in (reverse) order. - // Non-main index entries are typically arbitrary cross-references, - // so display them after other results. - let results = [...nonMainIndexResults, ...normalResults]; - - // remove duplicate search results - // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept - let seen = new Set(); - results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); - if (!seen.has(resultStr)) { - acc.push(result); - seen.add(resultStr); - } - return acc; - }, []); - - return results.reverse(); - }, - - query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); - - // for debugging - //Search.lastresults = results.slice(); // a copy - // console.info("search results:", Search.lastresults); - - // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); - }, - - /** - * search for object names - */ - performObjectSearch: (object, objectTerms) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const objects = Search._index.objects; - const objNames = Search._index.objnames; - const titles = Search._index.titles; - - const results = []; - - const objectSearchCallback = (prefix, match) => { - const name = match[4] - const fullname = (prefix ? prefix + "." : "") + name; - const fullnameLower = fullname.toLowerCase(); - if (fullnameLower.indexOf(object) < 0) return; - - let score = 0; - const parts = fullnameLower.split("."); - - // check for different match types: exact matches of full name or - // "last name" (i.e. last dotted part) - if (fullnameLower === object || parts.slice(-1)[0] === object) - score += Scorer.objNameMatch; - else if (parts.slice(-1)[0].indexOf(object) > -1) - score += Scorer.objPartialMatch; // matches in last name - - const objName = objNames[match[1]][2]; - const title = titles[match[0]]; - - // If more than one term searched for, we require other words to be - // found in the name/title/description - const otherTerms = new Set(objectTerms); - otherTerms.delete(object); - if (otherTerms.size > 0) { - const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); - if ( - [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) - ) - return; - } - - let anchor = match[3]; - if (anchor === "") anchor = fullname; - else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; - - const descr = objName + _(", in ") + title; - - // add custom score for some objects according to scorer - if (Scorer.objPrio.hasOwnProperty(match[2])) - score += Scorer.objPrio[match[2]]; - else score += Scorer.objPrioDefault; - - results.push([ - docNames[match[0]], - fullname, - "#" + anchor, - descr, - score, - filenames[match[0]], - SearchResultKind.object, - ]); - }; - Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) - ); - return results; - }, - - /** - * search for full-text terms in the index - */ - performTermsSearch: (searchTerms, excludedTerms) => { - // prepare search - const terms = Search._index.terms; - const titleTerms = Search._index.titleterms; - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - - const scoreMap = new Map(); - const fileMap = new Map(); - - // perform the search on the required terms - searchTerms.forEach((word) => { - const files = []; - const arr = [ - { files: terms[word], score: Scorer.term }, - { files: titleTerms[word], score: Scorer.title }, - ]; - // add support for partial matches - if (word.length > 2) { - const escapedWord = _escapeRegExp(word); - if (!terms.hasOwnProperty(word)) { - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - } - if (!titleTerms.hasOwnProperty(word)) { - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord)) - arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); - }); - } - } - - // no match but word was a required one - if (arr.every((record) => record.files === undefined)) return; - - // found search word in contents - arr.forEach((record) => { - if (record.files === undefined) return; - - let recordFiles = record.files; - if (recordFiles.length === undefined) recordFiles = [recordFiles]; - files.push(...recordFiles); - - // set score for the word in each file - recordFiles.forEach((file) => { - if (!scoreMap.has(file)) scoreMap.set(file, {}); - scoreMap.get(file)[word] = record.score; - }); - }); - - // create the mapping - files.forEach((file) => { - if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); - }); - }); - - // now check if the files don't contain excluded terms - const results = []; - for (const [file, wordList] of fileMap) { - // check if all requirements are matched - - // as search terms with length < 3 are discarded - const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 - ).length; - if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount - ) - continue; - - // ensure that none of the excluded terms is in the search result - if ( - [...excludedTerms].some( - (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) - ) - ) - break; - - // select one (max) score for the file. - const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); - // add result to the result list - results.push([ - docNames[file], - titles[file], - "", - null, - score, - filenames[file], - SearchResultKind.text, - ]); - } - return results; - }, - - /** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words. - */ - makeSearchSummary: (htmlText, keywords, anchor) => { - const text = Search.htmlToText(htmlText, anchor); - if (text === "") return null; - - const textLower = text.toLowerCase(); - const actualStartPosition = [...keywords] - .map((k) => textLower.indexOf(k.toLowerCase())) - .filter((i) => i > -1) - .slice(-1)[0]; - const startWithContext = Math.max(actualStartPosition - 120, 0); - - const top = startWithContext === 0 ? "" : "..."; - const tail = startWithContext + 240 < text.length ? "..." : ""; - - let summary = document.createElement("p"); - summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; - - return summary; - }, -}; - -_ready(Search.init); diff --git a/docs/_build/html/_static/sphinx_highlight.js b/docs/_build/html/_static/sphinx_highlight.js deleted file mode 100644 index 8a96c69..0000000 --- a/docs/_build/html/_static/sphinx_highlight.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Highlighting utilities for Sphinx HTML documentation. */ -"use strict"; - -const SPHINX_HIGHLIGHT_ENABLED = true - -/** - * highlight a given string on a node by wrapping it in - * span elements with the given class name. - */ -const _highlight = (node, addItems, text, className) => { - if (node.nodeType === Node.TEXT_NODE) { - const val = node.nodeValue; - const parent = node.parentNode; - const pos = val.toLowerCase().indexOf(text); - if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") - ) { - let span; - - const closestNode = parent.closest("body, svg, foreignObject"); - const isInSVG = closestNode && closestNode.matches("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.classList.add(className); - } - - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); - node.nodeValue = val.substr(0, pos); - /* There may be more occurrences of search term in this node. So call this - * function recursively on the remaining fragment. - */ - _highlight(rest, addItems, text, className); - - if (isInSVG) { - const rect = document.createElementNS( - "http://www.w3.org/2000/svg", - "rect" - ); - const bbox = parent.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute("class", className); - addItems.push({ parent: parent, target: rect }); - } - } - } else if (node.matches && !node.matches("button, select, textarea")) { - node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); - } -}; -const _highlightText = (thisNode, text, className) => { - let addItems = []; - _highlight(thisNode, addItems, text, className); - addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) - ); -}; - -/** - * Small JavaScript module for the documentation. - */ -const SphinxHighlight = { - - /** - * highlight the search words provided in localstorage in the text - */ - highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight - - // get and clear terms from localstorage - const url = new URL(window.location); - const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); - - // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); - if (terms.length === 0) return; // nothing to do - - // There should never be more than one element matching "div.body" - const divBody = document.querySelectorAll("div.body"); - const body = divBody.length ? divBody[0] : document.querySelector("body"); - window.setTimeout(() => { - terms.forEach((term) => _highlightText(body, term, "highlighted")); - }, 10); - - const searchBox = document.getElementById("searchbox"); - if (searchBox === null) return; - searchBox.appendChild( - document - .createRange() - .createContextualFragment( - '" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/docs/_build/html/api/detection.html b/docs/_build/html/api/detection.html deleted file mode 100644 index f678e35..0000000 --- a/docs/_build/html/api/detection.html +++ /dev/null @@ -1,1300 +0,0 @@ - - - - - - - - - Detection API — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Detection API

-

This page documents the watermark detection API.

-
-

Base Detector

-
-
-class detection.base.BaseDetector(threshold, device)[source]
-

Bases: ABC

-
-
-
-
-__init__(threshold, device)[source]
-
-
-
- -
-
-abstract eval_watermark(reversed_latents, reference_latents=None, detector_type='l1_distance')[source]
-
-
Return type:
-

float

-
-
-
- -
- -
-
-

Detection Methods

-
-

Tree-Ring Detection

-
-
-class detection.tr.tr_detection.TRDetector(watermarking_mask, gt_patch, threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(watermarking_mask, gt_patch, threshold, device)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='l1_distance')[source]
-
-
Return type:
-

float

-
-
-
- -
- -
-
-

Gaussian-Shading Detection

-
-
-class detection.gs.gs_detection.GSDetector(watermarking_mask, chacha, wm_key, channel_copy, hw_copy, vote_threshold, threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(watermarking_mask, chacha, wm_key, channel_copy, hw_copy, vote_threshold, threshold, device)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, detector_type='bit_acc')[source]
-

Evaluate watermark in reversed latents.

-
-
Return type:
-

float

-
-
-
- -
- -
-
-

ROBIN Detection

-
-
-class detection.robin.robin_detection.ROBINDetector(watermarking_mask, gt_patch, threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(watermarking_mask, gt_patch, threshold, device)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='l1_distance')[source]
-
-
Return type:
-

float

-
-
-
- -
- -
-
-

WIND Detection

-
-
-class detection.wind.wind_detection.WINDetector(noise_groups, group_patterns, threshold, device, group_radius=10)[source]
-

Bases: BaseDetector

-

WIND Watermark Detector (Two-Stage Robust Detection)

-
-
-
-
-__init__(noise_groups, group_patterns, threshold, device, group_radius=10)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='cosine_similarity')[source]
-

Two-stage watermark detection

-
-
Parameters:
-
    -
  • reversed_latents (Tensor) – Latents obtained through reverse diffusion [C,H,W]

  • -
  • reference_latents (Optional[Tensor]) – Not used (for API compatibility)

  • -
  • detector_type (str) – Detection method (‘cosine_similarity’ only supported)

  • -
-
-
Returns:
-

    -
  • group_id: Identified group ID

  • -
  • similarity: Highest similarity score

  • -
  • is_watermarked: Detection result

  • -
  • best_match: Best matching noise tensor

  • -
-

-
-
Return type:
-

Dictionary containing detection results

-
-
-
- -
- -
-
-

SFW Detection

-
-
-class detection.sfw.sfw_detection.SFWDetector(watermarking_mask, gt_patch, w_channel, threshold, device, wm_type='HSTR')[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(watermarking_mask, gt_patch, w_channel, threshold, device, wm_type='HSTR')[source]
-
-
-
- -
-
-get_distance_hsqr(qr_gt_bool, target_fft, p=1)
-

qr_gt_bool : (c_wm,42,42) boolean -target_fft : (1,4,64,64) complex64

-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='l1_distance')[source]
-
-
Return type:
-

float

-
-
-
- -
- -
-
-

GaussMarker Detection

-

GaussMarker detection utilities.

-

This module adapts the official GaussMarker detection pipeline to the -MarkDiffusion detection API. It evaluates recovered diffusion latents to -decide whether a watermark is present, reporting both hard decisions and -auxiliary scores (bit/message accuracies, frequency-domain distances).

-
-
-class detection.gm.gm_detection.GMDetector(watermark_generator, watermarking_mask, gt_patch, w_measurement, device, bit_threshold=None, message_threshold=None, l1_threshold=None, gnr_checkpoint=None, gnr_classifier_type=0, gnr_model_nf=128, gnr_binary_threshold=0.5, gnr_use_for_decision=True, gnr_threshold=None, fuser_checkpoint=None, fuser_threshold=None, fuser_frequency_scale=0.01)[source]
-

Bases: BaseDetector

-

Detector for GaussMarker watermarks.

-
-
Parameters:
-
    -
  • watermark_generator (GaussianShadingChaCha) – Instance of GaussianShadingChaCha that -holds the original watermark bits and ChaCha20 key stream.

  • -
  • watermarking_mask (Tensor) – Frequency-domain mask (or label map) indicating the -region that carries the watermark.

  • -
  • gt_patch (Tensor) – Reference watermark pattern in the frequency domain.

  • -
  • w_measurement (str) – Measurement mode (e.g., "l1_complex" or -"signal_complex"), mirroring the official implementation.

  • -
  • device (Union[str, device]) – Torch device used for evaluation.

  • -
  • bit_threshold (Optional[float]) – Optional override for the bit-accuracy decision -threshold. Defaults to the generator’s tau_bits value.

  • -
  • message_threshold (Optional[float]) – Optional threshold for message accuracy decisions.

  • -
  • l1_threshold (Optional[float]) – Optional threshold for frequency L1 distance decisions -(smaller is better).

  • -
-
-
-
-
-__init__(watermark_generator, watermarking_mask, gt_patch, w_measurement, device, bit_threshold=None, message_threshold=None, l1_threshold=None, gnr_checkpoint=None, gnr_classifier_type=0, gnr_model_nf=128, gnr_binary_threshold=0.5, gnr_use_for_decision=True, gnr_threshold=None, fuser_checkpoint=None, fuser_threshold=None, fuser_frequency_scale=0.01)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='bit_acc')[source]
-
-
Return type:
-

Dict[str, Union[bool, float]]

-
-
-
- -
- -
-
-

PRC Detection

-
-
-class detection.prc.prc_detection.PRCDetector(var, decoding_key, GF, threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(var, decoding_key, GF, threshold, device)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='is_watermarked')[source]
-

Evaluate watermark in reversed latents.

-
-
Return type:
-

float

-
-
-
- -
- -
-
-

SEAL Detection

-
-
-class detection.seal.seal_detection.SEALDetector(k, b, theta_mid, cap_processor, cap_model, sentence_transformer, patch_distance_threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(k, b, theta_mid, cap_processor, cap_model, sentence_transformer, patch_distance_threshold, device)[source]
-
-
-
- -
-
-eval_watermark(reversed_latents, reference_latents, detector_type='patch_accuracy')[source]
-
-
Return type:
-

float

-
-
-
- -
- -
-
-

VideoShield Detection

-
-
-class detection.videoshield.videoshield_detection.VideoShieldDetector(watermark, threshold, device, chacha_key=None, chacha_nonce=None, height=64, width=64, num_frames=0, k_f=8, k_c=1, k_h=4, k_w=4)[source]
-

Bases: BaseDetector

-

VideoShield watermark detector class.

-
-
-
-
-__init__(watermark, threshold, device, chacha_key=None, chacha_nonce=None, height=64, width=64, num_frames=0, k_f=8, k_c=1, k_h=4, k_w=4)[source]
-

Initialize the VideoShield detector.

-
-
Parameters:
-
    -
  • watermark (Tensor) – The watermarking bits

  • -
  • threshold (float) – Threshold for watermark detection

  • -
  • device (device) – The device to use for computation

  • -
  • chacha_key (Optional[bytes]) – ChaCha20 encryption key (optional)

  • -
  • chacha_nonce (Optional[bytes]) – ChaCha20 nonce (optional)

  • -
  • height (int) – Height of the video

  • -
  • width (int) – Width of the video

  • -
  • num_frames (int) – Number of frames in the video

  • -
  • k_f (int) – Frame repetition factor

  • -
  • k_c (int) – Channel repetition factor

  • -
  • k_h (int) – Height repetition factor

  • -
  • k_w (int) – Width repetition factor

  • -
-
-
-
- -
-
-eval_watermark(reversed_latents, detector_type='bit_acc')[source]
-

Evaluate the watermark in the reversed latents.

-
-
Parameters:
-
    -
  • reversed_latents (Tensor) – The reversed latents from forward diffusion

  • -
  • detector_type (str) – The type of detector to use (‘bit_acc’, ‘standard’, etc.)

  • -
-
-
Return type:
-

Dict[str, Union[bool, float]]

-
-
Returns:
-

Dict containing detection results and confidence scores

-
-
-
- -
- -
-
-

VideoMark Detection

-
-
-class detection.videomark.videomark_detection.VideoMarkDetector(message_sequence, watermark, num_frames, var, decoding_key, GF, threshold, device)[source]
-

Bases: BaseDetector

-
-
-
-
-__init__(message_sequence, watermark, num_frames, var, decoding_key, GF, threshold, device)[source]
-
-
-
- -
-
-bits_to_string(bits)[source]
-
- -
-
-recover(idx_list, message_list, distance_list, message_length)[source]
-

Recover the original message sequence from indices, messages, and distances.

-
    -
  • If idx != -1: use sorted valid entries (normal recovery)

  • -
  • If idx == -1: use the original message_list and distance_list directly

  • -
-
- -
-
-eval_watermark(reversed_latents, reference_latents=None, detector_type='bit_acc')[source]
-

Evaluate watermark in reversed latents.

-
-
Return type:
-

float

-
-
-
- -
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/api/evaluation.html b/docs/_build/html/api/evaluation.html deleted file mode 100644 index af348cb..0000000 --- a/docs/_build/html/api/evaluation.html +++ /dev/null @@ -1,3078 +0,0 @@ - - - - - - - - - Evaluation API — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Evaluation API

-

This page documents the evaluation API.

-
-

Pipelines

-
-

Detection Pipelines

-
-
-class evaluation.pipelines.detection.DetectionPipelineReturnType(value)[source]
-

Bases: Enum

-

An enumeration.

-
-
-FULL = 1
-
- -
-
-SCORES = 2
-
- -
-
-IS_WATERMARKED = 3
-
- -
- -
-
-class evaluation.pipelines.detection.WatermarkDetectionResult(generated_or_retrieved_media, edited_media, detect_result)[source]
-

Bases: object

-
-
-
-
-__init__(generated_or_retrieved_media, edited_media, detect_result)[source]
-
-
-
- -
- -
-
-class evaluation.pipelines.detection.WatermarkDetectionPipeline(dataset, media_editor_list=[], show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES)[source]
-

Bases: object

-
-
-
-
-__init__(dataset, media_editor_list=[], show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES)[source]
-
-
-
- -
-
-evaluate(watermark, detection_kwargs={}, generation_kwargs={})[source]
-
-
-
- -
- -
-
-class evaluation.pipelines.detection.WatermarkedMediaDetectionPipeline(dataset, media_editor_list, show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES, *args, **kwargs)[source]
-

Bases: WatermarkDetectionPipeline

-
-
-
-
-__init__(dataset, media_editor_list, show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.pipelines.detection.UnWatermarkedMediaDetectionPipeline(dataset, media_editor_list, media_source_mode='ground_truth', show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES, *args, **kwargs)[source]
-

Bases: WatermarkDetectionPipeline

-
-
-
-
-__init__(dataset, media_editor_list, media_source_mode='ground_truth', show_progress=True, detector_type='l1_distance', return_type=DetectionPipelineReturnType.SCORES, *args, **kwargs)[source]
-
-
-
- -
- -
-
-

Image Quality Analysis Pipelines

-
-
-class evaluation.pipelines.image_quality_analysis.QualityPipelineReturnType(value)[source]
-

Bases: Enum

-

Return type of the image quality analysis pipeline.

-
-
-FULL = 1
-
- -
-
-SCORES = 2
-
- -
-
-MEAN_SCORES = 3
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.DatasetForEvaluation(watermarked_images=<factory>, unwatermarked_images=<factory>, reference_images=<factory>, indexes=<factory>, prompts=<factory>)[source]
-

Bases: object

-

Dataset for evaluation.

-
-
-
-
-watermarked_images: List[Union[Image, List[Image]]]
-
- -
-
-unwatermarked_images: List[Union[Image, List[Image]]]
-
- -
-
-reference_images: List[Image]
-
- -
-
-indexes: List[int]
-
- -
-
-prompts: List[str]
-
- -
-
-__init__(watermarked_images=<factory>, unwatermarked_images=<factory>, reference_images=<factory>, indexes=<factory>, prompts=<factory>)
-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.QualityComparisonResult(store_path, watermarked_quality_scores, unwatermarked_quality_scores, prompts)[source]
-

Bases: object

-

Result of image quality comparison.

-
-
-
-
-__init__(store_path, watermarked_quality_scores, unwatermarked_quality_scores, prompts)[source]
-

Initialize the image quality comparison result.

-
-
Parameters:
-
    -
  • store_path (str) – The path to store the results.

  • -
  • watermarked_quality_scores (Dict[str, List[float]]) – The quality scores of the watermarked image.

  • -
  • unwatermarked_quality_scores (Dict[str, List[float]]) – The quality scores of the unwatermarked image.

  • -
  • prompts (List[str]) – The prompts used to generate the images.

  • -
-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.ImageQualityAnalysisPipeline(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: object

-

Pipeline for image quality analysis.

-
-
-
-
-__init__(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of watermarked and unwatermarked images.

-
- -
-
-evaluate(watermark, generation_kwargs={})[source]
-

Conduct evaluation utilizing the pipeline.

-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.DirectImageQualityAnalysisPipeline(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: ImageQualityAnalysisPipeline

-

Pipeline for direct image quality analysis.

-

This class analyzes the quality of images by directly comparing the characteristics -of watermarked images with unwatermarked images. It evaluates metrics such as PSNR, -SSIM, LPIPS, FID, BRISQUE without the need for any external reference image.

-

Use this pipeline to assess the impact of watermarking on image quality directly.

-
-
-
-
-__init__(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of watermarked and unwatermarked images.

-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.ReferencedImageQualityAnalysisPipeline(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: ImageQualityAnalysisPipeline

-

Pipeline for referenced image quality analysis.

-

This pipeline assesses image quality by comparing both watermarked and unwatermarked -images against a common reference image. It measures the degree of similarity or -deviation from the reference.

-

Ideal for scenarios where the impact of watermarking on image quality needs to be -assessed, particularly in relation to specific reference images or ground truth.

-
-
-
-
-__init__(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of watermarked and unwatermarked images.

-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.GroupImageQualityAnalysisPipeline(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: ImageQualityAnalysisPipeline

-

Pipeline for group-based image quality analysis.

-

This pipeline analyzes quality metrics that require comparing distributions -of multiple images (e.g., FID). It generates all images upfront and then -performs a single analysis on the entire collection.

-
-
-
-
-__init__(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of image groups.

-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.RepeatImageQualityAnalysisPipeline(dataset, prompt_per_image=20, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: ImageQualityAnalysisPipeline

-

Pipeline for repeat-based image quality analysis.

-

This pipeline analyzes diversity metrics by generating multiple images -for each prompt (e.g., LPIPS diversity). It generates multiple versions -per prompt and analyzes the diversity within each group.

-
-
-
-
-__init__(dataset, prompt_per_image=20, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze diversity of image batches.

-
-
-
- -
- -
-
-class evaluation.pipelines.image_quality_analysis.ComparedImageQualityAnalysisPipeline(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: ImageQualityAnalysisPipeline

-

Pipeline for compared image quality analysis.

-

This pipeline directly compares watermarked and unwatermarked images -to compute metrics like PSNR, SSIM, VIF, FSIM and MS-SSIM. The analyzer receives -both images and outputs a single comparison score.

-
-
-
-
-__init__(dataset, watermarked_image_editor_list=[], unwatermarked_image_editor_list=[], analyzers=None, unwatermarked_image_source='generated', reference_image_source='natural', show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_image_editor_list (List[ImageEditor]) – The list of image editors for watermarked images.

  • -
  • unwatermarked_image_editor_list (List[ImageEditor]) – The list of image editors for unwatermarked images.

  • -
  • analyzers (Optional[List[ImageQualityAnalyzer]]) – List of quality analyzers for images.

  • -
  • unwatermarked_image_source (str) – The source of unwatermarked images (‘natural’ or ‘generated’).

  • -
  • reference_image_source (str) – The source of reference images (‘natural’ or ‘generated’).

  • -
  • show_progress (bool) – Whether to show progress.

  • -
  • store_path (Optional[str]) – The path to store the results. If None, the generated images will not be stored.

  • -
  • return_type (QualityPipelineReturnType) – The return type of the pipeline.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality by comparing watermarked and unwatermarked images.

-
-
-
- -
- -
-
-

Video Quality Analysis Pipelines

-
-
-class evaluation.pipelines.video_quality_analysis.QualityPipelineReturnType(value)[source]
-

Bases: Enum

-

Return type of the image quality analysis pipeline.

-
-
-FULL = 1
-
- -
-
-SCORES = 2
-
- -
-
-MEAN_SCORES = 3
-
- -
- -
-
-class evaluation.pipelines.video_quality_analysis.DatasetForEvaluation(watermarked_videos=<factory>, unwatermarked_videos=<factory>, reference_videos=<factory>, indexes=<factory>)[source]
-

Bases: object

-

Dataset for evaluation.

-
-
-
-
-watermarked_videos: List[List[Image]]
-
- -
-
-unwatermarked_videos: List[List[Image]]
-
- -
-
-reference_videos: List[List[Image]]
-
- -
-
-indexes: List[int]
-
- -
-
-__init__(watermarked_videos=<factory>, unwatermarked_videos=<factory>, reference_videos=<factory>, indexes=<factory>)
-
-
-
- -
- -
-
-class evaluation.pipelines.video_quality_analysis.QualityComparisonResult(store_path, watermarked_quality_scores, unwatermarked_quality_scores, prompts)[source]
-

Bases: object

-

Result of quality comparison.

-
-
-
-
-__init__(store_path, watermarked_quality_scores, unwatermarked_quality_scores, prompts)[source]
-

Initialize the image quality comparison result.

-
-
Parameters:
-
    -
  • store_path (str) – The path to store the results.

  • -
  • watermarked_quality_scores (Dict[str, List[float]]) – The quality scores of the watermarked image.

  • -
  • unwatermarked_quality_scores (Dict[str, List[float]]) – The quality scores of the unwatermarked image.

  • -
  • prompts (List[str]) – The prompts used to generate the images.

  • -
-
-
-
- -
- -
-
-class evaluation.pipelines.video_quality_analysis.VideoQualityAnalysisPipeline(dataset, watermarked_video_editor_list=[], unwatermarked_video_editor_list=[], watermarked_frame_editor_list=[], unwatermarked_frame_editor_list=[], analyzers=None, show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: object

-

Pipeline for video quality analysis.

-
-
-
-
-__init__(dataset, watermarked_video_editor_list=[], unwatermarked_video_editor_list=[], watermarked_frame_editor_list=[], unwatermarked_frame_editor_list=[], analyzers=None, show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the image quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_video_editor_list (List[VideoEditor], optional) – The list of video editors for watermarked videos. Defaults to [].

  • -
  • unwatermarked_video_editor_list (List[VideoEditor], optional) – List of quality analyzers for videos. Defaults to [].

  • -
  • watermarked_frame_editor_list (List[ImageEditor], optional) – List of image editors for editing individual watermarked frames. Defaults to [].

  • -
  • unwatermarked_frame_editor_list (List[ImageEditor], optional) – List of image editors for editing individual unwatermarked frames. Defaults to [].

  • -
  • analyzers (List[VideoQualityAnalyzer], optional) – Whether to show progress. Defaults to None.

  • -
  • show_progress (bool, optional) – The path to store the results. Defaults to True.

  • -
  • store_path (str, optional) – The path to store the results. Defaults to None.

  • -
  • return_type (QualityPipelineReturnType, optional) – The return type of the pipeline. Defaults to QualityPipelineReturnType.MEAN_SCORES.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of watermarked and unwatermarked images.

-
- -
-
-evaluate(watermark, generation_kwargs={})[source]
-

Conduct evaluation utilizing the pipeline.

-
-
-
- -
- -
-
-class evaluation.pipelines.video_quality_analysis.DirectVideoQualityAnalysisPipeline(dataset, watermarked_video_editor_list=[], unwatermarked_video_editor_list=[], watermarked_frame_editor_list=[], unwatermarked_frame_editor_list=[], analyzers=None, show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Bases: VideoQualityAnalysisPipeline

-

Pipeline for direct video quality analysis.

-
-
-
-
-__init__(dataset, watermarked_video_editor_list=[], unwatermarked_video_editor_list=[], watermarked_frame_editor_list=[], unwatermarked_frame_editor_list=[], analyzers=None, show_progress=True, store_path=None, return_type=QualityPipelineReturnType.MEAN_SCORES)[source]
-

Initialize the video quality analysis pipeline.

-
-
Parameters:
-
    -
  • dataset (BaseDataset) – The dataset for evaluation.

  • -
  • watermarked_video_editor_list (List[VideoEditor], optional) – The list of video editors for watermarked videos. Defaults to [].

  • -
  • unwatermarked_video_editor_list (List[VideoEditor], optional) – List of quality analyzers for videos. Defaults to [].

  • -
  • watermarked_frame_editor_list (List[ImageEditor], optional) – List of image editors for editing individual watermarked frames. Defaults to [].

  • -
  • unwatermarked_frame_editor_list (List[ImageEditor], optional) – List of image editors for editing individual unwatermarked frames. Defaults to [].

  • -
  • analyzers (List[VideoQualityAnalyzer], optional) – Whether to show progress. Defaults to None.

  • -
  • show_progress (bool, optional) – Whether to show progress. Defaults to True.

  • -
  • store_path (str, optional) – The path to store the results. Defaults to None.

  • -
  • return_type (QualityPipelineReturnType, optional) – The return type of the pipeline. Defaults to QualityPipelineReturnType.MEAN_SCORES.

  • -
-
-
-
- -
-
-analyze_quality(prepared_data, analyzer)[source]
-

Analyze quality of watermarked and unwatermarked videos.

-
-
-
- -
- -
-
-
-

Datasets

-
-
-class evaluation.dataset.BaseDataset(max_samples=200)[source]
-

Bases: object

-

Base dataset class.

-
-
-
-
-__init__(max_samples=200)[source]
-

Initialize the dataset.

-
-
Parameters:
-

max_samples (int) – Maximum number of samples to load.

-
-
-
- -
-
-property num_samples: int
-

Number of samples in the dataset.

-
- -
-
-property num_references: int
-

Number of references in the dataset.

-
- -
-
-get_prompt(idx)[source]
-

Get the prompt at the given index.

-
-
Return type:
-

str

-
-
-
- -
-
-get_reference(idx)[source]
-

Get the reference Image at the given index.

-
-
Return type:
-

Image

-
-
-
- -
-
-__len__()[source]
-

Number of samples in the dataset.(Equivalent to num_samples)

-
-
Return type:
-

int

-
-
-
- -
-
-__getitem__(idx)[source]
-

Get the prompt (and reference Image if available) at the given index.

-
-
Return type:
-

tuple[str, Image]

-
-
-
- -
- -
-
-class evaluation.dataset.StableDiffusionPromptsDataset(max_samples=200, split='test', shuffle=False)[source]
-

Bases: BaseDataset

-

Stable Diffusion prompts dataset.

-
-
-
-
-__init__(max_samples=200, split='test', shuffle=False)[source]
-

Initialize the dataset.

-
-
Parameters:
-
    -
  • max_samples (int) – Maximum number of samples to load.

  • -
  • split (str) – Split to load.

  • -
  • shuffle (bool) – Whether to shuffle the dataset.

  • -
-
-
-
- -
-
-property name
-

Name of the dataset.

-
- -
- -
-
-class evaluation.dataset.MSCOCODataset(max_samples=200, shuffle=False)[source]
-

Bases: BaseDataset

-

MSCOCO 2017 dataset.

-
-
-
-
-__init__(max_samples=200, shuffle=False)[source]
-

Initialize the dataset.

-
-
Parameters:
-
    -
  • max_samples (int) – Maximum number of samples to load.

  • -
  • shuffle (bool) – Whether to shuffle the dataset.

  • -
-
-
-
- -
-
-property name
-

Name of the dataset.

-
- -
- -
-
-class evaluation.dataset.VBenchDataset(max_samples, dimension='subject_consistency', shuffle=False)[source]
-

Bases: BaseDataset

-

VBench dataset.

-
-
-
-
-__init__(max_samples, dimension='subject_consistency', shuffle=False)[source]
-

Initialize the dataset.

-
-
Parameters:
-
    -
  • max_samples (int) – Maximum number of samples to load.

  • -
  • dimension (str, optional) – Dimensions to load. Selected from “subject_consistency”, “background_consistency”, “imaging_quality”, “motion_smoothness”, “dynamic_degree”.

  • -
  • shuffle (bool, optional) – Whether to shuffle the dataset. Defaults to False.

  • -
-
-
-
- -
-
-property name
-

Name of the dataset.

-
- -
- -
-
-

Evaluation Tools

-
-

Success Rate Calculators

-
-
-class evaluation.tools.success_rate_calculator.DetectionResult(gold_label, detection_result)[source]
-

Bases: object

-
-
-
-
-__init__(gold_label, detection_result)[source]
-
-
-
- -
- -
-
-class evaluation.tools.success_rate_calculator.BaseSuccessRateCalculator(labels=['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'])[source]
-

Bases: object

-
-
-
-
-__init__(labels=['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'])[source]
-
-
-
- -
-
-calculate(watermarked_results, non_watermarked_results)[source]
-
-
Return type:
-

Dict[str, float]

-
-
-
- -
- -
-
-class evaluation.tools.success_rate_calculator.FundamentalSuccessRateCalculator(labels=['TPR', 'TNR', 'FPR', 'FNR', 'P', 'R', 'F1', 'ACC'])[source]
-

Bases: BaseSuccessRateCalculator

-

Calculator for fundamental success rates of watermark detection.

-

This class specifically handles the calculation of success rates for scenarios involving -watermark detection after fixed thresholding. It provides metrics based on comparisons -between expected watermarked results and actual detection outputs.

-

Use this class when you need to evaluate the effectiveness of watermark detection algorithms -under fixed thresholding conditions.

-
-
-
-
-__init__(labels=['TPR', 'TNR', 'FPR', 'FNR', 'P', 'R', 'F1', 'ACC'])[source]
-

Initialize the fundamental success rate calculator.

-
-
Parameters:
-

labels (List[str]) – The list of metric labels to include in the output.

-
-
-
- -
-
-calculate(watermarked_result, non_watermarked_result)[source]
-

calculate success rates of watermark detection based on provided results.

-
-
Return type:
-

Dict[str, float]

-
-
-
- -
- -
-
-class evaluation.tools.success_rate_calculator.DynamicThresholdSuccessRateCalculator(labels=['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'], rule='best', target_fpr=None, reverse=False)[source]
-

Bases: BaseSuccessRateCalculator

-
-
-
-
-__init__(labels=['TPR', 'TNR', 'FPR', 'FNR', 'F1', 'P', 'R', 'F1', 'ACC', 'AUC'], rule='best', target_fpr=None, reverse=False)[source]
-
-
-
- -
-
-calculate(watermarked_results, non_watermarked_results)[source]
-
-
Return type:
-

Dict[str, float]

-
-
-
- -
- -
-
-

Image Editors (Attacks)

-
-
-class evaluation.tools.image_editor.ImageEditor[source]
-

Bases: object

-
-
-__init__()[source]
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.JPEGCompression(quality=95)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(quality=95)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.Rotation(angle=30, expand=False)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(angle=30, expand=False)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.CrSc(crop_ratio=0.8)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(crop_ratio=0.8)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.GaussianBlurring(radius=2)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(radius=2)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.GaussianNoise(sigma=25.0)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(sigma=25.0)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.Brightness(factor=1.2)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(factor=1.2)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.Mask(mask_ratio=0.1, num_masks=5)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(mask_ratio=0.1, num_masks=5)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.Overlay(num_strokes=10, stroke_width=5, stroke_type='random')[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(num_strokes=10, stroke_width=5, stroke_type='random')[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-class evaluation.tools.image_editor.AdaptiveNoiseInjection(intensity=0.5, auto_select=True)[source]
-

Bases: ImageEditor

-
-
-
-
-__init__(intensity=0.5, auto_select=True)[source]
-
-
-
- -
-
-edit(image, prompt=None)[source]
-
-
Return type:
-

Image

-
-
-
- -
- -
-
-

Video Editors (Attacks)

-
-
-class evaluation.tools.video_editor.VideoEditor[source]
-

Bases: object

-

Base class for video editors.

-
-
-__init__()[source]
-
- -
-
-edit(frames, prompt=None)[source]
-
-
Return type:
-

List[Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.MPEG4Compression(fps=24.0)[source]
-

Bases: VideoEditor

-

MPEG-4 compression video editor.

-
-
-
-
-__init__(fps=24.0)[source]
-

Initialize the MPEG-4 compression video editor.

-
-
Parameters:
-

fps (float, optional) – The frames per second of the compressed video. Defaults to 24.0.

-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Compress the video using MPEG-4 compression.

-
-
Parameters:
-
    -
  • frames (List[Image.Image]) – The frames to compress.

  • -
  • prompt (str, optional) – The prompt for video editing. Defaults to None.

  • -
-
-
Returns:
-

The compressed frames.

-
-
Return type:
-

List[Image.Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.VideoCodecAttack(codec='h264', bitrate='2M', fps=24.0, ffmpeg_path=None)[source]
-

Bases: VideoEditor

-

Re-encode videos with specific codecs and bitrates to simulate platform processing.

-
-
-
-
-__init__(codec='h264', bitrate='2M', fps=24.0, ffmpeg_path=None)[source]
-

Initialize the codec attack editor.

-
-
Parameters:
-
    -
  • codec (str, optional) – Target codec (h264, h265/hevc, vp9, av1). Defaults to “h264”.

  • -
  • bitrate (str, optional) – Target bitrate passed to ffmpeg (e.g., “2M”). Defaults to “2M”.

  • -
  • fps (float, optional) – Frames per second used for intermediate encoding. Defaults to 24.0.

  • -
  • ffmpeg_path (str, optional) – Path to ffmpeg binary. If None, resolved via PATH.

  • -
-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Re-encode the video using the configured codec and bitrate.

-
-
Return type:
-

List[Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.FrameAverage(n_frames=3)[source]
-

Bases: VideoEditor

-

Frame average video editor.

-
-
-
-
-__init__(n_frames=3)[source]
-

Initialize the frame average video editor.

-
-
Parameters:
-

n_frames (int, optional) – The number of frames to average. Defaults to 3.

-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Average frames in a window of size n_frames.

-
-
Parameters:
-
    -
  • frames (List[Image.Image]) – The frames to average.

  • -
  • prompt (str, optional) – The prompt for video editing. Defaults to None.

  • -
-
-
Returns:
-

The averaged frames.

-
-
Return type:
-

List[Image.Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.FrameRateAdapter(source_fps=30.0, target_fps=24.0)[source]
-

Bases: VideoEditor

-

Resample videos to a target frame rate using linear interpolation.

-
-
-
-
-__init__(source_fps=30.0, target_fps=24.0)[source]
-

Initialize the frame rate adapter.

-
-
Parameters:
-
    -
  • source_fps (float, optional) – Original frames per second. Defaults to 30.0.

  • -
  • target_fps (float, optional) – Desired frames per second. Defaults to 24.0.

  • -
-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Resample frames to match the target frame rate while preserving duration.

-
-
Return type:
-

List[Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.FrameSwap(p=0.25)[source]
-

Bases: VideoEditor

-

Frame swap video editor.

-
-
-
-
-__init__(p=0.25)[source]
-

Initialize the frame swap video editor.

-
-
Parameters:
-

p (float, optional) – The probability of swapping neighbor frames. Defaults to 0.25.

-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Swap adjacent frames with probability p.

-
-
Parameters:
-
    -
  • frames (List[Image.Image]) – The frames to swap.

  • -
  • prompt (str, optional) – The prompt for video editing. Defaults to None.

  • -
-
-
Returns:
-

The swapped frames.

-
-
Return type:
-

List[Image.Image]

-
-
-
- -
- -
-
-class evaluation.tools.video_editor.FrameInterpolationAttack(interpolated_frames=1)[source]
-

Bases: VideoEditor

-

Insert interpolated frames to alter temporal sampling density.

-
-
-
-
-__init__(interpolated_frames=1)[source]
-

Initialize the interpolation attack editor.

-
-
Parameters:
-

interpolated_frames (int, optional) – Number of synthetic frames added between consecutive original frames. Defaults to 1.

-
-
-
- -
-
-edit(frames, prompt=None)[source]
-

Insert interpolated frames between originals using linear blending.

-
-
Return type:
-

List[Image]

-
-
-
- -
- -
-
-

Image Quality Analyzers

-
-
-class evaluation.tools.image_quality_analyzer.ImageQualityAnalyzer[source]
-

Bases: object

-

Base class for image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-abstract analyze()[source]
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.DirectImageQualityAnalyzer[source]
-

Bases: ImageQualityAnalyzer

-

Base class for direct image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-analyze(image, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.ReferencedImageQualityAnalyzer[source]
-

Bases: ImageQualityAnalyzer

-

Base class for referenced image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.GroupImageQualityAnalyzer[source]
-

Bases: ImageQualityAnalyzer

-

Base class for group image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-analyze(images, references, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.RepeatImageQualityAnalyzer[source]
-

Bases: ImageQualityAnalyzer

-

Base class for repeat image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-analyze(images, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.ComparedImageQualityAnalyzer[source]
-

Bases: ImageQualityAnalyzer

-

Base class for compare image quality analyzer.

-
-
-__init__()[source]
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.InceptionScoreCalculator(device='cuda', batch_size=32, splits=1)[source]
-

Bases: RepeatImageQualityAnalyzer

-

Inception Score (IS) calculator for evaluating image generation quality.

-

Inception Score measures both the quality and diversity of generated images -by evaluating how confidently an Inception model can classify them and how -diverse the predictions are across the image set.

-

Higher IS indicates better image quality and diversity (typical range: 1-10+).

-
-
-
-
-__init__(device='cuda', batch_size=32, splits=1)[source]
-

Initialize the Inception Score calculator.

-
-
Parameters:
-
    -
  • device (str) – Device to run the model on (“cuda” or “cpu”)

  • -
  • batch_size (int) – Batch size for processing images

  • -
  • splits (int) – Number of splits for computing IS (default: 1). The splits must be divisible by the number of images for fair comparison. -For calculating the mean and standard error of IS, the splits should be set greater than 1. -If splits is 1, the IS is calculated on the entire dataset.(Avg = IS, Std = 0)

  • -
-
-
-
- -
-
-analyze(images, *args, **kwargs)[source]
-

Calculate Inception Score for a set of generated images.

-
-
Parameters:
-

images (List[Image]) – List of generated images to evaluate

-
-
Returns:
-

Inception Score values for each split (higher is better, typical range: 1-10+)

-
-
Return type:
-

List[float]

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.CLIPScoreCalculator(device='cuda', model_name='ViT-B/32', reference_source='image')[source]
-

Bases: ReferencedImageQualityAnalyzer

-

CLIP score calculator for image quality analysis.

-

Calculates CLIP similarity between an image and a reference. -Higher scores indicate better semantic similarity.

-
-
-
-
-__init__(device='cuda', model_name='ViT-B/32', reference_source='image')[source]
-

Initialize the CLIP Score calculator.

-
-
Parameters:
-
    -
  • device (str) – Device to run the model on (“cuda” or “cpu”)

  • -
  • model_name (str) – CLIP model variant to use

  • -
  • reference_source (str) – The source of reference (‘image’ or ‘text’)

  • -
-
-
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-

Calculate CLIP similarity between image and reference.

-
-
Parameters:
-
    -
  • image (Image) – Input image to evaluate

  • -
  • reference (Union[Image, str]) – Reference image or text for comparison -- If reference_source is ‘image’: expects PIL Image -- If reference_source is ‘text’: expects string

  • -
-
-
Returns:
-

CLIP similarity score (0 to 1)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.FIDCalculator(device='cuda', batch_size=32, splits=1)[source]
-

Bases: GroupImageQualityAnalyzer

-

FID calculator for image quality analysis.

-

Calculates Fréchet Inception Distance between two sets of images. -Lower FID indicates better quality and similarity to reference distribution.

-
-
-
-
-__init__(device='cuda', batch_size=32, splits=1)[source]
-

Initialize the FID calculator.

-
-
Parameters:
-
    -
  • device (str) – Device to run the model on (“cuda” or “cpu”)

  • -
  • batch_size (int) – Batch size for processing images

  • -
  • splits (int) – Number of splits for computing FID (default: 5). The splits must be divisible by the number of images for fair comparison. -For calculating the mean and standard error of FID, the splits should be set greater than 1. -If splits is 1, the FID is calculated on the entire dataset.(Avg = FID, Std = 0)

  • -
-
-
-
- -
-
-analyze(images, references, *args, **kwargs)[source]
-

Calculate FID between two sets of images.

-
-
Parameters:
-
    -
  • images (List[Image]) – Set of images to evaluate

  • -
  • references (List[Image]) – Reference set of images

  • -
-
-
Returns:
-

FID values for each split

-
-
Return type:
-

List[float]

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.LPIPSAnalyzer(device='cuda', net='alex')[source]
-

Bases: RepeatImageQualityAnalyzer

-

LPIPS analyzer for image quality analysis.

-

Calculates perceptual diversity within a set of images. -Higher LPIPS indicates more diverse/varied images.

-
-
-
-
-__init__(device='cuda', net='alex')[source]
-

Initialize the LPIPS analyzer.

-
-
Parameters:
-
    -
  • device (str) – Device to run the model on (“cuda” or “cpu”)

  • -
  • net (str) – Network to use (‘alex’, ‘vgg’, or ‘squeeze’)

  • -
-
-
-
- -
-
-analyze(images, *args, **kwargs)[source]
-

Calculate average pairwise LPIPS distance within a set of images.

-
-
Parameters:
-

images (List[Image]) – List of images to analyze diversity

-
-
Returns:
-

Average LPIPS distance (diversity score)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.PSNRAnalyzer(max_pixel_value=255.0)[source]
-

Bases: ComparedImageQualityAnalyzer

-

PSNR analyzer for image quality analysis.

-

Calculates Peak Signal-to-Noise Ratio between two images. -Higher PSNR indicates better quality/similarity.

-
-
-
-
-__init__(max_pixel_value=255.0)[source]
-

Initialize the PSNR analyzer.

-
-
Parameters:
-

max_pixel_value (float) – Maximum pixel value (255 for 8-bit images)

-
-
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-

Calculate PSNR between two images.

-
-
Parameters:
-
    -
  • image (Image.Image) – Image to evaluate

  • -
  • reference (Image.Image) – Reference image

  • -
-
-
Returns:
-

PSNR value in dB

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.NIQECalculator(model_path='evaluation/tools/data/niqe_image_params.mat', patch_size=96, sigma=1.1666666666666667, C=1.0)[source]
-

Bases: DirectImageQualityAnalyzer

-

Natural Image Quality Evaluator (NIQE) for no-reference image quality assessment.

-

NIQE evaluates image quality based on deviations from natural scene statistics. -It uses a pre-trained model of natural image statistics to assess quality without -requiring reference images.

-

Lower NIQE scores indicate better/more natural image quality (typical range: 2-8).

-
-
-
-
-__init__(model_path='evaluation/tools/data/niqe_image_params.mat', patch_size=96, sigma=1.1666666666666667, C=1.0)[source]
-

Initialize NIQE calculator with pre-trained natural image statistics.

-
-
Parameters:
-
    -
  • model_path (str) – Path to the pre-trained NIQE model parameters (.mat file)

  • -
  • patch_size (int) – Size of patches for feature extraction (default: 96)

  • -
  • sigma (float) – Standard deviation for Gaussian window (default: 7/6)

  • -
  • C (float) – Constant for numerical stability in MSCN transform (default: 1.0)

  • -
-
-
-
- -
-
-analyze(image, *args, **kwargs)[source]
-

Calculate NIQE score for a single image.

-
-
Parameters:
-

image (Image) – Input image to evaluate

-
-
Returns:
-

NIQE score (lower is better, typical range: 2-8)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.SSIMAnalyzer(max_pixel_value=255.0)[source]
-

Bases: ComparedImageQualityAnalyzer

-

SSIM analyzer for image quality analysis.

-

Calculates Structural Similarity Index between two images. -Higher SSIM indicates better quality/similarity.

-
-
-
-
-__init__(max_pixel_value=255.0)[source]
-

Initialize the SSIM analyzer.

-
-
Parameters:
-

max_pixel_value (float) – Maximum pixel value (255 for 8-bit images)

-
-
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-

Calculate SSIM between two images.

-
-
Parameters:
-
    -
  • image (Image.Image) – Image to evaluate

  • -
  • reference (Image.Image) – Reference image

  • -
-
-
Returns:
-

SSIM value (0 to 1)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.BRISQUEAnalyzer(device='cuda')[source]
-

Bases: DirectImageQualityAnalyzer

-

BRISQUE analyzer for no-reference image quality analysis.

-

BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator) -evaluates perceptual quality of an image without requiring -a reference. Lower BRISQUE scores indicate better quality. -Typical range: 0 (best) ~ 100 (worst).

-
-
-
-
-__init__(device='cuda')[source]
-
-
-
- -
-
-analyze(image, *args, **kwargs)[source]
-

Calculate BRISQUE score for a single image.

-
-
Parameters:
-

image (Image) – PIL Image

-
-
Returns:
-

BRISQUE score (lower is better)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.VIFAnalyzer(device='cuda')[source]
-

Bases: ComparedImageQualityAnalyzer

-

VIF (Visual Information Fidelity) analyzer using piq.

-

VIF compares a distorted image with a reference image to -quantify the amount of visual information preserved. -Higher VIF indicates better quality/similarity. -Typical range: 0 ~ 1 (sometimes higher for good quality).

-
-
-
-
-__init__(device='cuda')[source]
-
-
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-

Calculate VIF score between image and reference.

-
-
Parameters:
-
    -
  • image (Image) – Distorted/test image (PIL)

  • -
  • reference (Image) – Reference image (PIL)

  • -
-
-
Returns:
-

VIF score (higher is better)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-class evaluation.tools.image_quality_analyzer.FSIMAnalyzer(device='cuda')[source]
-

Bases: ComparedImageQualityAnalyzer

-

FSIM (Feature Similarity Index) analyzer using piq.

-

FSIM compares structural similarity between two images -based on phase congruency and gradient magnitude. -Higher FSIM indicates better quality/similarity. -Typical range: 0 ~ 1.

-
-
-
-
-__init__(device='cuda')[source]
-
-
-
- -
-
-analyze(image, reference, *args, **kwargs)[source]
-

Calculate FSIM score between image and reference.

-
-
Parameters:
-
    -
  • image (Image) – Distorted/test image (PIL)

  • -
  • reference (Image) – Reference image (PIL)

  • -
-
-
Returns:
-

FSIM score (higher is better)

-
-
Return type:
-

float

-
-
-
- -
- -
-
-

Video Quality Analyzers

-
-
-evaluation.tools.video_quality_analyzer.dino_transform_Image(n_px)[source]
-

DINO transform for PIL Images.

-
- -
-
-class evaluation.tools.video_quality_analyzer.VideoQualityAnalyzer[source]
-

Bases: object

-

Video quality analyzer base class.

-
-
-__init__()[source]
-
- -
-
-analyze(frames)[source]
-

Analyze video quality.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Returns:
-

Quality score(s)

-
-
-
- -
- -
-
-class evaluation.tools.video_quality_analyzer.SubjectConsistencyAnalyzer(model_url='https://dl.fbaipublicfiles.com/dino/dino_vitbase16_pretrain/dino_vitbase16_pretrain_full_checkpoint.pth', model_path='dino_vitb16_full.pth', device='cuda')[source]
-

Bases: VideoQualityAnalyzer

-

Analyzer for evaluating subject consistency across video frames using DINO features.

-

This analyzer measures how consistently the main subject appears across frames by: -1. Extracting DINO features from each frame -2. Computing cosine similarity between consecutive frames and with the first frame -3. Averaging these similarities to get a consistency score

-
-
-
-
-__init__(model_url='https://dl.fbaipublicfiles.com/dino/dino_vitbase16_pretrain/dino_vitbase16_pretrain_full_checkpoint.pth', model_path='dino_vitb16_full.pth', device='cuda')[source]
-
-
-
- -
-
-transform(img)[source]
-

Transform PIL Image to tensor for DINO model.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-analyze(frames)[source]
-

Analyze subject consistency across video frames.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Return type:
-

float

-
-
Returns:
-

Subject consistency score (higher is better, range [0, 1])

-
-
-
- -
- -
-
-class evaluation.tools.video_quality_analyzer.MotionSmoothnessAnalyzer(model_path='model/amt/amt-s.pth', device='cuda', niters=1)[source]
-

Bases: VideoQualityAnalyzer

-

Analyzer for evaluating motion smoothness in videos using AMT-S model.

-

This analyzer measures motion smoothness by: -1. Extracting frames at even indices from the video -2. Using AMT-S model to interpolate between consecutive frames -3. Comparing interpolated frames with actual frames to compute smoothness score

-

The score represents how well the motion can be predicted/interpolated, -with smoother motion resulting in higher scores.

-
-
-
-
-__init__(model_path='model/amt/amt-s.pth', device='cuda', niters=1)[source]
-

Initialize the MotionSmoothnessAnalyzer.

-
-
Parameters:
-
    -
  • model_path (str) – Path to the AMT-S model checkpoint

  • -
  • device (str) – Device to run the model on (‘cuda’ or ‘cpu’)

  • -
  • niters (int) – Number of interpolation iterations (default: 1)

  • -
-
-
-
- -
-
-analyze(frames)[source]
-

Analyze motion smoothness in video frames.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Return type:
-

float

-
-
Returns:
-

Motion smoothness score (higher is better, range [0, 1])

-
-
-
- -
- -
-
-class evaluation.tools.video_quality_analyzer.DynamicDegreeAnalyzer(model_path='model/raft/raft-things.pth', device='cuda', sample_fps=8)[source]
-

Bases: VideoQualityAnalyzer

-

Analyzer for evaluating dynamic degree (motion intensity) in videos using RAFT optical flow.

-

This analyzer measures the amount and intensity of motion in videos by: -1. Computing optical flow between consecutive frames using RAFT -2. Calculating flow magnitude for each pixel -3. Extracting top 5% highest flow magnitudes -4. Determining if video has sufficient dynamic motion based on thresholds

-

The score represents whether the video contains dynamic motion (1.0) or is mostly static (0.0).

-
-
-
-
-__init__(model_path='model/raft/raft-things.pth', device='cuda', sample_fps=8)[source]
-

Initialize the DynamicDegreeAnalyzer.

-
-
Parameters:
-
    -
  • model_path (str) – Path to the RAFT model checkpoint

  • -
  • device (str) – Device to run the model on (‘cuda’ or ‘cpu’)

  • -
  • sample_fps (int) – Target FPS for frame sampling (default: 8)

  • -
-
-
-
- -
-
-analyze(frames)[source]
-

Analyze dynamic degree (motion intensity) in video frames.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Returns:
-

1.0 if video has dynamic motion, 0.0 if mostly static

-
-
Return type:
-

Dynamic degree score

-
-
-
- -
- -
-
-class evaluation.tools.video_quality_analyzer.BackgroundConsistencyAnalyzer(model_name='ViT-B/32', device='cuda')[source]
-

Bases: VideoQualityAnalyzer

-

Analyzer for evaluating background consistency across video frames using CLIP features.

-

This analyzer measures how consistently the background appears across frames by: -1. Extracting CLIP visual features from each frame -2. Computing cosine similarity between consecutive frames and with the first frame -3. Averaging these similarities to get a consistency score

-

Similar to SubjectConsistencyAnalyzer but focuses on overall visual consistency -including background elements, making it suitable for detecting background stability.

-
-
-
-
-__init__(model_name='ViT-B/32', device='cuda')[source]
-

Initialize the BackgroundConsistencyAnalyzer.

-
-
Parameters:
-
    -
  • model_name (str) – CLIP model name (default: “ViT-B/32”)

  • -
  • device (str) – Device to run the model on (‘cuda’ or ‘cpu’)

  • -
-
-
-
- -
-
-analyze(frames)[source]
-

Analyze background consistency across video frames.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Return type:
-

float

-
-
Returns:
-

Background consistency score (higher is better, range [0, 1])

-
-
-
- -
- -
-
-class evaluation.tools.video_quality_analyzer.ImagingQualityAnalyzer(model_path='model/musiq/musiq_spaq_ckpt-358bb6af.pth', device='cuda')[source]
-

Bases: VideoQualityAnalyzer

-

Analyzer for evaluating imaging quality of videos.

-

This analyzer measures the quality of videos by: -1. Inputting frames into MUSIQ image quality predictor -2. Determining if the video is blurry or has artifacts

-

The score represents the quality of the video (higher is better).

-
-
-
-
-__init__(model_path='model/musiq/musiq_spaq_ckpt-358bb6af.pth', device='cuda')[source]
-
-
-
- -
-
-analyze(frames)[source]
-

Analyze imaging quality of video frames.

-
-
Parameters:
-

frames (List[Image]) – List of PIL Image frames representing the video

-
-
Return type:
-

float

-
-
Returns:
-

Imaging quality score (higher is better, range [0, 1])

-
-
-
- -
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/api/utils.html b/docs/_build/html/api/utils.html deleted file mode 100644 index c61ee9b..0000000 --- a/docs/_build/html/api/utils.html +++ /dev/null @@ -1,1472 +0,0 @@ - - - - - - - - - Utilities API — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Utilities API

-

This page documents utility modules.

-
-

Diffusion Configuration

-
-
-class utils.diffusion_config.DiffusionConfig(scheduler, pipe, device, guidance_scale=7.5, num_images=1, num_inference_steps=50, num_inversion_steps=None, image_size=(512, 512), dtype=torch.float16, gen_seed=0, init_latents_seed=0, inversion_type='ddim', num_frames=-1, **kwargs)[source]
-

Bases: object

-

Configuration class for diffusion models and parameters.

-
-
-
-
-__init__(scheduler, pipe, device, guidance_scale=7.5, num_images=1, num_inference_steps=50, num_inversion_steps=None, image_size=(512, 512), dtype=torch.float16, gen_seed=0, init_latents_seed=0, inversion_type='ddim', num_frames=-1, **kwargs)[source]
-
-
-
- -
-
-property pipeline_type: str
-

Get the pipeline type.

-
- -
-
-property is_video_pipeline: bool
-

Check if this is a video pipeline.

-
- -
-
-property is_image_pipeline: bool
-

Check if this is an image pipeline.

-
- -
-
-property pipeline_requirements: Dict[str, Any]
-

Get the requirements for this pipeline type.

-
- -
- -
-
-

Pipeline Utilities

-
-
-utils.pipeline_utils.get_pipeline_type(pipeline)[source]
-

Determine the type of diffusion pipeline.

-
-
Parameters:
-

pipeline – The diffusion pipeline object

-
-
Returns:
-

One of the pipeline type constants or None if not recognized

-
-
Return type:
-

str

-
-
-
- -
-
-utils.pipeline_utils.is_video_pipeline(pipeline)[source]
-

Check if the pipeline is a video generation pipeline.

-
-
Parameters:
-

pipeline – The diffusion pipeline object

-
-
Returns:
-

True if the pipeline is a video generation pipeline, False otherwise

-
-
Return type:
-

bool

-
-
-
- -
-
-utils.pipeline_utils.is_image_pipeline(pipeline)[source]
-

Check if the pipeline is an image generation pipeline.

-
-
Parameters:
-

pipeline – The diffusion pipeline object

-
-
Returns:
-

True if the pipeline is an image generation pipeline, False otherwise

-
-
Return type:
-

bool

-
-
-
- -
-
-utils.pipeline_utils.is_t2v_pipeline(pipeline)[source]
-

Check if the pipeline is a text-to-video pipeline.

-
-
Parameters:
-

pipeline – The diffusion pipeline object

-
-
Returns:
-

True if the pipeline is a text-to-video pipeline, False otherwise

-
-
Return type:
-

bool

-
-
-
- -
-
-utils.pipeline_utils.is_i2v_pipeline(pipeline)[source]
-

Check if the pipeline is an image-to-video pipeline.

-
-
Parameters:
-

pipeline – The diffusion pipeline object

-
-
Returns:
-

True if the pipeline is an image-to-video pipeline, False otherwise

-
-
Return type:
-

bool

-
-
-
- -
-
-utils.pipeline_utils.get_pipeline_requirements(pipeline_type)[source]
-

Get the requirements for a specific pipeline type (required parameters, etc.)

-
-
Parameters:
-

pipeline_type (str) – The pipeline type string

-
-
Returns:
-

A dictionary containing the pipeline requirements

-
-
Return type:
-

Dict

-
-
-
- -
-
-

Media Utilities

-
-
-utils.media_utils.torch_to_numpy(tensor)[source]
-

Convert tensor to numpy array with proper scaling.

-
-
Return type:
-

ndarray

-
-
-
- -
-
-utils.media_utils.pil_to_torch(image, normalize=True)[source]
-

Convert PIL image to torch tensor.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-utils.media_utils.numpy_to_pil(img)[source]
-

Convert numpy array to PIL image.

-
-
Return type:
-

Image

-
-
-
- -
-
-utils.media_utils.cv2_to_pil(img)[source]
-

Convert cv2 image (numpy array) to PIL image.

-
-
Return type:
-

Image

-
-
-
- -
-
-utils.media_utils.pil_to_cv2(pil_img)[source]
-

Convert PIL image to cv2 format (numpy array).

-
-
Return type:
-

ndarray

-
-
-
- -
-
-utils.media_utils.transform_to_model_format(media, target_size=None)[source]
-

Transform image or video frames to model input format. -For image, media is a PIL image that will be resized to target_size`(if provided) and then normalized to [-1, 1] and permuted to [C, H, W] from [H, W, C]. -For video, `media is a list of frames (PIL images or numpy arrays) that will be normalized to [-1, 1] and permuted to [F, C, H, W] from [F, H, W, C].

-
-
Parameters:
-
    -
  • media (Union[Image, List[Image], ndarray, Tensor]) – PIL image or list of frames or video tensor

  • -
  • target_size (Optional[int]) – Target size for resize operations (for images)

  • -
-
-
Returns:
-

Normalized tensor ready for model input

-
-
Return type:
-

torch.Tensor

-
-
-
- -
-
-utils.media_utils.set_inversion(pipe, inversion_type)[source]
-

Set the inversion for the given pipe.

-
-
-
- -
-
-utils.media_utils.get_random_latents(pipe, latents=None, num_frames=None, height=512, width=512, generator=None)[source]
-

Get random latents for the given pipe.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-utils.media_utils.decoder_inv_optimization(pipe, latents, image, num_steps=100)[source]
-

Optimize latents to better reconstruct the input image by minimizing the error between -decoded latents and original image.

-
-
Parameters:
-
    -
  • pipe (StableDiffusionPipeline) – The diffusion pipeline

  • -
  • latents (FloatTensor) – Initial latents

  • -
  • image (FloatTensor) – Target image

  • -
  • num_steps (int) – Number of optimization steps

  • -
-
-
Returns:
-

Optimized latents

-
-
Return type:
-

torch.Tensor

-
-
-
- -
-
-utils.media_utils.tensor2vid(video, processor, output_type='np')[source]
-

Convert video tensor to desired output format.

-
-
Parameters:
-
    -
  • video (Tensor) – Video tensor [B, C, F, H, W]

  • -
  • processor – Video processor from the diffusion pipeline

  • -
  • output_type (str) – Output type - ‘np’, ‘pt’, or ‘pil’

  • -
-
-
Returns:
-

Video in requested format

-
-
-
- -
-
-utils.media_utils.convert_video_frames_to_images(frames)[source]
-

Convert video frames to a list of PIL.Image objects.

-
-
Parameters:
-

frames (List[Union[ndarray, Image]]) – List of video frames (numpy arrays or PIL images)

-
-
Returns:
-

List of PIL images

-
-
Return type:
-

List[Image.Image]

-
-
-
- -
-
-utils.media_utils.save_video_frames(frames, save_dir)[source]
-

Save video frames to a directory.

-
-
Parameters:
-
    -
  • frames (List[Union[ndarray, Image]]) – List of video frames (numpy arrays or PIL images)

  • -
  • save_dir (str) – Directory to save frames

  • -
-
-
Return type:
-

None

-
-
-
- -
-
-utils.media_utils.get_media_latents(pipe, media, sample=True, rng_generator=None, decoder_inv=False)[source]
-

Get latents from media (either image or video) based on pipeline type.

-
-
Parameters:
-
    -
  • pipe (Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline]) – Diffusion pipeline

  • -
  • media (Union[Tensor, List[Tensor]]) – Image tensor or video frames tensor

  • -
  • sample (bool) – Whether to sample from the latent distribution

  • -
  • rng_generator (Optional[Generator]) – Random generator for sampling

  • -
  • decoder_inv (bool) – Whether to use decoder inversion optimization

  • -
-
-
Returns:
-

Media latents

-
-
Return type:
-

torch.Tensor

-
-
-
- -
-
-utils.media_utils.decode_media_latents(pipe, latents, num_frames=None)[source]
-

Decode latents to media (either image or video) based on pipeline type.

-
-
Parameters:
-
    -
  • pipe (Union[StableDiffusionPipeline, TextToVideoSDPipeline, StableVideoDiffusionPipeline]) – Diffusion pipeline

  • -
  • latents (Tensor) – Media latents

  • -
  • num_frames (Optional[int]) – Number of frames (for video)

  • -
-
-
Returns:
-

Decoded media

-
-
Return type:
-

Union[torch.Tensor, np.ndarray]

-
-
-
- -
-
-

General Utilities

-
-
-utils.utils.inherit_docstring(cls)[source]
-

Inherit docstrings from base classes to methods without docstrings.

-

This decorator automatically applies the docstring from a base class method -to a derived class method if the derived method doesn’t have its own docstring.

-
-
Parameters:
-

cls – The class to enhance with inherited docstrings

-
-
Returns:
-

The enhanced class

-
-
Return type:
-

cls

-
-
-
- -
-
-utils.utils.load_config_file(path)[source]
-

Load a JSON configuration file from the specified path and return it as a dictionary.

-
-
Return type:
-

dict

-
-
-
- -
-
-utils.utils.load_json_as_list(input_file)[source]
-

Load a JSON file as a list of dictionaries.

-
-
Return type:
-

list

-
-
-
- -
-
-utils.utils.create_directory_for_file(file_path)[source]
-

Create the directory for the specified file path if it does not already exist.

-
-
Return type:
-

None

-
-
-
- -
-
-utils.utils.set_random_seed(seed)[source]
-

Set random seeds for reproducibility.

-
-
-
- -
-
-

Callbacks

-
-
-class utils.callbacks.DenoisingLatentsCollector(save_every_n_steps=1, to_cpu=True)[source]
-

Bases: object

-
-
-
-
-__init__(save_every_n_steps=1, to_cpu=True)[source]
-

Initialize the latents collector.

-
-
Parameters:
-
    -
  • save_every_n_steps (int, optional) – Save latents every n steps. Defaults to 1.

  • -
  • to_cpu (bool, optional) – Whether to move latents to CPU. Defaults to True.

  • -
-
-
-
- -
-
-property latents_list: List[torch.Tensor]
-

Return the list of latents.

-
- -
-
-property timesteps_list: List[int]
-

Return the list of timesteps.

-
- -
-
-get_latents_at_step(step)[source]
-

Get the latents at a specific step.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-clear()[source]
-

Clear the collected data.

-
- -
- -
-
-

Inversions

-
-

Base Inversion

-
-
-class inversions.base_inversion.BaseInversion(scheduler, unet, device)[source]
-

Bases: object

-
-
-__init__(scheduler, unet, device)[source]
-
- -
-
-forward_diffusion(use_old_emb_i=25, text_embeddings=None, old_text_embeddings=None, new_text_embeddings=None, latents=None, num_inference_steps=10, guidance_scale=7.5, callback=None, callback_steps=1, inverse_opt=True, inv_order=None, **kwargs)
-
-
-
- -
- -
-
-

DDIM Inversion

-
-
-class inversions.ddim_inversion.DDIMInversion(scheduler, unet, device)[source]
-

Bases: BaseInversion

-
-
-__init__(scheduler, unet, device)[source]
-
- -
-
-backward_diffusion(use_old_emb_i=25, text_embeddings=None, old_text_embeddings=None, new_text_embeddings=None, latents=None, num_inference_steps=50, guidance_scale=7.5, callback=None, callback_steps=1, reverse_process=False, **kwargs)
-

Generate image from text prompt and latents

-
-
-
- -
- -
-
-

Exact Inversion

-
-
-class inversions.exact_inversion.ExactInversion(scheduler, unet, device)[source]
-

Bases: BaseInversion

-
-
-__init__(scheduler, unet, device)[source]
-
- -
-
-forward_diffusion(use_old_emb_i=25, text_embeddings=None, old_text_embeddings=None, new_text_embeddings=None, latents=None, num_inference_steps=10, guidance_scale=7.5, callback=None, callback_steps=1, inverse_opt=False, inv_order=0, **kwargs)
-
-
-
- -
-
-backward_diffusion(latents=None, num_inference_steps=10, guidance_scale=7.5, callback=None, callback_steps=1, inv_order=None, **kwargs)
-

Reconstruct z_0 from z_T via the forward diffusion process

-

Sampling (Explicit Method): -Order 1: Forward Euler (DDIM) - Eq. (5) -Order 2: DPM-Solver++(2M) - Eq. (6)

-
-
-
- -
- -
-
-class inversions.exact_inversion.StepScheduler(*args, **kwargs)[source]
-

Bases: ReduceLROnPlateau

-
-
-__init__(mode='min', current_lr=0, factor=0.1, patience=10, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08, verbose=False)[source]
-
- -
-
-step(metrics, epoch=None)[source]
-
- -
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/api/visualization.html b/docs/_build/html/api/visualization.html deleted file mode 100644 index 4ccee9b..0000000 --- a/docs/_build/html/api/visualization.html +++ /dev/null @@ -1,2086 +0,0 @@ - - - - - - - - - Visualization API — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Visualization API

-

This page documents the visualization API.

-
-

AutoVisualizer

-
-
-class visualize.auto_visualization.AutoVisualizer[source]
-

Bases: object

-

Factory class for creating visualization data instances.

-

This is a generic visualization data factory that will instantiate the appropriate -visualization data class based on the algorithm name.

-

This class cannot be instantiated directly using __init__() (throws an error).

-
-
-__init__()[source]
-
- -
-
-classmethod load(algorithm_name, data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Load the visualization data instance based on the algorithm name.

-
-
Parameters:
-
    -
  • algorithm_name (str) – Name of the watermarking algorithm (e.g., ‘TR’, ‘GS’, ‘PRC’)

  • -
  • data_for_visualization (DataForVisualization) – DataForVisualization instance

  • -
-
-
Returns:
-

Instance of the appropriate visualization data class

-
-
Return type:
-

BaseVisualizer

-
-
Raises:
-

ValueError – If the algorithm name is not supported

-
-
-
- -
- -
-
-

Base Visualizer

-
-
-class visualize.base.BaseVisualizer(data_for_visualization, dpi=300, watermarking_step=-1, is_video=False)[source]
-

Bases: ABC

-

Base class for watermark visualization data

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1, is_video=False)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_orig_latents(channel=None, frame=None, title='Original Latents', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the original latents of the watermarked image.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • frame (Optional[int]) – The frame index for T2V models. If None, uses middle frame for videos.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_orig_latents_fft(channel=None, frame=None, title='Original Latents in Fourier Domain', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the original latents of the watermarked image in the Fourier domain.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • frame (Optional[int]) – The frame index for T2V models. If None, uses middle frame for videos.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_latents(channel=None, frame=None, step=None, title='Inverted Latents', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the inverted latents of the watermarked image.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • frame (Optional[int]) – The frame index for T2V models. If None, uses middle frame for videos.

  • -
  • step (Optional[int]) – The timestep of the inverted latents. If None, the last timestep is used.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_latents_fft(channel=None, frame=None, step=-1, title='Inverted Latents in Fourier Domain', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the inverted latents of the watermarked image in the Fourier domain.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • frame (Optional[int]) – The frame index for T2V models. If None, uses middle frame for videos.

  • -
  • step (Optional[int]) – The timestep of the inverted latents. If None, the last timestep is used.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_diff_latents_fft(channel=None, frame=None, title='Difference between Original and Inverted Latents in Fourier Domain', cmap='coolwarm', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the difference between the original and inverted initial latents of the watermarked image in the Fourier domain.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • frame (Optional[int]) – The frame index for T2V models. If None, uses middle frame for videos.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_watermarked_image(title='Watermarked Image', num_frames=4, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the watermarked image or video frames.

-

For images (is_video=False), displays a single image. -For videos (is_video=True), displays a grid of video frames.

-
-
Parameters:
-
    -
  • title (str) – The title of the plot.

  • -
  • num_frames (int) – Number of frames to display for videos (default: 4).

  • -
  • vmin (Optional[float]) – Minimum value for colormap.

  • -
  • vmax (Optional[float]) – Maximum value for colormap.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-visualize(rows, cols, methods, figsize=None, method_kwargs=None, save_path=None)[source]
-

Comprehensive visualization of watermark analysis.

-
-
Parameters:
-
    -
  • rows (int) – The number of rows of the subplots.

  • -
  • cols (int) – The number of columns of the subplots.

  • -
  • methods (List[str]) – List of methods to call.

  • -
  • method_kwargs (Optional[List[Dict[str, Any]]]) – List of keyword arguments for each method.

  • -
  • figsize (Tuple[int, int]) – The size of the figure.

  • -
  • save_path (Optional[str]) – The path to save the figure.

  • -
-
-
Returns:
-

The matplotlib figure object.

-
-
Return type:
-

plt.Figure

-
-
-
- -
- -
-
-

Algorithm-Specific Visualizers

-
-

Tree-Ring Visualizer

-
-
-class visualize.tr.tr_visualizer.TreeRingVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

Tree-Ring watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_pattern_fft(title='Tree-Ring FFT with Watermark Area', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with original watermark pattern, with all 0 background.

-
-
Parameters:
-
    -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_pattern_fft(step=None, title='Tree-Ring FFT with Inverted Watermark Area', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with inverted pattern, with all 0 background.

-
-
Parameters:
-
    -
  • step (Optional[int]) – The timestep of the inverted latents. If None, the last timestep is used.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

Gaussian-Shading Visualizer

-
-
-class visualize.gs.gs_visualizer.GaussianShadingVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

Gaussian Shading watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_watermark_bits(channel=None, title='Original Watermark Bits', cmap='binary', ax=None)[source]
-

Draw the original watermark bits.(sd in GS class). draw ch // channel_copy images in one ax.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel to visualize. If None, all channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_reconstructed_watermark_bits(channel=None, title='Reconstructed Watermark Bits', cmap='binary', ax=None)[source]
-

Draw the reconstructed watermark bits.(reversed_latents in GS class). draw ch // channel_copy images in one ax.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel to visualize. If None, all channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

ROBIN Visualizer

-
-
-class visualize.robin.robin_visualizer.ROBINVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

ROBIN watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_pattern_fft(title=None, cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with original watermark pattern, with all 0 background.

-
-
Parameters:
-
    -
  • title (str) – The title of the plot. If None, includes watermarking step info.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_pattern_fft(step=None, title=None, cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with inverted pattern, with all 0 background.

-
-
Parameters:
-
    -
  • step (Optional[int]) – The timestep of the inverted latents. If None, uses ROBIN’s specific step.

  • -
  • title (str) – The title of the plot. If None, includes watermarking step info.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_optimized_watermark(title=None, cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the optimized watermark pattern (ROBIN-specific).

-
-
Parameters:
-
    -
  • title (str) – The title of the plot. If None, includes watermarking step info.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

WIND Visualizer

-
-
-class visualize.wind.wind_visualizer.WINDVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

WIND watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_group_pattern_fft(channel=None, title='Group Pattern in Fourier Domain', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the group pattern in Fourier Domain.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_orig_noise_wo_group_pattern(channel=None, title='Original Noise without Group Pattern', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the original noise without group pattern.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (plt.Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

plt.axes.Axes

-
-
-
- -
-
-draw_inverted_noise_wo_group_pattern(channel=None, title='Inverted Noise without Group Pattern', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the inverted noise without group pattern.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (plt.Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

plt.axes.Axes

-
-
-
- -
-
-draw_diff_noise_wo_group_pattern(channel=None, title='Difference map without Group Pattern', cmap='coolwarm', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw the difference between original and inverted noise after removing group pattern.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel of the latent tensor to visualize. If None, all 4 channels are shown.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (plt.Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

plt.axes.Axes

-
-
-
- -
-
-draw_inverted_group_pattern_fft(channel=None, title='WIND Two-Stage Detection Visualization', cmap='viridis', use_color_bar=True, ax=None, **kwargs)[source]
-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

SFW Visualizer

-
-
-class visualize.sfw.sfw_visualizer.SFWVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

SFW watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_pattern_fft(title='SFW FFT with Watermark Area', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with original watermark pattern, with all 0 background.

-
-
Parameters:
-
    -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_pattern_fft(step=None, title='SFW FFT with Inverted Watermark Area', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Draw FFT visualization with inverted pattern, with all 0 background.

-
-
Parameters:
-
    -
  • step (Optional[int]) – The timestep of the inverted latents. If None, the last timestep is used.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • ax (Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

GaussMarker Visualizer

-
-
-class visualize.gm.gm_visualizer.GaussMarkerVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

GaussMarker watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_watermark_bits(channel=None, title='Original Watermark Bits', cmap='binary', ax=None)[source]
-

Visualize the original watermark bits generated by GaussMarker.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_reconstructed_watermark_bits(channel=None, step=None, title='Reconstructed Watermark Bits', cmap='binary', ax=None)[source]
-

Visualize watermark bits reconstructed from inverted latents.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_pattern_fft(channel=None, title='GaussMarker Target Pattern (FFT)', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Visualize the intended watermark pattern in the Fourier domain.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_inverted_pattern_fft(channel=None, step=None, title='Recovered Watermark Pattern (FFT)', cmap='viridis', use_color_bar=True, vmin=None, vmax=None, ax=None, **kwargs)[source]
-

Visualize the recovered watermark region in the Fourier domain.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_watermark_mask(channel=None, title='Watermark Mask', cmap='viridis', ax=None)[source]
-

Visualize the spatial region where the watermark is applied.

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

PRC Visualizer

-
-
-class visualize.prc.prc_visualizer.PRCVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

PRC (Pseudorandom Codes) watermark visualizer

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize PRC visualizer

-
-
Parameters:
-
    -
  • data_for_visualization (DataForVisualization) – DataForVisualization object containing visualization data

  • -
  • dpi (int) – DPI for visualization (default: 300)

  • -
  • watermarking_step (int) – The step for inserting the watermark (default: -1)

  • -
-
-
-
- -
-
-draw_generator_matrix(title='Generator Matrix G', cmap='Blues', use_color_bar=True, max_display_size=50, ax=None, **kwargs)[source]
-

Draw the generator matrix visualization

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • max_display_size (int) – Maximum size to display (for large matrices)

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_codeword(title='PRC Codeword', cmap='viridis', use_color_bar=True, ax=None, **kwargs)[source]
-

Draw the PRC codeword visualization

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_recovered_codeword(title='Recovered Codeword (c̃)', cmap='viridis', use_color_bar=True, vmin=-1.0, vmax=1.0, ax=None, **kwargs)[source]
-

Draw the recovered codeword (c̃) from PRC detection

-

This visualizes the recovered codeword from prc_detection.recovered_prc

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • vmin (float) – Minimum value for colormap (-1.0)

  • -
  • vmax (float) – Maximum value for colormap (1.0)

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_difference_map(title='Difference Map', cmap='hot', use_color_bar=True, channel=0, ax=None, **kwargs)[source]
-

Draw difference map between watermarked and inverted latents

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • channel (int) – The channel to visualize

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-

SEAL Visualizer

-
-
-class visualize.seal.seal_visualizer.SEALVisualizer(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Bases: BaseVisualizer

-

SEAL watermark visualization class

-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_embedding_distributions(title='Embedding Distributions', ax=None, show_legend=True, show_label=True, show_axis=True)[source]
-

Draw histogram of embedding distributions comparison(original_embedding vs inspected_embedding).

-
-
Parameters:
-
    -
  • title (str) – The title of the plot.

  • -
  • ax (plt.Axes) – The axes to plot on.

  • -
  • show_legend (bool) – Whether to show the legend. Default: True.

  • -
  • show_label (bool) – Whether to show axis labels. Default: True.

  • -
  • show_axis (bool) – Whether to show axis ticks and labels. Default: True.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_patch_diff(title='Patch Difference', cmap='RdBu', use_color_bar=True, vmin=None, vmax=None, show_number=False, ax=None, **kwargs)[source]
-

Draw the difference between the reference_noise and reversed_latents in patch.

-
-
Parameters:
-
    -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • use_color_bar (bool) – Whether to display the colorbar.

  • -
  • vmin (Optional[float]) – Minimum value for colormap normalization.

  • -
  • vmax (Optional[float]) – Maximum value for colormap normalization.

  • -
  • show_number (bool) – Whether to display numerical values on each patch. Default: False.

  • -
  • ax (plt.Axes) – The axes to plot on.

  • -
-
-
Returns:
-

The plotted axes.

-
-
Return type:
-

plt.axes.Axes

-
-
-
- -
- -
-
-

VideoShield Visualizer

-
-
-class visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer(data_for_visualization, dpi=300, watermarking_step=-1, is_video=True)[source]
-

Bases: BaseVisualizer

-

VideoShield watermark visualization class.

-

This visualizer handles watermark visualization for VideoShield algorithm, -which extends Gaussian Shading to the video domain by adding frame dimensions.

-
-
Key Members for VideoShieldVisualizer:
    -
  • self.data.orig_watermarked_latents: [B, C, F, H, W]

  • -
  • self.data.reversed_latents: List[[B, C, F, H, W]]

  • -
-
-
-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1, is_video=True)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_watermark_bits(channel=None, frame=None, title='Original Watermark Bits', cmap='binary', ax=None)[source]
-

Draw the original watermark bits for VideoShield.

-

For video watermarks, this method can visualize specific frames or average -across frames to create a 2D visualization.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel to visualize. If None, all channels are shown.

  • -
  • frame (Optional[int]) – The frame to visualize. If None, uses middle frame for videos.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • ax (Optional[Axes]) – The axes to plot on.

  • -
-
-
Return type:
-

Axes

-
-
Returns:
-

The plotted axes.

-
-
-
- -
-
-draw_reconstructed_watermark_bits(channel=None, frame=None, title='Reconstructed Watermark Bits', cmap='binary', ax=None)[source]
-

Draw the reconstructed watermark bits for VideoShield.

-
-
Parameters:
-
    -
  • channel (Optional[int]) – The channel to visualize. If None, all channels are shown.

  • -
  • frame (Optional[int]) – The frame to visualize. If None, uses middle frame for videos.

  • -
  • title (str) – The title of the plot.

  • -
  • cmap (str) – The colormap to use.

  • -
  • ax (Optional[Axes]) – The axes to plot on.

  • -
-
-
Return type:
-

Axes

-
-
Returns:
-

The plotted axes.

-
-
-
- -
-
-draw_watermarked_video_frames(num_frames=4, title='Watermarked Video Frames', ax=None)[source]
-

Draw multiple frames from the watermarked video.

-
-
DEPRECATED:

This method is deprecated and will be removed in a future version. -Please use draw_watermarked_image instead.

-
-
-

This method displays a grid of video frames to show the temporal -consistency of the watermarked video.

-
-
Parameters:
-
    -
  • num_frames (int) – Number of frames to display (default: 4)

  • -
  • title (str) – The title of the plot

  • -
  • ax (Optional[Axes]) – The axes to plot on

  • -
-
-
Return type:
-

Axes

-
-
Returns:
-

The plotted axes

-
-
-
- -
- -
-
-

VideoMark Visualizer

-
-
-class visualize.videomark.video_mark_visualizer.VideoMarkVisualizer(data_for_visualization, dpi=300, watermarking_step=-1, is_video=True)[source]
-

Bases: BaseVisualizer

-

VideoMark watermark visualization class.

-

This visualizer handles watermark visualization for VideoShield algorithm, -which extends Gaussian Shading to the video domain by adding frame dimensions.

-
-
Key Members for VideoMarkVisualizer:
    -
  • self.data.orig_watermarked_latents: [B, C, F, H, W]

  • -
  • self.data.reversed_latents: List[[B, C, F, H, W]]

  • -
-
-
-
-
-
-
-__init__(data_for_visualization, dpi=300, watermarking_step=-1, is_video=True)[source]
-

Initialize with common attributes

-
-
-
- -
-
-draw_watermarked_video_frames(num_frames=4, title='Watermarked Video Frames', ax=None)[source]
-

Draw multiple frames from the watermarked video.

-
-
DEPRECATED:

This method is deprecated and will be removed in a future version. -Please use draw_watermarked_image instead.

-
-
-

This method displays a grid of video frames to show the temporal -consistency of the watermarked video.

-
-
Parameters:
-
    -
  • num_frames (int) – Number of frames to display (default: 4)

  • -
  • title (str) – The title of the plot

  • -
  • ax (Optional[Axes]) – The axes to plot on

  • -
-
-
Return type:
-

Axes

-
-
Returns:
-

The plotted axes

-
-
-
- -
-
-draw_generator_matrix(title='Generator Matrix G', cmap='Blues', use_color_bar=True, max_display_size=50, ax=None, **kwargs)[source]
-

Draw the generator matrix visualization

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • max_display_size (int) – Maximum size to display (for large matrices)

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_codeword(title='VideoMark Codeword', cmap='viridis', use_color_bar=True, ax=None, **kwargs)[source]
-

Draw the PRC codeword visualization

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_recovered_codeword(title='Recovered Codeword (c̃)', cmap='viridis', use_color_bar=True, vmin=-1.0, vmax=1.0, ax=None, **kwargs)[source]
-
-
Return type:
-

Axes

-
-
-
- -
-
-draw_difference_map(title='Difference Map', cmap='hot', use_color_bar=True, channel=0, frame=0, ax=None, **kwargs)[source]
-

Draw difference map between watermarked and inverted latents

-
-
Parameters:
-
    -
  • title (str) – The title of the plot

  • -
  • cmap (str) – The colormap to use

  • -
  • use_color_bar (bool) – Whether to display the colorbar

  • -
  • channel (int) – The channel to visualize

  • -
  • ax (Axes) – The axes to plot on

  • -
-
-
Returns:
-

The plotted axes

-
-
Return type:
-

Axes

-
-
-
- -
- -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/api/watermark.html b/docs/_build/html/api/watermark.html deleted file mode 100644 index c700a68..0000000 --- a/docs/_build/html/api/watermark.html +++ /dev/null @@ -1,2394 +0,0 @@ - - - - - - - - - Watermark API — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Watermark API

-

This page documents the watermarking API.

-
-

AutoWatermark

-
-
-class watermark.auto_watermark.AutoWatermark[source]
-

Bases: object

-

This is a generic watermark class that will be instantiated as one of the watermark classes of the library when -created with the [AutoWatermark.load] class method.

-

This class cannot be instantiated directly using __init__() (throws an error).

-
-
-__init__()[source]
-
- -
-
-classmethod load(algorithm_name, algorithm_config=None, diffusion_config=None, *args, **kwargs)[source]
-

Load the watermark algorithm instance based on the algorithm name.

-
-
Return type:
-

BaseWatermark

-
-
-
- -
-
-classmethod list_supported_algorithms(pipeline_type=None)[source]
-

List all supported watermarking algorithms, optionally filtered by pipeline type.

-
-
-
- -
- -
-
-

Base Watermark

-
-
-class watermark.base.BaseWatermark(config, *args, **kwargs)[source]
-

Bases: ABC

-

Base class for diffusion watermarking methods.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the watermarking algorithm.

-
-
-
- -
-
-get_orig_watermarked_latents()[source]
-

Get the original watermarked latents.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-set_orig_watermarked_latents(value)[source]
-

Set the original watermarked latents.

-
-
Return type:
-

None

-
-
-
- -
-
-generate_watermarked_media(input_data, *args, **kwargs)[source]
-

Generate watermarked media (image or video) based on pipeline type.

-

This is the main interface for generating watermarked content with any -watermarking algorithm. It automatically routes to the appropriate generation -method based on the pipeline type (image or video).

-
-
Parameters:
-
    -
  • input_data (Union[str, Image]) – Text prompt (for T2I or T2V) or input image (for I2V)

  • -
  • *args – Additional positional arguments

  • -
  • **kwargs – Additional keyword arguments, including: -- guidance_scale: Guidance scale for generation -- num_inference_steps: Number of inference steps -- height, width: Dimensions of generated media -- seed: Random seed for generation

  • -
-
-
Returns:
-

Generated watermarked media -- For image pipelines: Returns a single PIL Image -- For video pipelines: Returns a list of PIL Images (frames)

-
-
Return type:
-

Union[Image.Image, List[Image.Image]]

-
-
-
-

Examples

-

```python -# Image watermarking -watermark = AutoWatermark.load(‘TR’, diffusion_config=config) -image = watermark.generate_watermarked_media(

-
-

input_data=”A beautiful landscape”, -guidance_scale=7.5, -num_inference_steps=50

-
-

)

-

# Video watermarking (T2V) -watermark = AutoWatermark.load(‘VideoShield’, diffusion_config=config) -frames = watermark.generate_watermarked_media(

-
-

input_data=”A dog running in a park”, -num_frames=16

-
-

)

-

# Video watermarking (I2V) -watermark = AutoWatermark.load(‘VideoShield’, diffusion_config=config) -frames = watermark.generate_watermarked_media(

-
-

input_data=reference_image, -num_frames=16

-
-
-
- -
-
-generate_unwatermarked_media(input_data, *args, **kwargs)[source]
-

Generate unwatermarked media (image or video) based on pipeline type.

-
-
Parameters:
-
    -
  • input_data (Union[str, Image]) – Text prompt (for T2I or T2V) or input image (for I2V)

  • -
  • *args – Additional positional arguments

  • -
  • **kwargs – Additional keyword arguments, including: -- save_path: Path to save the generated media

  • -
-
-
Returns:
-

Generated unwatermarked media

-
-
Return type:
-

Union[Image.Image, List[Image.Image]]

-
-
-
- -
-
-detect_watermark_in_media(media, *args, **kwargs)[source]
-

Detect watermark in media (image or video).

-
-
Parameters:
-
    -
  • media (Union[Image, List[Image], ndarray, Tensor]) – The media to detect watermark in (can be PIL image, list of frames, numpy array, or tensor)

  • -
  • *args – Additional positional arguments

  • -
  • **kwargs – Additional keyword arguments, including: -- prompt: Optional text prompt used to generate the media (for some algorithms) -- num_inference_steps: Optional number of inference steps -- guidance_scale: Optional guidance scale -- num_frames: Optional number of frames -- decoder_inv: Optional decoder inversion -- inv_order: Inverse order for Exact Inversion -- detector_type: Type of detector to use

  • -
-
-
Returns:
-

Detection results with metrics and possibly visualizations

-
-
Return type:
-

Dict[str, Any]

-
-
-
- -
-
-abstract get_data_for_visualize(media, *args, **kwargs)[source]
-

Get data for visualization.

-
- -
- -
-
-

Tree-Ring Watermark

-
-
-class watermark.tr.tr.TRConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for TR algorithm, load config file and initialize parameters.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.tr.tr.TRUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for TR algorithm, contains helper functions.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the Tree-Ring watermarking algorithm.

-
-
Parameters:
-

config (TRConfig) – Configuration for the Tree-Ring algorithm.

-
-
-
- -
-
-inject_watermark(init_latents)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.tr.tr.TR(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the TR watermarking algorithm.

-
-
Parameters:
-

watermark_config (TRConfig) – Configuration instance of the Tree-Ring algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=None, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization including detection inversion - similar to GS logic.

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

Gaussian-Shading Watermark

-
-
-class watermark.gs.gs.GSConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for Gaussian Shading algorithm.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.gs.gs.GSUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for Gaussian Shading algorithm.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the Gaussian Shading watermarking utility.

-
-
Parameters:
-

config (GSConfig) – Configuration for the Gaussian Shading watermarking algorithm.

-
-
-
- -
-
-inject_watermark()[source]
-

Inject watermark into latent space.

-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.gs.gs.GS(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-

Main class for Gaussian Shading watermarking algorithm.

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the Gaussian Shading watermarking algorithm.

-
-
Parameters:
-

watermark_config (GSConfig) – Configuration instance of the GS algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=1, decoder_inv=False, *args, **kwargs)[source]
-

Get Gaussian Shading visualization data

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

ROBIN Watermark

-
-
-class watermark.robin.robin.ROBINConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for ROBIN algorithm, load config file and initialize parameters.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.robin.robin.ROBINUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for ROBIN algorithm, contains helper functions.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the ROBIN watermarking algorithm.

-
-
Parameters:
-

config (ROBINConfig) – Configuration for the ROBIN algorithm.

-
-
-
- -
-
-build_generation_params(**kwargs)[source]
-

Build generation parameters from config and kwargs.

-
-
Return type:
-

Dict

-
-
-
- -
-
-generate_clean_images(dataset, **kwargs)[source]
-

Generate clean images for optimization.

-
-
Return type:
-

List[Image]

-
-
-
- -
-
-build_watermarking_args()[source]
-

Build watermarking arguments from config.

-
-
Return type:
-

SimpleNamespace

-
-
-
- -
-
-build_hyperparameters()[source]
-

Build hyperparameters for optimization from config.

-
-
Return type:
-

Dict

-
-
-
- -
-
-optimize_watermark(dataset, watermarking_args)[source]
-

Optimize watermark and watermarking signal.

-
-
Return type:
-

tuple

-
-
-
- -
-
-initialize_detector(watermarking_mask, optimized_watermark)[source]
-

Initialize the ROBIN detector.

-
-
Return type:
-

ROBINDetector

-
-
-
- -
-
-preprocess_image_for_detection(image, prompt, guidance_scale)[source]
-

Preprocess image and get text embeddings for detection.

-
-
Return type:
-

tuple

-
-
-
- -
-
-extract_latents_for_detection(image, prompt, guidance_scale, num_inference_steps, extract_latents_step, **kwargs)[source]
-

Extract and reverse latents for watermark detection.

-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.robin.robin.ROBIN(watermarking_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-
-
-
-
-__init__(watermarking_config, *args, **kwargs)[source]
-

Initialize the ROBIN watermarking algorithm.

-
-
Parameters:
-

watermarking_config (ROBINConfig) – Configuration for the ROBIN algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=None, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization.

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

WIND Watermark

-
-
-class watermark.wind.wind.WINDConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters. Should be overridden by subclasses.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.wind.wind.WINDUtils(config)[source]
-

Bases: object

-
-
-
-
-__init__(config)[source]
-
-
-
- -
-
-inject_watermark(index)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.wind.wind.WIND(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the WIND algorithm.

-
-
Parameters:
-

watermark_config (WINDConfig) – Configuration instance of the WIND algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=None, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization.

-
-
-
- -
- -
-
-

SFW Watermark

-
-
-class watermark.sfw.sfw.SFWConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for SFW algorithm, load config file and initialize parameters.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.sfw.sfw.SFWUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for SFW algorithm, contains helper functions.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the SFW watermarking algorithm.

-
-
Parameters:
-

config (SFWConfig) – Configuration for the SFW algorithm.

-
-
-
- -
-
-static fft(input_tensor)[source]
-
- -
-
-static ifft(input_tensor)[source]
-
- -
-
-static rfft(input_tensor)
-
- -
-
-static irfft(input_tensor)
-
- -
-
-circle_mask(size, r=16, x_offset=0, y_offset=0)[source]
-
-
-
- -
-
-enforce_hermitian_symmetry(freq_tensor)
-
- -
-
-make_Fourier_treering_pattern(pipe, shape, w_seed=999999, resolution=512)
-
- -
-
-class QRCodeGenerator(box_size=2, border=1, qr_version=1)[source]
-

Bases: object

-
-
-__init__(box_size=2, border=1, qr_version=1)[source]
-
- -
-
-make_qr_tensor(data, filename='qrcode.png', save_img=False)[source]
-
- -
-
-clear()[source]
-
- -
- -
-
-make_hsqr_pattern(idx)
-
-
-
- -
-
-class RounderRingMask(size=65, r_out=14)[source]
-

Bases: object

-
-
-__init__(size=65, r_out=14)[source]
-
- -
-
-get_ring_mask(r_out, r_in)[source]
-
- -
- -
-
-inject_wm(init_latents)
-
-
-
- -
-
-inject_hsqr(inverted_latent)
-
- -
- -
-
-class watermark.sfw.sfw.SFW(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the SFW watermarking algorithm.

-
-
Parameters:
-

watermark_config (SFWConfig) – Configuration instance of the SFW algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=None, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization including detection inversion

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

GaussMarker

-
-
-watermark.gm.gm.circle_mask(size, radius, x_offset=0, y_offset=0)[source]
-

Create a binary circle mask with optional offset.

-
-
Return type:
-

ndarray

-
-
-
- -
-
-watermark.gm.gm.set_complex_sign(original, sign_tensor)[source]
-

Apply complex-valued sign encoding (4-way) to a complex tensor.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-watermark.gm.gm.extract_complex_sign(complex_tensor)[source]
-

Extract complex-valued sign encoding (4-way) from a complex tensor.

-
-
Return type:
-

Tensor

-
-
-
- -
-
-class watermark.gm.gm.GaussianShadingChaCha(channel_copy, width_copy, height_copy, fpr, user_number, latent_channels, latent_height, latent_width, dtype, device, watermark_seed=None, key_seed=None, nonce_seed=None, watermark=None, key=None, nonce=None, message_bits=None)[source]
-

Bases: object

-
-
-
-
-channel_copy: int
-
- -
-
-width_copy: int
-
- -
-
-height_copy: int
-
- -
-
-fpr: float
-
- -
-
-user_number: int
-
- -
-
-latent_channels: int
-
- -
-
-latent_height: int
-
- -
-
-latent_width: int
-
- -
-
-dtype: dtype
-
- -
-
-device: device
-
- -
-
-watermark_seed: Optional[int] = None
-
- -
-
-key_seed: Optional[int] = None
-
- -
-
-nonce_seed: Optional[int] = None
-
- -
-
-watermark: Optional[Tensor] = None
-
- -
-
-key: Optional[bytes] = None
-
- -
-
-nonce: Optional[bytes] = None
-
- -
-
-message_bits: Optional[ndarray] = None
-
- -
-
-create_watermark_and_return_w_m()[source]
-
-
Return type:
-

Tuple[Tensor, Tensor]

-
-
-
- -
-
-diffusion_inverse(spread_tensor)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-pred_m_from_latent(reversed_latents)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-pred_w_from_latent(reversed_latents)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-pred_w_from_m(reversed_m)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-watermark_tensor(device=None)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-__init__(channel_copy, width_copy, height_copy, fpr, user_number, latent_channels, latent_height, latent_width, dtype, device, watermark_seed=None, key_seed=None, nonce_seed=None, watermark=None, key=None, nonce=None, message_bits=None)
-
-
-
- -
- -
-
-class watermark.gm.gm.GMUtils(config)[source]
-

Bases: object

-
-
-
-
-__init__(config)[source]
-
-
-
- -
-
-inject_watermark(base_latents)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-generate_watermarked_latents(seed=None)[source]
-
-
Return type:
-

Tensor

-
-
-
- -
-
-detect_from_latents(reversed_latents, detector_type=None)[source]
-
-
Return type:
-

Dict[str, Union[float, bool]]

-
-
-
- -
- -
-
-class watermark.gm.gm.GMConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters. Should be overridden by subclasses.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.gm.gm.GM(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the watermarking algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=None, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization.

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

PRC Watermark

-
-
-class watermark.prc.prc.PRCConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for PRC algorithm.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.prc.prc.PRCUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for PRC algorithm.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize PRC utility.

-
-
-
- -
-
-inject_watermark()[source]
-

Generate watermarked latents from PRC codeword.

-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.prc.prc.PRC(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-

PRC watermark class.

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize PRC watermarking algorithm.

-
-
Parameters:
-

watermark_config (PRCConfig) – Configuration instance of the PRC algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=1, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization.

-
-
Return type:
-

DataForVisualization

-
-
-
- -
- -
-
-

SEAL Watermark

-
-
-class watermark.seal.seal.SEALConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for SEAL algorithm.

-
-
-
-
-initialize_parameters()[source]
-

Initialize parameters for SEAL algorithm.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the name of the algorithm.

-
- -
- -
-
-class watermark.seal.seal.SEALUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for SEAL algorithm.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize SEAL utility.

-
-
-
- -
-
-generate_caption(image)[source]
-

Generate caption for an image.

-
-
Parameters:
-

image (Image) – PIL Image object

-
-
Return type:
-

str

-
-
Returns:
-

Caption string

-
-
-
- -
-
-generate_initial_noise(embedding, k, b, seed)[source]
-

Generates initial noise using improved simhash approach.

-
-
Parameters:
-
    -
  • embedding (Tensor) – Input embedding vector(Nomalized)

  • -
  • k (int) – k_value(Patch number)

  • -
  • b (int) – b_value(Bit number per patch)

  • -
  • seed (int) – Random seed(secret_salt)

  • -
-
-
Return type:
-

Tensor

-
-
Returns:
-

Noise tensor with shape [1, 4, 64, 64]

-
-
-
- -
- -
-
-class watermark.seal.seal.SEAL(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-

SEAL watermarking algorithm.

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the SEAL algorithm.

-
-
Parameters:
-

watermark_config (SEALConfig) – Configuration instance of the SEAL algorithm.

-
-
-
- -
-
-get_data_for_visualize(image, prompt='', guidance_scale=1, decoder_inv=False, *args, **kwargs)[source]
-

Get data for visualization of SEAL watermarking process.

-
-
Parameters:
-
    -
  • image (Image) – Input image for visualization

  • -
  • prompt (str) – Text prompt used for generation

  • -
  • guidance_scale (float) – Guidance scale for diffusion process

  • -
  • decoder_inv (bool) – Whether to use decoder inversion

  • -
-
-
Return type:
-

DataForVisualization

-
-
Returns:
-

DataForVisualization object with SEAL-specific data

-
-
-
- -
- -
-
-

VideoShield

-
-
-class watermark.videoshield.video_shield.VideoShieldConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for VideoShield algorithm.

-
-
-
-
-initialize_parameters()[source]
-

Initialize VideoShield configuration.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.videoshield.video_shield.VideoShieldUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for VideoShield algorithm.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize the VideoShield watermarking utility.

-
-
-
- -
-
-create_watermark_and_return_w()[source]
-

Create watermark pattern and return watermarked initial latents.

-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.videoshield.video_shield.VideoShieldWatermark(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-

Main class for VideoShield watermarking algorithm.

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the VideoShield watermarking algorithm.

-
-
Parameters:
-

watermark_config (VideoShieldConfig) – Configuration instance of the VideoShield algorithm

-
-
-
- -
-
-get_data_for_visualize(video_frames, prompt='', guidance_scale=1, *args, **kwargs)[source]
-

Get VideoShield visualization data.

-

This method generates the necessary data for visualizing VideoShield watermarks, -including original watermarked latents and reversed latents from inversion.

-
-
Parameters:
-
    -
  • image – The image to visualize watermarks for (can be None for generation only)

  • -
  • prompt (str) – The text prompt used for generation

  • -
  • guidance_scale (float) – Guidance scale for generation and inversion

  • -
-
-
Return type:
-

DataForVisualization

-
-
Returns:
-

DataForVisualization object containing visualization data

-
-
-
- -
- -
-
-

VideoMark

-
-
-class watermark.videomark.video_mark.VideoMarkConfig(algorithm_config, diffusion_config, *args, **kwargs)[source]
-

Bases: BaseConfig

-

Config class for VideoMark algorithm.

-
-
-
-
-initialize_parameters()[source]
-

Initialize algorithm-specific parameters.

-
-
Return type:
-

None

-
-
-
- -
-
-property algorithm_name: str
-

Return the algorithm name.

-
- -
- -
-
-class watermark.videomark.video_mark.VideoMarkUtils(config, *args, **kwargs)[source]
-

Bases: object

-

Utility class for VideoMark algorithm.

-
-
-
-
-__init__(config, *args, **kwargs)[source]
-

Initialize PRC utility.

-
-
-
- -
-
-inject_watermark()[source]
-

Generate watermarked latents from PRC codeword.

-
-
Return type:
-

Tensor

-
-
-
- -
- -
-
-class watermark.videomark.video_mark.VideoMarkWatermark(watermark_config, *args, **kwargs)[source]
-

Bases: BaseWatermark

-

Main class for VideoMark watermarking algorithm.

-
-
-
-
-__init__(watermark_config, *args, **kwargs)[source]
-

Initialize the VideoShield watermarking algorithm.

-
-
Parameters:
-

watermark_config (VideoMarkConfig) – Configuration instance of the VideoMark algorithm

-
-
-
- -
-
-get_data_for_visualize(video_frames, prompt='', guidance_scale=1, *args, **kwargs)[source]
-

Get VideoMark visualization data.

-

This method generates the necessary data for visualizing VideoMark watermarks, -including original watermarked latents and reversed latents from inversion.

-
-
Parameters:
-
    -
  • image – The image to visualize watermarks for (can be None for generation only)

  • -
  • prompt (str) – The text prompt used for generation

  • -
  • guidance_scale (float) – Guidance scale for generation and inversion

  • -
-
-
Return type:
-

DataForVisualization

-
-
Returns:
-

DataForVisualization object containing visualization data

-
-
-
- -
- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/changelog.html b/docs/_build/html/changelog.html deleted file mode 100644 index 00765c8..0000000 --- a/docs/_build/html/changelog.html +++ /dev/null @@ -1,1020 +0,0 @@ - - - - - - - - - Changelog — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Changelog

-

All notable changes to MarkDiffusion will be documented in this file.

-
-

Version 1.0.0 (2025-01-XX)

-
-

Initial Release

-

Implemented Algorithms:

-
    -
  • Tree-Ring (TR) - Pattern-based watermarking

  • -
  • Ring-ID (RI) - Multi-key identification

  • -
  • ROBIN - Robust and invisible watermarking

  • -
  • WIND - Two-stage robust watermarking

  • -
  • SFW - Semantic Fourier watermarking

  • -
  • Gaussian-Shading (GS) - Performance-lossless watermarking

  • -
  • GaussMarker (GM) - Dual-domain watermarking

  • -
  • PRC - Undetectable watermarking

  • -
  • SEAL - Semantic-aware watermarking

  • -
  • VideoShield - Video watermarking

  • -
  • VideoMark - Distortion-free video watermarking

  • -
-

Features:

-
    -
  • Unified implementation framework for watermarking algorithms

  • -
  • Comprehensive evaluation module with 24 tools

  • -
  • 8 automated evaluation pipelines

  • -
  • Custom visualization tools for all algorithms

  • -
  • Support for both image and video watermarking

  • -
  • Extensive documentation and tutorials

  • -
-

Evaluation Tools:

-

Detectability: -- FundamentalSuccessRateCalculator -- DynamicThresholdSuccessRateCalculator

-

Image Attacks: -- JPEG Compression -- Gaussian Blur -- Gaussian Noise -- Rotation -- Crop & Scale -- Brightness Adjustment -- Masking -- Overlay -- Adaptive Noise Injection

-

Video Attacks: -- MPEG-4 Compression -- Frame Averaging -- Frame Swapping -- Video Codec Attack (H.264/H.265/VP9/AV1) -- Frame Rate Adapter -- Frame Interpolation Attack

-

Image Quality Metrics: -- PSNR (Peak Signal-to-Noise Ratio) -- SSIM (Structural Similarity Index) -- LPIPS (Learned Perceptual Image Patch Similarity) -- CLIP Score -- FID (Fréchet Inception Distance) -- Inception Score -- NIQE (Natural Image Quality Evaluator) -- BRISQUE -- VIF (Visual Information Fidelity) -- FSIM (Feature Similarity Index)

-

Video Quality Metrics: -- Subject Consistency -- Background Consistency -- Motion Smoothness -- Dynamic Degree -- Imaging Quality

-
-
-
-

Recent Updates

-

2025.10.10

-
    -
  • Added Mask, Overlay, AdaptiveNoiseInjection image attack tools

  • -
  • Thanks to Zheyu Fu for the contribution

  • -
-

2025.10.09

-
    -
  • Added VideoCodecAttack, FrameRateAdapter, FrameInterpolationAttack video attack tools

  • -
  • Thanks to Luyang Si for the contribution

  • -
-

2025.10.08

-
    -
  • Added SSIM, BRISQUE, VIF, FSIM image quality analyzers

  • -
  • Thanks to Huan Wang for the contribution

  • -
-

2025.10.07

-
    -
  • Added SFW (Semantic Fourier Watermarking) algorithm

  • -
  • Thanks to Huan Wang for the contribution

  • -
-

2025.10.07

-
    -
  • Added VideoMark watermarking algorithm

  • -
  • Thanks to Hanqian Li for the contribution

  • -
-

2025.09.29

-
    -
  • Added GaussMarker watermarking algorithm

  • -
  • Thanks to Luyang Si for the contribution

  • -
-
-
-

Upcoming Features

-

Planned for v1.1.0:

-
    -
  • Additional watermarking algorithms

  • -
  • More evaluation metrics

  • -
  • Enhanced visualization capabilities

  • -
  • Performance optimizations

  • -
  • Extended documentation

  • -
-

Under Consideration:

-
    -
  • Real-time watermarking support

  • -
  • Web interface for demonstration

  • -
  • Pre-trained model zoo

  • -
  • Integration with more diffusion models

  • -
  • Support for additional modalities (3D, audio)

  • -
-
-
-

Contributing

-

We welcome contributions! See Contributing to MarkDiffusion for guidelines.

-

To report bugs or request features, please open an issue on GitHub: -https://github.com/THU-BPM/MarkDiffusion/issues

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/citation.html b/docs/_build/html/citation.html deleted file mode 100644 index 5d0dff9..0000000 --- a/docs/_build/html/citation.html +++ /dev/null @@ -1,1101 +0,0 @@ - - - - - - - - - Citation — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Citation

-

If you use MarkDiffusion in your research, please cite our paper:

-
-

BibTeX

-
@article{pan2025markdiffusion,
-  title={MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models},
-  author={Pan, Leyi and Guan, Sheng and Fu, Zheyu and Si, Luyang and Wang, Zian and Hu, Xuming and King, Irwin and Yu, Philip S and Liu, Aiwei and Wen, Lijie},
-  journal={arXiv preprint arXiv:2509.10569},
-  year={2025}
-}
-
-
-
-
-

APA Style

-

Pan, L., Guan, S., Fu, Z., Si, L., Wang, Z., Hu, X., King, I., Yu, P. S., Liu, A., & Wen, L. (2025). -MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models. -arXiv preprint arXiv:2509.10569.

-
-
-

MLA Style

-

Pan, Leyi, et al. “MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models.” -arXiv preprint arXiv:2509.10569 (2025).

-
-
-

Algorithm-Specific Citations

-

If you use specific algorithms, please also cite their original papers:

-
-

Tree-Ring Watermark

-
@misc{wen2023treeringwatermarksfingerprintsdiffusion,
-   title={Tree-Ring Watermarks: Fingerprints for Diffusion Images that are Invisible and Robust},
-   author={Yuxin Wen and John Kirchenbauer and Jonas Geiping and Tom Goldstein},
-   year={2023},
-   eprint={2305.20030},
-   archivePrefix={arXiv},
-   primaryClass={cs.LG},
-   url={https://arxiv.org/abs/2305.20030},
-}
-
-
-
-
-

Ring-ID

-
@article{ci2024ringid,
-   title={RingID: Rethinking Tree-Ring Watermarking for Enhanced Multi-Key Identification},
-   author={Ci, Hai and Yang, Pei and Song, Yiren and Shou, Mike Zheng},
-   journal={arXiv preprint arXiv:2404.14055},
-   year={2024}
-}
-
-
-
-
-

ROBIN

-
@inproceedings{huangrobin,
-   title={ROBIN: Robust and Invisible Watermarks for Diffusion Models with Adversarial Optimization},
-   author={Huang, Huayang and Wu, Yu and Wang, Qian},
-   booktitle={The Thirty-eighth Annual Conference on Neural Information Processing Systems}
-}
-
-
-
-
-

WIND

-
@article{arabi2024hidden,
-   title={Hidden in the Noise: Two-Stage Robust Watermarking for Images},
-   author={Arabi, Kasra and Feuer, Benjamin and Witter, R Teal and Hegde, Chinmay and Cohen, Niv},
-   journal={arXiv preprint arXiv:2412.04653},
-   year={2024}
-}
-
-
-
-
-

SFW

-
@inproceedings{lee2025semantic,
-   title={Semantic Watermarking Reinvented: Enhancing Robustness and Generation Quality with Fourier Integrity},
-   author={Lee, Sung Ju and Cho, Nam Ik},
-   booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision},
-   pages={18759--18769},
-   year={2025}
-}
-
-
-
-
-

Gaussian-Shading

-
@article{yang2024gaussian,
-   title={Gaussian Shading: Provable Performance-Lossless Image Watermarking for Diffusion Models},
-   author={Yang, Zijin and Zeng, Kai and Chen, Kejiang and Fang, Han and Zhang, Weiming and Yu, Nenghai},
-   journal={arXiv preprint arXiv:2404.04956},
-   year={2024},
-}
-
-
-
-
-

GaussMarker

-
@misc{li2025gaussmarkerrobustdualdomainwatermark,
-   title={GaussMarker: Robust Dual-Domain Watermark for Diffusion Models},
-   author={Kecen Li and Zhicong Huang and Xinwen Hou and Cheng Hong},
-   year={2025},
-   eprint={2506.11444},
-   archivePrefix={arXiv},
-   primaryClass={cs.CR},
-   url={https://arxiv.org/abs/2506.11444},
-}
-
-
-
-
-

PRC

-
@article{gunn2025undetectable,
-   title={An undetectable watermark for generative image models},
-   author={Gunn, Sam and Zhao, Xuandong and Song, Dawn},
-   journal={arXiv preprint arXiv:2410.07369},
-   year={2024}
-}
-
-
-
-
-

SEAL

-
@article{arabi2025seal,
-   title={SEAL: Semantic Aware Image Watermarking},
-   author={Arabi, Kasra and Witter, R Teal and Hegde, Chinmay and Cohen, Niv},
-   journal={arXiv preprint arXiv:2503.12172},
-   year={2025}
-}
-
-
-
-
-

VideoShield

-
@inproceedings{hu2025videoshield,
-   title={VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking},
-   author={Runyi Hu and Jie Zhang and Yiming Li and Jiwei Li and Qing Guo and Han Qiu and Tianwei Zhang},
-   booktitle={International Conference on Learning Representations (ICLR)},
-   year={2025}
-}
-
-
-
-
-

VideoMark

-
@article{hu2025videomark,
-   title={VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models},
-   author={Hu, Xuming and Li, Hanqian and Li, Jungang and Liu, Aiwei},
-   journal={arXiv preprint arXiv:2504.16359},
-   year={2025}
-}
-
-
-
-
-
-

Acknowledgments

-

We would like to thank:

-
    -
  • All contributors to the MarkDiffusion project

  • -
  • The authors of the watermarking algorithms implemented in this toolkit

  • -
  • The open-source community for their valuable feedback and contributions

  • -
  • Research institutions supporting this work

  • -
-
-
-

Using MarkDiffusion in Publications

-

When using MarkDiffusion in your research:

-
    -
  1. Cite the main MarkDiffusion paper (required)

  2. -
  3. Cite specific algorithm papers you use (required)

  4. -
  5. Mention the toolkit in your acknowledgments

  6. -
  7. Link to the GitHub repository

  8. -
-

Example acknowledgment text:

-
-

“This research utilized MarkDiffusion [1], an open-source toolkit for generative -watermarking. We specifically employed the Gaussian-Shading algorithm [2] for -watermark embedding and detection.”

-
-
-
-

License

-

MarkDiffusion is released under the MIT License. See the LICENSE file for details.

-

When using MarkDiffusion, please ensure compliance with the licenses of:

-
    -
  • Individual watermarking algorithms

  • -
  • Pre-trained models

  • -
  • Datasets used for evaluation

  • -
-
-
-

Contact

-

For questions about citation or collaboration:

- -
-
-

Updates

-

This citation information was last updated: November 2025

-

For the most up-to-date citation information, please check:

-
    -
  • The project README

  • -
  • The paper on arXiv

  • -
  • The project homepage

  • -
-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/code_of_conduct.html b/docs/_build/html/code_of_conduct.html deleted file mode 100644 index c0f264c..0000000 --- a/docs/_build/html/code_of_conduct.html +++ /dev/null @@ -1,938 +0,0 @@ - - - - - - - - - Code of Conduct — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Code of Conduct

-
-

Community Standards

-

We are committed to providing a welcoming and harassment-free experience for everyone in the MarkDiffusion community.

-
-

Important

-

For the complete Code of Conduct, please refer to the code_of_conduct.md -file in the repository root.

-
-

The complete document includes detailed information about:

-
    -
  • Our pledge to create an inclusive, diverse, and healthy community

  • -
  • Standards for acceptable and unacceptable behavior

  • -
  • Enforcement responsibilities of community leaders

  • -
  • Scope of application

  • -
  • Reporting procedures for violations

  • -
  • Enforcement guidelines and consequences

  • -
-
-
-

Quick Reference

-

Expected Behavior:

-
    -
  • Demonstrate empathy and kindness toward other people

  • -
  • Be respectful of differing opinions, viewpoints, and experiences

  • -
  • Give and gracefully accept constructive feedback

  • -
  • Focus on what is best for the overall community

  • -
-

Unacceptable Behavior:

-
    -
  • Use of sexualized language or imagery

  • -
  • Trolling, insulting or derogatory comments, and personal or political attacks

  • -
  • Public or private harassment

  • -
  • Publishing others’ private information without permission

  • -
-
-
-

Reporting

-

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders.

-

For full details, see code_of_conduct.md.

-
-
-

Attribution

-

This Code of Conduct is adapted from the Contributor Covenant, version 2.0.

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/contributing.html b/docs/_build/html/contributing.html deleted file mode 100644 index 9554cb6..0000000 --- a/docs/_build/html/contributing.html +++ /dev/null @@ -1,1140 +0,0 @@ - - - - - - - - - Contributing to MarkDiffusion — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Contributing to MarkDiffusion

-

We welcome contributions from the community! This guide will help you get started.

-
-

Note

-

Please read our Code of Conduct and the detailed -Contributing Guidelines in the repository root before contributing.

-
-
-

Overview

-

This document provides technical guidelines for contributing to MarkDiffusion. For general contribution -workflow (forking, cloning, creating branches, submitting PRs), please refer to the -Contributing Guidelines in the repository root.

-
-
-

Development Guidelines

-
-

Code Style

-

We follow PEP 8 style guidelines. Please ensure your code:

-
    -
  • Uses 4 spaces for indentation

  • -
  • Has descriptive variable and function names

  • -
  • Includes docstrings for all public functions and classes

  • -
  • Stays under 100 characters per line when practical

  • -
-

Example:

-
def generate_watermarked_media(self, input_data, **kwargs):
-    """
-    Generate watermarked media from input.
-
-    Args:
-        input_data (str): Text prompt or input data
-        **kwargs: Additional generation parameters
-
-    Returns:
-        PIL.Image: Watermarked image
-
-    Examples:
-        >>> watermark = AutoWatermark.load('GS', 'config/GS.json', config)
-        >>> image = watermark.generate_watermarked_media("A sunset")
-    """
-    # Implementation
-    pass
-
-
-
-
-

Documentation

-

All new features should include:

-
    -
  • Docstrings following Google or NumPy style

  • -
  • Type hints for function arguments and returns

  • -
  • Usage examples in docstrings

  • -
  • Updates to relevant documentation pages

  • -
-

Example with type hints:

-
from typing import Union, Dict, Any
-from PIL import Image
-
-def detect_watermark_in_media(
-    self,
-    media: Union[Image.Image, list],
-    **kwargs: Any
-) -> Dict[str, Any]:
-    """
-    Detect watermark in media.
-
-    Args:
-        media: PIL Image or list of PIL Images (for video)
-        **kwargs: Additional detection parameters
-
-    Returns:
-        Dictionary containing detection results with keys:
-            - detected (bool): Whether watermark was detected
-            - score (float): Detection confidence score
-            - threshold (float): Detection threshold used
-    """
-    pass
-
-
-
-
-

Testing

-

All new code should include tests:

-
import unittest
-from watermark.auto_watermark import AutoWatermark
-
-class TestMyFeature(unittest.TestCase):
-    def setUp(self):
-        """Set up test fixtures."""
-        self.watermark = AutoWatermark.load('GS', 'config/GS.json', config)
-
-    def test_generation(self):
-        """Test watermarked image generation."""
-        image = self.watermark.generate_watermarked_media("Test prompt")
-        self.assertIsNotNone(image)
-        self.assertEqual(image.size, (512, 512))
-
-    def test_detection(self):
-        """Test watermark detection."""
-        image = self.watermark.generate_watermarked_media("Test prompt")
-        result = self.watermark.detect_watermark_in_media(image)
-        self.assertTrue(result['detected'])
-
-
-

Run tests:

-
python -m pytest test/
-# Or for specific test
-python -m pytest test/test_watermark.py::TestMyFeature::test_generation
-
-
-
-
-
-

Contribution Process

-
-

Adding a New Algorithm

-

To add a new watermarking algorithm:

-
    -
  1. Create algorithm directory structure

    -
    watermark/my_algorithm/
    -├── __init__.py
    -├── my_algorithm.py
    -detection/my_algorithm/
    -├── __init__.py
    -├── my_algorithm_detection.py
    -visualize/my_algorithm/
    -├── __init__.py
    -├── my_algorithm_visualizer.py
    -config/
    -├── MyAlgorithm.json
    -test/
    -├── test_my_algorithm.py
    -
    -
    -
  2. -
  3. Implement the algorithm

    -

    Implement the watermark generation and detection logic.

    -
  4. -
  5. Add configuration

    -

    Create config/MyAlgorithm.json with algorithm parameters.

    -
  6. -
  7. Register algorithm

    -

    Add to watermark/auto_watermark.py:

    -
    from watermark.my_algorithm.my_algorithm import MyAlgorithm
    -
    -class AutoWatermark:
    -    ALGORITHM_MAP = {
    -        # ... existing algorithms
    -        'MA': MyAlgorithm,
    -    }
    -
    -
    -
  8. -
  9. Write tests

    -

    Create comprehensive tests in test/test_my_algorithm.py.

    -
  10. -
  11. Update documentation

    -
      -
    • Add algorithm description to docs/user_guide/algorithms.rst

    • -
    • Update docs/index.rst to list the new algorithm

    • -
    • Add usage examples

    • -
    -
  12. -
  13. Submit pull request

    -

    See Pull Request Guidelines below.

    -
  14. -
-
-
-

Adding Evaluation Tools

-

To add a new evaluation tool:

-
    -
  1. Implement the tool

    -

    For image attacks:

    -
    from evaluation.tools.image_editor import BaseImageEditor
    -
    -class MyAttack(BaseImageEditor):
    -    def __init__(self, param1, param2, **kwargs):
    -        super().__init__(**kwargs)
    -        self.param1 = param1
    -        self.param2 = param2
    -
    -    def edit_image(self, image):
    -        # Implement attack
    -        return modified_image
    -
    -
    -

    For quality metrics:

    -
    from evaluation.tools.image_quality_analyzer import BaseImageQualityAnalyzer
    -
    -class MyMetric(BaseImageQualityAnalyzer):
    -    def analyze(self, image1, image2=None):
    -        # Implement metric
    -        return score
    -
    -
    -
  2. -
  3. Add tests

  4. -
  5. Update documentation

  6. -
  7. Submit pull request

  8. -
-
-
-
-

Submission Checklist

-

Before submitting your contribution, ensure:

-

Testing

-
python -m pytest test/
-
-
-

Code Style

-
flake8 watermark/ detection/ evaluation/ visualize/
-black --check watermark/ detection/ evaluation/ visualize/
-
-
-

Documentation

-
    -
  • Update relevant documentation

  • -
  • Add entry to CHANGELOG.md

  • -
  • Ensure docstrings are complete

  • -
-

Pull Request

-

For the complete pull request process and guidelines, please refer to contributing.md -in the repository root.

-
-
-

Additional Information

-

Community Guidelines

-

All participants are expected to follow our Code of Conduct. -Please be respectful, constructive, and help create a welcoming environment for everyone.

-

Reporting Issues

-

For bug reports and feature requests, please use the appropriate templates configured in the GitHub repository.

-

Questions?

-

If you have questions:

- -

Contact

-

For major contributions or collaborations:

- -

Thank you for contributing to MarkDiffusion!

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html deleted file mode 100644 index 8784bd6..0000000 --- a/docs/_build/html/genindex.html +++ /dev/null @@ -1,2682 +0,0 @@ - - - - - - - - Index — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Index

- -
- _ - | A - | B - | C - | D - | E - | F - | G - | H - | I - | J - | K - | L - | M - | N - | O - | P - | Q - | R - | S - | T - | U - | V - | W - -
-

_

- - - -
- -

A

- - - -
- -

B

- - - -
- -

C

- - - -
- -

D

- - - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - - -
- -

H

- - -
- -

I

- - - -
- -

J

- - -
- -

K

- - - -
- -

L

- - - -
- -

M

- - - -
- -

N

- - - -
- -

O

- - - -
- -

P

- - - -
- -

Q

- - - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
    -
  • - utils.media_utils - -
  • -
  • - utils.pipeline_utils - -
  • -
  • - utils.utils - -
  • -
- -

V

- - - -
    -
  • - visualize.gs.gs_visualizer - -
  • -
  • - visualize.prc.prc_visualizer - -
  • -
  • - visualize.robin.robin_visualizer - -
  • -
  • - visualize.seal.seal_visualizer - -
  • -
  • - visualize.sfw.sfw_visualizer - -
  • -
  • - visualize.tr.tr_visualizer - -
  • -
  • - visualize.videomark.video_mark_visualizer - -
  • -
  • - visualize.videoshield.video_shield_visualizer - -
  • -
  • - visualize.wind.wind_visualizer - -
  • -
- -

W

- - - -
- - - -
-
-
- -
- -
-

© Copyright 2025, MarkDiffusion Team.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html deleted file mode 100644 index 25fcdc1..0000000 --- a/docs/_build/html/index.html +++ /dev/null @@ -1,1163 +0,0 @@ - - - - - - - - - MarkDiffusion Documentation — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

MarkDiffusion Documentation

-Homepage - -Paper - -HF Models - -
-

Welcome to MarkDiffusion

-

MarkDiffusion is an open-source Python toolkit for generative watermarking of latent diffusion models. -As the use of diffusion-based generative models expands, ensuring the authenticity and origin of generated -media becomes critical. MarkDiffusion simplifies the access, understanding, and assessment of watermarking -technologies, making it accessible to both researchers and the broader community.

-
-

Note

-

If you are interested in LLM watermarking (text watermark), please refer to the -MarkLLM toolkit from our group.

-
-
-
-

Key Features

-
-
🚀 Unified Implementation Framework

MarkDiffusion provides a modular architecture supporting eleven state-of-the-art generative -image/video watermarking algorithms of LDMs.

-
-
📦 Comprehensive Algorithm Support

Currently implements 11 watermarking algorithms from two major categories:

-
    -
  • Pattern-based methods: Tree-Ring, Ring-ID, ROBIN, WIND, SFW

  • -
  • Key-based methods: Gaussian-Shading, GaussMarker, PRC, SEAL, VideoShield, VideoMark

  • -
-
-
🔍 Visualization Solutions

The toolkit includes custom visualization tools that enable clear and insightful views into -how different watermarking algorithms operate under various scenarios.

-
-
📊 Comprehensive Evaluation Module

With 24 evaluation tools covering detectability, robustness, and impact on output quality, -MarkDiffusion provides comprehensive assessment capabilities with 8 automated evaluation pipelines.

-
-
-
-
-

Quick Example

-

Here’s a simple example to get you started with MarkDiffusion:

-
import torch
-from watermark.auto_watermark import AutoWatermark
-from utils.diffusion_config import DiffusionConfig
-from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
-
-# Device setup
-device = 'cuda' if torch.cuda.is_available() else 'cpu'
-
-# Configure diffusion pipeline
-scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler")
-pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device)
-diffusion_config = DiffusionConfig(
-    scheduler=scheduler,
-    pipe=pipe,
-    device=device,
-    image_size=(512, 512),
-    num_inference_steps=50,
-    guidance_scale=7.5,
-    gen_seed=42,
-    inversion_type="ddim"
-)
-
-# Load watermark algorithm
-watermark = AutoWatermark.load('TR',
-                              algorithm_config='config/TR.json',
-                              diffusion_config=diffusion_config)
-
-# Generate watermarked media
-prompt = "A beautiful sunset over the ocean"
-watermarked_image = watermark.generate_watermarked_media(prompt)
-
-# Detect watermark
-detection_result = watermark.detect_watermark_in_media(watermarked_image)
-print(f"Watermark detected: {detection_result}")
-
-
-
-
-

Documentation Contents

- - - - -
-
-
-

Indices and tables

- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/installation.html b/docs/_build/html/installation.html deleted file mode 100644 index 1169aaa..0000000 --- a/docs/_build/html/installation.html +++ /dev/null @@ -1,1072 +0,0 @@ - - - - - - - - - Installation — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Installation

-

This guide will help you install MarkDiffusion and its dependencies.

-
-

Requirements

-
    -
  • Python 3.10 or higher

  • -
  • PyTorch (with CUDA support recommended for GPU acceleration)

  • -
  • 8GB+ RAM (16GB+ recommended for video watermarking)

  • -
  • CUDA-compatible GPU (optional but highly recommended)

  • -
-
-
-

Basic Installation

-
    -
  1. Clone the Repository

    -
    git clone https://github.com/THU-BPM/MarkDiffusion.git
    -cd MarkDiffusion
    -
    -
    -
  2. -
  3. Install Dependencies

    -
    pip install -r requirements.txt
    -
    -
    -
  4. -
  5. Download Pre-trained Models

    -

    MarkDiffusion uses pre-trained models stored on Hugging Face. Download the required models:

    -
    # The models will be downloaded to the ckpts/ directory
    -# Visit: https://huggingface.co/Generative-Watermark-Toolkits
    -
    -
    -

    For each algorithm you plan to use, download the corresponding model weights from the -Generative-Watermark-Toolkits -repository and place them in the appropriate ckpts/ subdirectory.

    -
  6. -
-
-
-

Installation with Conda

-

If you prefer using Conda for environment management:

-
# Create a new conda environment
-conda create -n markdiffusion python=3.10
-conda activate markdiffusion
-
-# Install PyTorch with CUDA support
-conda install pytorch torchvision torchaudio pytorch-cuda=12.6 -c pytorch -c nvidia
-
-# Install other dependencies
-pip install -r requirements.txt
-
-
-
-
-

GPU Support

-

For GPU acceleration, make sure you have:

-
    -
  1. NVIDIA GPU with CUDA support

  2. -
  3. CUDA Toolkit installed (version 11.8 or higher)

  4. -
  5. cuDNN library

  6. -
-

To verify GPU availability:

-
import torch
-print(f"CUDA available: {torch.cuda.is_available()}")
-print(f"CUDA version: {torch.version.cuda}")
-print(f"Device count: {torch.cuda.device_count()}")
-
-
-
-
-

Algorithm-Specific Setup

-

Some algorithms require additional model checkpoints beyond the base installation:

-
-

GaussMarker (GM)

-

GaussMarker requires two pre-trained models for watermark detection and restoration:

-
    -
  1. GNR Model (Generative Noise Restoration): A UNet-based model for restoring watermark bits from noisy latents

  2. -
  3. Fuser Model: A classifier for fusion-based watermark detection decisions

  4. -
-

Setup:

-
# Create the checkpoint directory
-mkdir -p watermark/gm/ckpts/
-
-# Download models from Hugging Face
-# Visit: https://huggingface.co/Generative-Watermark-Toolkits/GaussMarker
-# Place the following files:
-#   - model_final.pth -> watermark/gm/ckpts/model_final.pth
-#   - sd21_cls2.pkl -> watermark/gm/ckpts/sd21_cls2.pkl
-
-
-

Configuration Path (in config/GM.json):

-
    -
  • gnr_checkpoint: "watermark/gm/ckpts/model_final.pth"

  • -
  • fuser_checkpoint: "watermark/gm/ckpts/sd21_cls2.pkl"

  • -
-
-
-

SEAL

-

SEAL uses pre-trained models from Hugging Face for caption generation and embedding:

-
    -
  1. BLIP2 Model: For generating image captions (blip2-flan-t5-xl)

  2. -
  3. Sentence Transformer: For caption embedding

  4. -
-

Setup:

-
# These models will be automatically downloaded from Hugging Face on first use
-# Or you can pre-download them:
-
-# Download BLIP2 model
-python -c "from transformers import Blip2Processor, Blip2ForConditionalGeneration; \
-Blip2Processor.from_pretrained('Salesforce/blip2-flan-t5-xl'); \
-Blip2ForConditionalGeneration.from_pretrained('Salesforce/blip2-flan-t5-xl')"
-
-# Download sentence transformer (if using custom fine-tuned model)
-# Update config/SEAL.json paths accordingly
-
-
-

Configuration (in config/SEAL.json):

-
    -
  • cap_processor: Path or model name for BLIP2 processor

  • -
  • cap_model: Path or model name for BLIP2 model

  • -
  • sentence_model: Path or model name for sentence transformer

  • -
-
-

Note

-

SEAL models are large (~15GB for BLIP2). Ensure you have sufficient disk space and memory.

-
-
-
-

Other Algorithms

-

The following algorithms work with the base installation and do not require additional checkpoints:

-
    -
  • Tree-Ring (TR), Ring-ID (RI), ROBIN, WIND, SFW: Pattern-based methods using frequency domain manipulation

  • -
  • Gaussian-Shading (GS), PRC: Key-based methods with built-in watermark generation

  • -
  • VideoShield, VideoMark: Video watermarking algorithms using temporal consistency

  • -
-
-
-
-

Verification

-

To verify your installation, run the test suite:

-
python -m pytest test/
-
-
-

Or try a simple example:

-
from watermark.auto_watermark import AutoWatermark
-print("MarkDiffusion successfully installed!")
-
-
-
-
-

Troubleshooting

-
-

Common Issues

-

Issue: Module not found error

-

Solution: Make sure all dependencies are installed:

-
pip install -r requirements.txt --upgrade
-
-
-

Issue: Model weights not found

-

Solution: Download the required models from Hugging Face and place them in the correct directory structure.

-
-
-

Getting Help

-

If you encounter issues:

-
    -
  1. Check the GitHub Issues

  2. -
  3. Consult the FAQ

  4. -
  5. Open a new issue with detailed information about your problem

  6. -
-
-
-
-

Next Steps

-

Now that you have installed MarkDiffusion, proceed to:

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv deleted file mode 100644 index 70ea1dc..0000000 Binary files a/docs/_build/html/objects.inv and /dev/null differ diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html deleted file mode 100644 index 4b64eac..0000000 --- a/docs/_build/html/py-modindex.html +++ /dev/null @@ -1,1240 +0,0 @@ - - - - - - - - Python Module Index — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - -

Python Module Index

- -
- d | - e | - i | - u | - v | - w -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- d
- detection -
    - detection.gm.gm_detection -
    - detection.gs.gs_detection -
    - detection.prc.prc_detection -
    - detection.robin.robin_detection -
    - detection.seal.seal_detection -
    - detection.sfw.sfw_detection -
    - detection.tr.tr_detection -
    - detection.videomark.videomark_detection -
    - detection.videoshield.videoshield_detection -
    - detection.wind.wind_detection -
 
- e
- evaluation -
    - evaluation.dataset -
    - evaluation.pipelines.detection -
    - evaluation.pipelines.image_quality_analysis -
    - evaluation.pipelines.video_quality_analysis -
    - evaluation.tools.image_editor -
    - evaluation.tools.image_quality_analyzer -
    - evaluation.tools.success_rate_calculator -
    - evaluation.tools.video_editor -
    - evaluation.tools.video_quality_analyzer -
 
- i
- inversions -
    - inversions.base_inversion -
    - inversions.ddim_inversion -
    - inversions.exact_inversion -
 
- u
- utils -
    - utils.callbacks -
    - utils.diffusion_config -
    - utils.media_utils -
    - utils.pipeline_utils -
    - utils.utils -
 
- v
- visualize -
    - visualize.gm.gm_visualizer -
    - visualize.gs.gs_visualizer -
    - visualize.prc.prc_visualizer -
    - visualize.robin.robin_visualizer -
    - visualize.seal.seal_visualizer -
    - visualize.sfw.sfw_visualizer -
    - visualize.tr.tr_visualizer -
    - visualize.videomark.video_mark_visualizer -
    - visualize.videoshield.video_shield_visualizer -
    - visualize.wind.wind_visualizer -
 
- w
- watermark -
    - watermark.gm.gm -
    - watermark.gs.gs -
    - watermark.prc.prc -
    - watermark.robin.robin -
    - watermark.seal.seal -
    - watermark.sfw.sfw -
    - watermark.tr.tr -
    - watermark.videomark.video_mark -
    - watermark.videoshield.video_shield -
    - watermark.wind.wind -
- - -
-
-
- -
- -
-

© Copyright 2025, MarkDiffusion Team.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/quickstart.html b/docs/_build/html/quickstart.html deleted file mode 100644 index e5555ce..0000000 --- a/docs/_build/html/quickstart.html +++ /dev/null @@ -1,1122 +0,0 @@ - - - - - - - - - Quick Start — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Quick Start

-

This guide will help you get started with MarkDiffusion quickly.

-
-

Basic Workflow

-

The typical workflow with MarkDiffusion consists of three main steps:

-
    -
  1. Configure the diffusion model and watermarking algorithm

  2. -
  3. Generate watermarked images or videos

  4. -
  5. Detect watermarks in media

  6. -
-
-
-

Step 1: Setup and Configuration

-

First, import the necessary modules and configure your diffusion model:

-
import torch
-from watermark.auto_watermark import AutoWatermark
-from utils.diffusion_config import DiffusionConfig
-from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
-
-# Device setup
-device = 'cuda' if torch.cuda.is_available() else 'cpu'
-
-# Configure the diffusion model
-model_id = "stabilityai/stable-diffusion-2-1"
-scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler")
-pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device)
-
-# Create diffusion configuration
-diffusion_config = DiffusionConfig(
-    scheduler=scheduler,
-    pipe=pipe,
-    device=device,
-    image_size=(512, 512),
-    num_inference_steps=50,
-    guidance_scale=7.5,
-    gen_seed=42,
-    inversion_type="ddim"
-)
-
-
-
-
-

Step 2: Load a Watermarking Algorithm

-

MarkDiffusion supports multiple watermarking algorithms. Here’s how to load one:

-
# Load Tree-Ring watermarking algorithm
-watermark = AutoWatermark.load(
-    'TR',                              # Algorithm name
-    algorithm_config='config/TR.json', # Configuration file
-    diffusion_config=diffusion_config  # Diffusion settings
-)
-
-
-

Available algorithms:

-
    -
  • TR: Tree-Ring

  • -
  • RI: Ring-ID

  • -
  • ROBIN: ROBIN

  • -
  • WIND: WIND

  • -
  • SFW: Semantic Fourier Watermark

  • -
  • GS: Gaussian-Shading

  • -
  • GM: GaussMarker

  • -
  • PRC: PRC

  • -
  • SEAL: SEAL

  • -
  • VideoShield: VideoShield

  • -
  • VideoMark: VideoMark

  • -
-
-
-

Step 3: Generate Watermarked Media

-
-

Image Generation

-
# Define your prompt
-prompt = "A beautiful landscape with mountains and a lake at sunset"
-
-# Generate watermarked image
-watermarked_image = watermark.generate_watermarked_media(prompt)
-
-# Save the image
-watermarked_image.save("watermarked_output.png")
-
-# Display the image
-watermarked_image.show()
-
-
-
-
-

Video Generation

-

For video watermarking algorithms (VideoShield, VideoMark):

-
# Load video watermarking algorithm
-video_watermark = AutoWatermark.load(
-    'VideoShield',
-    algorithm_config='config/VideoShield.json',
-    diffusion_config=video_diffusion_config
-)
-
-# Generate watermarked video
-prompt = "A cat walking through a garden"
-video_frames = video_watermark.generate_watermarked_media(prompt)
-
-# Save video frames
-for i, frame in enumerate(video_frames):
-    frame.save(f"output/frame_{i:04d}.png")
-
-
-
-
-
-

Step 4: Detect Watermarks

-

After generating watermarked media, you can detect the watermark:

-
# Detect watermark in the generated image
-detection_result = watermark.detect_watermark_in_media(watermarked_image)
-
-# Print detection results
-print(f"Detection result: {detection_result}")
-
-# For algorithms that return a score
-if 'score' in detection_result:
-    print(f"Confidence score: {detection_result['score']}")
-    print(f"Threshold: {detection_result.get('threshold', 'N/A')}")
-
-
-
-
-

Complete Example

-

Here’s a complete example putting it all together:

-
import torch
-from watermark.auto_watermark import AutoWatermark
-from utils.diffusion_config import DiffusionConfig
-from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
-
-def main():
-    # Setup
-    device = 'cuda' if torch.cuda.is_available() else 'cpu'
-    model_id = "stabilityai/stable-diffusion-2-1"
-
-    # Load diffusion model
-    scheduler = DPMSolverMultistepScheduler.from_pretrained(
-        model_id, subfolder="scheduler"
-    )
-    pipe = StableDiffusionPipeline.from_pretrained(
-        model_id, scheduler=scheduler
-    ).to(device)
-
-    # Configure diffusion
-    diffusion_config = DiffusionConfig(
-        scheduler=scheduler,
-        pipe=pipe,
-        device=device,
-        image_size=(512, 512),
-        num_inference_steps=50,
-        guidance_scale=7.5,
-        gen_seed=42,
-        inversion_type="ddim"
-    )
-
-    # Load watermarking algorithm
-    watermark = AutoWatermark.load(
-        'GS',  # Gaussian-Shading
-        algorithm_config='config/GS.json',
-        diffusion_config=diffusion_config
-    )
-
-    # Generate watermarked image
-    prompt = "A serene Japanese garden with cherry blossoms"
-    print("Generating watermarked image...")
-    watermarked_image = watermark.generate_watermarked_media(prompt)
-
-    # Save image
-    watermarked_image.save("output.png")
-    print("Image saved to output.png")
-
-    # Detect watermark
-    print("Detecting watermark...")
-    detection_result = watermark.detect_watermark_in_media(watermarked_image)
-    print(f"Detection result: {detection_result}")
-
-    return watermarked_image, detection_result
-
-if __name__ == "__main__":
-    image, result = main()
-
-
-
-
-

Comparing Multiple Algorithms

-

You can easily compare different algorithms:

-
algorithms = ['TR', 'GS', 'ROBIN', 'SEAL']
-prompt = "A futuristic city skyline at night"
-
-results = {}
-for algo_name in algorithms:
-    print(f"\nTesting {algo_name}...")
-
-    # Load algorithm
-    watermark = AutoWatermark.load(
-        algo_name,
-        algorithm_config=f'config/{algo_name}.json',
-        diffusion_config=diffusion_config
-    )
-
-    # Generate and detect
-    img = watermark.generate_watermarked_media(prompt)
-    detection = watermark.detect_watermark_in_media(img)
-
-    results[algo_name] = {
-        'image': img,
-        'detection': detection
-    }
-
-    # Save image
-    img.save(f"output_{algo_name}.png")
-
-# Print comparison
-print("\n=== Comparison Results ===")
-for algo, data in results.items():
-    print(f"{algo}: {data['detection']}")
-
-
-
-
-

Next Steps

-

Now that you’re familiar with the basics:

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html deleted file mode 100644 index 09e0172..0000000 --- a/docs/_build/html/search.html +++ /dev/null @@ -1,955 +0,0 @@ - - - - - - - - Search — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • -
-
-
-
-
- - - - -
- -
- -
-
-
- -
- -
-

© Copyright 2025, MarkDiffusion Team.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js deleted file mode 100644 index 0812ce1..0000000 --- a/docs/_build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({"alltitles": {"APA Style": [[7, "apa-style"]], "API Reference": [[10, null]], "Acknowledgments": [[7, "acknowledgments"]], "Adding Evaluation Tools": [[9, "adding-evaluation-tools"]], "Adding a New Algorithm": [[9, "adding-a-new-algorithm"]], "Additional Information": [[9, "additional-information"]], "Additional Resources": [[10, null]], "Advanced Visualization": [[16, "advanced-visualization"]], "Algorithm Comparison": [[14, "algorithm-comparison"], [14, "id1"]], "Algorithm Configuration": [[17, "algorithm-configuration"]], "Algorithm Overview": [[14, "algorithm-overview"]], "Algorithm-Specific Citations": [[7, "algorithm-specific-citations"]], "Algorithm-Specific Setup": [[11, "algorithm-specific-setup"]], "Algorithm-Specific Visualizers": [[4, "algorithm-specific-visualizers"]], "All formats": [[0, "all-formats"]], "Attribution": [[8, "attribution"]], "AutoVisualizer": [[4, "autovisualizer"]], "AutoWatermark": [[5, "autowatermark"]], "Base Detector": [[1, "base-detector"]], "Base Inversion": [[3, "module-inversions.base_inversion"]], "Base Visualizer": [[4, "base-visualizer"]], "Base Watermark": [[5, "base-watermark"]], "Basic Detection": [[17, "basic-detection"]], "Basic Detection Evaluation": [[15, "basic-detection-evaluation"]], "Basic Installation": [[11, "basic-installation"]], "Basic Usage": [[16, "basic-usage"]], "Basic Video Watermarking": [[13, "basic-video-watermarking"]], "Basic Workflow": [[12, "basic-workflow"], [17, "basic-workflow"]], "Batch Detection": [[17, "batch-detection"]], "Batch Processing": [[13, "batch-processing"]], "Best Practices": [[15, "best-practices"]], "BibTeX": [[7, "bibtex"]], "Build Warnings": [[0, "build-warnings"]], "Building HTML Documentation": [[0, "building-html-documentation"]], "Building MarkDiffusion Documentation": [[0, null]], "Building Other Formats": [[0, "building-other-formats"]], "Callbacks": [[3, "module-utils.callbacks"]], "Changelog": [[6, null]], "Choosing the Right Algorithm": [[14, "choosing-the-right-algorithm"]], "Citation": [[7, null]], "Cleaning Build Files": [[0, "cleaning-build-files"]], "Clear Cache": [[0, "clear-cache"]], "Code Style": [[9, "code-style"]], "Code of Conduct": [[8, null]], "Combined Attacks": [[15, "combined-attacks"]], "Common Issues": [[11, "common-issues"]], "Community Standards": [[8, "community-standards"]], "Compare Multiple Algorithms": [[15, "compare-multiple-algorithms"]], "Compared Quality Analysis": [[15, "compared-quality-analysis"]], "Comparing Methods": [[16, "comparing-methods"]], "Comparing Multiple Algorithms": [[12, "comparing-multiple-algorithms"]], "Complete Example": [[12, "complete-example"]], "Comprehensive Evaluation": [[15, "comprehensive-evaluation"]], "Configuration": [[17, "configuration"]], "Configuration File Structure": [[17, "configuration-file-structure"]], "Contact": [[7, "contact"]], "Contributing": [[6, "contributing"]], "Contributing to Documentation": [[0, "contributing-to-documentation"]], "Contributing to MarkDiffusion": [[9, null]], "Contribution Process": [[9, "contribution-process"]], "Custom Layout": [[16, "custom-layout"]], "DDIM Inversion": [[3, "module-inversions.ddim_inversion"]], "Datasets": [[2, "module-evaluation.dataset"]], "Detectability Evaluation": [[15, "detectability-evaluation"]], "Detection": [[17, "detection"]], "Detection API": [[1, null]], "Detection Methods": [[1, "detection-methods"]], "Detection Metrics": [[15, "detection-metrics"]], "Detection Pipelines": [[2, "module-evaluation.pipelines.detection"]], "Detection with Custom Parameters": [[17, "detection-with-custom-parameters"]], "Development Guidelines": [[9, "development-guidelines"]], "Diffusion Configuration": [[3, "module-utils.diffusion_config"]], "Diffusion Model Setup": [[17, "diffusion-model-setup"]], "DiffusionConfig Parameters": [[17, "diffusionconfig-parameters"]], "Direct Quality Analysis": [[15, "direct-quality-analysis"]], "Documentation": [[9, "documentation"]], "Documentation Contents": [[10, "documentation-contents"]], "Documentation Structure": [[0, "documentation-structure"]], "Evaluation": [[15, null]], "Evaluation API": [[2, null]], "Evaluation Components": [[15, "evaluation-components"]], "Evaluation Dimensions": [[15, "evaluation-dimensions"]], "Evaluation Tools": [[2, "evaluation-tools"]], "Exact Inversion": [[3, "module-inversions.exact_inversion"]], "Examples": [[5, null]], "Fixed Threshold Evaluation": [[15, "fixed-threshold-evaluation"]], "Full Evaluation Suite": [[15, "full-evaluation-suite"]], "GPU Support": [[11, "gpu-support"]], "GaussMarker": [[5, "module-watermark.gm.gm"], [7, "gaussmarker"]], "GaussMarker (GM)": [[11, "gaussmarker-gm"], [14, "gaussmarker-gm"], [16, "gaussmarker-gm"]], "GaussMarker Detection": [[1, "module-detection.gm.gm_detection"]], "GaussMarker Visualizer": [[4, "module-visualize.gm.gm_visualizer"]], "Gaussian-Shading": [[7, "gaussian-shading"]], "Gaussian-Shading (GS)": [[14, "gaussian-shading-gs"], [16, "gaussian-shading-gs"]], "Gaussian-Shading Detection": [[1, "module-detection.gs.gs_detection"]], "Gaussian-Shading Visualization": [[13, "gaussian-shading-visualization"]], "Gaussian-Shading Visualizer": [[4, "module-visualize.gs.gs_visualizer"]], "Gaussian-Shading Watermark": [[5, "module-watermark.gs.gs"]], "General Utilities": [[3, "module-utils.utils"]], "Generation": [[17, "generation"]], "Getting Help": [[11, "getting-help"]], "Getting Started": [[10, null]], "Group Quality Analysis": [[15, "group-quality-analysis"]], "Image Attacks": [[15, "image-attacks"]], "Image Editors (Attacks)": [[2, "module-evaluation.tools.image_editor"]], "Image Generation": [[12, "image-generation"], [17, "image-generation"]], "Image Quality Analysis Pipelines": [[2, "module-evaluation.pipelines.image_quality_analysis"]], "Image Quality Analyzers": [[2, "module-evaluation.tools.image_quality_analyzer"]], "Image Quality Evaluation": [[13, "image-quality-evaluation"]], "Image Quality Metrics": [[15, "image-quality-metrics"]], "Indices and tables": [[10, "indices-and-tables"]], "Initial Release": [[6, "initial-release"]], "Installation": [[0, "installation"], [11, null]], "Installation with Conda": [[11, "installation-with-conda"]], "Inversions": [[3, "inversions"]], "Key Features": [[10, "key-features"]], "Key-Based Methods": [[14, "key-based-methods"]], "License": [[7, "license"]], "Links": [[0, "links"]], "Live Reload (Development)": [[0, "live-reload-development"]], "MLA Style": [[7, "mla-style"]], "MarkDiffusion Documentation": [[10, null]], "Media Utilities": [[3, "module-utils.media_utils"]], "Missing Dependencies": [[0, "missing-dependencies"]], "Multiple Attacks Evaluation": [[13, "multiple-attacks-evaluation"]], "Next Steps": [[11, "next-steps"], [12, "next-steps"], [13, "next-steps"], [14, "next-steps"], [15, "next-steps"], [16, "next-steps"], [17, "next-steps"]], "On Linux/Mac:": [[0, "on-linux-mac"]], "On Windows:": [[0, "on-windows"]], "Other Algorithms": [[11, "other-algorithms"]], "Overview": [[9, "overview"], [15, "overview"], [16, "overview"]], "PDF": [[0, "pdf"]], "PRC": [[7, "prc"], [14, "prc"]], "PRC Detection": [[1, "module-detection.prc.prc_detection"]], "PRC Visualizer": [[4, "module-visualize.prc.prc_visualizer"]], "PRC Watermark": [[5, "module-watermark.prc.prc"]], "Pattern-Based Methods": [[14, "pattern-based-methods"]], "Pipeline Utilities": [[3, "module-utils.pipeline_utils"]], "Pipelines": [[2, "pipelines"]], "Prerequisites": [[0, "prerequisites"]], "Quality Evaluation": [[15, "quality-evaluation"]], "Quick Example": [[10, "quick-example"]], "Quick Reference": [[8, "quick-reference"]], "Quick Start": [[12, null]], "ROBIN": [[7, "robin"], [14, "robin"], [16, "robin"]], "ROBIN Detection": [[1, "module-detection.robin.robin_detection"]], "ROBIN Visualization": [[13, "robin-visualization"]], "ROBIN Visualizer": [[4, "module-visualize.robin.robin_visualizer"]], "ROBIN Watermark": [[5, "module-watermark.robin.robin"]], "Read the Docs": [[0, "read-the-docs"]], "Recent Updates": [[6, "recent-updates"]], "Referenced Quality Analysis": [[15, "referenced-quality-analysis"]], "Repeat Quality Analysis": [[15, "repeat-quality-analysis"]], "Reporting": [[8, "reporting"]], "Requirements": [[11, "requirements"]], "Ring-ID": [[7, "ring-id"]], "Ring-ID (RI)": [[14, "ring-id-ri"]], "Robustness Evaluation": [[13, "robustness-evaluation"], [15, "robustness-evaluation"]], "SEAL": [[7, "seal"], [11, "seal"], [14, "seal"]], "SEAL Detection": [[1, "module-detection.seal.seal_detection"]], "SEAL Visualizer": [[4, "module-visualize.seal.seal_visualizer"]], "SEAL Watermark": [[5, "module-watermark.seal.seal"]], "SFW": [[7, "sfw"], [14, "sfw"]], "SFW Detection": [[1, "module-detection.sfw.sfw_detection"]], "SFW Visualizer": [[4, "module-visualize.sfw.sfw_visualizer"]], "SFW Watermark": [[5, "module-watermark.sfw.sfw"]], "Sample Size Selection": [[15, "sample-size-selection"]], "Simple Visualization": [[16, "simple-visualization"]], "Step 1: Setup and Configuration": [[12, "step-1-setup-and-configuration"]], "Step 2: Load a Watermarking Algorithm": [[12, "step-2-load-a-watermarking-algorithm"]], "Step 3: Generate Watermarked Media": [[12, "step-3-generate-watermarked-media"]], "Step 4: Detect Watermarks": [[12, "step-4-detect-watermarks"]], "Submission Checklist": [[9, "submission-checklist"]], "Success Rate Calculators": [[2, "module-evaluation.tools.success_rate_calculator"]], "Testing": [[9, "testing"]], "Testing Against Attacks": [[17, "testing-against-attacks"]], "Tree-Ring (TR)": [[14, "tree-ring-tr"], [16, "tree-ring-tr"]], "Tree-Ring Detection": [[1, "module-detection.tr.tr_detection"]], "Tree-Ring Visualization": [[13, "tree-ring-visualization"]], "Tree-Ring Visualizer": [[4, "module-visualize.tr.tr_visualizer"]], "Tree-Ring Watermark": [[5, "module-watermark.tr.tr"], [7, "tree-ring-watermark"]], "Troubleshooting": [[0, "troubleshooting"], [11, "troubleshooting"]], "Tutorial": [[13, null]], "Tutorial 1: Basic Image Watermarking": [[13, "tutorial-1-basic-image-watermarking"]], "Tutorial 2: Visualizing Watermark Mechanisms": [[13, "tutorial-2-visualizing-watermark-mechanisms"]], "Tutorial 3: Evaluating Watermark Quality": [[13, "tutorial-3-evaluating-watermark-quality"]], "Tutorial 4: Video Watermarking": [[13, "tutorial-4-video-watermarking"]], "Tutorial 5: Custom Configuration": [[13, "tutorial-5-custom-configuration"]], "Upcoming Features": [[6, "upcoming-features"]], "Updates": [[7, "updates"]], "User Guide": [[10, null]], "Using Gaussian-Shading Watermark": [[13, "using-gaussian-shading-watermark"]], "Using MarkDiffusion in Publications": [[7, "using-markdiffusion-in-publications"]], "Using Tree-Ring Watermark": [[13, "using-tree-ring-watermark"]], "Utilities API": [[3, null]], "Verification": [[11, "verification"]], "Version 1.0.0 (2025-01-XX)": [[6, "version-1-0-0-2025-01-xx"]], "Video Attacks": [[15, "video-attacks"]], "Video Detection": [[17, "video-detection"]], "Video Editors (Attacks)": [[2, "module-evaluation.tools.video_editor"]], "Video Generation": [[12, "video-generation"], [17, "video-generation"]], "Video Quality Analysis Pipelines": [[2, "module-evaluation.pipelines.video_quality_analysis"]], "Video Quality Analyzers": [[2, "module-evaluation.tools.video_quality_analyzer"]], "Video Quality Evaluation": [[13, "video-quality-evaluation"]], "Video Quality Metrics": [[15, "video-quality-metrics"]], "Video Watermarking Methods": [[14, "video-watermarking-methods"]], "VideoMark": [[5, "module-watermark.videomark.video_mark"], [7, "videomark"], [14, "videomark"]], "VideoMark Detection": [[1, "module-detection.videomark.videomark_detection"]], "VideoMark Visualizer": [[4, "module-visualize.videomark.video_mark_visualizer"]], "VideoShield": [[5, "module-watermark.videoshield.video_shield"], [7, "videoshield"], [14, "videoshield"], [16, "videoshield"]], "VideoShield Detection": [[1, "module-detection.videoshield.videoshield_detection"]], "VideoShield Visualizer": [[4, "module-visualize.videoshield.video_shield_visualizer"]], "Visualization": [[16, null]], "Visualization API": [[4, null]], "Visualization Methods by Algorithm": [[16, "visualization-methods-by-algorithm"]], "WIND": [[7, "wind"], [14, "wind"]], "WIND Detection": [[1, "module-detection.wind.wind_detection"]], "WIND Visualizer": [[4, "module-visualize.wind.wind_visualizer"]], "WIND Watermark": [[5, "module-watermark.wind.wind"]], "Watermark API": [[5, null]], "Watermark Removal Prevention": [[17, "watermark-removal-prevention"]], "Watermarking Algorithms": [[14, null]], "Watermarking Workflow": [[17, null]], "Welcome to MarkDiffusion": [[10, "welcome-to-markdiffusion"]], "ePub": [[0, "epub"]]}, "docnames": ["BUILD", "api/detection", "api/evaluation", "api/utils", "api/visualization", "api/watermark", "changelog", "citation", "code_of_conduct", "contributing", "index", "installation", "quickstart", "tutorial", "user_guide/algorithms", "user_guide/evaluation", "user_guide/visualization", "user_guide/watermarking"], "envversion": {"nbsphinx": 4, "sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["BUILD.md", "api/detection.rst", "api/evaluation.rst", "api/utils.rst", "api/visualization.rst", "api/watermark.rst", "changelog.rst", "citation.rst", "code_of_conduct.rst", "contributing.rst", "index.rst", "installation.rst", "quickstart.rst", "tutorial.rst", "user_guide/algorithms.rst", "user_guide/evaluation.rst", "user_guide/visualization.rst", "user_guide/watermarking.rst"], "indexentries": {"__init__() (visualize.auto_visualization.autovisualizer method)": [[4, "visualize.auto_visualization.AutoVisualizer.__init__", false]], "__init__() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.__init__", false]], "__init__() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.__init__", false]], "__init__() (visualize.gs.gs_visualizer.gaussianshadingvisualizer method)": [[4, "visualize.gs.gs_visualizer.GaussianShadingVisualizer.__init__", false]], "__init__() (visualize.prc.prc_visualizer.prcvisualizer method)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer.__init__", false]], "__init__() (visualize.robin.robin_visualizer.robinvisualizer method)": [[4, "visualize.robin.robin_visualizer.ROBINVisualizer.__init__", false]], "__init__() (visualize.seal.seal_visualizer.sealvisualizer method)": [[4, "visualize.seal.seal_visualizer.SEALVisualizer.__init__", false]], "__init__() (visualize.sfw.sfw_visualizer.sfwvisualizer method)": [[4, "visualize.sfw.sfw_visualizer.SFWVisualizer.__init__", false]], "__init__() (visualize.tr.tr_visualizer.treeringvisualizer method)": [[4, "visualize.tr.tr_visualizer.TreeRingVisualizer.__init__", false]], "__init__() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.__init__", false]], "__init__() (visualize.videoshield.video_shield_visualizer.videoshieldvisualizer method)": [[4, "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer.__init__", false]], "__init__() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.__init__", false]], "autovisualizer (class in visualize.auto_visualization)": [[4, "visualize.auto_visualization.AutoVisualizer", false]], "basevisualizer (class in visualize.base)": [[4, "visualize.base.BaseVisualizer", false]], "draw_codeword() (visualize.prc.prc_visualizer.prcvisualizer method)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer.draw_codeword", false]], "draw_codeword() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.draw_codeword", false]], "draw_diff_latents_fft() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_diff_latents_fft", false]], "draw_diff_noise_wo_group_pattern() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.draw_diff_noise_wo_group_pattern", false]], "draw_difference_map() (visualize.prc.prc_visualizer.prcvisualizer method)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer.draw_difference_map", false]], "draw_difference_map() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.draw_difference_map", false]], "draw_embedding_distributions() (visualize.seal.seal_visualizer.sealvisualizer method)": [[4, "visualize.seal.seal_visualizer.SEALVisualizer.draw_embedding_distributions", false]], "draw_generator_matrix() (visualize.prc.prc_visualizer.prcvisualizer method)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer.draw_generator_matrix", false]], "draw_generator_matrix() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.draw_generator_matrix", false]], "draw_group_pattern_fft() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.draw_group_pattern_fft", false]], "draw_inverted_group_pattern_fft() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.draw_inverted_group_pattern_fft", false]], "draw_inverted_latents() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_inverted_latents", false]], "draw_inverted_latents_fft() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_inverted_latents_fft", false]], "draw_inverted_noise_wo_group_pattern() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.draw_inverted_noise_wo_group_pattern", false]], "draw_inverted_pattern_fft() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.draw_inverted_pattern_fft", false]], "draw_inverted_pattern_fft() (visualize.robin.robin_visualizer.robinvisualizer method)": [[4, "visualize.robin.robin_visualizer.ROBINVisualizer.draw_inverted_pattern_fft", false]], "draw_inverted_pattern_fft() (visualize.sfw.sfw_visualizer.sfwvisualizer method)": [[4, "visualize.sfw.sfw_visualizer.SFWVisualizer.draw_inverted_pattern_fft", false]], "draw_inverted_pattern_fft() (visualize.tr.tr_visualizer.treeringvisualizer method)": [[4, "visualize.tr.tr_visualizer.TreeRingVisualizer.draw_inverted_pattern_fft", false]], "draw_optimized_watermark() (visualize.robin.robin_visualizer.robinvisualizer method)": [[4, "visualize.robin.robin_visualizer.ROBINVisualizer.draw_optimized_watermark", false]], "draw_orig_latents() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_orig_latents", false]], "draw_orig_latents_fft() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_orig_latents_fft", false]], "draw_orig_noise_wo_group_pattern() (visualize.wind.wind_visualizer.windvisualizer method)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer.draw_orig_noise_wo_group_pattern", false]], "draw_patch_diff() (visualize.seal.seal_visualizer.sealvisualizer method)": [[4, "visualize.seal.seal_visualizer.SEALVisualizer.draw_patch_diff", false]], "draw_pattern_fft() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.draw_pattern_fft", false]], "draw_pattern_fft() (visualize.robin.robin_visualizer.robinvisualizer method)": [[4, "visualize.robin.robin_visualizer.ROBINVisualizer.draw_pattern_fft", false]], "draw_pattern_fft() (visualize.sfw.sfw_visualizer.sfwvisualizer method)": [[4, "visualize.sfw.sfw_visualizer.SFWVisualizer.draw_pattern_fft", false]], "draw_pattern_fft() (visualize.tr.tr_visualizer.treeringvisualizer method)": [[4, "visualize.tr.tr_visualizer.TreeRingVisualizer.draw_pattern_fft", false]], "draw_reconstructed_watermark_bits() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.draw_reconstructed_watermark_bits", false]], "draw_reconstructed_watermark_bits() (visualize.gs.gs_visualizer.gaussianshadingvisualizer method)": [[4, "visualize.gs.gs_visualizer.GaussianShadingVisualizer.draw_reconstructed_watermark_bits", false]], "draw_reconstructed_watermark_bits() (visualize.videoshield.video_shield_visualizer.videoshieldvisualizer method)": [[4, "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer.draw_reconstructed_watermark_bits", false]], "draw_recovered_codeword() (visualize.prc.prc_visualizer.prcvisualizer method)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer.draw_recovered_codeword", false]], "draw_recovered_codeword() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.draw_recovered_codeword", false]], "draw_watermark_bits() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.draw_watermark_bits", false]], "draw_watermark_bits() (visualize.gs.gs_visualizer.gaussianshadingvisualizer method)": [[4, "visualize.gs.gs_visualizer.GaussianShadingVisualizer.draw_watermark_bits", false]], "draw_watermark_bits() (visualize.videoshield.video_shield_visualizer.videoshieldvisualizer method)": [[4, "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer.draw_watermark_bits", false]], "draw_watermark_mask() (visualize.gm.gm_visualizer.gaussmarkervisualizer method)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer.draw_watermark_mask", false]], "draw_watermarked_image() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.draw_watermarked_image", false]], "draw_watermarked_video_frames() (visualize.videomark.video_mark_visualizer.videomarkvisualizer method)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer.draw_watermarked_video_frames", false]], "draw_watermarked_video_frames() (visualize.videoshield.video_shield_visualizer.videoshieldvisualizer method)": [[4, "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer.draw_watermarked_video_frames", false]], "gaussianshadingvisualizer (class in visualize.gs.gs_visualizer)": [[4, "visualize.gs.gs_visualizer.GaussianShadingVisualizer", false]], "gaussmarkervisualizer (class in visualize.gm.gm_visualizer)": [[4, "visualize.gm.gm_visualizer.GaussMarkerVisualizer", false]], "load() (visualize.auto_visualization.autovisualizer class method)": [[4, "visualize.auto_visualization.AutoVisualizer.load", false]], "module": [[4, "module-visualize.gm.gm_visualizer", false], [4, "module-visualize.gs.gs_visualizer", false], [4, "module-visualize.prc.prc_visualizer", false], [4, "module-visualize.robin.robin_visualizer", false], [4, "module-visualize.seal.seal_visualizer", false], [4, "module-visualize.sfw.sfw_visualizer", false], [4, "module-visualize.tr.tr_visualizer", false], [4, "module-visualize.videomark.video_mark_visualizer", false], [4, "module-visualize.videoshield.video_shield_visualizer", false], [4, "module-visualize.wind.wind_visualizer", false]], "prcvisualizer (class in visualize.prc.prc_visualizer)": [[4, "visualize.prc.prc_visualizer.PRCVisualizer", false]], "robinvisualizer (class in visualize.robin.robin_visualizer)": [[4, "visualize.robin.robin_visualizer.ROBINVisualizer", false]], "sealvisualizer (class in visualize.seal.seal_visualizer)": [[4, "visualize.seal.seal_visualizer.SEALVisualizer", false]], "sfwvisualizer (class in visualize.sfw.sfw_visualizer)": [[4, "visualize.sfw.sfw_visualizer.SFWVisualizer", false]], "treeringvisualizer (class in visualize.tr.tr_visualizer)": [[4, "visualize.tr.tr_visualizer.TreeRingVisualizer", false]], "videomarkvisualizer (class in visualize.videomark.video_mark_visualizer)": [[4, "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer", false]], "videoshieldvisualizer (class in visualize.videoshield.video_shield_visualizer)": [[4, "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer", false]], "visualize() (visualize.base.basevisualizer method)": [[4, "visualize.base.BaseVisualizer.visualize", false]], "visualize.gm.gm_visualizer": [[4, "module-visualize.gm.gm_visualizer", false]], "visualize.gs.gs_visualizer": [[4, "module-visualize.gs.gs_visualizer", false]], "visualize.prc.prc_visualizer": [[4, "module-visualize.prc.prc_visualizer", false]], "visualize.robin.robin_visualizer": [[4, "module-visualize.robin.robin_visualizer", false]], "visualize.seal.seal_visualizer": [[4, "module-visualize.seal.seal_visualizer", false]], "visualize.sfw.sfw_visualizer": [[4, "module-visualize.sfw.sfw_visualizer", false]], "visualize.tr.tr_visualizer": [[4, "module-visualize.tr.tr_visualizer", false]], "visualize.videomark.video_mark_visualizer": [[4, "module-visualize.videomark.video_mark_visualizer", false]], "visualize.videoshield.video_shield_visualizer": [[4, "module-visualize.videoshield.video_shield_visualizer", false]], "visualize.wind.wind_visualizer": [[4, "module-visualize.wind.wind_visualizer", false]], "windvisualizer (class in visualize.wind.wind_visualizer)": [[4, "visualize.wind.wind_visualizer.WINDVisualizer", false]]}, "objects": {"detection.base": [[1, 0, 1, "", "BaseDetector"]], "detection.base.BaseDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.gm": [[1, 2, 0, "-", "gm_detection"]], "detection.gm.gm_detection": [[1, 0, 1, "", "GMDetector"]], "detection.gm.gm_detection.GMDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.gs": [[1, 2, 0, "-", "gs_detection"]], "detection.gs.gs_detection": [[1, 0, 1, "", "GSDetector"]], "detection.gs.gs_detection.GSDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.prc": [[1, 2, 0, "-", "prc_detection"]], "detection.prc.prc_detection": [[1, 0, 1, "", "PRCDetector"]], "detection.prc.prc_detection.PRCDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.robin": [[1, 2, 0, "-", "robin_detection"]], "detection.robin.robin_detection": [[1, 0, 1, "", "ROBINDetector"]], "detection.robin.robin_detection.ROBINDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.seal": [[1, 2, 0, "-", "seal_detection"]], "detection.seal.seal_detection": [[1, 0, 1, "", "SEALDetector"]], "detection.seal.seal_detection.SEALDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.sfw": [[1, 2, 0, "-", "sfw_detection"]], "detection.sfw.sfw_detection": [[1, 0, 1, "", "SFWDetector"]], "detection.sfw.sfw_detection.SFWDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"], [1, 1, 1, "", "get_distance_hsqr"]], "detection.tr": [[1, 2, 0, "-", "tr_detection"]], "detection.tr.tr_detection": [[1, 0, 1, "", "TRDetector"]], "detection.tr.tr_detection.TRDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.videomark": [[1, 2, 0, "-", "videomark_detection"]], "detection.videomark.videomark_detection": [[1, 0, 1, "", "VideoMarkDetector"]], "detection.videomark.videomark_detection.VideoMarkDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "bits_to_string"], [1, 1, 1, "", "eval_watermark"], [1, 1, 1, "", "recover"]], "detection.videoshield": [[1, 2, 0, "-", "videoshield_detection"]], "detection.videoshield.videoshield_detection": [[1, 0, 1, "", "VideoShieldDetector"]], "detection.videoshield.videoshield_detection.VideoShieldDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "detection.wind": [[1, 2, 0, "-", "wind_detection"]], "detection.wind.wind_detection": [[1, 0, 1, "", "WINDetector"]], "detection.wind.wind_detection.WINDetector": [[1, 1, 1, "", "__init__"], [1, 1, 1, "", "eval_watermark"]], "evaluation": [[2, 2, 0, "-", "dataset"]], "evaluation.dataset": [[2, 0, 1, "", "BaseDataset"], [2, 0, 1, "", "MSCOCODataset"], [2, 0, 1, "", "StableDiffusionPromptsDataset"], [2, 0, 1, "", "VBenchDataset"]], "evaluation.dataset.BaseDataset": [[2, 1, 1, "", "__getitem__"], [2, 1, 1, "", "__init__"], [2, 1, 1, "", "__len__"], [2, 1, 1, "", "get_prompt"], [2, 1, 1, "", "get_reference"], [2, 3, 1, "", "num_references"], [2, 3, 1, "", "num_samples"]], "evaluation.dataset.MSCOCODataset": [[2, 1, 1, "", "__init__"], [2, 3, 1, "", "name"]], "evaluation.dataset.StableDiffusionPromptsDataset": [[2, 1, 1, "", "__init__"], [2, 3, 1, "", "name"]], "evaluation.dataset.VBenchDataset": [[2, 1, 1, "", "__init__"], [2, 3, 1, "", "name"]], "evaluation.pipelines": [[2, 2, 0, "-", "detection"], [2, 2, 0, "-", "image_quality_analysis"], [2, 2, 0, "-", "video_quality_analysis"]], "evaluation.pipelines.detection": [[2, 0, 1, "", "DetectionPipelineReturnType"], [2, 0, 1, "", "UnWatermarkedMediaDetectionPipeline"], [2, 0, 1, "", "WatermarkDetectionPipeline"], [2, 0, 1, "", "WatermarkDetectionResult"], [2, 0, 1, "", "WatermarkedMediaDetectionPipeline"]], "evaluation.pipelines.detection.DetectionPipelineReturnType": [[2, 4, 1, "", "FULL"], [2, 4, 1, "", "IS_WATERMARKED"], [2, 4, 1, "", "SCORES"]], "evaluation.pipelines.detection.UnWatermarkedMediaDetectionPipeline": [[2, 1, 1, "", "__init__"]], "evaluation.pipelines.detection.WatermarkDetectionPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "evaluate"]], "evaluation.pipelines.detection.WatermarkDetectionResult": [[2, 1, 1, "", "__init__"]], "evaluation.pipelines.detection.WatermarkedMediaDetectionPipeline": [[2, 1, 1, "", "__init__"]], "evaluation.pipelines.image_quality_analysis": [[2, 0, 1, "", "ComparedImageQualityAnalysisPipeline"], [2, 0, 1, "", "DatasetForEvaluation"], [2, 0, 1, "", "DirectImageQualityAnalysisPipeline"], [2, 0, 1, "", "GroupImageQualityAnalysisPipeline"], [2, 0, 1, "", "ImageQualityAnalysisPipeline"], [2, 0, 1, "", "QualityComparisonResult"], [2, 0, 1, "", "QualityPipelineReturnType"], [2, 0, 1, "", "ReferencedImageQualityAnalysisPipeline"], [2, 0, 1, "", "RepeatImageQualityAnalysisPipeline"]], "evaluation.pipelines.image_quality_analysis.ComparedImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.image_quality_analysis.DatasetForEvaluation": [[2, 1, 1, "", "__init__"], [2, 4, 1, "", "indexes"], [2, 4, 1, "", "prompts"], [2, 4, 1, "", "reference_images"], [2, 4, 1, "", "unwatermarked_images"], [2, 4, 1, "", "watermarked_images"]], "evaluation.pipelines.image_quality_analysis.DirectImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.image_quality_analysis.GroupImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.image_quality_analysis.ImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"], [2, 1, 1, "", "evaluate"]], "evaluation.pipelines.image_quality_analysis.QualityComparisonResult": [[2, 1, 1, "", "__init__"]], "evaluation.pipelines.image_quality_analysis.QualityPipelineReturnType": [[2, 4, 1, "", "FULL"], [2, 4, 1, "", "MEAN_SCORES"], [2, 4, 1, "", "SCORES"]], "evaluation.pipelines.image_quality_analysis.ReferencedImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.image_quality_analysis.RepeatImageQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.video_quality_analysis": [[2, 0, 1, "", "DatasetForEvaluation"], [2, 0, 1, "", "DirectVideoQualityAnalysisPipeline"], [2, 0, 1, "", "QualityComparisonResult"], [2, 0, 1, "", "QualityPipelineReturnType"], [2, 0, 1, "", "VideoQualityAnalysisPipeline"]], "evaluation.pipelines.video_quality_analysis.DatasetForEvaluation": [[2, 1, 1, "", "__init__"], [2, 4, 1, "", "indexes"], [2, 4, 1, "", "reference_videos"], [2, 4, 1, "", "unwatermarked_videos"], [2, 4, 1, "", "watermarked_videos"]], "evaluation.pipelines.video_quality_analysis.DirectVideoQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"]], "evaluation.pipelines.video_quality_analysis.QualityComparisonResult": [[2, 1, 1, "", "__init__"]], "evaluation.pipelines.video_quality_analysis.QualityPipelineReturnType": [[2, 4, 1, "", "FULL"], [2, 4, 1, "", "MEAN_SCORES"], [2, 4, 1, "", "SCORES"]], "evaluation.pipelines.video_quality_analysis.VideoQualityAnalysisPipeline": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze_quality"], [2, 1, 1, "", "evaluate"]], "evaluation.tools": [[2, 2, 0, "-", "image_editor"], [2, 2, 0, "-", "image_quality_analyzer"], [2, 2, 0, "-", "success_rate_calculator"], [2, 2, 0, "-", "video_editor"], [2, 2, 0, "-", "video_quality_analyzer"]], "evaluation.tools.image_editor": [[2, 0, 1, "", "AdaptiveNoiseInjection"], [2, 0, 1, "", "Brightness"], [2, 0, 1, "", "CrSc"], [2, 0, 1, "", "GaussianBlurring"], [2, 0, 1, "", "GaussianNoise"], [2, 0, 1, "", "ImageEditor"], [2, 0, 1, "", "JPEGCompression"], [2, 0, 1, "", "Mask"], [2, 0, 1, "", "Overlay"], [2, 0, 1, "", "Rotation"]], "evaluation.tools.image_editor.AdaptiveNoiseInjection": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.Brightness": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.CrSc": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.GaussianBlurring": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.GaussianNoise": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.ImageEditor": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.JPEGCompression": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.Mask": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.Overlay": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_editor.Rotation": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.image_quality_analyzer": [[2, 0, 1, "", "BRISQUEAnalyzer"], [2, 0, 1, "", "CLIPScoreCalculator"], [2, 0, 1, "", "ComparedImageQualityAnalyzer"], [2, 0, 1, "", "DirectImageQualityAnalyzer"], [2, 0, 1, "", "FIDCalculator"], [2, 0, 1, "", "FSIMAnalyzer"], [2, 0, 1, "", "GroupImageQualityAnalyzer"], [2, 0, 1, "", "ImageQualityAnalyzer"], [2, 0, 1, "", "InceptionScoreCalculator"], [2, 0, 1, "", "LPIPSAnalyzer"], [2, 0, 1, "", "NIQECalculator"], [2, 0, 1, "", "PSNRAnalyzer"], [2, 0, 1, "", "ReferencedImageQualityAnalyzer"], [2, 0, 1, "", "RepeatImageQualityAnalyzer"], [2, 0, 1, "", "SSIMAnalyzer"], [2, 0, 1, "", "VIFAnalyzer"]], "evaluation.tools.image_quality_analyzer.BRISQUEAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.CLIPScoreCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.ComparedImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.DirectImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.FIDCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.FSIMAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.GroupImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.ImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.InceptionScoreCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.LPIPSAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.NIQECalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.PSNRAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.ReferencedImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.RepeatImageQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.SSIMAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.image_quality_analyzer.VIFAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.success_rate_calculator": [[2, 0, 1, "", "BaseSuccessRateCalculator"], [2, 0, 1, "", "DetectionResult"], [2, 0, 1, "", "DynamicThresholdSuccessRateCalculator"], [2, 0, 1, "", "FundamentalSuccessRateCalculator"]], "evaluation.tools.success_rate_calculator.BaseSuccessRateCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "calculate"]], "evaluation.tools.success_rate_calculator.DetectionResult": [[2, 1, 1, "", "__init__"]], "evaluation.tools.success_rate_calculator.DynamicThresholdSuccessRateCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "calculate"]], "evaluation.tools.success_rate_calculator.FundamentalSuccessRateCalculator": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "calculate"]], "evaluation.tools.video_editor": [[2, 0, 1, "", "FrameAverage"], [2, 0, 1, "", "FrameInterpolationAttack"], [2, 0, 1, "", "FrameRateAdapter"], [2, 0, 1, "", "FrameSwap"], [2, 0, 1, "", "MPEG4Compression"], [2, 0, 1, "", "VideoCodecAttack"], [2, 0, 1, "", "VideoEditor"]], "evaluation.tools.video_editor.FrameAverage": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.FrameInterpolationAttack": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.FrameRateAdapter": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.FrameSwap": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.MPEG4Compression": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.VideoCodecAttack": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_editor.VideoEditor": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "edit"]], "evaluation.tools.video_quality_analyzer": [[2, 0, 1, "", "BackgroundConsistencyAnalyzer"], [2, 0, 1, "", "DynamicDegreeAnalyzer"], [2, 0, 1, "", "ImagingQualityAnalyzer"], [2, 0, 1, "", "MotionSmoothnessAnalyzer"], [2, 0, 1, "", "SubjectConsistencyAnalyzer"], [2, 0, 1, "", "VideoQualityAnalyzer"], [2, 5, 1, "", "dino_transform_Image"]], "evaluation.tools.video_quality_analyzer.BackgroundConsistencyAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.video_quality_analyzer.DynamicDegreeAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.video_quality_analyzer.ImagingQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.video_quality_analyzer.MotionSmoothnessAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "evaluation.tools.video_quality_analyzer.SubjectConsistencyAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"], [2, 1, 1, "", "transform"]], "evaluation.tools.video_quality_analyzer.VideoQualityAnalyzer": [[2, 1, 1, "", "__init__"], [2, 1, 1, "", "analyze"]], "inversions": [[3, 2, 0, "-", "base_inversion"], [3, 2, 0, "-", "ddim_inversion"], [3, 2, 0, "-", "exact_inversion"]], "inversions.base_inversion": [[3, 0, 1, "", "BaseInversion"]], "inversions.base_inversion.BaseInversion": [[3, 1, 1, "", "__init__"], [3, 1, 1, "", "forward_diffusion"]], "inversions.ddim_inversion": [[3, 0, 1, "", "DDIMInversion"]], "inversions.ddim_inversion.DDIMInversion": [[3, 1, 1, "", "__init__"], [3, 1, 1, "", "backward_diffusion"]], "inversions.exact_inversion": [[3, 0, 1, "", "ExactInversion"], [3, 0, 1, "", "StepScheduler"]], "inversions.exact_inversion.ExactInversion": [[3, 1, 1, "", "__init__"], [3, 1, 1, "", "backward_diffusion"], [3, 1, 1, "", "forward_diffusion"]], "inversions.exact_inversion.StepScheduler": [[3, 1, 1, "", "__init__"], [3, 1, 1, "", "step"]], "utils": [[3, 2, 0, "-", "callbacks"], [3, 2, 0, "-", "diffusion_config"], [3, 2, 0, "-", "media_utils"], [3, 2, 0, "-", "pipeline_utils"], [3, 2, 0, "-", "utils"]], "utils.callbacks": [[3, 0, 1, "", "DenoisingLatentsCollector"]], "utils.callbacks.DenoisingLatentsCollector": [[3, 1, 1, "", "__init__"], [3, 1, 1, "", "clear"], [3, 1, 1, "", "get_latents_at_step"], [3, 3, 1, "", "latents_list"], [3, 3, 1, "", "timesteps_list"]], "utils.diffusion_config": [[3, 0, 1, "", "DiffusionConfig"]], "utils.diffusion_config.DiffusionConfig": [[3, 1, 1, "", "__init__"], [3, 3, 1, "", "is_image_pipeline"], [3, 3, 1, "", "is_video_pipeline"], [3, 3, 1, "", "pipeline_requirements"], [3, 3, 1, "", "pipeline_type"]], "utils.media_utils": [[3, 5, 1, "", "convert_video_frames_to_images"], [3, 5, 1, "", "cv2_to_pil"], [3, 5, 1, "", "decode_media_latents"], [3, 5, 1, "", "decoder_inv_optimization"], [3, 5, 1, "", "get_media_latents"], [3, 5, 1, "", "get_random_latents"], [3, 5, 1, "", "numpy_to_pil"], [3, 5, 1, "", "pil_to_cv2"], [3, 5, 1, "", "pil_to_torch"], [3, 5, 1, "", "save_video_frames"], [3, 5, 1, "", "set_inversion"], [3, 5, 1, "", "tensor2vid"], [3, 5, 1, "", "torch_to_numpy"], [3, 5, 1, "", "transform_to_model_format"]], "utils.pipeline_utils": [[3, 5, 1, "", "get_pipeline_requirements"], [3, 5, 1, "", "get_pipeline_type"], [3, 5, 1, "", "is_i2v_pipeline"], [3, 5, 1, "", "is_image_pipeline"], [3, 5, 1, "", "is_t2v_pipeline"], [3, 5, 1, "", "is_video_pipeline"]], "utils.utils": [[3, 5, 1, "", "create_directory_for_file"], [3, 5, 1, "", "inherit_docstring"], [3, 5, 1, "", "load_config_file"], [3, 5, 1, "", "load_json_as_list"], [3, 5, 1, "", "set_random_seed"]], "visualize.auto_visualization": [[4, 0, 1, "", "AutoVisualizer"]], "visualize.auto_visualization.AutoVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "load"]], "visualize.base": [[4, 0, 1, "", "BaseVisualizer"]], "visualize.base.BaseVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_diff_latents_fft"], [4, 1, 1, "", "draw_inverted_latents"], [4, 1, 1, "", "draw_inverted_latents_fft"], [4, 1, 1, "", "draw_orig_latents"], [4, 1, 1, "", "draw_orig_latents_fft"], [4, 1, 1, "", "draw_watermarked_image"], [4, 1, 1, "", "visualize"]], "visualize.gm": [[4, 2, 0, "-", "gm_visualizer"]], "visualize.gm.gm_visualizer": [[4, 0, 1, "", "GaussMarkerVisualizer"]], "visualize.gm.gm_visualizer.GaussMarkerVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_inverted_pattern_fft"], [4, 1, 1, "", "draw_pattern_fft"], [4, 1, 1, "", "draw_reconstructed_watermark_bits"], [4, 1, 1, "", "draw_watermark_bits"], [4, 1, 1, "", "draw_watermark_mask"]], "visualize.gs": [[4, 2, 0, "-", "gs_visualizer"]], "visualize.gs.gs_visualizer": [[4, 0, 1, "", "GaussianShadingVisualizer"]], "visualize.gs.gs_visualizer.GaussianShadingVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_reconstructed_watermark_bits"], [4, 1, 1, "", "draw_watermark_bits"]], "visualize.prc": [[4, 2, 0, "-", "prc_visualizer"]], "visualize.prc.prc_visualizer": [[4, 0, 1, "", "PRCVisualizer"]], "visualize.prc.prc_visualizer.PRCVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_codeword"], [4, 1, 1, "", "draw_difference_map"], [4, 1, 1, "", "draw_generator_matrix"], [4, 1, 1, "", "draw_recovered_codeword"]], "visualize.robin": [[4, 2, 0, "-", "robin_visualizer"]], "visualize.robin.robin_visualizer": [[4, 0, 1, "", "ROBINVisualizer"]], "visualize.robin.robin_visualizer.ROBINVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_inverted_pattern_fft"], [4, 1, 1, "", "draw_optimized_watermark"], [4, 1, 1, "", "draw_pattern_fft"]], "visualize.seal": [[4, 2, 0, "-", "seal_visualizer"]], "visualize.seal.seal_visualizer": [[4, 0, 1, "", "SEALVisualizer"]], "visualize.seal.seal_visualizer.SEALVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_embedding_distributions"], [4, 1, 1, "", "draw_patch_diff"]], "visualize.sfw": [[4, 2, 0, "-", "sfw_visualizer"]], "visualize.sfw.sfw_visualizer": [[4, 0, 1, "", "SFWVisualizer"]], "visualize.sfw.sfw_visualizer.SFWVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_inverted_pattern_fft"], [4, 1, 1, "", "draw_pattern_fft"]], "visualize.tr": [[4, 2, 0, "-", "tr_visualizer"]], "visualize.tr.tr_visualizer": [[4, 0, 1, "", "TreeRingVisualizer"]], "visualize.tr.tr_visualizer.TreeRingVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_inverted_pattern_fft"], [4, 1, 1, "", "draw_pattern_fft"]], "visualize.videomark": [[4, 2, 0, "-", "video_mark_visualizer"]], "visualize.videomark.video_mark_visualizer": [[4, 0, 1, "", "VideoMarkVisualizer"]], "visualize.videomark.video_mark_visualizer.VideoMarkVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_codeword"], [4, 1, 1, "", "draw_difference_map"], [4, 1, 1, "", "draw_generator_matrix"], [4, 1, 1, "", "draw_recovered_codeword"], [4, 1, 1, "", "draw_watermarked_video_frames"]], "visualize.videoshield": [[4, 2, 0, "-", "video_shield_visualizer"]], "visualize.videoshield.video_shield_visualizer": [[4, 0, 1, "", "VideoShieldVisualizer"]], "visualize.videoshield.video_shield_visualizer.VideoShieldVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_reconstructed_watermark_bits"], [4, 1, 1, "", "draw_watermark_bits"], [4, 1, 1, "", "draw_watermarked_video_frames"]], "visualize.wind": [[4, 2, 0, "-", "wind_visualizer"]], "visualize.wind.wind_visualizer": [[4, 0, 1, "", "WINDVisualizer"]], "visualize.wind.wind_visualizer.WINDVisualizer": [[4, 1, 1, "", "__init__"], [4, 1, 1, "", "draw_diff_noise_wo_group_pattern"], [4, 1, 1, "", "draw_group_pattern_fft"], [4, 1, 1, "", "draw_inverted_group_pattern_fft"], [4, 1, 1, "", "draw_inverted_noise_wo_group_pattern"], [4, 1, 1, "", "draw_orig_noise_wo_group_pattern"]], "watermark.auto_watermark": [[5, 0, 1, "", "AutoWatermark"]], "watermark.auto_watermark.AutoWatermark": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "list_supported_algorithms"], [5, 1, 1, "", "load"]], "watermark.base": [[5, 0, 1, "", "BaseWatermark"]], "watermark.base.BaseWatermark": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "detect_watermark_in_media"], [5, 1, 1, "", "generate_unwatermarked_media"], [5, 1, 1, "", "generate_watermarked_media"], [5, 1, 1, "", "get_data_for_visualize"], [5, 1, 1, "", "get_orig_watermarked_latents"], [5, 1, 1, "", "set_orig_watermarked_latents"]], "watermark.gm": [[5, 2, 0, "-", "gm"]], "watermark.gm.gm": [[5, 0, 1, "", "GM"], [5, 0, 1, "", "GMConfig"], [5, 0, 1, "", "GMUtils"], [5, 0, 1, "", "GaussianShadingChaCha"], [5, 5, 1, "", "circle_mask"], [5, 5, 1, "", "extract_complex_sign"], [5, 5, 1, "", "set_complex_sign"]], "watermark.gm.gm.GM": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.gm.gm.GMConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.gm.gm.GMUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "detect_from_latents"], [5, 1, 1, "", "generate_watermarked_latents"], [5, 1, 1, "", "inject_watermark"]], "watermark.gm.gm.GaussianShadingChaCha": [[5, 1, 1, "", "__init__"], [5, 4, 1, "", "channel_copy"], [5, 1, 1, "", "create_watermark_and_return_w_m"], [5, 4, 1, "", "device"], [5, 1, 1, "", "diffusion_inverse"], [5, 4, 1, "", "dtype"], [5, 4, 1, "", "fpr"], [5, 4, 1, "", "height_copy"], [5, 4, 1, "", "key"], [5, 4, 1, "", "key_seed"], [5, 4, 1, "", "latent_channels"], [5, 4, 1, "", "latent_height"], [5, 4, 1, "", "latent_width"], [5, 4, 1, "", "message_bits"], [5, 4, 1, "", "nonce"], [5, 4, 1, "", "nonce_seed"], [5, 1, 1, "", "pred_m_from_latent"], [5, 1, 1, "", "pred_w_from_latent"], [5, 1, 1, "", "pred_w_from_m"], [5, 4, 1, "", "user_number"], [5, 4, 1, "", "watermark"], [5, 4, 1, "", "watermark_seed"], [5, 1, 1, "", "watermark_tensor"], [5, 4, 1, "", "width_copy"]], "watermark.gs": [[5, 2, 0, "-", "gs"]], "watermark.gs.gs": [[5, 0, 1, "", "GS"], [5, 0, 1, "", "GSConfig"], [5, 0, 1, "", "GSUtils"]], "watermark.gs.gs.GS": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.gs.gs.GSConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.gs.gs.GSUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "inject_watermark"]], "watermark.prc": [[5, 2, 0, "-", "prc"]], "watermark.prc.prc": [[5, 0, 1, "", "PRC"], [5, 0, 1, "", "PRCConfig"], [5, 0, 1, "", "PRCUtils"]], "watermark.prc.prc.PRC": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.prc.prc.PRCConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.prc.prc.PRCUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "inject_watermark"]], "watermark.robin": [[5, 2, 0, "-", "robin"]], "watermark.robin.robin": [[5, 0, 1, "", "ROBIN"], [5, 0, 1, "", "ROBINConfig"], [5, 0, 1, "", "ROBINUtils"]], "watermark.robin.robin.ROBIN": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.robin.robin.ROBINConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.robin.robin.ROBINUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "build_generation_params"], [5, 1, 1, "", "build_hyperparameters"], [5, 1, 1, "", "build_watermarking_args"], [5, 1, 1, "", "extract_latents_for_detection"], [5, 1, 1, "", "generate_clean_images"], [5, 1, 1, "", "initialize_detector"], [5, 1, 1, "", "optimize_watermark"], [5, 1, 1, "", "preprocess_image_for_detection"]], "watermark.seal": [[5, 2, 0, "-", "seal"]], "watermark.seal.seal": [[5, 0, 1, "", "SEAL"], [5, 0, 1, "", "SEALConfig"], [5, 0, 1, "", "SEALUtils"]], "watermark.seal.seal.SEAL": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.seal.seal.SEALConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.seal.seal.SEALUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "generate_caption"], [5, 1, 1, "", "generate_initial_noise"]], "watermark.sfw": [[5, 2, 0, "-", "sfw"]], "watermark.sfw.sfw": [[5, 0, 1, "", "SFW"], [5, 0, 1, "", "SFWConfig"], [5, 0, 1, "", "SFWUtils"]], "watermark.sfw.sfw.SFW": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.sfw.sfw.SFWConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.sfw.sfw.SFWUtils": [[5, 0, 1, "", "QRCodeGenerator"], [5, 0, 1, "", "RounderRingMask"], [5, 1, 1, "", "__init__"], [5, 1, 1, "", "circle_mask"], [5, 1, 1, "", "enforce_hermitian_symmetry"], [5, 1, 1, "", "fft"], [5, 1, 1, "", "ifft"], [5, 1, 1, "", "inject_hsqr"], [5, 1, 1, "", "inject_wm"], [5, 1, 1, "", "irfft"], [5, 1, 1, "", "make_Fourier_treering_pattern"], [5, 1, 1, "", "make_hsqr_pattern"], [5, 1, 1, "", "rfft"]], "watermark.sfw.sfw.SFWUtils.QRCodeGenerator": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "clear"], [5, 1, 1, "", "make_qr_tensor"]], "watermark.sfw.sfw.SFWUtils.RounderRingMask": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_ring_mask"]], "watermark.tr": [[5, 2, 0, "-", "tr"]], "watermark.tr.tr": [[5, 0, 1, "", "TR"], [5, 0, 1, "", "TRConfig"], [5, 0, 1, "", "TRUtils"]], "watermark.tr.tr.TR": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.tr.tr.TRConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.tr.tr.TRUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "inject_watermark"]], "watermark.videomark": [[5, 2, 0, "-", "video_mark"]], "watermark.videomark.video_mark": [[5, 0, 1, "", "VideoMarkConfig"], [5, 0, 1, "", "VideoMarkUtils"], [5, 0, 1, "", "VideoMarkWatermark"]], "watermark.videomark.video_mark.VideoMarkConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.videomark.video_mark.VideoMarkUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "inject_watermark"]], "watermark.videomark.video_mark.VideoMarkWatermark": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.videoshield": [[5, 2, 0, "-", "video_shield"]], "watermark.videoshield.video_shield": [[5, 0, 1, "", "VideoShieldConfig"], [5, 0, 1, "", "VideoShieldUtils"], [5, 0, 1, "", "VideoShieldWatermark"]], "watermark.videoshield.video_shield.VideoShieldConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.videoshield.video_shield.VideoShieldUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "create_watermark_and_return_w"]], "watermark.videoshield.video_shield.VideoShieldWatermark": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.wind": [[5, 2, 0, "-", "wind"]], "watermark.wind.wind": [[5, 0, 1, "", "WIND"], [5, 0, 1, "", "WINDConfig"], [5, 0, 1, "", "WINDUtils"]], "watermark.wind.wind.WIND": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "get_data_for_visualize"]], "watermark.wind.wind.WINDConfig": [[5, 3, 1, "", "algorithm_name"], [5, 1, 1, "", "initialize_parameters"]], "watermark.wind.wind.WINDUtils": [[5, 1, 1, "", "__init__"], [5, 1, 1, "", "inject_watermark"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "module", "Python module"], "3": ["py", "property", "Python property"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "function", "Python function"]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:module", "3": "py:property", "4": "py:attribute", "5": "py:function"}, "terms": {"": [1, 2, 4, 7, 10, 12, 13], "0": [0, 1, 2, 3, 4, 5, 8, 13, 14, 15, 16, 17], "0001": 3, "0005": 14, "01": [1, 15], "03d": 13, "04653": 7, "04956": 7, "04d": [12, 13, 17], "05": [13, 15], "07": 6, "07369": 7, "08": [3, 6], "09": 6, "1": [0, 1, 2, 3, 4, 5, 7, 10, 14, 15, 16, 17], "10": [0, 1, 2, 3, 6, 11, 14, 15, 16], "100": [2, 3, 9, 13, 15], "1000": 15, "10569": 7, "11": [10, 11, 14], "11444": 7, "12": 11, "12172": 7, "123456": 14, "127": 0, "128": 1, "14": [5, 14, 16], "14055": 7, "15": [13, 15, 16, 17], "15gb": 11, "16": [5, 13, 17], "16359": 7, "1666666666666667": 2, "16gb": 11, "18759": 7, "18769": 7, "1e": 3, "2": [2, 3, 5, 7, 8, 10, 15, 16, 17], "20": [2, 13, 15], "200": [2, 15], "2000": 14, "20030": 7, "2017": 2, "2023": 7, "2024": 7, "2025": 7, "2305": 7, "24": [2, 6, 10], "2404": 7, "2410": 7, "2412": 7, "25": [2, 3], "2503": 7, "2504": 7, "2506": 7, "2509": 7, "255": 2, "256": 17, "264": 6, "265": 6, "29": 6, "2d": 4, "2f": [13, 15], "2m": [2, 3, 15], "3": [0, 2, 10, 11, 14, 15, 16, 17], "30": [2, 14], "300": [4, 16], "32": 2, "320": [13, 17], "35": 14, "358bb6af": 2, "3d": 6, "4": [1, 2, 4, 5, 6, 9, 10, 14, 16, 17], "40": [13, 17], "42": [1, 10, 12, 13, 14, 17], "45": [14, 15], "4f": [13, 15], "5": [1, 2, 3, 5, 10, 12, 14, 15, 16, 17], "50": [3, 4, 5, 10, 12, 13, 14, 15, 17], "500": 15, "512": [3, 5, 9, 10, 12, 13, 17], "576": [13, 17], "6": [2, 3, 11, 17], "64": [1, 5, 14], "65": 5, "7": [2, 3, 5, 10, 12, 13, 14, 17], "75": [13, 15, 17], "789012": 14, "8": [1, 2, 6, 9, 10, 11, 14], "8000": 0, "8gb": 11, "90": 15, "95": 2, "96": 2, "999999": [5, 14], "A": [3, 5, 7, 9, 10, 11, 12, 13, 14, 16, 17], "As": 10, "Be": 8, "For": [0, 2, 3, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17], "If": [0, 1, 2, 4, 7, 9, 10, 11], "It": [1, 2, 5], "No": [14, 15], "Not": 1, "One": 3, "Or": [9, 11], "The": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 14, 16, 17], "Then": 0, "These": 11, "To": [0, 6, 9, 11], "With": 10, "__getitem__": 2, "__init__": [1, 2, 3, 4, 5, 9], "__len__": 2, "__main__": 12, "__name__": 12, "_build": 0, "_static": 0, "_templat": 0, "ab": 7, "abc": [1, 4, 5], "about": [7, 8, 11, 12, 16, 17], "abstract": [1, 2, 5], "abus": 8, "acc": 2, "acceler": 11, "accept": 8, "access": 10, "accordingli": 11, "accuraci": [1, 15, 16], "across": [2, 4, 15], "activ": 11, "actual": 2, "ad": [0, 2, 4, 6], "adapt": [1, 2, 6, 8, 14], "adaptivenois": 15, "adaptivenoiseinject": [2, 6, 15], "add": 9, "addit": [5, 6, 11, 14], "adjac": 2, "adjust": [6, 16], "advanc": [0, 10, 13], "adversari": [7, 14, 16], "affect": 15, "after": [2, 4, 12, 13, 16, 17], "against": [2, 13, 15], "aiwei": 7, "al": 7, "alarm": 15, "alex": 2, "algo": [12, 15, 16], "algo_nam": [12, 15], "algorithm": [0, 2, 5, 6, 10, 13], "algorithm_comparison": 16, "algorithm_config": [5, 10, 12, 13, 14, 15, 17], "algorithm_map": 9, "algorithm_nam": [4, 5, 17], "all": [2, 4, 5, 6, 7, 9, 11, 12, 15, 16, 17], "alreadi": 3, "also": 7, "alter": 2, "amount": 2, "amt": 2, "an": [2, 3, 4, 5, 6, 7, 8, 10, 14], "analysi": [4, 13, 14, 16], "analyz": [6, 9, 13, 15], "analyze_qu": 2, "angl": [2, 13, 15, 17], "ani": [2, 3, 4, 5, 9], "annual": 7, "api": [0, 13, 15, 16], "appear": 2, "append": [15, 17], "appli": [3, 4, 5, 15], "applic": 8, "approach": [5, 13, 14], "appropri": [4, 5, 9, 11], "ar": [2, 4, 7, 8, 9, 10, 11, 14], "arabi": 7, "arabi2024hidden": 7, "arabi2025s": 7, "architectur": 10, "archiveprefix": 7, "area": [4, 15], "arg": [2, 3, 5, 9], "argument": [4, 5, 9], "arrai": [3, 5], "art": [10, 14], "articl": 7, "artifact": 2, "arxiv": 7, "aspect": 15, "assertequ": 9, "assertisnotnon": 9, "asserttru": 9, "assess": [2, 10, 13, 15], "assign": 14, "assigned_kei": 14, "attack": [6, 8, 9, 14], "attack_editor": [13, 15], "attack_nam": [13, 15], "attribut": 4, "auc": [2, 15], "audio": 6, "authent": 10, "author": 7, "auto": 0, "auto_select": 2, "auto_visu": [4, 13, 16], "auto_watermark": [5, 9, 10, 11, 12, 13, 14, 16, 17], "autobuild": 0, "autom": [6, 10, 15], "automat": [0, 3, 5, 11], "autovisu": [10, 13, 16], "autowatermark": [9, 10, 11, 12, 13, 14, 15, 16, 17], "auxiliari": 1, "av1": [2, 6], "avail": [2, 11, 12, 15, 16], "averag": [2, 4, 6, 13, 15], "avg": 2, "avg_scor": [13, 15], "awar": [6, 7, 14], "ax": [4, 16], "axi": [4, 16], "b": [1, 2, 3, 4, 5], "b_valu": 5, "background": [2, 4, 6], "background_consist": [2, 15], "backgroundconsistencyanalyz": [2, 15], "backward": 14, "backward_diffus": 3, "base": [2, 6, 7, 10, 11, 13, 17], "base_invers": 3, "base_lat": 5, "baseconfig": 5, "basedataset": 2, "basedetector": 1, "baseimageeditor": 9, "baseimagequalityanalyz": 9, "baseinvers": 3, "basesuccessratecalcul": 2, "basevisu": 4, "basewatermark": 5, "basic": 10, "bat": 0, "batch": 2, "batch_output": 13, "batch_siz": 2, "bbox_inch": 16, "beach": 13, "beauti": [5, 10, 12, 17], "becom": 10, "befor": 9, "behavior": 8, "below": 9, "benjamin": 7, "best": [1, 2, 8, 10], "best_match": 1, "better": [1, 2, 3], "between": [2, 3, 4], "beyond": 11, "binari": [2, 4, 5], "bit": [1, 2, 4, 5, 11, 16], "bit_acc": 1, "bit_threshold": 1, "bitrat": [2, 15], "bits_to_str": 1, "black": 9, "blend": 2, "blind": 2, "blip2": 11, "blip2forconditionalgener": 11, "blip2processor": 11, "blossom": 12, "blue": 4, "blur": [6, 13, 15, 17], "blur_editor": 17, "blur_result": 17, "blurred_imag": 17, "blurri": 2, "booktitl": 7, "bool": [1, 2, 3, 4, 5, 9], "boolean": 1, "border": 5, "both": [1, 2, 6, 10], "box_siz": 5, "bpm": [0, 6, 7, 9, 11], "branch": [0, 9], "bright": [2, 6, 15], "brisqu": [2, 6, 15], "brisqueanalyz": [2, 15], "broader": 10, "browser": 0, "bug": [6, 9], "build": 5, "build_generation_param": 5, "build_hyperparamet": 5, "build_watermarking_arg": 5, "built": [0, 11], "bustl": 13, "byte": [1, 5], "c": [1, 2, 3, 4, 7, 11], "c_wm": 1, "calcul": 15, "call": 4, "callback": 10, "callback_step": 3, "can": [2, 4, 5, 11, 12, 15, 17], "cannot": [4, 5], "cap_model": [1, 11], "cap_processor": [1, 11], "capabl": [6, 10], "caption": [5, 11], "carri": 1, "cat": [12, 17], "categori": [10, 14], "cd": [0, 11], "center": 16, "cerspens": [13, 17], "ch": 4, "chacha": [1, 14], "chacha20": 1, "chacha_kei": 1, "chacha_key_se": 14, "chacha_nonc": 1, "chacha_nonce_se": 14, "chang": 6, "changelog": [0, 9, 10], "channel": [1, 4, 13, 14], "channel_copi": [1, 4, 5, 14], "charact": 9, "characterist": 2, "check": [3, 7, 9, 11], "checkpoint": [2, 11], "chen": 7, "cheng": 7, "cherri": 12, "chinmai": 7, "cho": 7, "ci": 7, "ci2024ringid": 7, "circl": [5, 14], "circle_mask": 5, "circular": 14, "citat": [0, 10], "cite": 7, "citi": [12, 13], "ckpt": 11, "cl": 3, "class": [1, 2, 3, 4, 5, 9], "classifi": [2, 11, 17], "classmethod": [4, 5], "clean": 5, "clear": [3, 5, 10], "clip": [2, 6, 15], "clipscor": 15, "clipscorecalcul": [2, 15], "clone": [9, 11], "cmap": 4, "cn": 9, "co": 11, "code": [4, 10, 16], "code_of_conduct": 8, "codec": [2, 6, 15], "codeword": [4, 5], "cohen": 7, "coher": 14, "col": [4, 13, 16], "collabor": [7, 9], "collect": [2, 3], "collector": 3, "color": 13, "colorbar": 4, "colormap": 4, "column": 4, "com": [0, 2, 6, 7, 9, 11], "combin": [14, 16], "combined_attack": 15, "comment": 8, "commit": 8, "common": [2, 4, 14], "commun": [7, 9, 10], "compar": [2, 10], "comparedimagequalityanalysispipelin": [2, 13, 15], "comparedimagequalityanalyz": 2, "comparison": [2, 4, 10, 12, 15, 16], "comparison_data": 15, "comparison_result": 15, "compat": [1, 11, 14], "complet": [8, 9, 10, 13, 17], "complex": 5, "complex64": 1, "complex_tensor": 5, "complianc": 7, "compon": [12, 16], "comprehens": [4, 6, 9, 10], "comprehensive_evalu": 15, "compress": [2, 6, 13, 14, 17], "compressed_imag": 17, "comput": [1, 2, 7], "conda": 10, "condit": 2, "conduct": [2, 9, 10], "conf": 0, "confer": 7, "confid": [1, 2, 9, 12, 16], "config": [5, 9, 10, 11, 12, 13, 14, 15, 16, 17], "configur": [0, 2, 5, 9, 10, 11, 14, 15], "congruenc": 2, "consecut": 2, "consequ": 8, "consider": 6, "consist": [2, 4, 6, 11, 12, 13, 14, 16, 17], "constant": [2, 3, 14], "construct": [8, 9], "consult": 11, "contact": 9, "contain": [1, 2, 3, 4, 5, 9], "content": [5, 14], "contribut": [7, 10], "contributor": [7, 8], "convert": 3, "convert_video_frames_to_imag": 3, "cooldown": 3, "coolwarm": 4, "copi": 14, "correct": [11, 15], "correspond": 11, "cosin": 2, "cosine_similar": 1, "count": 11, "coven": 8, "cover": 10, "cpu": [2, 3, 10, 12, 13, 17], "cr": 7, "creat": [3, 4, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17], "create_directory_for_fil": 3, "create_watermark_and_return_w": 5, "create_watermark_and_return_w_m": 5, "critic": 10, "crop": [6, 15], "crop_ratio": [2, 15], "cropscal": 15, "crsc": [2, 15], "css": 0, "cuda": [2, 10, 11, 12, 13, 15, 17], "cudnn": 11, "current": 10, "current_lr": 3, "curv": 15, "custom": [0, 6, 10, 11], "custom_algorithm": 0, "custom_visu": 16, "cutoff": 14, "cv2": 3, "cv2_to_pil": 3, "cvf": 7, "data": [2, 3, 4, 5, 9, 12, 13, 16], "data_for_visu": [4, 13, 16], "dataforvisu": [4, 5], "datafram": 15, "dataset": [5, 7, 10, 13, 15], "datasetforevalu": 2, "date": 7, "dawn": [7, 13], "db": [2, 13, 15], "ddim": [10, 12, 13, 17], "ddim_invers": 3, "ddiminvers": 3, "ddpm": 17, "debug": 16, "decid": 1, "decis": [1, 11], "decod": [3, 5], "decode_media_lat": 3, "decoder_inv": [3, 5], "decoder_inv_optim": 3, "decoding_kei": 1, "decor": 3, "def": [9, 12, 15], "default": [1, 2, 3, 4], "defin": [12, 15], "degrad": 14, "degre": [2, 6], "demonstr": [6, 8], "denois": 17, "denoisinglatentscollector": 3, "densiti": 2, "depend": 11, "deprec": 4, "deriv": 3, "derogatori": 8, "descript": [9, 14, 17], "design": 14, "desir": [2, 3], "det_result": 15, "detail": [0, 7, 8, 9, 11, 12, 13, 14], "detect": [0, 4, 5, 6, 7, 9, 10, 11, 13, 14, 16], "detect_from_lat": 5, "detect_result": 2, "detect_watermark_in_media": [5, 9, 10, 12, 13, 14, 16, 17], "detection_kwarg": [2, 13, 15], "detection_pipelin": 13, "detection_result": [2, 10, 12, 13, 17], "detection_threshold": 17, "detectionpipelinereturntyp": [2, 13, 15], "detectionresult": 2, "detector": [5, 10], "detector_typ": [1, 2, 5], "determin": [2, 3], "deviat": 2, "devic": [1, 2, 3, 5, 10, 11, 12, 13, 15, 17], "device_count": 11, "df": 15, "dict": [1, 2, 3, 4, 5, 9], "dictionari": [1, 3, 9], "differ": [4, 8, 10, 12, 13, 14, 15, 16], "diffus": [1, 2, 5, 6, 7, 10, 12, 13, 14], "diffusion_config": [3, 5, 10, 12, 13, 14, 15, 16, 17], "diffusion_invers": 5, "diffusionconfig": [3, 10, 12, 13], "diffusionpipelin": [13, 17], "dim": 15, "dim_nam": 15, "dimens": [2, 4, 5, 13], "dino": 2, "dino_transform_imag": 2, "dino_vitb16_ful": 2, "dino_vitbase16_pretrain": 2, "dino_vitbase16_pretrain_full_checkpoint": 2, "direct": 2, "directimagequalityanalysispipelin": [2, 15], "directimagequalityanalyz": 2, "directli": [1, 2, 4, 5], "directori": [3, 9, 11], "directvideoqualityanalysispipelin": [2, 13, 15], "discuss": 9, "disk": 11, "displai": [4, 12, 17], "distanc": [1, 2, 6], "distance_list": 1, "distort": [2, 6, 7, 14], "distribut": [2, 3, 4], "divers": [2, 8, 15], "divis": 2, "dl": 2, "do": [11, 15], "doc": 9, "docstr": [3, 9], "document": [1, 2, 3, 4, 5, 6, 8, 13], "doe": 3, "doesn": 3, "dog": 5, "domain": [1, 4, 6, 7, 11, 13, 14, 16], "download": 11, "dpi": [4, 16], "dpm": [3, 17], "dpmsolvermultistepschedul": [10, 12, 13, 17], "draw": [4, 16], "draw_bit_accuracy_heatmap": 16, "draw_codeword": 4, "draw_combined_watermark": 16, "draw_consistency_map": 16, "draw_detection_map": 16, "draw_diff_latents_fft": 4, "draw_diff_noise_wo_group_pattern": 4, "draw_difference_map": 4, "draw_embedding_distribut": 4, "draw_extracted_messag": 16, "draw_frame_differ": 16, "draw_frame_sequ": 16, "draw_frequency_analysi": 16, "draw_frequency_watermark": 16, "draw_generated_imag": 16, "draw_generator_matrix": 4, "draw_gnr_output": 16, "draw_group_pattern_fft": 4, "draw_init_lat": 16, "draw_init_latents_fft": 16, "draw_inverted_group_pattern_fft": 4, "draw_inverted_lat": [4, 13, 16], "draw_inverted_latents_fft": [4, 13, 16], "draw_inverted_noise_wo_group_pattern": 4, "draw_inverted_pattern_fft": [4, 13], "draw_optical_flow": 16, "draw_optimized_watermark": 4, "draw_orig_lat": [4, 13], "draw_orig_latents_fft": [4, 13], "draw_orig_noise_wo_group_pattern": 4, "draw_patch_diff": 4, "draw_pattern_fft": [4, 13], "draw_perturbation_pattern": 16, "draw_reconstructed_watermark_bit": [4, 13, 16], "draw_recovered_codeword": 4, "draw_robustness_map": 16, "draw_spatial_watermark": 16, "draw_temporal_watermark": 16, "draw_watermark_bit": [4, 13, 16], "draw_watermark_mask": 4, "draw_watermark_messag": 16, "draw_watermark_pattern": 16, "draw_watermarked_imag": [4, 13], "draw_watermarked_lat": 16, "draw_watermarked_latents_fft": 16, "draw_watermarked_video_fram": 4, "drone": 13, "dtype": [3, 5], "dual": [6, 7, 14, 16], "durat": 2, "dynam": [2, 6], "dynamic_degre": [2, 15], "dynamicdegreeanalyz": [2, 15], "dynamicthresholdsuccessratecalcul": [2, 6, 13, 15], "e": [1, 2, 4, 17], "each": [2, 4, 11, 12, 14, 15, 17], "easili": 12, "edit": 2, "edit_imag": [9, 17], "edited_media": 2, "edu": 9, "educ": 16, "effect": 2, "effici": 14, "eighth": 7, "either": 3, "element": 2, "eleven": 10, "els": [10, 12, 13, 15, 17], "email": 9, "emb": [13, 14], "embed": [4, 5, 7, 11, 14], "embed_dim": 17, "empathi": 8, "emploi": 7, "enabl": [10, 14], "encod": [2, 5], "encount": 11, "encrypt": [1, 14], "endswith": 17, "enforc": 8, "enforce_hermitian_symmetri": 5, "enhanc": [3, 6, 7, 14], "ensur": [7, 9, 10, 11], "entir": 2, "entri": [1, 9], "enum": 2, "enumer": [2, 12, 13, 16, 17], "environ": [9, 11], "ep": 3, "epoch": 3, "eprint": 7, "eq": 3, "equival": 2, "error": [0, 2, 3, 4, 5, 11], "et": 7, "etc": [1, 3], "euler": 3, "eval_result": 15, "eval_watermark": 1, "evalu": [0, 1, 6, 7, 10, 12, 14, 16, 17], "evaluation_pipelin": 0, "even": 2, "everi": 3, "everyon": [8, 9], "evolut": 16, "exact": [5, 17], "exact_invers": 3, "exactinvers": 3, "exampl": [7, 9, 11, 13, 17], "exist": [3, 9], "exist_ok": [13, 17], "expand": [2, 10], "expect": [2, 8, 9], "experi": 8, "explain": [0, 17], "explicit": 3, "explor": [11, 13], "extend": [4, 6, 14], "extens": 6, "extern": 2, "extract": [2, 5, 14, 16], "extract_complex_sign": 5, "extract_latents_for_detect": 5, "extract_latents_step": 5, "ey": 14, "f": [3, 4, 10, 11, 12, 13, 15, 16, 17], "f1": [2, 15], "f1_score": 15, "face": 11, "factor": [1, 2, 3, 15], "factori": [2, 4], "fail": 0, "fair": 2, "fals": [2, 3, 4, 5, 15], "familiar": 12, "fang": 7, "faq": 11, "fbaipublicfil": 2, "featur": [2, 9, 13, 14], "feedback": [7, 8], "feuer": 7, "ffmpeg": 2, "ffmpeg_path": 2, "fft": [4, 5, 16], "fid": [2, 6, 15], "fidcalcul": [2, 15], "fidel": [2, 6], "fig": [13, 16], "figsiz": [4, 16], "figur": [4, 14, 16], "file": [2, 3, 5, 6, 7, 8, 11, 12], "file_path": 3, "filenam": [5, 17], "filter": 5, "final": 16, "fine": 11, "fingerprint": [7, 14], "first": [2, 11, 12, 17], "fix": 2, "fixtur": 9, "flake8": 9, "flan": 11, "float": [1, 2, 4, 5, 9, 17], "float16": [3, 13, 17], "floattensor": 3, "flow": [2, 16], "flower": 13, "fly": 13, "fnr": [2, 15], "focu": 8, "focus": 2, "follow": [9, 11, 14], "fontsiz": 16, "fork": 9, "format": 3, "forward": [1, 3], "forward_diffus": 3, "found": 11, "fourier": [4, 6, 7, 12, 14], "fp": [2, 15], "fpr": [2, 5, 15], "frame": [1, 2, 3, 4, 5, 6, 12, 13, 14, 16, 17], "frame_": [12, 13, 17], "frame_result": 17, "frameaverag": [2, 15], "frameavg": 15, "frameinterpolationattack": [2, 6, 15], "framerateadapt": [2, 6, 15], "frameswap": [2, 15], "framework": [6, 7, 10, 14], "free": [6, 7, 8, 14, 17], "freq_tensor": 5, "frequenc": [1, 11, 13, 14, 16], "from": [1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "from_pretrain": [10, 11, 12, 13, 17], "fr\u00e9chet": [2, 6], "fsim": [2, 6, 15], "fsimanalyz": [2, 15], "fu": [6, 7], "full": [2, 8], "function": [5, 9], "fundament": 2, "fundamentalsuccessratecalcul": [2, 6, 15], "fuser": 11, "fuser_checkpoint": [1, 11], "fuser_frequency_scal": 1, "fuser_threshold": 1, "fusion": 11, "futur": 4, "futurist": 12, "g": [1, 2, 4, 5, 6, 9, 11, 12, 13, 15, 17], "garden": [12, 13, 17], "gaussian": [2, 6, 10, 11, 12, 15], "gaussianblur": [2, 13, 15, 17], "gaussiannois": [2, 13, 15], "gaussianshadingchacha": [1, 5], "gaussianshadingvisu": 4, "gaussmark": [6, 10, 12], "gaussmarkervisu": 4, "geip": 7, "gen_se": [3, 10, 12, 13, 17], "gener": [1, 2, 4, 5, 7, 9, 10, 11, 13, 14, 15, 16], "generate_capt": 5, "generate_clean_imag": 5, "generate_initial_nois": 5, "generate_unwatermarked_media": 5, "generate_watermarked_lat": 5, "generate_watermarked_media": [5, 9, 10, 12, 13, 14, 16, 17], "generated_or_retrieved_media": 2, "generation_kwarg": 2, "get": [0, 2, 3, 5, 9, 12, 13, 16], "get_data_for_visu": [5, 13, 16], "get_distance_hsqr": 1, "get_latents_at_step": 3, "get_media_lat": 3, "get_orig_watermarked_lat": 5, "get_pipeline_requir": 3, "get_pipeline_typ": 3, "get_prompt": 2, "get_random_lat": 3, "get_refer": 2, "get_ring_mask": 5, "gf": 1, "git": 11, "github": [0, 6, 7, 9, 11], "give": 8, "given": [2, 3], "gm": [1, 4, 5, 6, 12], "gm_detect": 1, "gm_visual": 4, "gmconfig": 5, "gmdetector": 1, "gmutil": 5, "gnr": [11, 14, 16], "gnr_binary_threshold": 1, "gnr_checkpoint": [1, 11], "gnr_classifier_typ": 1, "gnr_model_nf": 1, "gnr_threshold": 1, "gnr_use_for_decis": 1, "go": 0, "gold_label": 2, "goldstein": 7, "good": 2, "googl": 9, "gpu": 10, "gracefulli": 8, "gradient": 2, "greater": 2, "grid": [4, 16], "ground": 2, "ground_truth": 2, "group": [1, 2, 4, 10], "group_id": 1, "group_pattern": 1, "group_radiu": 1, "groupimagequalityanalysispipelin": [2, 15], "groupimagequalityanalyz": 2, "gs_detect": 1, "gs_visual": 4, "gs_watermark": 13, "gs_watermark_visu": 13, "gsconfig": 5, "gsdetector": 1, "gsutil": 5, "gt_patch": 1, "guan": 7, "guid": [0, 9, 11, 12, 17], "guidanc": [5, 17], "guidance_scal": [3, 5, 10, 12, 13, 15, 17], "guidelin": [0, 6, 8], "gunn": 7, "gunn2025undetect": 7, "guo": 7, "h": [1, 3, 4, 6], "h264": [2, 15], "h265": [2, 15], "ha": [2, 9, 16, 17], "hai": 7, "han": 7, "hand": 14, "handl": [2, 4], "hanqian": [6, 7], "harass": 8, "hard": 1, "harmon": 15, "have": [3, 9, 11], "healthi": 8, "heatmap": 16, "hegd": 7, "height": [1, 3, 5, 14, 17], "height_copi": 5, "help": [9, 12, 16], "helper": 5, "here": [10, 12], "heter_watermark_channel": 14, "heterogen": 14, "hevc": 2, "hidden": [7, 14], "hide": 14, "high": 14, "higher": [0, 2, 11], "highest": [1, 2], "highli": 11, "hint": 9, "histogram": 4, "hold": 1, "homepag": 7, "hong": 7, "host": 0, "hot": 4, "hou": 7, "how": [0, 2, 10, 12, 13, 14, 15, 16], "hstr": 1, "http": [0, 2, 6, 7, 9, 11], "hu": 7, "hu2025videomark": 7, "hu2025videoshield": 7, "huan": 6, "huang": 7, "huangrobin": 7, "huayang": 7, "hug": 11, "huggingfac": 11, "human": 14, "hw_copi": [1, 14], "hyperparamet": 5, "i": [0, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 15, 16, 17], "i2v": 5, "iclr": 7, "id": [1, 6, 10, 11, 12], "ideal": 2, "identif": [6, 7, 14], "identifi": 1, "idx": [1, 2, 5], "idx_list": 1, "ieee": 7, "ifft": 5, "ik": 7, "imag": [0, 3, 4, 5, 6, 7, 9, 10, 11, 14, 16], "image1": 9, "image2": 9, "image_dir": 17, "image_editor": [2, 9, 13, 15, 17], "image_quality_analysi": [2, 13, 15], "image_quality_analyz": [2, 9, 13, 15], "image_s": [3, 10, 12, 13, 17], "imageeditor": 2, "imagequalityanalysispipelin": 2, "imagequalityanalyz": 2, "imageri": 8, "imaging_qu": [2, 15], "imagingqualityanalyz": [2, 15], "img": [2, 3, 12, 13, 16, 17], "img_path": 17, "impact": [2, 10, 14], "impercept": 14, "implement": [1, 6, 7, 9, 10, 14], "import": [0, 9, 10, 11, 12, 13, 14, 15, 16, 17], "improv": [5, 14], "incept": [2, 6], "inceptionscor": 15, "inceptionscorecalcul": [2, 15], "includ": [2, 4, 5, 8, 9, 10], "inclus": 8, "indent": 9, "index": [0, 2, 4, 5, 6, 9, 10, 14, 15], "indic": [1, 2], "individu": [2, 7, 15], "infer": 5, "info": 4, "inform": [0, 2, 6, 7, 8, 11, 14, 16], "inherit": 3, "inherit_docstr": 3, "init_lat": 5, "init_latents_se": 3, "initi": [1, 2, 3, 4, 5, 14, 16], "initialize_detector": 5, "initialize_paramet": 5, "inject": [5, 6, 14, 16], "inject_hsqr": 5, "inject_watermark": 5, "inject_wm": 5, "inproceed": 7, "input": [2, 3, 5, 9], "input_data": [5, 9], "input_fil": 3, "input_tensor": 5, "insert": [2, 4], "insight": [10, 16], "inspected_embed": 4, "instal": 10, "instanc": [1, 4, 5, 8], "instanti": [4, 5], "instead": 4, "institut": 7, "insult": 8, "int": [1, 2, 3, 4, 5, 17], "integr": [6, 7, 14], "intend": 4, "intens": 2, "inter": 16, "interest": 10, "interfac": [5, 6], "intermedi": 2, "intern": [7, 16], "interpol": [2, 6, 15], "interpolated_fram": 2, "inv_ord": [3, 5], "invers": [5, 10, 17], "inverse_opt": 3, "inversion_typ": [3, 10, 12, 13, 17], "invert": [4, 16], "inverted_lat": 5, "invis": [6, 7, 13, 14], "involv": 2, "io": [0, 7, 9], "ipynb": 13, "irfft": 5, "irwin": 7, "is_avail": [10, 11, 12, 13, 17], "is_i2v_pipelin": 3, "is_image_pipelin": 3, "is_t2v_pipelin": 3, "is_video": 4, "is_video_pipelin": 3, "is_watermark": [1, 2], "issu": [6, 9, 16], "item": [12, 13, 15, 17], "iter": 2, "its": [3, 11, 17], "japanes": 12, "jie": 7, "jiwei": 7, "john": 7, "join": 17, "jona": 7, "journal": 7, "jpeg": [6, 13, 15, 17], "jpeg_editor": 17, "jpeg_result": 17, "jpegcompress": [2, 13, 15, 17], "jpg": 17, "json": [3, 9, 10, 11, 12, 13, 14, 15, 16, 17], "ju": 7, "jungang": 7, "k": [1, 5], "k_c": 1, "k_f": 1, "k_h": 1, "k_valu": 5, "k_w": 1, "kai": 7, "kasra": 7, "kecen": 7, "keep": 0, "kei": [1, 4, 5, 6, 7, 9, 11, 13, 15], "kejiang": 7, "kernel_s": [13, 15, 17], "key_se": 5, "keyword": [4, 5], "kind": 8, "king": 7, "kirchenbau": 7, "kwarg": [2, 3, 4, 5, 9], "l": 7, "l1": 1, "l1_complex": 1, "l1_distanc": [1, 2], "l1_threshold": 1, "label": [1, 2, 4, 15], "lake": [12, 13], "lambda": 15, "landscap": [5, 12, 13, 17], "languag": 8, "larg": [4, 11], "last": [4, 7], "latent": [1, 3, 4, 5, 7, 10, 11, 14, 16, 17], "latent_channel": 5, "latent_height": 5, "latent_width": 5, "latents_list": 3, "latexpdf": 0, "ldm": 10, "leader": 8, "learn": [6, 7, 11, 12, 13, 14, 16, 17], "learning_r": 14, "lee": 7, "lee2025semant": 7, "legend": 4, "len": [13, 15, 16], "level": [14, 16], "leverag": 14, "leyi": 7, "lg": 7, "li": [6, 7], "li2025gaussmarkerrobustdualdomainwatermark": 7, "librari": [5, 11], "liji": 7, "like": [2, 7], "line": 9, "linear": 2, "link": 7, "list": [2, 3, 4, 5, 9], "list_supported_algorithm": 5, "listdir": 17, "liu": 7, "llm": 10, "load": [2, 3, 4, 5, 9, 10, 13, 14, 15, 16, 17], "load_config_fil": 3, "load_json_as_list": 3, "local": 0, "logic": [5, 9], "lossless": [6, 7, 13, 14], "lower": [2, 14], "lpip": [2, 6, 15], "lpipsanalyz": [2, 15], "luyang": [6, 7], "m": [2, 9, 11], "ma": 9, "magnitud": 2, "mai": 8, "mail": 9, "main": [0, 2, 5, 7, 12, 13, 17], "majest": 13, "major": [9, 10], "make": [0, 2, 10, 11, 14], "make_fourier_treering_pattern": 5, "make_hsqr_pattern": 5, "make_qr_tensor": 5, "makedir": [13, 17], "makefil": 0, "manag": 11, "manipul": 11, "map": [1, 4, 16], "markdiffus": [1, 6, 8, 11, 12, 13, 14, 15, 16, 17], "markdiffusion_demo": 13, "markllm": 10, "mask": [1, 2, 4, 5, 6, 14, 15], "mask_ratio": 2, "mask_siz": 15, "mat": 2, "match": [1, 2], "matplotlib": [4, 16], "matric": 4, "matrix": 4, "max_display_s": 4, "max_pixel_valu": 2, "max_sampl": [2, 13, 15], "max_train_step": 14, "maximum": [2, 4, 14], "md": [8, 9], "mean": [2, 15], "mean_scor": [2, 13, 15], "measur": [1, 2], "mechan": [10, 12, 16, 17], "media": [5, 9, 10, 17], "media_editor_list": [2, 13, 15], "media_source_mod": 2, "media_util": 3, "member": 4, "memori": 11, "mention": 7, "messag": [1, 16], "message_bit": 5, "message_length": [1, 17], "message_list": 1, "message_sequ": 1, "message_threshold": 1, "method": [3, 4, 5, 10, 11, 13, 15], "method_kwarg": [4, 13], "metric": [2, 3, 5, 6, 9], "middl": 4, "mike": 7, "min": 3, "min_lr": 3, "minim": [3, 14], "minimum": 4, "mirror": 1, "misc": 7, "miss": 15, "mit": 7, "mkdir": 11, "modal": 6, "mode": [1, 3], "model": [2, 3, 4, 6, 7, 10, 11, 12, 14], "model_fin": 11, "model_id": [12, 13, 17], "model_nam": 2, "model_path": [2, 10], "model_url": 2, "modified_imag": 9, "modul": [1, 3, 6, 10, 11, 12, 15, 16], "modular": 10, "more": [2, 6, 13], "most": 7, "mostli": 2, "motion": [2, 6, 16], "motion_smooth": [2, 15], "motionsmoothnessanalyz": [2, 13, 15], "mountain": [12, 13, 17], "move": 3, "mpeg": [2, 6], "mpeg4": 15, "mpeg4compress": [2, 15], "mscn": 2, "mscoco": 2, "mscoco_dataset": 15, "mscocodataset": [2, 15], "much": 15, "multi": [6, 7, 14], "multipl": [2, 4, 10, 14, 16, 17], "musiq": 2, "musiq_spaq_ckpt": 2, "must": 2, "my_algorithm": 9, "my_algorithm_detect": 9, "my_algorithm_visu": 9, "myalgorithm": 9, "myattack": 9, "mymetr": 9, "mywatermark": 13, "n": [3, 11, 12, 13, 15, 16], "n_frame": 2, "n_px": 2, "nam": 7, "name": [2, 4, 5, 9, 11, 12, 17], "natur": [2, 6, 15], "ndarrai": [3, 5], "ndetect": 15, "necessari": [5, 12], "need": [0, 2, 13, 14], "neg": 15, "neighbor": 2, "nenghai": 7, "net": 2, "network": [2, 14, 16], "neural": [7, 14], "nevalu": 15, "new": [0, 11], "new_text_embed": 3, "next": 10, "night": [12, 13], "niqe": [2, 6, 15], "niqe_image_param": 2, "niqecalcul": [2, 15], "niter": 2, "niv": 7, "nois": [1, 2, 4, 5, 6, 7, 11, 13, 14, 15], "noise_group": 1, "noise_typ": 15, "noisi": 11, "nomal": 5, "non_watermarked_result": 2, "nonc": [1, 5, 14], "nonce_se": 5, "none": [1, 2, 3, 4, 5, 9], "normal": [1, 3, 4], "notabl": 6, "novemb": 7, "now": [11, 12], "np": 3, "nqualiti": 15, "nrobust": 15, "nscore": 16, "ntest": 12, "num_fram": [1, 3, 4, 5, 13, 17], "num_imag": 3, "num_inference_step": [3, 5, 10, 12, 13, 15, 17], "num_inversion_step": 3, "num_mask": [2, 15], "num_refer": 2, "num_sampl": 2, "num_step": 3, "num_strok": [2, 15], "number": [1, 2, 3, 4, 5, 14, 17], "numer": [2, 4], "numpi": [3, 5, 9], "numpy_to_pil": 3, "nvidia": 11, "o": [13, 17], "object": [2, 3, 4, 5, 17], "observ": 14, "obtain": 1, "ocean": 10, "off": 16, "offici": 1, "offset": 5, "old_text_embed": 3, "one": [4, 5, 12], "onli": [1, 5], "open": [0, 6, 7, 9, 10, 11, 17], "oper": [3, 10], "opinion": 8, "optic": 2, "optim": [3, 4, 5, 6, 7, 14, 16], "optimize_watermark": 5, "optimized_watermark": 5, "option": [1, 2, 3, 4, 5, 11], "order": [3, 5], "org": [0, 7], "orig_watermarked_lat": 4, "origin": [1, 2, 3, 4, 5, 7, 10, 16, 17], "original_embed": 4, "original_result": 17, "other": 8, "otherwis": [3, 8], "our": [7, 8, 9, 10], "output": [2, 3, 10, 12, 15, 16, 17], "output_": 12, "output_dir": 13, "output_typ": 3, "output_video": 13, "over": [10, 13, 16], "overal": [2, 8, 15], "overlai": [2, 6, 15, 16], "overrid": 1, "overridden": 5, "overview": 10, "own": [3, 17], "p": [1, 2, 7, 11], "packag": 0, "page": [0, 1, 2, 3, 4, 5, 7, 9, 10], "pairwis": 2, "pan": 7, "pan2025markdiffus": 7, "panda": 15, "panly24": 9, "paper": [7, 16], "paradis": 13, "param1": 9, "param2": 9, "paramet": [1, 2, 3, 4, 5, 9, 13, 14], "park": 5, "particip": 9, "particularli": 2, "pass": [2, 9], "patch": [2, 4, 5, 6], "patch_accuraci": 1, "patch_distance_threshold": 1, "patch_siz": 2, "path": [2, 3, 4, 5, 11, 17], "patienc": 3, "pattern": [1, 4, 5, 6, 10, 11, 13, 16], "pd": 15, "pdf": 13, "peak": [2, 6, 13], "pei": 7, "peopl": 8, "pep": 9, "per": [2, 5, 9, 15], "perceptu": [2, 6, 14], "perform": [2, 6, 7, 13, 14, 15, 16], "permiss": 8, "permut": 3, "persist": 17, "person": 8, "perturb": 16, "phase": 2, "philip": 7, "pil": [2, 3, 5, 9, 17], "pil_img": 3, "pil_to_cv2": 3, "pil_to_torch": 3, "pip": [0, 11], "pipe": [3, 5, 10, 12, 13, 17], "pipelin": [1, 5, 6, 10, 13, 15, 17], "pipeline_requir": 3, "pipeline_typ": [3, 5], "pipeline_util": 3, "piq": 2, "pixel": 2, "pkl": 11, "place": 11, "plan": [6, 11], "platform": 2, "pleas": [4, 6, 7, 8, 9, 10], "pledg": 8, "plot": [4, 16], "plt": [4, 16], "png": [5, 12, 13, 16, 17], "polit": 8, "posit": [5, 15], "possibli": 5, "power": 16, "pr": 9, "practic": [9, 10], "prc": [6, 10, 11, 12], "prc_detect": [1, 4], "prc_visual": 4, "prcconfig": 5, "prcdetector": 1, "prcutil": 5, "prcvisual": 4, "pre": [2, 6, 7, 11], "precis": 15, "pred_m_from_lat": 5, "pred_w_from_lat": 5, "pred_w_from_m": 5, "predefin": 14, "predict": [2, 15], "predictor": 2, "prefer": 11, "prepared_data": 2, "preprint": 7, "preprocess": 5, "preprocess_image_for_detect": 5, "present": [1, 16], "preserv": [2, 14], "prevent": 10, "primaryclass": 7, "print": [10, 11, 12, 13, 15, 17], "privat": 8, "probabl": 2, "problem": 11, "proce": 11, "procedur": 8, "proceed": 7, "process": [2, 3, 5, 7, 14, 17], "processor": [3, 11], "progress": 2, "project": [0, 7], "prompt": [2, 3, 5, 9, 10, 12, 13, 14, 15, 16, 17], "prompt_per_imag": [2, 15], "proper": 3, "properti": [2, 3, 5], "provabl": [7, 13, 14], "provid": [2, 3, 8, 9, 10, 13, 14, 15, 16], "pseudorandom": 4, "psnr": [2, 6, 13, 15], "psnranalyz": [2, 13, 15], "pt": 3, "pth": [2, 11], "public": [8, 9, 15], "publish": 8, "pull": 9, "push": 0, "put": 12, "py": [0, 9, 14], "pyplot": 16, "pytest": [9, 11], "python": [0, 5, 9, 10, 11, 14], "pytorch": 11, "qian": 7, "qing": 7, "qiu": 7, "qr_gt_bool": 1, "qr_version": 5, "qrcode": 5, "qrcodegener": 5, "qualiti": [6, 7, 9, 10, 12, 14, 17], "quality_pipelin": 15, "quality_result": 15, "qualitycomparisonresult": 2, "qualitypipelinereturntyp": [2, 13, 15], "quantifi": 2, "quantiz": 14, "quantization_level": 14, "question": [7, 9], "quick": [0, 11, 15], "quickli": 12, "quickstart": 0, "r": [0, 2, 5, 7, 11], "r_in": 5, "r_out": 5, "radiu": [2, 5, 14], "radius_cutoff": 14, "raft": 2, "rais": 4, "ram": 11, "random": [2, 3, 5, 17], "rang": [2, 14], "rate": [6, 14, 15], "ratio": [2, 6], "rdbu": 4, "re": [2, 12], "read": 9, "readi": 3, "readm": 7, "readthedoc": [0, 9], "real": 6, "recal": 15, "receiv": 2, "recogn": 3, "recommend": 11, "reconstruct": [3, 4, 16], "recov": [1, 4], "recovered_prc": 4, "recoveri": 1, "reducelronplateau": 3, "refer": [0, 1, 2, 9, 13, 14, 15, 16], "referenc": 2, "reference_imag": [2, 5], "reference_image_sourc": [2, 15], "reference_lat": 1, "reference_nois": 4, "reference_sourc": 2, "reference_video": 2, "referencedimagequalityanalysispipelin": [2, 15], "referencedimagequalityanalyz": 2, "referenceless": 2, "region": [1, 4], "regist": 9, "regul": [7, 14], "reinvent": [7, 14], "reject": 15, "rel": 3, "relat": [0, 2], "releas": 7, "relev": 9, "reliabl": 15, "remov": [4, 10], "repeat": 2, "repeatimagequalityanalysispipelin": [2, 15], "repeatimagequalityanalyz": 2, "repetit": 1, "report": [1, 6, 9, 16], "repositori": [7, 8, 9, 11], "repres": 2, "represent": [7, 16], "reproduc": [3, 17], "request": [3, 6, 9], "requir": [0, 2, 3, 7, 10, 15, 17], "resampl": 2, "research": [7, 10], "residu": 14, "resist": 14, "resiz": 3, "resolut": 5, "resolv": 2, "respect": [8, 9], "respons": 8, "restor": 11, "result": [1, 2, 5, 9, 12, 13, 14, 15, 16, 17], "rethink": [7, 14], "return": [1, 2, 3, 4, 5, 9, 12, 15], "return_typ": [2, 13, 15], "revers": [1, 2, 5, 15], "reverse_process": 3, "reversed_lat": [1, 4, 5], "reversed_m": 5, "review": 9, "rf": 0, "rfft": 5, "ri": [6, 11, 12], "ring": [6, 10, 11, 12], "ring_value_rang": 14, "ring_watermark_channel": 14, "ring_width": 14, "ringid": [7, 14], "rm": 0, "rng_gener": 3, "rob_": 15, "robin": [6, 10, 11, 12, 15], "robin_detect": 1, "robin_visu": 4, "robin_watermark_visu": 13, "robinconfig": 5, "robindetector": [1, 5], "robinutil": 5, "robinvisu": 4, "robust": [1, 6, 7, 10, 12, 14, 16, 17], "robustness_result": 15, "roc": 15, "root": [8, 9], "rotat": [2, 6, 13, 15, 17], "rotated_imag": 17, "rotation_editor": 17, "rotation_result": 17, "rounderringmask": 5, "rout": 5, "row": [4, 13, 15, 16], "rst": [0, 9], "rule": 2, "run": [2, 5, 9, 11, 15, 17], "runyi": 7, "salesforc": 11, "sam": 7, "same": [13, 15], "sampl": [2, 3], "sample_fp": 2, "save": [3, 4, 5, 12, 13, 16, 17], "save_dir": 3, "save_every_n_step": 3, "save_img": 5, "save_path": [4, 5, 13], "save_video_fram": 3, "savefig": 16, "scale": [3, 5, 6, 15, 17], "scenario": [2, 10, 14], "scene": 2, "schedul": [3, 10, 12, 13, 17], "scope": 8, "score": [1, 2, 6, 9, 12, 13, 15, 16], "script": 0, "sd": 4, "sd21_cls2": 11, "seal": [6, 10, 12, 15], "seal_detect": 1, "seal_visu": 4, "sealconfig": 5, "sealdetector": 1, "sealutil": 5, "sealvisu": 4, "search": 10, "second": 2, "secret": 14, "secret_kei": 17, "secret_salt": 5, "secur": 14, "see": [0, 6, 7, 8, 9], "seed": [3, 5, 14, 17], "select": [2, 17], "self": [4, 9], "semant": [2, 6, 7, 12, 14], "sentenc": 11, "sentence_model": 11, "sentence_transform": 1, "sequenc": 1, "seren": [12, 13], "set": [2, 3, 5, 9, 12, 15, 17], "set_complex_sign": 5, "set_invers": 3, "set_orig_watermarked_lat": 5, "set_random_se": 3, "setup": [9, 10, 13, 15], "sexual": 8, "sfw": [6, 10, 11, 12], "sfw_detect": 1, "sfw_visual": 4, "sfwconfig": 5, "sfwdetector": 1, "sfwutil": 5, "sfwvisual": 4, "shade": [6, 10, 11, 12], "shape": [5, 14], "sheng": 7, "shou": 7, "should": [2, 5, 9], "show": [2, 4, 12, 14, 16, 17], "show_axi": 4, "show_label": 4, "show_legend": 4, "show_numb": 4, "show_progress": [2, 13, 15], "shown": 4, "shuffl": 2, "si": [6, 7], "side": 16, "sigma": 2, "sign": 5, "sign_tensor": 5, "signal": [2, 5, 6], "signal_complex": 1, "simhash": 5, "similar": [1, 2, 5, 6], "simpl": [10, 11, 14], "simplenamespac": 5, "simplifi": 10, "simul": 2, "simultan": 15, "singl": [2, 4, 5, 15, 17], "size": [2, 3, 4, 5, 9, 17], "skylin": 12, "smaller": 1, "smooth": [2, 6], "smoother": 2, "snow": 13, "snowi": 13, "solut": [10, 11], "solver": 3, "some": [5, 11, 17], "sometim": [0, 2], "song": 7, "sort": [1, 15], "sourc": [1, 2, 3, 4, 5, 7, 10], "source_fp": 2, "space": [5, 9, 11], "spatial": [2, 4, 14, 16], "specif": [2, 3, 5, 9, 10, 12, 13, 14, 15, 16, 17], "specifi": 3, "spectrum": 16, "sphinx": 0, "sphinxopt": 0, "split": 2, "spread_tensor": 5, "squeez": 2, "ssim": [2, 6, 13, 15], "ssimanalyz": [2, 13, 15], "stabil": 2, "stabilityai": [12, 13, 17], "stabl": [2, 12, 13, 17], "stablediffusionpipelin": [3, 10, 12, 13, 17], "stablediffusionpromptsdataset": [2, 13, 15], "stablevideodiffusionpipelin": 3, "stage": [1, 4, 6, 7, 14, 17], "stai": 9, "standard": [1, 2, 15], "start": [0, 9, 11], "state": [10, 14], "static": [0, 2, 5], "statist": 2, "std": [2, 13, 15], "step": [3, 4, 5, 10], "stepschedul": 3, "store": [2, 11], "store_path": 2, "str": [1, 2, 3, 4, 5, 9, 17], "stream": 1, "street": 13, "string": [2, 3, 5], "stroke_typ": 2, "stroke_width": 2, "structur": [2, 6, 9, 11], "style": 0, "subclass": 5, "subdirectori": 11, "subfold": [10, 12, 13, 17], "subject": [2, 6, 13], "subject_consist": [2, 13, 15], "subjectconsistencyanalyz": [2, 13, 15], "submit": 9, "subplot": [4, 16], "success_rate_calcul": [2, 13, 15], "successfulli": 11, "suffici": [2, 11], "suit": 11, "suitabl": 2, "sum": [13, 15], "summari": 15, "sung": 7, "sunset": [9, 10, 12, 13], "super": 9, "support": [1, 4, 5, 6, 7, 10, 12, 14, 17], "sure": 11, "surviv": 15, "swap": [2, 6], "swap_prob": 15, "synthet": 2, "system": [7, 14], "t": 3, "t2i": 5, "t2v": [4, 5], "t5": 11, "tabl": 15, "target": [2, 3, 4, 14, 15], "target_fft": 1, "target_fp": [2, 15], "target_fpr": [2, 15], "target_s": 3, "tau_bit": 1, "teal": 7, "technic": 9, "technologi": 10, "templat": [0, 9], "tempor": [2, 4, 11, 14, 16], "tensor": [1, 2, 3, 4, 5], "tensor2vid": 3, "test": [2, 11, 13, 15], "test_detect": 9, "test_gener": 9, "test_my_algorithm": 9, "test_watermark": 9, "testcas": 9, "testmyfeatur": 9, "text": [2, 3, 5, 7, 9, 10, 15, 16], "text_embed": 3, "texttovideosdpipelin": 3, "than": 2, "thank": [6, 7, 9], "them": [2, 11, 14], "theta_mid": 1, "thi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 17], "thing": 2, "thirti": 7, "three": [12, 15, 17], "threshold": [1, 2, 3, 9, 12, 14, 17], "threshold_mod": 3, "through": [1, 11, 12, 14], "throw": [4, 5], "thu": [0, 6, 7, 9, 11], "tianwei": 7, "tick": 4, "tight": 16, "tight_layout": 16, "time": [6, 16], "timestep": [3, 4], "timesteps_list": 3, "tip": 15, "titl": [4, 7], "tnr": [2, 15], "to_cpu": 3, "to_str": 15, "togeth": [12, 15], "tom": 7, "tool": [6, 10, 13, 15, 16, 17], "toolkit": [7, 10, 11], "top": 2, "topic": [0, 13], "torch": [1, 3, 10, 11, 12, 13, 17], "torch_dtyp": [13, 17], "torch_to_numpi": 3, "torchaudio": 11, "torchvis": 11, "toward": 8, "tpr": [2, 15], "tr": [1, 4, 5, 6, 10, 11, 12, 13, 15], "tr_detect": 1, "tr_visual": 4, "tr_watermark": 13, "tr_watermark_visu": 13, "track": 14, "train": [2, 6, 7, 11, 14], "train_gnr": 14, "transform": [2, 3, 11, 14], "transform_to_model_format": 3, "trconfig": 5, "trdetector": 1, "tree": [6, 10, 11, 12], "treeringvisu": 4, "troll": 8, "tropic": 13, "troubleshoot": 10, "true": [1, 2, 3, 4, 13, 14, 15, 17], "truth": 2, "trutil": 5, "try": 11, "tsinghua": 9, "tune": 11, "tupl": [2, 4, 5, 17], "tutori": [0, 6, 10, 11, 12, 14], "two": [1, 2, 4, 6, 7, 10, 11, 14], "txt": [0, 11], "type": [1, 2, 3, 4, 5, 9, 14, 17], "typic": [2, 12], "unaccept": 8, "under": [2, 6, 7, 9, 10, 15], "understand": [10, 16], "undetect": [6, 7, 14], "unet": [3, 11], "unifi": [6, 10], "union": [1, 2, 3, 5, 9], "unittest": 9, "unix": 0, "unwatermark": [2, 5, 15], "unwatermarked_frame_editor_list": [2, 13, 15], "unwatermarked_imag": 2, "unwatermarked_image_editor_list": [2, 13, 15], "unwatermarked_image_sourc": [2, 15], "unwatermarked_pipelin": 15, "unwatermarked_quality_scor": 2, "unwatermarked_scor": 15, "unwatermarked_video": 2, "unwatermarked_video_editor_list": [2, 13, 15], "unwatermarkedmediadetectionpipelin": [2, 15], "unwm_scor": 15, "up": [7, 9, 17], "updat": [0, 9, 11], "upfront": 2, "upgrad": 11, "upper": 14, "url": 7, "us": [1, 2, 3, 4, 5, 8, 9, 10, 11, 14, 15], "usag": [9, 10, 14], "use_color_bar": 4, "use_old_emb_i": 3, "user": [0, 14, 16], "user_guid": [0, 9], "user_numb": 5, "util": [0, 1, 2, 5, 7, 10, 12, 13, 17], "v": [4, 15], "v1": 6, "va": 16, "valid": 1, "valu": [1, 2, 4, 5, 14, 15], "valuabl": 7, "valueerror": 4, "var": 1, "vari": 2, "variabl": 9, "variant": 2, "variou": [10, 13, 15], "vbench": 2, "vbenchdataset": [2, 13, 15], "vector": [5, 16], "verbos": 3, "verif": 10, "verifi": [11, 17], "version": [2, 4, 8, 11], "vgg": 2, "via": [2, 3, 7, 14], "video": [1, 3, 4, 5, 6, 7, 9, 10, 11, 16], "video_attack": 15, "video_config": 17, "video_dataset": [13, 15], "video_diffusion_config": [12, 13, 14], "video_editor": [2, 15], "video_fram": [5, 12, 13], "video_mark": 5, "video_mark_visu": 4, "video_model_id": 13, "video_output": 17, "video_pip": [13, 17], "video_pipelin": 13, "video_quality_analysi": [2, 13, 15], "video_quality_analyz": [2, 13, 15], "video_quality_result": 15, "video_robustness_result": 15, "video_shield": 5, "video_shield_visu": 4, "video_watermark": [12, 13, 15, 17], "videocodecattack": [2, 6, 15], "videoeditor": 2, "videomark": [6, 10, 11, 12, 13], "videomark_detect": 1, "videomarkconfig": 5, "videomarkdetector": 1, "videomarkutil": 5, "videomarkvisu": 4, "videomarkwatermark": 5, "videoqualityanalysispipelin": 2, "videoqualityanalyz": 2, "videoshield": [6, 10, 11, 12, 13, 17], "videoshield_detect": 1, "videoshieldconfig": 5, "videoshielddetector": 1, "videoshieldutil": 5, "videoshieldvisu": 4, "videoshieldwatermark": 5, "view": 10, "viewpoint": 8, "vif": [2, 6, 15], "vifanalyz": [2, 15], "violat": 8, "viridi": 4, "visibl": 16, "vision": 7, "visit": 11, "visual": [0, 2, 5, 6, 9, 10, 12, 14, 17], "vit": 2, "viz_data": 16, "vmax": 4, "vmin": 4, "vote_threshold": 1, "vp9": [2, 6], "w": [0, 1, 3, 4], "w_channel": [1, 14], "w_low_radiu": 14, "w_mask_shap": 14, "w_measur": 1, "w_pattern": 14, "w_pattern_const": 14, "w_radiu": 14, "w_seed": [5, 14], "w_up_radiu": 14, "wa": [7, 9], "wai": 5, "walk": [12, 17], "wang": [6, 7], "watermark": [0, 1, 2, 4, 6, 9, 10, 11, 15, 16], "watermark_algo": 15, "watermark_config": 5, "watermark_gener": 1, "watermark_se": 5, "watermark_strength": 17, "watermark_tensor": 5, "watermarkdetectionpipelin": 2, "watermarkdetectionresult": 2, "watermarked_": 13, "watermarked_frame_editor_list": [2, 13, 15], "watermarked_imag": [2, 10, 12, 13, 16, 17], "watermarked_image_editor_list": [2, 13, 15], "watermarked_img": 13, "watermarked_output": 12, "watermarked_pipelin": 15, "watermarked_quality_scor": 2, "watermarked_result": 2, "watermarked_scor": 15, "watermarked_video": 2, "watermarked_video_editor_list": [2, 13, 15], "watermarkedmediadetectionpipelin": [2, 13, 15], "watermarking_arg": 5, "watermarking_config": 5, "watermarking_mask": [1, 5], "watermarking_step": [4, 14], "we": [6, 7, 8, 9], "web": 6, "weight": 11, "weim": 7, "welcom": [6, 8, 9], "well": [2, 15], "wen": 7, "wen2023treeringwatermarksfingerprintsdiffus": 7, "what": 8, "when": [0, 2, 5, 7, 9], "where": [2, 4], "whether": [1, 2, 3, 4, 5, 9], "which": 4, "while": 2, "width": [1, 3, 5, 14, 17], "width_copi": 5, "wind": [6, 10, 11, 12], "wind_detect": 1, "wind_visu": 4, "windconfig": 5, "windetector": 1, "window": 2, "window_s": 15, "windutil": 5, "windvisu": 4, "wise": 16, "within": 2, "without": [2, 3, 4, 8], "witter": 7, "wm": 16, "wm_kei": [1, 14], "wm_score": 15, "wm_type": 1, "work": [7, 11, 14, 16], "workflow": [9, 10, 14, 15], "worst": 2, "would": 7, "write": [0, 9], "wu": 7, "x": [7, 15], "x_offset": 5, "xinwen": 7, "xl": 11, "xuandong": 7, "xume": 7, "y_offset": 5, "yaml": 0, "yang": 7, "yang2024gaussian": 7, "year": 7, "yime": 7, "yiren": 7, "you": [0, 2, 7, 9, 10, 11, 12, 16, 17], "your": [0, 7, 9, 11, 12, 13, 17], "yu": 7, "yuxin": 7, "z": 7, "z_0": 3, "z_t": 3, "zeng": 7, "zero": 14, "zeroscope_v2_576w": [13, 17], "zhang": 7, "zhao": 7, "zheng": 7, "zheyu": [6, 7], "zhicong": 7, "zian": 7, "zijin": 7, "zoo": 6}, "titles": ["Building MarkDiffusion Documentation", "Detection API", "Evaluation API", "Utilities API", "Visualization API", "Watermark API", "Changelog", "Citation", "Code of Conduct", "Contributing to MarkDiffusion", "MarkDiffusion Documentation", "Installation", "Quick Start", "Tutorial", "Watermarking Algorithms", "Evaluation", "Visualization", "Watermarking Workflow"], "titleterms": {"0": 6, "01": 6, "1": [6, 12, 13], "2": [12, 13], "2025": 6, "3": [12, 13], "4": [12, 13], "5": 13, "On": 0, "acknowledg": 7, "ad": 9, "addit": [9, 10], "advanc": 16, "against": 17, "algorithm": [4, 7, 9, 11, 12, 14, 15, 16, 17], "all": 0, "analysi": [2, 15], "analyz": 2, "apa": 7, "api": [1, 2, 3, 4, 5, 10], "attack": [2, 13, 15, 17], "attribut": 8, "autovisu": 4, "autowatermark": 5, "base": [1, 3, 4, 5, 14], "basic": [11, 12, 13, 15, 16, 17], "batch": [13, 17], "best": 15, "bibtex": 7, "build": 0, "cach": 0, "calcul": 2, "callback": 3, "changelog": 6, "checklist": 9, "choos": 14, "citat": 7, "clean": 0, "clear": 0, "code": [8, 9], "combin": 15, "common": 11, "commun": 8, "compar": [12, 15, 16], "comparison": 14, "complet": 12, "compon": 15, "comprehens": 15, "conda": 11, "conduct": 8, "configur": [3, 12, 13, 17], "contact": 7, "content": 10, "contribut": [0, 6, 9], "custom": [13, 16, 17], "dataset": 2, "ddim": 3, "depend": 0, "detect": [1, 2, 12, 15, 17], "detector": 1, "develop": [0, 9], "diffus": [3, 17], "diffusionconfig": 17, "dimens": 15, "direct": 15, "doc": 0, "document": [0, 9, 10], "editor": 2, "epub": 0, "evalu": [2, 9, 13, 15], "exact": 3, "exampl": [5, 10, 12], "featur": [6, 10], "file": [0, 17], "fix": 15, "format": 0, "full": 15, "g": [14, 16], "gaussian": [1, 4, 5, 7, 13, 14, 16], "gaussmark": [1, 4, 5, 7, 11, 14, 16], "gener": [3, 12, 17], "get": [10, 11], "gm": [11, 14, 16], "gpu": 11, "group": 15, "guid": 10, "guidelin": 9, "help": 11, "html": 0, "id": [7, 14], "imag": [2, 12, 13, 15, 17], "indic": 10, "inform": 9, "initi": 6, "instal": [0, 11], "invers": 3, "issu": 11, "kei": [10, 14], "layout": 16, "licens": 7, "link": 0, "linux": 0, "live": 0, "load": 12, "mac": 0, "markdiffus": [0, 7, 9, 10], "mechan": 13, "media": [3, 12], "method": [1, 14, 16], "metric": 15, "miss": 0, "mla": 7, "model": 17, "multipl": [12, 13, 15], "new": 9, "next": [11, 12, 13, 14, 15, 16, 17], "other": [0, 11], "overview": [9, 14, 15, 16], "paramet": 17, "pattern": 14, "pdf": 0, "pipelin": [2, 3], "practic": 15, "prc": [1, 4, 5, 7, 14], "prerequisit": 0, "prevent": 17, "process": [9, 13], "public": 7, "qualiti": [2, 13, 15], "quick": [8, 10, 12], "rate": 2, "read": 0, "recent": 6, "refer": [8, 10], "referenc": 15, "releas": 6, "reload": 0, "remov": 17, "repeat": 15, "report": 8, "requir": 11, "resourc": 10, "ri": 14, "right": 14, "ring": [1, 4, 5, 7, 13, 14, 16], "robin": [1, 4, 5, 7, 13, 14, 16], "robust": [13, 15], "sampl": 15, "seal": [1, 4, 5, 7, 11, 14], "select": 15, "setup": [11, 12, 17], "sfw": [1, 4, 5, 7, 14], "shade": [1, 4, 5, 7, 13, 14, 16], "simpl": 16, "size": 15, "specif": [4, 7, 11], "standard": 8, "start": [10, 12], "step": [11, 12, 13, 14, 15, 16, 17], "structur": [0, 17], "style": [7, 9], "submiss": 9, "success": 2, "suit": 15, "support": 11, "tabl": 10, "test": [9, 17], "threshold": 15, "tool": [2, 9], "tr": [14, 16], "tree": [1, 4, 5, 7, 13, 14, 16], "troubleshoot": [0, 11], "tutori": 13, "upcom": 6, "updat": [6, 7], "us": [7, 13], "usag": 16, "user": 10, "util": 3, "verif": 11, "version": 6, "video": [2, 12, 13, 14, 15, 17], "videomark": [1, 4, 5, 7, 14], "videoshield": [1, 4, 5, 7, 14, 16], "visual": [4, 13, 16], "warn": 0, "watermark": [5, 7, 12, 13, 14, 17], "welcom": 10, "wind": [1, 4, 5, 7, 14], "window": 0, "workflow": [12, 17], "xx": 6}}) \ No newline at end of file diff --git a/docs/_build/html/tutorial.html b/docs/_build/html/tutorial.html deleted file mode 100644 index cbc7d76..0000000 --- a/docs/_build/html/tutorial.html +++ /dev/null @@ -1,1238 +0,0 @@ - - - - - - - - - Tutorial — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Tutorial

-

This tutorial provides step-by-step examples for using MarkDiffusion’s main features.

-
-

Tutorial 1: Basic Image Watermarking

-

Learn how to watermark images using different algorithms.

-
-

Using Tree-Ring Watermark

-

Tree-Ring is a pattern-based watermarking method that embeds invisible patterns in the frequency domain.

-
import torch
-from watermark.auto_watermark import AutoWatermark
-from utils.diffusion_config import DiffusionConfig
-from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
-
-# Setup
-device = 'cuda' if torch.cuda.is_available() else 'cpu'
-model_id = "stabilityai/stable-diffusion-2-1"
-
-scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler")
-pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device)
-
-diffusion_config = DiffusionConfig(
-    scheduler=scheduler,
-    pipe=pipe,
-    device=device,
-    image_size=(512, 512),
-    num_inference_steps=50,
-    guidance_scale=7.5,
-    gen_seed=42,
-    inversion_type="ddim"
-)
-
-# Load Tree-Ring watermark
-tr_watermark = AutoWatermark.load(
-    'TR',
-    algorithm_config='config/TR.json',
-    diffusion_config=diffusion_config
-)
-
-# Generate watermarked image
-prompt = "A majestic mountain landscape with snow peaks"
-watermarked_img = tr_watermark.generate_watermarked_media(prompt)
-watermarked_img.save("tr_watermarked.png")
-
-# Detect watermark
-result = tr_watermark.detect_watermark_in_media(watermarked_img)
-print(f"Tree-Ring Detection: {result}")
-
-
-
-
-

Using Gaussian-Shading Watermark

-

Gaussian-Shading is a key-based method that provides provable performance-lossless watermarking.

-
# Load Gaussian-Shading watermark
-gs_watermark = AutoWatermark.load(
-    'GS',
-    algorithm_config='config/GS.json',
-    diffusion_config=diffusion_config
-)
-
-# Generate watermarked image
-watermarked_img = gs_watermark.generate_watermarked_media(prompt)
-watermarked_img.save("gs_watermarked.png")
-
-# Detect watermark
-result = gs_watermark.detect_watermark_in_media(watermarked_img)
-print(f"Gaussian-Shading Detection: {result}")
-
-
-
-
-
-

Tutorial 2: Visualizing Watermark Mechanisms

-

Learn how to visualize watermarking mechanisms using the same approach as in MarkDiffusion_demo.ipynb.

-
-

Tree-Ring Visualization

-
from visualize.auto_visualization import AutoVisualizer
-
-# Get visualization data
-data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image)
-
-# Load visualizer
-visualizer = AutoVisualizer.load('TR', data_for_visualization=data_for_visualization)
-
-# Create visualization with specific methods and channels
-method_kwargs = [{}, {"channel": 0}, {}, {"channel": 0}, {}]
-fig = visualizer.visualize(
-    rows=1,
-    cols=5,
-    methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image',
-             'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'],
-    method_kwargs=method_kwargs,
-    save_path='TR_watermark_visualization.pdf'
-)
-
-
-
-
-

Gaussian-Shading Visualization

-
from visualize.auto_visualization import AutoVisualizer
-
-data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image)
-visualizer = AutoVisualizer.load('GS', data_for_visualization=data_for_visualization)
-
-method_kwargs = [{"channel": 0}, {"channel": 0}, {}, {"channel": 0}, {"channel": 0}]
-fig = visualizer.visualize(
-    rows=1,
-    cols=5,
-    methods=['draw_watermark_bits', 'draw_orig_latents', 'draw_watermarked_image',
-             'draw_inverted_latents', 'draw_reconstructed_watermark_bits'],
-    method_kwargs=method_kwargs,
-    save_path='GS_watermark_visualization.pdf'
-)
-
-
-
-
-

ROBIN Visualization

-
from visualize.auto_visualization import AutoVisualizer
-
-data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image)
-visualizer = AutoVisualizer.load('ROBIN', data_for_visualization=data_for_visualization)
-
-method_kwargs = [{}, {"channel": 3}, {}, {"channel": 3}, {}]
-fig = visualizer.visualize(
-    rows=1,
-    cols=5,
-    methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image',
-             'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'],
-    method_kwargs=method_kwargs,
-    save_path='ROBIN_watermark_visualization.pdf'
-)
-
-
-
-
-
-

Tutorial 3: Evaluating Watermark Quality

-

Assess the quality and robustness of watermarks.

-
-

Image Quality Evaluation

-
from evaluation.dataset import StableDiffusionPromptsDataset
-from evaluation.pipelines.image_quality_analysis import ComparedImageQualityAnalysisPipeline
-from evaluation.tools.image_quality_analyzer import PSNRAnalyzer, SSIMAnalyzer
-from evaluation.pipelines.image_quality_analysis import QualityPipelineReturnType
-
-# Create dataset
-dataset = StableDiffusionPromptsDataset(max_samples=50)
-
-# Setup quality analysis pipeline
-pipeline = ComparedImageQualityAnalysisPipeline(
-    dataset=dataset,
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[PSNRAnalyzer(), SSIMAnalyzer()],
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-# Evaluate watermark quality
-results = pipeline.evaluate(gs_watermark)
-print(f"PSNR: {results['PSNR']:.2f} dB")
-print(f"SSIM: {results['SSIM']:.4f}")
-
-
-
-
-

Robustness Evaluation

-

Test watermark robustness against various attacks:

-
from evaluation.tools.image_editor import (
-    JPEGCompression, GaussianBlurring, GaussianNoise, Rotation
-)
-from evaluation.pipelines.detection import WatermarkedMediaDetectionPipeline
-from evaluation.pipelines.detection import DetectionPipelineReturnType
-from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator
-
-# Test against JPEG compression
-dataset = StableDiffusionPromptsDataset(max_samples=100)
-
-# Create detection pipeline with JPEG attack
-detection_pipeline = WatermarkedMediaDetectionPipeline(
-    dataset=dataset,
-    media_editor_list=[JPEGCompression(quality=75)],
-    show_progress=True,
-    return_type=DetectionPipelineReturnType.SCORES
-)
-
-# Evaluate detection after attack
-detection_kwargs = {
-    "num_inference_steps": 50,
-    "guidance_scale": 1.0,
-}
-
-scores = detection_pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs)
-print(f"Detection scores after JPEG compression: {scores}")
-
-
-
-
-

Multiple Attacks Evaluation

-
# Test robustness against multiple attacks
-attacks = {
-    'JPEG-75': JPEGCompression(quality=75),
-    'JPEG-50': JPEGCompression(quality=50),
-    'Blur': GaussianBlurring(kernel_size=3),
-    'Noise': GaussianNoise(std=0.05),
-    'Rotation': Rotation(angle=15)
-}
-
-results = {}
-for attack_name, attack_editor in attacks.items():
-    pipeline = WatermarkedMediaDetectionPipeline(
-        dataset=dataset,
-        media_editor_list=[attack_editor],
-        show_progress=True,
-        return_type=DetectionPipelineReturnType.SCORES
-    )
-    scores = pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs)
-    results[attack_name] = scores
-
-# Print results
-print("\n=== Robustness Results ===")
-for attack, scores in results.items():
-    avg_score = sum(scores) / len(scores) if scores else 0
-    print(f"{attack}: Average Score = {avg_score:.4f}")
-
-
-
-
-
-

Tutorial 4: Video Watermarking

-

Learn how to watermark videos using VideoShield or VideoMark.

-
-

Basic Video Watermarking

-
from diffusers import DiffusionPipeline
-
-# Setup for video generation
-video_model_id = "cerspense/zeroscope_v2_576w"
-video_pipe = DiffusionPipeline.from_pretrained(
-    video_model_id,
-    torch_dtype=torch.float16
-).to(device)
-
-video_diffusion_config = DiffusionConfig(
-    scheduler=video_pipe.scheduler,
-    pipe=video_pipe,
-    device=device,
-    image_size=(576, 320),
-    num_inference_steps=40,
-    guidance_scale=7.5,
-    gen_seed=42,
-    num_frames=16
-)
-
-# Load VideoShield watermark
-video_watermark = AutoWatermark.load(
-    'VideoShield',
-    algorithm_config='config/VideoShield.json',
-    diffusion_config=video_diffusion_config
-)
-
-# Generate watermarked video
-prompt = "A drone flying over a beach at sunset"
-video_frames = video_watermark.generate_watermarked_media(prompt)
-
-# Save frames
-import os
-os.makedirs("output_video", exist_ok=True)
-for i, frame in enumerate(video_frames):
-    frame.save(f"output_video/frame_{i:04d}.png")
-
-# Detect watermark in video
-detection_result = video_watermark.detect_watermark_in_media(video_frames)
-print(f"Video watermark detection: {detection_result}")
-
-
-
-
-

Video Quality Evaluation

-
from evaluation.dataset import VBenchDataset
-from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline
-from evaluation.tools.video_quality_analyzer import (
-    SubjectConsistencyAnalyzer,
-    MotionSmoothnessAnalyzer
-)
-
-# Create video dataset
-video_dataset = VBenchDataset(max_samples=20, dimension='subject_consistency')
-
-# Setup video quality pipeline
-video_pipeline = DirectVideoQualityAnalysisPipeline(
-    dataset=video_dataset,
-    watermarked_video_editor_list=[],
-    unwatermarked_video_editor_list=[],
-    watermarked_frame_editor_list=[],
-    unwatermarked_frame_editor_list=[],
-    analyzers=[SubjectConsistencyAnalyzer(device=device)],
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-# Evaluate video quality
-results = video_pipeline.evaluate(video_watermark)
-print(f"Subject consistency: {results}")
-
-
-
-
-
-

Tutorial 5: Custom Configuration

-

Customize watermarking parameters for your specific needs.

-
-

Batch Processing

-
prompts = [
-    "A serene lake at dawn",
-    "A bustling city street at night",
-    "A colorful flower garden",
-    "A snowy mountain peak",
-    "A tropical beach paradise"
-]
-
-output_dir = "batch_output"
-os.makedirs(output_dir, exist_ok=True)
-
-for i, prompt in enumerate(prompts):
-    print(f"Processing {i+1}/{len(prompts)}: {prompt}")
-
-    # Generate watermarked image
-    img = gs_watermark.generate_watermarked_media(prompt)
-    img.save(f"{output_dir}/watermarked_{i:03d}.png")
-
-    # Detect watermark
-    result = gs_watermark.detect_watermark_in_media(img)
-    print(f"  Detection: {result}")
-
-
-
-
-
-

Next Steps

-

Explore more advanced topics:

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/user_guide/algorithms.html b/docs/_build/html/user_guide/algorithms.html deleted file mode 100644 index 015040f..0000000 --- a/docs/_build/html/user_guide/algorithms.html +++ /dev/null @@ -1,1349 +0,0 @@ - - - - - - - - - Watermarking Algorithms — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Watermarking Algorithms

-

MarkDiffusion supports 11 state-of-the-art watermarking algorithms for latent diffusion models.

-
-

Algorithm Overview

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Algorithm

Category

Target

Description

Tree-Ring (TR)

Pattern

Image

Embeds invisible ring patterns in frequency domain

Ring-ID (RI)

Pattern

Image

Multi-key identification with tree-ring patterns

ROBIN

Pattern

Image

Robust and invisible watermarks with adversarial optimization

WIND

Pattern

Image

Two-stage robust watermarking hidden in noise

SFW

Pattern

Image

Semantic watermarking with Fourier integrity

Gaussian-Shading (GS)

Key

Image

Provable performance-lossless image watermarking

GaussMarker (GM)

Key

Image

Robust dual-domain watermarking

PRC

Key

Image

Undetectable watermark for generative models

SEAL

Key

Image

Semantic-aware image watermarking

VideoShield

Key

Video

Video diffusion model regulation via watermarking

VideoMark

Key

Video

Distortion-free robust watermarking for video

-
-
-

Pattern-Based Methods

-

Pattern-based methods embed predefined patterns into the generation process.

-
-

Tree-Ring (TR)

-

Reference: Tree-Ring Watermarks: Fingerprints for Diffusion Images that are Invisible and Robust

-

Tree-Ring embeds circular patterns in the Fourier domain of initial latents, making them invisible in the spatial domain but detectable through frequency analysis.

-

Key Features:

-
    -
  • Invisible watermarks in spatial domain

  • -
  • Robust to common image transformations

  • -
  • No need for additional neural networks

  • -
-

Usage:

-
from watermark.auto_watermark import AutoWatermark
-
-watermark = AutoWatermark.load(
-    'TR',
-    algorithm_config='config/TR.json',
-    diffusion_config=diffusion_config
-)
-
-# Generate watermarked image
-image = watermark.generate_watermarked_media(prompt)
-
-# Detect watermark
-result = watermark.detect_watermark_in_media(image)
-
-
-

Configuration Parameters:

-

From config/TR.json:

-
    -
  • w_seed: 999999 - Watermark seed

  • -
  • w_channel: 0 - Channel index to embed watermark

  • -
  • w_pattern: “zeros” - Pattern type

  • -
  • w_mask_shape: “circle” - Mask shape

  • -
  • w_radius: 10 - Ring radius in frequency domain

  • -
  • w_pattern_const: 0 - Pattern constant

  • -
  • threshold: 50 - Detection threshold

  • -
-
-
-

Ring-ID (RI)

-

Reference: RingID: Rethinking Tree-Ring Watermarking for Enhanced Multi-Key Identification

-

Ring-ID extends Tree-Ring to support multiple keys, enabling multi-user identification and improved robustness.

-

Key Features:

-
    -
  • Multi-key identification

  • -
  • Enhanced robustness

  • -
  • Backward compatible with Tree-Ring

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'RI',
-    algorithm_config='config/RI.json',
-    diffusion_config=diffusion_config
-)
-
-
-

Configuration Parameters:

-

From config/RI.json:

-
    -
  • ring_width: 1 - Ring width

  • -
  • quantization_levels: 4 - Quantization levels

  • -
  • ring_value_range: 64 - Ring value range

  • -
  • assigned_keys: 10 - Number of assigned keys

  • -
  • radius: 14 - Ring radius

  • -
  • radius_cutoff: 3 - Radius cutoff

  • -
  • heter_watermark_channel: [0] - Heterogeneous watermark channel

  • -
  • ring_watermark_channel: [3] - Ring watermark channel

  • -
  • threshold: 50 - Detection threshold

  • -
-
-
-

ROBIN

-

Reference: ROBIN: Robust and Invisible Watermarks for Diffusion Models with Adversarial Optimization

-

ROBIN uses adversarial optimization to create robust watermarks that are invisible to human eyes and resistant to attacks.

-

Key Features:

-
    -
  • Adversarial optimization for robustness

  • -
  • Invisible watermarks

  • -
  • Trained watermark generator

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'ROBIN',
-    algorithm_config='config/ROBIN.json',
-    diffusion_config=diffusion_config
-)
-
-
-

Configuration Parameters:

-

From config/ROBIN.json:

-
    -
  • w_seed: 999999 - Watermark seed

  • -
  • w_channel: 3 - Watermark channel

  • -
  • w_pattern: “ring” - Pattern type

  • -
  • w_up_radius: 30 - Upper radius

  • -
  • w_low_radius: 5 - Lower radius

  • -
  • watermarking_step: 35 - Watermarking injection step

  • -
  • threshold: 45 - Detection threshold

  • -
  • learning_rate: 0.0005 - Training learning rate

  • -
  • max_train_steps: 2000 - Maximum training steps

  • -
-
-
-

WIND

-

Reference: Hidden in the Noise: Two-Stage Robust Watermarking for Images

-

WIND implements a two-stage watermarking approach that hides watermarks in the noise initialization.

-

Key Features:

-
    -
  • Two-stage watermarking

  • -
  • Hidden in initial noise

  • -
  • High robustness

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'WIND',
-    algorithm_config='config/WIND.json',
-    diffusion_config=diffusion_config
-)
-
-
-
-
-

SFW

-

Reference: Semantic Watermarking Reinvented: Enhancing Robustness and Generation Quality with Fourier Integrity

-

SFW combines semantic information with Fourier domain watermarking for enhanced robustness and quality.

-

Key Features:

-
    -
  • Semantic-aware watermarking

  • -
  • Fourier integrity preservation

  • -
  • Minimal quality degradation

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'SFW',
-    algorithm_config='config/SFW.json',
-    diffusion_config=diffusion_config
-)
-
-
-
-
-
-

Key-Based Methods

-

Key-based methods use secret keys to embed and extract watermarks.

-
-

Gaussian-Shading (GS)

-

Reference: Gaussian Shading: Provable Performance-Lossless Image Watermarking for Diffusion Models

-

Gaussian-Shading provides provably performance-lossless watermarking by injecting Gaussian noise into the generation process.

-

Key Features:

-
    -
  • Performance-lossless (provable)

  • -
  • No quality degradation

  • -
  • Simple and efficient

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'GS',
-    algorithm_config='config/GS.json',
-    diffusion_config=diffusion_config
-)
-
-
-

Configuration Parameters:

-

From config/GS.json:

-
    -
  • channel_copy: 1 - Channel to copy watermark

  • -
  • wm_key: 42 - Watermark key

  • -
  • hw_copy: 8 - Height/width copy parameter

  • -
  • chacha: true - Use ChaCha encryption

  • -
  • chacha_key_seed: 123456 - ChaCha key seed

  • -
  • chacha_nonce_seed: 789012 - ChaCha nonce seed

  • -
  • threshold: 0.7 - Detection threshold

  • -
-
-
-

GaussMarker (GM)

-

Reference: GaussMarker: Robust Dual-Domain Watermark for Diffusion Models

-

GaussMarker combines spatial and frequency domain watermarking for enhanced robustness.

-

Key Features:

-
    -
  • Dual-domain watermarking

  • -
  • Trained GNR (Gaussian Noise Residual) network

  • -
  • High robustness to attacks

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'GM',
-    algorithm_config='config/GM.json',
-    diffusion_config=diffusion_config
-)
-
-
-

Training GNR Network:

-
python watermark/gm/train_GNR.py --config config/GM.json
-
-
-
-
-

PRC

-

Reference: An undetectable watermark for generative image models

-

PRC creates undetectable watermarks that are imperceptible to human observers and detection systems.

-

Key Features:

-
    -
  • Undetectable by design

  • -
  • High security

  • -
  • Minimal perceptual impact

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'PRC',
-    algorithm_config='config/PRC.json',
-    diffusion_config=diffusion_config
-)
-
-
-
-
-

SEAL

-

Reference: SEAL: Semantic Aware Image Watermarking

-

SEAL leverages semantic information to embed watermarks that adapt to image content.

-

Key Features:

-
    -
  • Semantic-aware embedding

  • -
  • Content-adaptive watermarking

  • -
  • Preserved semantic integrity

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'SEAL',
-    algorithm_config='config/SEAL.json',
-    diffusion_config=diffusion_config
-)
-
-
-
-
-
-

Video Watermarking Methods

-
-

VideoShield

-

Reference: VideoShield: Regulating Diffusion-based Video Generation Models via Watermarking

-

VideoShield embeds watermarks into video generation models for content regulation and tracking.

-

Key Features:

-
    -
  • Video-specific watermarking

  • -
  • Temporal consistency

  • -
  • Frame-level detection

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'VideoShield',
-    algorithm_config='config/VideoShield.json',
-    diffusion_config=video_diffusion_config
-)
-
-# Generate watermarked video frames
-frames = watermark.generate_watermarked_media(prompt)
-
-# Detect in video
-result = watermark.detect_watermark_in_media(frames)
-
-
-
-
-

VideoMark

-

Reference: VideoMark: A Distortion-Free Robust Watermarking Framework for Video Diffusion Models

-

VideoMark provides distortion-free watermarking specifically designed for video diffusion models.

-

Key Features:

-
    -
  • Distortion-free embedding

  • -
  • Robust to video compression

  • -
  • Temporal coherence preservation

  • -
-

Usage:

-
watermark = AutoWatermark.load(
-    'VideoMark',
-    algorithm_config='config/VideoMark.json',
-    diffusion_config=video_diffusion_config
-)
-
-
-
-
-
-

Algorithm Comparison

-
-

Choosing the Right Algorithm

-

For High Invisibility:

-
    -
  • Gaussian-Shading (GS) - Provably lossless

  • -
  • PRC - Designed for undetectability

  • -
  • Tree-Ring (TR) - Invisible in spatial domain

  • -
-

For High Robustness:

-
    -
  • ROBIN - Adversarial optimization

  • -
  • GaussMarker (GM) - Dual-domain approach

  • -
  • WIND - Two-stage robustness

  • -
-

For Video Content:

-
    -
  • VideoShield - Video regulation

  • -
  • VideoMark - Distortion-free video watermarking

  • -
-

For Multi-User Scenarios:

-
    -
  • Ring-ID (RI) - Multi-key support

  • -
  • SEAL - Semantic-aware adaptation

  • -
-
-
-

Algorithm Comparison

-

The following figure shows the performance comparison of different watermarking algorithms:

-Algorithm Performance Comparison - -
-
-
-

Next Steps

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/user_guide/evaluation.html b/docs/_build/html/user_guide/evaluation.html deleted file mode 100644 index a4f047f..0000000 --- a/docs/_build/html/user_guide/evaluation.html +++ /dev/null @@ -1,1472 +0,0 @@ - - - - - - - - - Evaluation — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Evaluation

-

MarkDiffusion provides comprehensive evaluation tools to assess watermark performance across three key dimensions: detectability, robustness, and output quality.

-
-

Overview

-
-

Evaluation Dimensions

-
    -
  1. Detectability - How reliably can watermarks be detected?

  2. -
  3. Robustness - How well do watermarks survive attacks?

  4. -
  5. Quality - How much do watermarks affect output quality?

  6. -
-
-
-

Evaluation Components

-
    -
  • Pipelines - Automated evaluation workflows

  • -
  • Tools - Individual evaluation metrics and attack methods

  • -
  • Analyzers - Quality assessment modules

  • -
  • Calculators - Detection performance metrics

  • -
-
-
-
-

Detectability Evaluation

-
-

Basic Detection Evaluation

-
from evaluation.dataset import StableDiffusionPromptsDataset
-from evaluation.pipelines.detection import (
-    WatermarkedMediaDetectionPipeline,
-    UnWatermarkedMediaDetectionPipeline,
-    DetectionPipelineReturnType
-)
-from evaluation.tools.success_rate_calculator import (
-    DynamicThresholdSuccessRateCalculator
-)
-
-# Create dataset
-dataset = StableDiffusionPromptsDataset(max_samples=200)
-
-# Setup pipelines
-watermarked_pipeline = WatermarkedMediaDetectionPipeline(
-    dataset=dataset,
-    media_editor_list=[],  # No attacks
-    show_progress=True,
-    return_type=DetectionPipelineReturnType.SCORES
-)
-
-unwatermarked_pipeline = UnWatermarkedMediaDetectionPipeline(
-    dataset=dataset,
-    media_editor_list=[],
-    show_progress=True,
-    return_type=DetectionPipelineReturnType.SCORES
-)
-
-# Configure detection
-detection_kwargs = {
-    "num_inference_steps": 50,
-    "guidance_scale": 1.0,
-}
-
-# Evaluate
-watermarked_scores = watermarked_pipeline.evaluate(
-    watermark, detection_kwargs=detection_kwargs
-)
-unwatermarked_scores = unwatermarked_pipeline.evaluate(
-    watermark, detection_kwargs=detection_kwargs
-)
-
-# Calculate metrics
-calculator = DynamicThresholdSuccessRateCalculator(
-    labels=['watermarked'] * len(watermarked_scores) +
-           ['unwatermarked'] * len(unwatermarked_scores),
-    target_fpr=0.01  # Target false positive rate
-)
-
-results = calculator.calculate(watermarked_scores, unwatermarked_scores)
-print(f"TPR at 1% FPR: {results['tpr']:.4f}")
-print(f"AUC: {results['auc']:.4f}")
-
-
-
-
-

Detection Metrics

-

Available detection metrics:

-
    -
  • TPR (True Positive Rate) - Watermark detection rate

  • -
  • FPR (False Positive Rate) - False alarm rate

  • -
  • TNR (True Negative Rate) - Correct rejection rate

  • -
  • FNR (False Negative Rate) - Miss rate

  • -
  • Accuracy - Overall detection accuracy

  • -
  • Precision - Positive predictive value

  • -
  • Recall - Same as TPR

  • -
  • F1-Score - Harmonic mean of precision and recall

  • -
  • AUC (Area Under ROC Curve) - Overall performance

  • -
-
-
-

Fixed Threshold Evaluation

-
from evaluation.tools.success_rate_calculator import (
-    FundamentalSuccessRateCalculator
-)
-
-# Use fixed threshold
-calculator = FundamentalSuccessRateCalculator(
-    threshold=0.5  # Fixed detection threshold
-)
-
-results = calculator.calculate(watermarked_scores, unwatermarked_scores)
-print(f"Accuracy: {results['accuracy']:.4f}")
-print(f"F1-Score: {results['f1_score']:.4f}")
-
-
-
-
-
-

Robustness Evaluation

-
-

Image Attacks

-

Test watermark robustness against various image attacks:

-
from evaluation.tools.image_editor import (
-    JPEGCompression,
-    GaussianBlurring,
-    GaussianNoise,
-    Rotation,
-    CrSc,  # Crop and Scale
-    Brightness,
-    Mask,
-    Overlay,
-    AdaptiveNoiseInjection
-)
-
-# Define attacks
-attacks = {
-    'JPEG-90': JPEGCompression(quality=90),
-    'JPEG-75': JPEGCompression(quality=75),
-    'JPEG-50': JPEGCompression(quality=50),
-    'Blur-3': GaussianBlurring(kernel_size=3),
-    'Blur-5': GaussianBlurring(kernel_size=5),
-    'Noise-0.01': GaussianNoise(std=0.01),
-    'Noise-0.05': GaussianNoise(std=0.05),
-    'Rotate-15': Rotation(angle=15),
-    'Rotate-45': Rotation(angle=45),
-    'CropScale-0.75': CrSc(crop_ratio=0.75),
-    'Brightness-1.2': Brightness(factor=1.2),
-    'Mask': Mask(num_masks=5, mask_size=50),
-    'Overlay': Overlay(num_strokes=10),
-    'AdaptiveNoise': AdaptiveNoiseInjection(noise_type='gaussian')
-}
-
-# Evaluate against each attack
-robustness_results = {}
-for attack_name, attack_editor in attacks.items():
-    print(f"\nEvaluating: {attack_name}")
-
-    pipeline = WatermarkedMediaDetectionPipeline(
-        dataset=dataset,
-        media_editor_list=[attack_editor],
-        show_progress=True,
-        return_type=DetectionPipelineReturnType.SCORES
-    )
-
-    scores = pipeline.evaluate(watermark, detection_kwargs=detection_kwargs)
-    avg_score = sum(scores) / len(scores) if scores else 0
-    robustness_results[attack_name] = avg_score
-
-# Print results
-print("\n=== Robustness Results ===")
-for attack, score in sorted(robustness_results.items(),
-                             key=lambda x: x[1], reverse=True):
-    print(f"{attack:20s}: {score:.4f}")
-
-
-
-
-

Video Attacks

-

Test video watermark robustness:

-
from evaluation.tools.video_editor import (
-    MPEG4Compression,
-    FrameAverage,
-    FrameSwap,
-    VideoCodecAttack,
-    FrameRateAdapter,
-    FrameInterpolationAttack
-)
-
-# Define video attacks
-video_attacks = {
-    'MPEG4': MPEG4Compression(quality=20),
-    'FrameAvg': FrameAverage(window_size=3),
-    'FrameSwap': FrameSwap(swap_probability=0.1),
-    'H264': VideoCodecAttack(codec='h264', bitrate='2M'),
-    'H265': VideoCodecAttack(codec='h265', bitrate='2M'),
-    'FPS-15': FrameRateAdapter(target_fps=15),
-    'Interpolate': FrameInterpolationAttack(factor=2)
-}
-
-# Evaluate video robustness
-from evaluation.dataset import VBenchDataset
-
-video_dataset = VBenchDataset(max_samples=50)
-video_robustness_results = {}
-
-for attack_name, attack_editor in video_attacks.items():
-    pipeline = WatermarkedMediaDetectionPipeline(
-        dataset=video_dataset,
-        media_editor_list=[attack_editor],
-        show_progress=True,
-        return_type=DetectionPipelineReturnType.SCORES
-    )
-
-    scores = pipeline.evaluate(video_watermark, detection_kwargs=detection_kwargs)
-    video_robustness_results[attack_name] = sum(scores) / len(scores)
-
-
-
-
-

Combined Attacks

-

Test against multiple simultaneous attacks:

-
# Combine multiple attacks
-combined_attacks = [
-    JPEGCompression(quality=75),
-    GaussianBlurring(kernel_size=3),
-    Rotation(angle=10)
-]
-
-pipeline = WatermarkedMediaDetectionPipeline(
-    dataset=dataset,
-    media_editor_list=combined_attacks,  # All attacks applied
-    show_progress=True,
-    return_type=DetectionPipelineReturnType.SCORES
-)
-
-scores = pipeline.evaluate(watermark, detection_kwargs=detection_kwargs)
-print(f"Combined attack score: {sum(scores)/len(scores):.4f}")
-
-
-
-
-
-

Quality Evaluation

-
-

Image Quality Metrics

-
-

Direct Quality Analysis

-

For single image quality metrics:

-
from evaluation.pipelines.image_quality_analysis import (
-    DirectImageQualityAnalysisPipeline,
-    QualityPipelineReturnType
-)
-from evaluation.tools.image_quality_analyzer import (
-    NIQECalculator, BRISQUEAnalyzer
-)
-
-pipeline = DirectImageQualityAnalysisPipeline(
-    dataset=dataset,
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[NIQECalculator(), BRISQUEAnalyzer()],
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-results = pipeline.evaluate(watermark)
-print(f"NIQE (watermarked): {results['watermarked']['NIQE']:.4f}")
-print(f"NIQE (unwatermarked): {results['unwatermarked']['NIQE']:.4f}")
-print(f"BRISQUE (watermarked): {results['watermarked']['BRISQUE']:.4f}")
-
-
-
-
-

Referenced Quality Analysis

-

For metrics requiring reference images or text:

-
from evaluation.pipelines.image_quality_analysis import (
-    ReferencedImageQualityAnalysisPipeline
-)
-from evaluation.tools.image_quality_analyzer import CLIPScoreCalculator
-from evaluation.dataset import MSCOCODataset
-
-mscoco_dataset = MSCOCODataset(max_samples=100)
-
-pipeline = ReferencedImageQualityAnalysisPipeline(
-    dataset=mscoco_dataset,
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[CLIPScoreCalculator()],
-    unwatermarked_image_source='generated',
-    reference_image_source='natural',
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-results = pipeline.evaluate(watermark)
-print(f"CLIP Score: {results['CLIPScore']:.4f}")
-
-
-
-
-

Compared Quality Analysis

-

Compare watermarked vs unwatermarked images:

-
from evaluation.pipelines.image_quality_analysis import (
-    ComparedImageQualityAnalysisPipeline
-)
-from evaluation.tools.image_quality_analyzer import (
-    PSNRAnalyzer, SSIMAnalyzer, LPIPSAnalyzer,
-    VIFAnalyzer, FSIMAnalyzer
-)
-
-pipeline = ComparedImageQualityAnalysisPipeline(
-    dataset=dataset,
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[
-        PSNRAnalyzer(),
-        SSIMAnalyzer(),
-        LPIPSAnalyzer(),
-        VIFAnalyzer(),
-        FSIMAnalyzer()
-    ],
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-results = pipeline.evaluate(watermark)
-print(f"PSNR: {results['PSNR']:.2f} dB")
-print(f"SSIM: {results['SSIM']:.4f}")
-print(f"LPIPS: {results['LPIPS']:.4f}")
-print(f"VIF: {results['VIF']:.4f}")
-print(f"FSIM: {results['FSIM']:.4f}")
-
-
-
-
-

Group Quality Analysis

-

Metrics requiring sets of images:

-
from evaluation.pipelines.image_quality_analysis import (
-    GroupImageQualityAnalysisPipeline
-)
-from evaluation.tools.image_quality_analyzer import (
-    FIDCalculator, InceptionScoreCalculator
-)
-
-pipeline = GroupImageQualityAnalysisPipeline(
-    dataset=mscoco_dataset,
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[FIDCalculator(), InceptionScoreCalculator()],
-    unwatermarked_image_source='generated',
-    reference_image_source='natural',
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-results = pipeline.evaluate(watermark)
-print(f"FID: {results['FID']:.2f}")
-print(f"IS: {results['InceptionScore']:.2f}")
-
-
-
-
-

Repeat Quality Analysis

-

For diversity evaluation:

-
from evaluation.pipelines.image_quality_analysis import (
-    RepeatImageQualityAnalysisPipeline
-)
-
-pipeline = RepeatImageQualityAnalysisPipeline(
-    dataset=StableDiffusionPromptsDataset(max_samples=10),
-    prompt_per_image=20,  # Generate 20 images per prompt
-    watermarked_image_editor_list=[],
-    unwatermarked_image_editor_list=[],
-    analyzers=[LPIPSAnalyzer()],
-    show_progress=True,
-    return_type=QualityPipelineReturnType.MEAN_SCORES
-)
-
-results = pipeline.evaluate(watermark)
-print(f"Average LPIPS (diversity): {results['LPIPS']:.4f}")
-
-
-
-
-
-

Video Quality Metrics

-
from evaluation.dataset import VBenchDataset
-from evaluation.pipelines.video_quality_analysis import (
-    DirectVideoQualityAnalysisPipeline
-)
-from evaluation.tools.video_quality_analyzer import (
-    SubjectConsistencyAnalyzer,
-    BackgroundConsistencyAnalyzer,
-    MotionSmoothnessAnalyzer,
-    DynamicDegreeAnalyzer,
-    ImagingQualityAnalyzer
-)
-
-# Evaluate different video quality dimensions
-dimensions = {
-    'subject_consistency': SubjectConsistencyAnalyzer(device='cuda'),
-    'background_consistency': BackgroundConsistencyAnalyzer(device='cuda'),
-    'motion_smoothness': MotionSmoothnessAnalyzer(device='cuda'),
-    'dynamic_degree': DynamicDegreeAnalyzer(device='cuda'),
-    'imaging_quality': ImagingQualityAnalyzer(device='cuda')
-}
-
-video_quality_results = {}
-for dim_name, analyzer in dimensions.items():
-    video_dataset = VBenchDataset(max_samples=50, dimension=dim_name)
-
-    pipeline = DirectVideoQualityAnalysisPipeline(
-        dataset=video_dataset,
-        watermarked_video_editor_list=[],
-        unwatermarked_video_editor_list=[],
-        watermarked_frame_editor_list=[],
-        unwatermarked_frame_editor_list=[],
-        analyzers=[analyzer],
-        show_progress=True,
-        return_type=QualityPipelineReturnType.MEAN_SCORES
-    )
-
-    results = pipeline.evaluate(video_watermark)
-    video_quality_results[dim_name] = results
-
-# Print results
-print("\n=== Video Quality Results ===")
-for dim, score in video_quality_results.items():
-    print(f"{dim}: {score}")
-
-
-
-
-
-

Comprehensive Evaluation

-
-

Full Evaluation Suite

-

Evaluate all aspects together:

-
def comprehensive_evaluation(watermark_algo, dataset, attacks):
-    """Run comprehensive evaluation on a watermark algorithm."""
-    results = {
-        'detectability': {},
-        'robustness': {},
-        'quality': {}
-    }
-
-    # 1. Detectability
-    print("=== Detectability Evaluation ===")
-    watermarked_pipeline = WatermarkedMediaDetectionPipeline(
-        dataset=dataset, media_editor_list=[], show_progress=True,
-        return_type=DetectionPipelineReturnType.SCORES
-    )
-    unwatermarked_pipeline = UnWatermarkedMediaDetectionPipeline(
-        dataset=dataset, media_editor_list=[], show_progress=True,
-        return_type=DetectionPipelineReturnType.SCORES
-    )
-
-    wm_scores = watermarked_pipeline.evaluate(watermark_algo)
-    unwm_scores = unwatermarked_pipeline.evaluate(watermark_algo)
-
-    calculator = DynamicThresholdSuccessRateCalculator(target_fpr=0.01)
-    det_results = calculator.calculate(wm_scores, unwm_scores)
-    results['detectability'] = det_results
-
-    # 2. Robustness
-    print("\n=== Robustness Evaluation ===")
-    for attack_name, attack in attacks.items():
-        pipeline = WatermarkedMediaDetectionPipeline(
-            dataset=dataset, media_editor_list=[attack],
-            show_progress=True,
-            return_type=DetectionPipelineReturnType.SCORES
-        )
-        scores = pipeline.evaluate(watermark_algo)
-        results['robustness'][attack_name] = sum(scores) / len(scores)
-
-    # 3. Quality
-    print("\n=== Quality Evaluation ===")
-    quality_pipeline = ComparedImageQualityAnalysisPipeline(
-        dataset=dataset,
-        watermarked_image_editor_list=[],
-        unwatermarked_image_editor_list=[],
-        analyzers=[PSNRAnalyzer(), SSIMAnalyzer(), LPIPSAnalyzer()],
-        show_progress=True,
-        return_type=QualityPipelineReturnType.MEAN_SCORES
-    )
-    quality_results = quality_pipeline.evaluate(watermark_algo)
-    results['quality'] = quality_results
-
-    return results
-
-# Run evaluation
-attacks = {
-    'JPEG-75': JPEGCompression(quality=75),
-    'Blur': GaussianBlurring(kernel_size=3),
-    'Noise': GaussianNoise(std=0.05),
-    'Rotation': Rotation(angle=15)
-}
-
-eval_results = comprehensive_evaluation(watermark, dataset, attacks)
-
-# Print summary
-print("\n" + "="*50)
-print("COMPREHENSIVE EVALUATION SUMMARY")
-print("="*50)
-print(f"\nDetectability:")
-print(f"  TPR @ 1% FPR: {eval_results['detectability']['tpr']:.4f}")
-print(f"  AUC: {eval_results['detectability']['auc']:.4f}")
-print(f"\nRobustness:")
-for attack, score in eval_results['robustness'].items():
-    print(f"  {attack}: {score:.4f}")
-print(f"\nQuality:")
-print(f"  PSNR: {eval_results['quality']['PSNR']:.2f} dB")
-print(f"  SSIM: {eval_results['quality']['SSIM']:.4f}")
-print(f"  LPIPS: {eval_results['quality']['LPIPS']:.4f}")
-
-
-
-
-

Compare Multiple Algorithms

-
algorithms = ['TR', 'GS', 'ROBIN', 'SEAL']
-comparison_results = {}
-
-for algo_name in algorithms:
-    print(f"\n{'='*50}")
-    print(f"Evaluating {algo_name}")
-    print('='*50)
-
-    # Load algorithm
-    algo = AutoWatermark.load(
-        algo_name,
-        algorithm_config=f'config/{algo_name}.json',
-        diffusion_config=diffusion_config
-    )
-
-    # Evaluate
-    results = comprehensive_evaluation(algo, dataset, attacks)
-    comparison_results[algo_name] = results
-
-# Print comparison table
-import pandas as pd
-
-# Create comparison dataframe
-comparison_data = []
-for algo, results in comparison_results.items():
-    row = {
-        'Algorithm': algo,
-        'TPR@1%FPR': results['detectability']['tpr'],
-        'AUC': results['detectability']['auc'],
-        'PSNR': results['quality']['PSNR'],
-        'SSIM': results['quality']['SSIM'],
-    }
-    for attack in attacks:
-        row[f'Rob_{attack}'] = results['robustness'][attack]
-    comparison_data.append(row)
-
-df = pd.DataFrame(comparison_data)
-print("\n" + "="*100)
-print("ALGORITHM COMPARISON")
-print("="*100)
-print(df.to_string(index=False))
-
-
-
-
-
-

Best Practices

-
-

Sample Size Selection

-
    -
  • Quick test: 50-100 samples

  • -
  • Standard evaluation: 200-500 samples

  • -
  • Publication: 1000+ samples

  • -
-
-
-
-

Next Steps

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/user_guide/visualization.html b/docs/_build/html/user_guide/visualization.html deleted file mode 100644 index f69a478..0000000 --- a/docs/_build/html/user_guide/visualization.html +++ /dev/null @@ -1,1148 +0,0 @@ - - - - - - - - - Visualization — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Visualization

-

MarkDiffusion provides powerful visualization tools to understand how watermarking algorithms work.

-
-

Overview

-

Visualization helps you:

-
    -
  • Understand watermarking mechanisms

  • -
  • Debug watermarking issues

  • -
  • Present results in papers/reports

  • -
  • Educate users about watermarking

  • -
-

The visualization module creates insightful plots showing:

-
    -
  • Watermark patterns in frequency domain

  • -
  • Latent representations

  • -
  • Watermark bits and reconstruction

  • -
  • Spatial and frequency domain comparisons

  • -
-
-
-

Basic Usage

-
-

Simple Visualization

-
from watermark.auto_watermark import AutoWatermark
-from visualize.auto_visualization import AutoVisualizer
-
-# Generate watermarked image
-watermark = AutoWatermark.load('GS', 'config/GS.json', diffusion_config)
-watermarked_image = watermark.generate_watermarked_media(prompt)
-
-# Get visualization data
-viz_data = watermark.get_data_for_visualize(watermarked_image)
-
-# Create visualizer
-visualizer = AutoVisualizer.load('GS', data_for_visualization=viz_data)
-
-# Generate visualization
-fig = visualizer.visualize(
-    rows=2,
-    cols=2,
-    methods=['draw_watermark_bits', 'draw_reconstructed_watermark_bits',
-             'draw_inverted_latents', 'draw_inverted_latents_fft']
-)
-
-# Save figure
-fig.savefig('visualization.png', dpi=300, bbox_inches='tight')
-
-
-
-
-
-

Visualization Methods by Algorithm

-
-

Tree-Ring (TR)

-

Tree-Ring visualizations show frequency domain patterns:

-
visualizer = AutoVisualizer.load('TR', data_for_visualization=viz_data)
-
-fig = visualizer.visualize(
-    rows=2,
-    cols=3,
-    methods=[
-        'draw_init_latents',           # Initial latent representation
-        'draw_init_latents_fft',       # FFT of initial latents
-        'draw_watermark_pattern',      # Ring pattern
-        'draw_watermarked_latents',    # Watermarked latents
-        'draw_watermarked_latents_fft',# FFT showing rings
-        'draw_generated_image'         # Final image
-    ]
-)
-
-
-

Available Methods:

-
    -
  • draw_init_latents - Initial latent vectors

  • -
  • draw_init_latents_fft - Frequency spectrum of initial latents

  • -
  • draw_watermark_pattern - The ring pattern overlay

  • -
  • draw_watermarked_latents - Latents after watermark injection

  • -
  • draw_watermarked_latents_fft - Frequency domain with rings visible

  • -
  • draw_generated_image - Final generated image

  • -
-
-
-

Gaussian-Shading (GS)

-

Gaussian-Shading visualizations show bit-level watermark information:

-
visualizer = AutoVisualizer.load('GS', data_for_visualization=viz_data)
-
-fig = visualizer.visualize(
-    rows=2,
-    cols=2,
-    methods=[
-        'draw_watermark_bits',              # Original watermark bits
-        'draw_reconstructed_watermark_bits', # Extracted bits
-        'draw_inverted_latents',            # Inverted latent codes
-        'draw_inverted_latents_fft'         # Frequency analysis
-    ]
-)
-
-
-

Available Methods:

-
    -
  • draw_watermark_bits - Original watermark message bits

  • -
  • draw_reconstructed_watermark_bits - Extracted watermark bits

  • -
  • draw_bit_accuracy_heatmap - Bit-wise accuracy visualization

  • -
  • draw_inverted_latents - Inverted latent representation

  • -
  • draw_inverted_latents_fft - FFT of inverted latents

  • -
  • draw_generated_image - Final watermarked image

  • -
-
-
-

ROBIN

-

ROBIN visualizations show adversarially optimized patterns:

-
visualizer = AutoVisualizer.load('ROBIN', data_for_visualization=viz_data)
-
-fig = visualizer.visualize(
-    rows=2,
-    cols=2,
-    methods=[
-        'draw_watermark_message',
-        'draw_extracted_message',
-        'draw_perturbation_pattern',
-        'draw_frequency_analysis'
-    ]
-)
-
-
-

Available Methods:

-
    -
  • draw_watermark_message - Original message

  • -
  • draw_extracted_message - Detected message

  • -
  • draw_perturbation_pattern - Adversarial perturbation

  • -
  • draw_frequency_analysis - Frequency domain analysis

  • -
  • draw_robustness_map - Spatial robustness heatmap

  • -
-
-
-

GaussMarker (GM)

-

GaussMarker shows dual-domain watermarking:

-
visualizer = AutoVisualizer.load('GM', data_for_visualization=viz_data)
-
-fig = visualizer.visualize(
-    rows=2,
-    cols=3,
-    methods=[
-        'draw_spatial_watermark',
-        'draw_frequency_watermark',
-        'draw_combined_watermark',
-        'draw_gnr_output',
-        'draw_detection_map',
-        'draw_generated_image'
-    ]
-)
-
-
-

Available Methods:

-
    -
  • draw_spatial_watermark - Spatial domain component

  • -
  • draw_frequency_watermark - Frequency domain component

  • -
  • draw_combined_watermark - Combined dual-domain

  • -
  • draw_gnr_output - GNR network output

  • -
  • draw_detection_map - Detection confidence map

  • -
-
-
-

VideoShield

-

Video visualizations show temporal patterns:

-
visualizer = AutoVisualizer.load('VideoShield', data_for_visualization=viz_data)
-
-fig = visualizer.visualize(
-    rows=3,
-    cols=4,
-    methods=[
-        'draw_frame_sequence',        # All frames
-        'draw_temporal_watermark',    # Watermark over time
-        'draw_optical_flow',          # Motion patterns
-        'draw_consistency_map'        # Temporal consistency
-    ]
-)
-
-
-

Available Methods:

-
    -
  • draw_frame_sequence - Video frame grid

  • -
  • draw_temporal_watermark - Watermark evolution over frames

  • -
  • draw_optical_flow - Motion flow visualization

  • -
  • draw_consistency_map - Temporal consistency

  • -
  • draw_frame_differences - Inter-frame differences

  • -
-
-
-
-

Advanced Visualization

-
-

Custom Layout

-

Create custom visualization layouts:

-
import matplotlib.pyplot as plt
-
-# Create custom figure
-fig, axes = plt.subplots(2, 3, figsize=(15, 10))
-
-# Get visualizer
-visualizer = AutoVisualizer.load('TR', data_for_visualization=viz_data)
-
-# Draw on specific axes
-visualizer.draw_init_latents(ax=axes[0, 0])
-visualizer.draw_init_latents_fft(ax=axes[0, 1])
-visualizer.draw_watermark_pattern(ax=axes[0, 2])
-visualizer.draw_watermarked_latents(ax=axes[1, 0])
-visualizer.draw_watermarked_latents_fft(ax=axes[1, 1])
-visualizer.draw_generated_image(ax=axes[1, 2])
-
-# Adjust layout
-plt.tight_layout()
-fig.savefig('custom_visualization.png', dpi=300)
-
-
-
-
-

Comparing Methods

-

Visualize multiple algorithms side-by-side:

-
import matplotlib.pyplot as plt
-
-algorithms = ['TR', 'GS', 'ROBIN']
-fig, axes = plt.subplots(len(algorithms), 3, figsize=(15, len(algorithms)*4))
-
-for i, algo in enumerate(algorithms):
-    # Load watermark
-    wm = AutoWatermark.load(algo, f'config/{algo}.json', diffusion_config)
-    img = wm.generate_watermarked_media(prompt)
-    viz_data = wm.get_data_for_visualize(img)
-
-    # Visualize
-    visualizer = AutoVisualizer.load(algo, data_for_visualization=viz_data)
-    visualizer.draw_generated_image(ax=axes[i, 0])
-    visualizer.draw_inverted_latents_fft(ax=axes[i, 1])
-
-    # Detection heatmap
-    detection = wm.detect_watermark_in_media(img)
-    axes[i, 2].text(0.5, 0.5, f"{algo}\nScore: {detection.get('score', 'N/A')}",
-                    ha='center', va='center', fontsize=14)
-    axes[i, 2].axis('off')
-
-plt.tight_layout()
-fig.savefig('algorithm_comparison.png', dpi=300)
-
-
-
-
-
-

Next Steps

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/_build/html/user_guide/watermarking.html b/docs/_build/html/user_guide/watermarking.html deleted file mode 100644 index 0d879a3..0000000 --- a/docs/_build/html/user_guide/watermarking.html +++ /dev/null @@ -1,1187 +0,0 @@ - - - - - - - - - Watermarking Workflow — MarkDiffusion 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

Watermarking Workflow

-

This guide explains the complete workflow for watermarking images and videos with MarkDiffusion.

-
-

Basic Workflow

-

The watermarking process consists of three main stages:

-
    -
  1. Configuration - Set up the diffusion model and watermarking algorithm

  2. -
  3. Generation - Generate watermarked media

  4. -
  5. Detection - Detect and verify watermarks

  6. -
-
-
-

Configuration

-
-

Diffusion Model Setup

-

First, configure your diffusion model:

-
import torch
-from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
-from utils.diffusion_config import DiffusionConfig
-
-# Device selection
-device = 'cuda' if torch.cuda.is_available() else 'cpu'
-
-# Load model
-model_id = "stabilityai/stable-diffusion-2-1"
-scheduler = DPMSolverMultistepScheduler.from_pretrained(
-    model_id,
-    subfolder="scheduler"
-)
-pipe = StableDiffusionPipeline.from_pretrained(
-    model_id,
-    scheduler=scheduler
-).to(device)
-
-# Create configuration
-diffusion_config = DiffusionConfig(
-    scheduler=scheduler,
-    pipe=pipe,
-    device=device,
-    image_size=(512, 512),
-    num_inference_steps=50,
-    guidance_scale=7.5,
-    gen_seed=42,
-    inversion_type="ddim"
-)
-
-
-
-
-

DiffusionConfig Parameters

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Parameter

Type

Description

scheduler

Scheduler

Diffusion scheduler (e.g., DDPM, DDIM, DPM)

pipe

Pipeline

Diffusion pipeline object

device

str

Device to run on (‘cuda’ or ‘cpu’)

image_size

tuple

Output image size (height, width)

num_inference_steps

int

Number of denoising steps

guidance_scale

float

Classifier-free guidance scale

gen_seed

int

Random seed for reproducibility

inversion_type

str

Type of latent inversion (‘ddim’ or ‘exact’)

-
-
-

Algorithm Configuration

-

Each watermarking algorithm has its own configuration file:

-
from watermark.auto_watermark import AutoWatermark
-
-# Load watermark with configuration
-watermark = AutoWatermark.load(
-    'GS',  # Algorithm name
-    algorithm_config='config/GS.json',  # Config file path
-    diffusion_config=diffusion_config
-)
-
-
-
-
-

Configuration File Structure

-

Example GS.json configuration:

-
{
-  "algorithm_name": "GS",
-  "secret_key": 42,
-  "message_length": 256,
-  "embed_dim": 4,
-  "watermark_strength": 1.0,
-  "detection_threshold": 0.5
-}
-
-
-

You can customize these parameters based on your requirements.

-
-
-
-

Generation

-
-

Image Generation

-

Basic image generation with watermark:

-
# Single image generation
-prompt = "A beautiful landscape with mountains"
-watermarked_image = watermark.generate_watermarked_media(prompt)
-
-# Save image
-watermarked_image.save("output.png")
-
-# Display image
-watermarked_image.show()
-
-
-
-
-

Video Generation

-

For video watermarking:

-
from diffusers import DiffusionPipeline
-
-# Setup video pipeline
-video_pipe = DiffusionPipeline.from_pretrained(
-    "cerspense/zeroscope_v2_576w",
-    torch_dtype=torch.float16
-).to(device)
-
-video_config = DiffusionConfig(
-    scheduler=video_pipe.scheduler,
-    pipe=video_pipe,
-    device=device,
-    image_size=(576, 320),
-    num_inference_steps=40,
-    guidance_scale=7.5,
-    gen_seed=42,
-    num_frames=16
-)
-
-# Load video watermark
-video_watermark = AutoWatermark.load(
-    'VideoShield',
-    algorithm_config='config/VideoShield.json',
-    diffusion_config=video_config
-)
-
-# Generate watermarked video
-prompt = "A cat walking in a garden"
-frames = video_watermark.generate_watermarked_media(prompt)
-
-# Save frames
-import os
-os.makedirs("video_output", exist_ok=True)
-for i, frame in enumerate(frames):
-    frame.save(f"video_output/frame_{i:04d}.png")
-
-
-
-
-
-

Detection

-
-

Basic Detection

-

Detect watermark in a generated image:

-
# Detect watermark
-detection_result = watermark.detect_watermark_in_media(watermarked_image)
-
-print(f"Detection result: {detection_result}")
-
-
-
-
-

Batch Detection

-

Detect watermarks in multiple images:

-
import os
-from PIL import Image
-
-# Load images
-image_dir = "watermarked_images"
-results = {}
-
-for filename in os.listdir(image_dir):
-    if filename.endswith(('.png', '.jpg', '.jpeg')):
-        img_path = os.path.join(image_dir, filename)
-        img = Image.open(img_path)
-
-        # Detect watermark
-        result = watermark.detect_watermark_in_media(img)
-        results[filename] = result
-
-# Print results
-for filename, result in results.items():
-    print(f"{filename}: {result}")
-
-
-
-
-

Detection with Custom Parameters

-

Some algorithms support custom detection parameters:

-
detection_result = watermark.detect_watermark_in_media(
-    watermarked_image,
-    num_inference_steps=50,
-    guidance_scale=1.0,
-    detection_threshold=0.6  # Custom threshold
-)
-
-
-
-
-

Video Detection

-

Detect watermarks in video frames:

-
# Detect in all frames
-detection_result = video_watermark.detect_watermark_in_media(frames)
-
-# Frame-by-frame detection
-frame_results = []
-for i, frame in enumerate(frames):
-    result = video_watermark.detect_watermark_in_media(frame)
-    frame_results.append(result)
-    print(f"Frame {i}: {result}")
-
-
-
-
-
-

Watermark Removal Prevention

-
-

Testing Against Attacks

-

Verify watermark persistence after attacks:

-
from evaluation.tools.image_editor import (
-    JPEGCompression, GaussianBlurring, Rotation
-)
-
-# Original detection
-original_result = watermark.detect_watermark_in_media(watermarked_image)
-print(f"Original: {original_result}")
-
-# After JPEG compression
-jpeg_editor = JPEGCompression(quality=75)
-compressed_image = jpeg_editor.edit_image(watermarked_image)
-jpeg_result = watermark.detect_watermark_in_media(compressed_image)
-print(f"After JPEG: {jpeg_result}")
-
-# After blur
-blur_editor = GaussianBlurring(kernel_size=3)
-blurred_image = blur_editor.edit_image(watermarked_image)
-blur_result = watermark.detect_watermark_in_media(blurred_image)
-print(f"After blur: {blur_result}")
-
-# After rotation
-rotation_editor = Rotation(angle=15)
-rotated_image = rotation_editor.edit_image(watermarked_image)
-rotation_result = watermark.detect_watermark_in_media(rotated_image)
-print(f"After rotation: {rotation_result}")
-
-
-
-
-
-

Next Steps

- -
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/api/detection.rst b/docs/api/detection.rst deleted file mode 100644 index 3e4a025..0000000 --- a/docs/api/detection.rst +++ /dev/null @@ -1,96 +0,0 @@ -Detection API -============= - -This page documents the watermark detection API. - -Base Detector -------------- - -.. autoclass:: detection.base.BaseDetector - :members: - :undoc-members: - :show-inheritance: - -Detection Methods ------------------ - -Tree-Ring Detection -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.tr.tr_detection - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Detection -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.gs.gs_detection - :members: - :undoc-members: - :show-inheritance: - -ROBIN Detection -~~~~~~~~~~~~~~~ - -.. automodule:: detection.robin.robin_detection - :members: - :undoc-members: - :show-inheritance: - -WIND Detection -~~~~~~~~~~~~~~ - -.. automodule:: detection.wind.wind_detection - :members: - :undoc-members: - :show-inheritance: - -SFW Detection -~~~~~~~~~~~~~ - -.. automodule:: detection.sfw.sfw_detection - :members: - :undoc-members: - :show-inheritance: - -GaussMarker Detection -~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.gm.gm_detection - :members: - :undoc-members: - :show-inheritance: - -PRC Detection -~~~~~~~~~~~~~ - -.. automodule:: detection.prc.prc_detection - :members: - :undoc-members: - :show-inheritance: - -SEAL Detection -~~~~~~~~~~~~~~ - -.. automodule:: detection.seal.seal_detection - :members: - :undoc-members: - :show-inheritance: - -VideoShield Detection -~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.videoshield.videoshield_detection - :members: - :undoc-members: - :show-inheritance: - -VideoMark Detection -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: detection.videomark.videomark_detection - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/api/evaluation.rst b/docs/api/evaluation.rst index 43633f8..b69a613 100644 --- a/docs/api/evaluation.rst +++ b/docs/api/evaluation.rst @@ -1,83 +1,193 @@ Evaluation API ============== -This page documents the evaluation API. +This page documents the evaluation APIs for testing watermark robustness and quality. -Pipelines ---------- +Datasets +-------- + +Dataset classes for loading prompts and test data. + +MSCOCODataset +~~~~~~~~~~~~~ + +.. py:class:: evaluation.dataset.MSCOCODataset + + Dataset for loading MS-COCO captions and images. + + :param parquet_file: Path to the parquet file containing COCO data + :param max_samples: Maximum number of samples to load (optional) + +StableDiffusionPromptsDataset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. py:class:: evaluation.dataset.StableDiffusionPromptsDataset + + Dataset for loading text prompts for Stable Diffusion. + + :param parquet_file: Path to the parquet file containing prompts + :param max_samples: Maximum number of samples to load (optional) + +VBenchDataset +~~~~~~~~~~~~~ + +.. py:class:: evaluation.dataset.VBenchDataset + + Dataset for loading VBench video prompts. + + :param prompt_file: Path to the text file containing prompts + :param max_samples: Maximum number of samples to load (optional) + +Evaluation Pipelines +-------------------- Detection Pipelines ~~~~~~~~~~~~~~~~~~~ -.. automodule:: evaluation.pipelines.detection - :members: - :undoc-members: - :show-inheritance: +.. py:class:: evaluation.pipelines.detection.WatermarkedMediaDetectionPipeline -Image Quality Analysis Pipelines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Pipeline for evaluating detection performance on watermarked media. + + **Key Methods:** + + - ``run(watermark, dataset, **kwargs)`` - Run detection evaluation + - ``get_results()`` - Get evaluation results -.. automodule:: evaluation.pipelines.image_quality_analysis - :members: - :undoc-members: - :show-inheritance: +.. py:class:: evaluation.pipelines.detection.UnWatermarkedMediaDetectionPipeline -Video Quality Analysis Pipelines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Pipeline for evaluating false positive rate on unwatermarked media. + + **Key Methods:** + + - ``run(watermark, dataset, **kwargs)`` - Run detection evaluation + - ``get_results()`` - Get evaluation results -.. automodule:: evaluation.pipelines.video_quality_analysis - :members: - :undoc-members: - :show-inheritance: +Quality Analysis Pipelines +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Datasets --------- +.. py:class:: evaluation.pipelines.image_quality_analysis.DirectImageQualityAnalysisPipeline + + Pipeline for analyzing image quality directly without reference. + + **Key Methods:** + + - ``run(watermark, dataset, quality_analyzers, **kwargs)`` - Run quality analysis + - ``get_results()`` - Get analysis results -.. automodule:: evaluation.dataset - :members: - :undoc-members: - :show-inheritance: +.. py:class:: evaluation.pipelines.video_quality_analysis.DirectVideoQualityAnalysisPipeline + + Pipeline for analyzing video quality. + + **Key Methods:** + + - ``run(watermark, dataset, quality_analyzers, **kwargs)`` - Run quality analysis + - ``get_results()`` - Get analysis results Evaluation Tools ---------------- -Success Rate Calculators -~~~~~~~~~~~~~~~~~~~~~~~~ +Image Attacks/Editors +~~~~~~~~~~~~~~~~~~~~~ -.. automodule:: evaluation.tools.success_rate_calculator - :members: - :undoc-members: - :show-inheritance: +Common image attack methods for testing watermark robustness: -Image Editors (Attacks) -~~~~~~~~~~~~~~~~~~~~~~~ +.. py:class:: evaluation.tools.image_editor.JPEGCompression -.. automodule:: evaluation.tools.image_editor - :members: - :undoc-members: - :show-inheritance: + JPEG compression attack. + + :param quality: JPEG quality (0-100) -Video Editors (Attacks) -~~~~~~~~~~~~~~~~~~~~~~~ +.. py:class:: evaluation.tools.image_editor.GaussianBlur + + Gaussian blur attack. + + :param kernel_size: Size of the Gaussian kernel + +.. py:class:: evaluation.tools.image_editor.GaussianNoise + + Gaussian noise attack. + + :param std: Standard deviation of the noise + +.. py:class:: evaluation.tools.image_editor.Rotation + + Rotation attack. + + :param angle: Rotation angle in degrees -.. automodule:: evaluation.tools.video_editor - :members: - :undoc-members: - :show-inheritance: +.. py:class:: evaluation.tools.image_editor.CenterCrop -Image Quality Analyzers -~~~~~~~~~~~~~~~~~~~~~~~~ + Center crop attack. + + :param crop_ratio: Ratio of image to keep (0-1) -.. automodule:: evaluation.tools.image_quality_analyzer - :members: - :undoc-members: - :show-inheritance: +Quality Analyzers +~~~~~~~~~~~~~~~~~ -Video Quality Analyzers -~~~~~~~~~~~~~~~~~~~~~~~~ +Image quality metrics: -.. automodule:: evaluation.tools.video_quality_analyzer - :members: - :undoc-members: - :show-inheritance: +.. py:class:: evaluation.tools.image_quality_analyzer.PSNRAnalyzer + + Peak Signal-to-Noise Ratio analyzer. + +.. py:class:: evaluation.tools.image_quality_analyzer.SSIMAnalyzer + + Structural Similarity Index analyzer. + +Video quality metrics: + +.. py:class:: evaluation.tools.video_quality_analyzer.SubjectConsistencyAnalyzer + + Video subject consistency analyzer. + +Success Rate Calculator +~~~~~~~~~~~~~~~~~~~~~~~ +.. py:class:: evaluation.tools.success_rate_calculator.DynamicThresholdSuccessRateCalculator + + Calculate detection success rates with dynamic thresholds. + +**Example Usage:** + +.. code-block:: python + + from evaluation.dataset import MSCOCODataset + from evaluation.pipelines.detection import WatermarkedMediaDetectionPipeline + from evaluation.tools.image_editor import JPEGCompression + from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator + + # Load dataset + dataset = MSCOCODataset('dataset/mscoco/mscoco.parquet', max_samples=100) + + # Create pipeline + pipeline = WatermarkedMediaDetectionPipeline( + attack=JPEGCompression(quality=50), + success_rate_calculator=DynamicThresholdSuccessRateCalculator() + ) + + # Run evaluation + pipeline.run(watermark, dataset) + results = pipeline.get_results() + print(results) + +.. code-block:: python + + from evaluation.pipelines.image_quality_analysis import DirectImageQualityAnalysisPipeline + from evaluation.dataset import StableDiffusionPromptsDataset + from evaluation.tools.image_quality_analyzer import PSNRAnalyzer, SSIMAnalyzer + + # Load dataset + dataset = StableDiffusionPromptsDataset('dataset/prompts.parquet', max_samples=50) + + # Create pipeline with quality analyzers + pipeline = DirectImageQualityAnalysisPipeline( + quality_analyzers=[PSNRAnalyzer(), SSIMAnalyzer()] + ) + + # Run analysis + pipeline.run(watermark, dataset) + results = pipeline.get_results() + print(results) + +.. note:: + For detailed evaluation examples and workflows, see :doc:`../user_guide/evaluation`. diff --git a/docs/api/utils.rst b/docs/api/utils.rst index 874f5a4..5f0af80 100644 --- a/docs/api/utils.rst +++ b/docs/api/utils.rst @@ -1,72 +1,75 @@ -Utilities API -============= +Configuration and Utilities +=========================== -This page documents utility modules. +This page documents the configuration and utility APIs for MarkDiffusion. -Diffusion Configuration ------------------------ - -.. automodule:: utils.diffusion_config - :members: - :undoc-members: - :show-inheritance: - -Pipeline Utilities ------------------- - -.. automodule:: utils.pipeline_utils - :members: - :undoc-members: - :show-inheritance: - -Media Utilities +DiffusionConfig --------------- -.. automodule:: utils.media_utils - :members: - :undoc-members: - :show-inheritance: - -General Utilities ------------------ - -.. automodule:: utils.utils - :members: - :undoc-members: - :show-inheritance: - -Callbacks ---------- - -.. automodule:: utils.callbacks - :members: - :undoc-members: - :show-inheritance: - -Inversions ----------- - -Base Inversion -~~~~~~~~~~~~~~ - -.. automodule:: inversions.base_inversion - :members: - :undoc-members: - :show-inheritance: - -DDIM Inversion -~~~~~~~~~~~~~~ - -.. automodule:: inversions.ddim_inversion - :members: - :undoc-members: - :show-inheritance: - -Exact Inversion -~~~~~~~~~~~~~~~ - -.. automodule:: inversions.exact_inversion - :members: - :undoc-members: - :show-inheritance: - +The ``DiffusionConfig`` class configures the diffusion model parameters for watermarking. + +.. py:class:: utils.diffusion_config.DiffusionConfig + + Configuration class for diffusion model settings. + + :param scheduler: Diffusion scheduler (e.g., DPMSolverMultistepScheduler) + :param pipe: Diffusion pipeline (e.g., StableDiffusionPipeline) + :param device: Device to run on ('cuda' or 'cpu') + :param image_size: Size of generated images (tuple, e.g., (512, 512)) + :param num_inference_steps: Number of denoising steps (default: 50) + :param guidance_scale: Classifier-free guidance scale (default: 7.5) + :param gen_seed: Random seed for generation (default: 42) + :param inversion_type: Type of inversion ('ddim' or 'exact', default: 'ddim') + :param num_frames: Number of frames for video (optional, for video watermarks) + :param fps: Frames per second for video (optional, for video watermarks) + +**Example Usage:** + +.. code-block:: python + + from utils.diffusion_config import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler + import torch + + # Initialize diffusion components + device = 'cuda' if torch.cuda.is_available() else 'cpu' + scheduler = DPMSolverMultistepScheduler.from_pretrained( + "model_path", subfolder="scheduler" + ) + pipe = StableDiffusionPipeline.from_pretrained( + "model_path", scheduler=scheduler + ).to(device) + + # Create configuration + diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + num_inference_steps=50, + guidance_scale=7.5, + gen_seed=42, + inversion_type="ddim" + ) + +**For Video Watermarks:** + +.. code-block:: python + + # Video configuration + video_diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=video_pipe, + device=device, + image_size=(512, 512), + num_frames=16, + fps=8, + num_inference_steps=50, + guidance_scale=7.5, + gen_seed=42, + inversion_type="ddim" + ) + +.. note:: + Most parameters have sensible defaults. You primarily need to provide the scheduler, + pipeline, and device. Other parameters can be adjusted based on your specific requirements. diff --git a/docs/api/visualization.rst b/docs/api/visualization.rst index 85132bd..9ea04e2 100644 --- a/docs/api/visualization.rst +++ b/docs/api/visualization.rst @@ -1,104 +1,66 @@ Visualization API ================= -This page documents the visualization API. +This page documents the visualization APIs for analyzing watermarking mechanisms. AutoVisualizer -------------- -.. autoclass:: visualize.auto_visualization.AutoVisualizer - :members: - :undoc-members: - :show-inheritance: - -Base Visualizer ---------------- - -.. autoclass:: visualize.base.BaseVisualizer - :members: - :undoc-members: - :show-inheritance: - -Algorithm-Specific Visualizers -------------------------------- - -Tree-Ring Visualizer -~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.tr.tr_visualizer - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Visualizer -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.gs.gs_visualizer - :members: - :undoc-members: - :show-inheritance: - -ROBIN Visualizer -~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.robin.robin_visualizer - :members: - :undoc-members: - :show-inheritance: - -WIND Visualizer -~~~~~~~~~~~~~~~ - -.. automodule:: visualize.wind.wind_visualizer - :members: - :undoc-members: - :show-inheritance: - -SFW Visualizer -~~~~~~~~~~~~~~ - -.. automodule:: visualize.sfw.sfw_visualizer - :members: - :undoc-members: - :show-inheritance: - -GaussMarker Visualizer -~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.gm.gm_visualizer - :members: - :undoc-members: - :show-inheritance: - -PRC Visualizer -~~~~~~~~~~~~~~ - -.. automodule:: visualize.prc.prc_visualizer - :members: - :undoc-members: - :show-inheritance: - -SEAL Visualizer -~~~~~~~~~~~~~~~ - -.. automodule:: visualize.seal.seal_visualizer - :members: - :undoc-members: - :show-inheritance: - -VideoShield Visualizer -~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.videoshield.video_shield_visualizer - :members: - :undoc-members: - :show-inheritance: - -VideoMark Visualizer -~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: visualize.videomark.video_mark_visualizer - :members: - :undoc-members: - :show-inheritance: - +The ``AutoVisualizer`` class provides unified visualization for all watermarking algorithms. + +**Key Methods:** + +.. py:class:: visualize.auto_visualization.AutoVisualizer + + .. py:staticmethod:: load(algorithm_name, data_for_visualization) + + Load a visualizer for the specified algorithm. + + :param algorithm_name: Name of the watermarking algorithm (e.g., 'TR', 'GS', 'PRC') + :param data_for_visualization: Data obtained from ``get_data_for_visualize()`` + :return: An AutoVisualizer instance + + .. py:method:: visualize(rows, cols, methods, method_kwargs=None, save_path=None, **kwargs) + + Generate visualization figures. + + :param rows: Number of rows in the figure grid + :param cols: Number of columns in the figure grid + :param methods: List of visualization methods to use + :param method_kwargs: List of keyword arguments for each method + :param save_path: Path to save the figure (optional) + :return: matplotlib figure object + +**Example Usage:** + +.. code-block:: python + + from visualize.auto_visualization import AutoVisualizer + + # Get visualization data from watermark + data_for_vis = watermark.get_data_for_visualize(watermarked_image) + + # Load visualizer + visualizer = AutoVisualizer.load('TR', data_for_visualization=data_for_vis) + + # Create visualization + fig = visualizer.visualize( + rows=1, + cols=5, + methods=['draw_pattern_fft', 'draw_orig_latents_fft', + 'draw_watermarked_image', 'draw_inverted_latents_fft', + 'draw_inverted_pattern_fft'], + save_path='visualization.pdf' + ) + +**Available Visualization Methods:** + +Each algorithm has specific visualization methods. Common methods include: + +- ``draw_watermarked_image`` - Display the watermarked image +- ``draw_orig_latents`` / ``draw_orig_latents_fft`` - Original latent representations +- ``draw_inverted_latents`` / ``draw_inverted_latents_fft`` - Inverted latent representations +- Algorithm-specific methods (e.g., ``draw_pattern_fft`` for Tree-Ring, ``draw_watermark_bits`` for Gaussian-Shading) + +.. note:: + For detailed visualization examples, see :doc:`../user_guide/visualization`. diff --git a/docs/api/watermark.rst b/docs/api/watermark.rst index 401cd43..5c323f0 100644 --- a/docs/api/watermark.rst +++ b/docs/api/watermark.rst @@ -1,101 +1,48 @@ Watermark API ============= -This page documents the watermarking API. +This page documents the core watermarking APIs that users directly interact with. AutoWatermark ------------- -.. autoclass:: watermark.auto_watermark.AutoWatermark - :members: - :undoc-members: - :show-inheritance: - -Base Watermark --------------- - -.. autoclass:: watermark.base.BaseWatermark - :members: - :undoc-members: - :show-inheritance: - -Tree-Ring Watermark -------------------- - -.. automodule:: watermark.tr.tr - :members: - :undoc-members: - :show-inheritance: - -Gaussian-Shading Watermark --------------------------- - -.. automodule:: watermark.gs.gs - :members: - :undoc-members: - :show-inheritance: - -ROBIN Watermark ---------------- +The ``AutoWatermark`` class is the primary interface for watermarking operations. -.. automodule:: watermark.robin.robin - :members: - :undoc-members: - :show-inheritance: - -WIND Watermark --------------- - -.. automodule:: watermark.wind.wind - :members: - :undoc-members: - :show-inheritance: - -SFW Watermark -------------- - -.. automodule:: watermark.sfw.sfw - :members: - :undoc-members: - :show-inheritance: +.. autoclass:: watermark.auto_watermark.AutoWatermark + :members: load, generate_watermarked_media, generate_unwatermarked_media, detect_watermark_in_media, get_data_for_visualize -GaussMarker ------------ +**Key Methods:** -.. automodule:: watermark.gm.gm - :members: - :undoc-members: - :show-inheritance: +- ``load(algorithm_name, algorithm_config, diffusion_config)`` - Load a watermarking algorithm +- ``generate_watermarked_media(input_data, **kwargs)`` - Generate watermarked media (image or video) +- ``generate_unwatermarked_media(input_data, **kwargs)`` - Generate clean media without watermark +- ``detect_watermark_in_media(media, **kwargs)`` - Detect watermark in media +- ``get_data_for_visualize(media, **kwargs)`` - Get data for visualization -PRC Watermark -------------- +**Supported Algorithms:** -.. automodule:: watermark.prc.prc - :members: - :undoc-members: - :show-inheritance: +- Image watermarks: ``TR``, ``GS``, ``PRC``, ``RI``, ``SEAL``, ``ROBIN``, ``WIND``, ``GM``, ``SFW`` +- Video watermarks: ``VideoShield``, ``VideoMark`` -SEAL Watermark --------------- +**Example Usage:** -.. automodule:: watermark.seal.seal - :members: - :undoc-members: - :show-inheritance: +.. code-block:: python -VideoShield ------------ + from watermark.auto_watermark import AutoWatermark + from utils.diffusion_config import DiffusionConfig -.. automodule:: watermark.videoshield.video_shield - :members: - :undoc-members: - :show-inheritance: + # Load a watermark algorithm + watermark = AutoWatermark.load('TR', + algorithm_config='config/TR.json', + diffusion_config=diffusion_config) -VideoMark ---------- + # Generate watermarked image + watermarked_image = watermark.generate_watermarked_media("A sunset over mountains") -.. automodule:: watermark.videomark.video_mark - :members: - :undoc-members: - :show-inheritance: + # Detect watermark + result = watermark.detect_watermark_in_media(watermarked_image) + print(result) +.. note:: + For algorithm-specific implementation details, please refer to the + :doc:`../user_guide/algorithms` page. diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index cc98289..0000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,138 +0,0 @@ -Changelog -========= - -All notable changes to MarkDiffusion will be documented in this file. - -Version 1.0.0 (2025-01-XX) --------------------------- - -Initial Release -~~~~~~~~~~~~~~~ - -**Implemented Algorithms:** - -- Tree-Ring (TR) - Pattern-based watermarking -- Ring-ID (RI) - Multi-key identification -- ROBIN - Robust and invisible watermarking -- WIND - Two-stage robust watermarking -- SFW - Semantic Fourier watermarking -- Gaussian-Shading (GS) - Performance-lossless watermarking -- GaussMarker (GM) - Dual-domain watermarking -- PRC - Undetectable watermarking -- SEAL - Semantic-aware watermarking -- VideoShield - Video watermarking -- VideoMark - Distortion-free video watermarking - -**Features:** - -- Unified implementation framework for watermarking algorithms -- Comprehensive evaluation module with 24 tools -- 8 automated evaluation pipelines -- Custom visualization tools for all algorithms -- Support for both image and video watermarking -- Extensive documentation and tutorials - -**Evaluation Tools:** - -*Detectability:* -- FundamentalSuccessRateCalculator -- DynamicThresholdSuccessRateCalculator - -*Image Attacks:* -- JPEG Compression -- Gaussian Blur -- Gaussian Noise -- Rotation -- Crop & Scale -- Brightness Adjustment -- Masking -- Overlay -- Adaptive Noise Injection - -*Video Attacks:* -- MPEG-4 Compression -- Frame Averaging -- Frame Swapping -- Video Codec Attack (H.264/H.265/VP9/AV1) -- Frame Rate Adapter -- Frame Interpolation Attack - -*Image Quality Metrics:* -- PSNR (Peak Signal-to-Noise Ratio) -- SSIM (Structural Similarity Index) -- LPIPS (Learned Perceptual Image Patch Similarity) -- CLIP Score -- FID (Fréchet Inception Distance) -- Inception Score -- NIQE (Natural Image Quality Evaluator) -- BRISQUE -- VIF (Visual Information Fidelity) -- FSIM (Feature Similarity Index) - -*Video Quality Metrics:* -- Subject Consistency -- Background Consistency -- Motion Smoothness -- Dynamic Degree -- Imaging Quality - -Recent Updates --------------- - -**2025.10.10** - -- Added Mask, Overlay, AdaptiveNoiseInjection image attack tools -- Thanks to Zheyu Fu for the contribution - -**2025.10.09** - -- Added VideoCodecAttack, FrameRateAdapter, FrameInterpolationAttack video attack tools -- Thanks to Luyang Si for the contribution - -**2025.10.08** - -- Added SSIM, BRISQUE, VIF, FSIM image quality analyzers -- Thanks to Huan Wang for the contribution - -**2025.10.07** - -- Added SFW (Semantic Fourier Watermarking) algorithm -- Thanks to Huan Wang for the contribution - -**2025.10.07** - -- Added VideoMark watermarking algorithm -- Thanks to Hanqian Li for the contribution - -**2025.09.29** - -- Added GaussMarker watermarking algorithm -- Thanks to Luyang Si for the contribution - -Upcoming Features ------------------ - -**Planned for v1.1.0:** - -- Additional watermarking algorithms -- More evaluation metrics -- Enhanced visualization capabilities -- Performance optimizations -- Extended documentation - -**Under Consideration:** - -- Real-time watermarking support -- Web interface for demonstration -- Pre-trained model zoo -- Integration with more diffusion models -- Support for additional modalities (3D, audio) - -Contributing ------------- - -We welcome contributions! See :doc:`contributing` for guidelines. - -To report bugs or request features, please open an issue on GitHub: -https://github.com/THU-BPM/MarkDiffusion/issues - diff --git a/docs/contributing.rst b/docs/contributing.rst index 48657fb..c215a07 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -89,37 +89,42 @@ Example with type hints: Testing ~~~~~~~ -All new code should include tests: +All new code should include comprehensive tests. We use pytest for testing. -.. code-block:: python - - import unittest - from watermark.auto_watermark import AutoWatermark +.. note:: - class TestMyFeature(unittest.TestCase): - def setUp(self): - """Set up test fixtures.""" - self.watermark = AutoWatermark.load('GS', 'config/GS.json', config) - - def test_generation(self): - """Test watermarked image generation.""" - image = self.watermark.generate_watermarked_media("Test prompt") - self.assertIsNotNone(image) - self.assertEqual(image.size, (512, 512)) - - def test_detection(self): - """Test watermark detection.""" - image = self.watermark.generate_watermarked_media("Test prompt") - result = self.watermark.detect_watermark_in_media(image) - self.assertTrue(result['detected']) + For detailed testing instructions, including test structure, markers, and running options, + please refer to the ``test/README.md`` file in the repository. + + For test code examples, please refer to the existing test files in the ``test/`` directory: + + - ``test/test_watermark_algorithms.py`` - Watermark algorithm tests + - ``test/test_pipelines.py`` - Pipeline tests + - ``test/test_image_editor.py`` - Image editing tests + - ``test/test_video_editor.py`` - Video editing tests + - ``test/test_utils.py`` - Utility function tests + - ``test/test_dataset.py`` - Dataset tests Run tests: .. code-block:: bash - python -m pytest test/ - # Or for specific test - python -m pytest test/test_watermark.py::TestMyFeature::test_generation + # Install test dependencies + pip install -r test/requirements-test.txt + + # Run all tests with coverage and HTML report + pytest test -v --cov=. --cov-report=html --cov-report=term-missing --html=report.html + + # Test all algorithms + pytest test/test_watermark_algorithms.py -v + + # Test a specific algorithm + pytest test/test_watermark_algorithms.py -v --algorithm TR + + # Test all pipelines + pytest test/test_pipelines.py -v + +For more test commands and options, see ``test/README.md``. Contribution Process -------------------- @@ -226,31 +231,62 @@ To add a new evaluation tool: Submission Checklist -------------------- -Before submitting your contribution, ensure: +Before submitting your contribution, please ensure you have completed the following: + +Type of Change +~~~~~~~~~~~~~~ + +Please indicate the type of your contribution: + +- 🐛 **Bug fix** (non-breaking change which fixes an issue) +- ✨ **New feature** (non-breaking change which adds functionality) +- 📝 **Documentation update** (changes to documentation only) +- 💥 **Breaking change** (fix or feature that would cause existing functionality to not work as expected) +- 🔧 **Refactor/Optimization** (code improvement without changing logic) + +Pre-Submission Checklist +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- [ ] I have read and followed the Contributing Guidelines +- [ ] I have performed a self-review of my own code +- [ ] My code follows the project's coding style/standards (PEP 8) +- [ ] I have added/updated necessary comments and documentation +- [ ] I have added corresponding test cases (if new feature or bug fix) +- [ ] All local tests pass successfully **Testing** .. code-block:: bash - python -m pytest test/ + # Install test dependencies + pip install -r test/requirements-test.txt + + # Run all tests with coverage + pytest test -v --cov=. --cov-report=html --cov-report=term-missing --html=report.html + +For detailed testing instructions, see ``test/README.md``. **Code Style** .. code-block:: bash + # Check code style flake8 watermark/ detection/ evaluation/ visualize/ black --check watermark/ detection/ evaluation/ visualize/ + + # Auto-format code + black watermark/ detection/ evaluation/ visualize/ **Documentation** -- Update relevant documentation -- Add entry to CHANGELOG.md -- Ensure docstrings are complete +- Update relevant documentation files +- Add usage examples where appropriate +- Ensure all docstrings are complete and follow the project style **Pull Request** -For the complete pull request process and guidelines, please refer to `contributing.md <../contributing.md>`_ -in the repository root. +For the complete pull request process and guidelines, please refer to the +`Contributing Guidelines <../CONTRIBUTING.md>`_ in the repository root. Additional Information ---------------------- diff --git a/docs/index.rst b/docs/index.rst index 018513d..0ca7264 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,17 +1,33 @@ MarkDiffusion Documentation ============================ -.. image:: https://img.shields.io/badge/Homepage-5F259F?style=for-the-badge&logo=homepage&logoColor=white +.. image:: https://img.shields.io/badge/Home-5F259F?style=for-the-badge&logo=homepage&logoColor=white :target: https://generative-watermark.github.io/ - :alt: Homepage + :alt: Home .. image:: https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white :target: https://arxiv.org/abs/2509.10569 :alt: Paper -.. image:: https://img.shields.io/badge/HF--Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black +.. image:: https://img.shields.io/badge/Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black :target: https://huggingface.co/Generative-Watermark-Toolkits - :alt: HF Models + :alt: Models + +.. image:: https://img.shields.io/badge/Google--Colab-%23D97700?style=for-the-badge&logo=Google-colab&logoColor=white + :target: https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing + :alt: Colab + +.. image:: https://img.shields.io/badge/Readthedocs-%2300A89C?style=for-the-badge&logo=readthedocs&logoColor=%238CA1AF + :target: https://markdiffusion.readthedocs.io + :alt: DOC + +.. image:: https://img.shields.io/badge/PYPI-%23193440?style=for-the-badge&logo=pypi&logoColor=%233775A9 + :target: https://pypi.org/project/markdiffusion + :alt: PYPI + +.. image:: https://img.shields.io/badge/Conda--Forge-%23000000?style=for-the-badge&logo=condaforge&logoColor=%23FFFFFF + :target: https://github.com/conda-forge/markdiffusion-feedstock + :alt: CONDA-FORGE Welcome to MarkDiffusion ------------------------- @@ -21,9 +37,6 @@ As the use of diffusion-based generative models expands, ensuring the authentici media becomes critical. MarkDiffusion simplifies the access, understanding, and assessment of watermarking technologies, making it accessible to both researchers and the broader community. -.. note:: - If you are interested in LLM watermarking (text watermark), please refer to the - `MarkLLM `_ toolkit from our group. Key Features ------------ @@ -43,65 +56,36 @@ Key Features how different watermarking algorithms operate under various scenarios. 📊 **Comprehensive Evaluation Module** - With 24 evaluation tools covering detectability, robustness, and impact on output quality, - MarkDiffusion provides comprehensive assessment capabilities with 8 automated evaluation pipelines. - -Quick Example -------------- - -Here's a simple example to get you started with MarkDiffusion: - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - # Device setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - - # Configure diffusion pipeline - scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load watermark algorithm - watermark = AutoWatermark.load('TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config) - - # Generate watermarked media - prompt = "A beautiful sunset over the ocean" - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Detect watermark - detection_result = watermark.detect_watermark_in_media(watermarked_image) - print(f"Watermark detected: {detection_result}") + With 31 evaluation tools covering detectability, robustness, and impact on output quality, + MarkDiffusion provides comprehensive assessment capabilities with 6 automated evaluation pipelines. + +.. image:: ../img/fig1_overview.png + :alt: MarkDiffusion Overview + :width: 100% + :align: center + + +A Quick Example of Generating and Detecting Watermarked Image via MarkDiffusion Toolkit +--------------- + +.. image:: ../img/A_Quick_Example.png + :alt: A quick example of generating and detecting watermarked image via MarkDiffusion toolkit + :width: 100% + :align: center Documentation Contents ---------------------- .. toctree:: - :maxdepth: 2 - :caption: Getting Started + :maxdepth: 1 + :caption: Quick Start - installation quickstart - tutorial .. toctree:: :maxdepth: 2 - :caption: User Guide + :caption: Background Information and + Detailed Guidance user_guide/algorithms user_guide/watermarking @@ -113,19 +97,25 @@ Documentation Contents :caption: API Reference api/watermark - api/detection api/visualization - api/evaluation api/utils + api/evaluation + +.. toctree:: + :maxdepth: 2 + :caption: Test System + + test_system/ci_cd_test + test_system/comprehensive_test .. toctree:: :maxdepth: 1 :caption: Additional Resources - changelog contributing code_of_conduct citation + resources Indices and tables ================== diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 6e84a27..0000000 --- a/docs/installation.rst +++ /dev/null @@ -1,201 +0,0 @@ -Installation -============ - -This guide will help you install MarkDiffusion and its dependencies. - -Requirements ------------- - -- Python 3.10 or higher -- PyTorch (with CUDA support recommended for GPU acceleration) -- 8GB+ RAM (16GB+ recommended for video watermarking) -- CUDA-compatible GPU (optional but highly recommended) - -Basic Installation ------------------- - -1. **Clone the Repository** - - .. code-block:: bash - - git clone https://github.com/THU-BPM/MarkDiffusion.git - cd MarkDiffusion - -2. **Install Dependencies** - - .. code-block:: bash - - pip install -r requirements.txt - -3. **Download Pre-trained Models** - - MarkDiffusion uses pre-trained models stored on Hugging Face. Download the required models: - - .. code-block:: bash - - # The models will be downloaded to the ckpts/ directory - # Visit: https://huggingface.co/Generative-Watermark-Toolkits - - For each algorithm you plan to use, download the corresponding model weights from the - `Generative-Watermark-Toolkits `_ - repository and place them in the appropriate ``ckpts/`` subdirectory. - -Installation with Conda ------------------------ - -If you prefer using Conda for environment management: - -.. code-block:: bash - - # Create a new conda environment - conda create -n markdiffusion python=3.10 - conda activate markdiffusion - - # Install PyTorch with CUDA support - conda install pytorch torchvision torchaudio pytorch-cuda=12.6 -c pytorch -c nvidia - - # Install other dependencies - pip install -r requirements.txt - -GPU Support ------------ - -For GPU acceleration, make sure you have: - -1. **NVIDIA GPU** with CUDA support -2. **CUDA Toolkit** installed (version 11.8 or higher) -3. **cuDNN** library - -To verify GPU availability: - -.. code-block:: python - - import torch - print(f"CUDA available: {torch.cuda.is_available()}") - print(f"CUDA version: {torch.version.cuda}") - print(f"Device count: {torch.cuda.device_count()}") - -Algorithm-Specific Setup ------------------------- - -Some algorithms require additional model checkpoints beyond the base installation: - -GaussMarker (GM) -~~~~~~~~~~~~~~~~ - -GaussMarker requires two pre-trained models for watermark detection and restoration: - -1. **GNR Model** (Generative Noise Restoration): A UNet-based model for restoring watermark bits from noisy latents -2. **Fuser Model**: A classifier for fusion-based watermark detection decisions - -**Setup:** - -.. code-block:: bash - - # Create the checkpoint directory - mkdir -p watermark/gm/ckpts/ - - # Download models from Hugging Face - # Visit: https://huggingface.co/Generative-Watermark-Toolkits/GaussMarker - # Place the following files: - # - model_final.pth -> watermark/gm/ckpts/model_final.pth - # - sd21_cls2.pkl -> watermark/gm/ckpts/sd21_cls2.pkl - -**Configuration Path** (in ``config/GM.json``): - -- ``gnr_checkpoint``: ``"watermark/gm/ckpts/model_final.pth"`` -- ``fuser_checkpoint``: ``"watermark/gm/ckpts/sd21_cls2.pkl"`` - -SEAL -~~~~ - -SEAL uses pre-trained models from Hugging Face for caption generation and embedding: - -1. **BLIP2 Model**: For generating image captions (blip2-flan-t5-xl) -2. **Sentence Transformer**: For caption embedding - -**Setup:** - -.. code-block:: bash - - # These models will be automatically downloaded from Hugging Face on first use - # Or you can pre-download them: - - # Download BLIP2 model - python -c "from transformers import Blip2Processor, Blip2ForConditionalGeneration; \ - Blip2Processor.from_pretrained('Salesforce/blip2-flan-t5-xl'); \ - Blip2ForConditionalGeneration.from_pretrained('Salesforce/blip2-flan-t5-xl')" - - # Download sentence transformer (if using custom fine-tuned model) - # Update config/SEAL.json paths accordingly - -**Configuration** (in ``config/SEAL.json``): - -- ``cap_processor``: Path or model name for BLIP2 processor -- ``cap_model``: Path or model name for BLIP2 model -- ``sentence_model``: Path or model name for sentence transformer - -.. note:: - SEAL models are large (~15GB for BLIP2). Ensure you have sufficient disk space and memory. - -Other Algorithms -~~~~~~~~~~~~~~~~ - -The following algorithms work with the base installation and do not require additional checkpoints: - -- **Tree-Ring (TR)**, **Ring-ID (RI)**, **ROBIN**, **WIND**, **SFW**: Pattern-based methods using frequency domain manipulation -- **Gaussian-Shading (GS)**, **PRC**: Key-based methods with built-in watermark generation -- **VideoShield**, **VideoMark**: Video watermarking algorithms using temporal consistency - -Verification ------------- - -To verify your installation, run the test suite: - -.. code-block:: bash - - python -m pytest test/ - -Or try a simple example: - -.. code-block:: python - - from watermark.auto_watermark import AutoWatermark - print("MarkDiffusion successfully installed!") - -Troubleshooting ---------------- - -Common Issues -~~~~~~~~~~~~~ - -**Issue: Module not found error** - -Solution: Make sure all dependencies are installed: - -.. code-block:: bash - - pip install -r requirements.txt --upgrade - -**Issue: Model weights not found** - -Solution: Download the required models from Hugging Face and place them in the correct directory structure. - -Getting Help -~~~~~~~~~~~~ - -If you encounter issues: - -1. Check the `GitHub Issues `_ -2. Consult the `FAQ `_ -3. Open a new issue with detailed information about your problem - -Next Steps ----------- - -Now that you have installed MarkDiffusion, proceed to: - -- :doc:`quickstart` - Get started with basic examples -- :doc:`tutorial` - Learn through step-by-step tutorials -- :doc:`user_guide/algorithms` - Explore available algorithms - diff --git a/docs/quickstart.rst b/docs/quickstart.rst index da107f7..fa11df9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,21 +1,52 @@ Quick Start =========== -This guide will help you get started with MarkDiffusion quickly. +Google Colab Demo +----------------- -Basic Workflow --------------- +If you're interested in trying out MarkDiffusion without installing anything, you can use +`Google Colab `_ +to see how it works. -The typical workflow with MarkDiffusion consists of three main steps: +Installation +------------ -1. **Configure** the diffusion model and watermarking algorithm -2. **Generate** watermarked images or videos -3. **Detect** watermarks in media +**(Recommended)** We released PyPI package for MarkDiffusion. You can install it directly with pip: -Step 1: Setup and Configuration --------------------------------- +.. code-block:: bash -First, import the necessary modules and configure your diffusion model: + conda create -n markdiffusion python=3.11 + conda activate markdiffusion + pip install markdiffusion[optional] + +**(Alternative)** For users who are restricted only to use conda environment, we also provide a +conda-forge package, which can be installed with the following commands: + +.. code-block:: bash + + conda create -n markdiffusion python=3.11 + conda activate markdiffusion + conda config --add channels conda-forge + conda config --set channel_priority strict + conda install markdiffusion + +.. note:: + Some advanced features require additional packages that are not available on conda and cannot + be included in the release. You will need to install those separately if necessary. + +How to Use the Toolkit +---------------------- + +After installation, there are two ways to use MarkDiffusion: + +Method 1: Clone Repository for Development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Clone the repository to try the demos or use it for custom development.** + +The ``MarkDiffusion_demo.ipynb`` notebook offers detailed demonstrations for various use cases—please +review it for guidance. Here's a quick example of generating and detecting watermarked image with the +TR algorithm: .. code-block:: python @@ -27,12 +58,9 @@ First, import the necessary modules and configure your diffusion model: # Device setup device = 'cuda' if torch.cuda.is_available() else 'cpu' - # Configure the diffusion model - model_id = "stabilityai/stable-diffusion-2-1" - scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device) - - # Create diffusion configuration + # Configure diffusion pipeline + scheduler = DPMSolverMultistepScheduler.from_pretrained("model_path", subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained("model_path", scheduler=scheduler).to(device) diffusion_config = DiffusionConfig( scheduler=scheduler, pipe=pipe, @@ -44,202 +72,86 @@ First, import the necessary modules and configure your diffusion model: inversion_type="ddim" ) -Step 2: Load a Watermarking Algorithm --------------------------------------- - -MarkDiffusion supports multiple watermarking algorithms. Here's how to load one: + # Load watermark algorithm + watermark = AutoWatermark.load('TR', + algorithm_config='config/TR.json', + diffusion_config=diffusion_config) -.. code-block:: python - - # Load Tree-Ring watermarking algorithm - watermark = AutoWatermark.load( - 'TR', # Algorithm name - algorithm_config='config/TR.json', # Configuration file - diffusion_config=diffusion_config # Diffusion settings - ) - -Available algorithms: - -- **TR**: Tree-Ring -- **RI**: Ring-ID -- **ROBIN**: ROBIN -- **WIND**: WIND -- **SFW**: Semantic Fourier Watermark -- **GS**: Gaussian-Shading -- **GM**: GaussMarker -- **PRC**: PRC -- **SEAL**: SEAL -- **VideoShield**: VideoShield -- **VideoMark**: VideoMark - -Step 3: Generate Watermarked Media ------------------------------------ - -Image Generation -~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Define your prompt - prompt = "A beautiful landscape with mountains and a lake at sunset" - - # Generate watermarked image + # Generate watermarked media + prompt = "A beautiful sunset over the ocean" watermarked_image = watermark.generate_watermarked_media(prompt) + watermarked_image.save("watermarked_image.png") - # Save the image - watermarked_image.save("watermarked_output.png") - - # Display the image - watermarked_image.show() - -Video Generation -~~~~~~~~~~~~~~~~ - -For video watermarking algorithms (VideoShield, VideoMark): - -.. code-block:: python - - # Load video watermarking algorithm - video_watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_diffusion_config - ) - - # Generate watermarked video - prompt = "A cat walking through a garden" - video_frames = video_watermark.generate_watermarked_media(prompt) + # Detect watermark + detection_result = watermark.detect_watermark_in_media(watermarked_image) + print(f"Watermark detected: {detection_result}") - # Save video frames - for i, frame in enumerate(video_frames): - frame.save(f"output/frame_{i:04d}.png") +Method 2: Import as Library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Step 4: Detect Watermarks --------------------------- +**Import markdiffusion library directly in your code without cloning the repository.** -After generating watermarked media, you can detect the watermark: +The ``MarkDiffusion_pypi_demo.ipynb`` notebook provides comprehensive examples for using MarkDiffusion +via the markdiffusion library—please review it for guidance. Here's a quick example: .. code-block:: python - # Detect watermark in the generated image - detection_result = watermark.detect_watermark_in_media(watermarked_image) + import torch + from markdiffusion.watermark import AutoWatermark + from markdiffusion.utils import DiffusionConfig + from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - # Print detection results - print(f"Detection result: {detection_result}") + # Device + device = "cuda" if torch.cuda.is_available() else "cpu" + print(f"Using device: {device}") - # For algorithms that return a score - if 'score' in detection_result: - print(f"Confidence score: {detection_result['score']}") - print(f"Threshold: {detection_result.get('threshold', 'N/A')}") + # Model path + MODEL_PATH = "huanzi05/stable-diffusion-2-1-base" -Complete Example ----------------- + # Initialize scheduler and pipeline + scheduler = DPMSolverMultistepScheduler.from_pretrained(MODEL_PATH, subfolder="scheduler") + pipe = StableDiffusionPipeline.from_pretrained( + MODEL_PATH, + scheduler=scheduler, + torch_dtype=torch.float16 if device == "cuda" else torch.float32, + safety_checker=None, + ).to(device) -Here's a complete example putting it all together: + # Create DiffusionConfig for image generation + image_diffusion_config = DiffusionConfig( + scheduler=scheduler, + pipe=pipe, + device=device, + image_size=(512, 512), + guidance_scale=7.5, + num_inference_steps=50, + gen_seed=42, + inversion_type="ddim" + ) -.. code-block:: python + # Load Tree-Ring watermark algorithm + tr_watermark = AutoWatermark.load('TR', diffusion_config=image_diffusion_config) + print("TR watermark algorithm loaded successfully!") - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler + # Generate watermarked image + prompt = "A beautiful landscape with mountains and a river at sunset" - def main(): - # Setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - model_id = "stabilityai/stable-diffusion-2-1" - - # Load diffusion model - scheduler = DPMSolverMultistepScheduler.from_pretrained( - model_id, subfolder="scheduler" - ) - pipe = StableDiffusionPipeline.from_pretrained( - model_id, scheduler=scheduler - ).to(device) - - # Configure diffusion - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load watermarking algorithm - watermark = AutoWatermark.load( - 'GS', # Gaussian-Shading - algorithm_config='config/GS.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - prompt = "A serene Japanese garden with cherry blossoms" - print("Generating watermarked image...") - watermarked_image = watermark.generate_watermarked_media(prompt) - - # Save image - watermarked_image.save("output.png") - print("Image saved to output.png") - - # Detect watermark - print("Detecting watermark...") - detection_result = watermark.detect_watermark_in_media(watermarked_image) - print(f"Detection result: {detection_result}") - - return watermarked_image, detection_result - - if __name__ == "__main__": - image, result = main() - -Comparing Multiple Algorithms ------------------------------- - -You can easily compare different algorithms: + watermarked_image = tr_watermark.generate_watermarked_media(input_data=prompt) -.. code-block:: python + # Display the watermarked image + watermarked_image.save("watermarked_image.png") + print("Watermarked image generated!") - algorithms = ['TR', 'GS', 'ROBIN', 'SEAL'] - prompt = "A futuristic city skyline at night" - - results = {} - for algo_name in algorithms: - print(f"\nTesting {algo_name}...") - - # Load algorithm - watermark = AutoWatermark.load( - algo_name, - algorithm_config=f'config/{algo_name}.json', - diffusion_config=diffusion_config - ) - - # Generate and detect - img = watermark.generate_watermarked_media(prompt) - detection = watermark.detect_watermark_in_media(img) - - results[algo_name] = { - 'image': img, - 'detection': detection - } - - # Save image - img.save(f"output_{algo_name}.png") - - # Print comparison - print("\n=== Comparison Results ===") - for algo, data in results.items(): - print(f"{algo}: {data['detection']}") + # Detect watermark in the watermarked image + detection_result = tr_watermark.detect_watermark_in_media(watermarked_image) + print("Watermarked image detection result:") + print(detection_result) Next Steps ---------- -Now that you're familiar with the basics: +Now that you're familiar with the basics, explore more: -- :doc:`tutorial` - Detailed tutorials for each component - :doc:`user_guide/algorithms` - Learn about specific algorithms - :doc:`user_guide/visualization` - Visualize watermarking mechanisms - :doc:`user_guide/evaluation` - Evaluate watermark quality and robustness - diff --git a/docs/resources.rst b/docs/resources.rst new file mode 100644 index 0000000..4077b45 --- /dev/null +++ b/docs/resources.rst @@ -0,0 +1,59 @@ +All Resources +============= + +Here are all the available resources for MarkDiffusion: + +Home +---- + +.. image:: https://img.shields.io/badge/Home-5F259F?style=for-the-badge&logo=homepage&logoColor=white + :target: https://generative-watermark.github.io/ + :alt: Home + +Official project homepage with comprehensive information and demonstrations. + +Paper +----- + +.. image:: https://img.shields.io/badge/Paper-A42C25?style=for-the-badge&logo=arxiv&logoColor=white + :target: https://arxiv.org/abs/2509.10569 + :alt: Paper + +Research paper detailing the methodology and evaluation of MarkDiffusion. + +Models +------ + +.. image:: https://img.shields.io/badge/Models-%23FFD14D?style=for-the-badge&logo=huggingface&logoColor=black + :target: https://huggingface.co/Generative-Watermark-Toolkits + :alt: Models + +Pre-trained models and checkpoints hosted on Hugging Face. + +Google Colab +------------ + +.. image:: https://img.shields.io/badge/Google--Colab-%23D97700?style=for-the-badge&logo=Google-colab&logoColor=white + :target: https://colab.research.google.com/drive/1N1C9elDAB5zwF4FxKKYMCqR3eSpCSqAW?usp=sharing + :alt: Colab + +Interactive notebook to try MarkDiffusion without local installation. + +PyPI Package +------------ + +.. image:: https://img.shields.io/badge/PYPI-%23193440?style=for-the-badge&logo=pypi&logoColor=%233775A9 + :target: https://pypi.org/project/markdiffusion + :alt: PYPI + +Python package available for easy installation via pip. + +Conda-Forge +----------- + +.. image:: https://img.shields.io/badge/Conda--Forge-%23000000?style=for-the-badge&logo=condaforge&logoColor=%23FFFFFF + :target: https://github.com/conda-forge/markdiffusion-feedstock + :alt: CONDA-FORGE + +Conda package available for installation in conda environments. + diff --git a/docs/test_system/ci_cd_test.rst b/docs/test_system/ci_cd_test.rst new file mode 100644 index 0000000..d04a8f4 --- /dev/null +++ b/docs/test_system/ci_cd_test.rst @@ -0,0 +1,74 @@ +CI/CD Testing +============= + +MarkDiffusion includes a continuous integration testing system via GitHub Actions (workflow: ``selective-tests.yml``). +Due to the extensive functionality of the repository, running the complete test suite takes considerable time. +Therefore, our CI/CD pipeline focuses on essential tests to ensure code quality while maintaining efficiency. + +The CI/CD tests use a lightweight test suite located in ``tests_ci/`` directory, which contains the same test +structure as the full test suite but runs faster by focusing on initialization and interface validation. + +Workflow Overview +----------------- + +The CI/CD workflow automatically detects changed files and runs targeted tests based on the modifications: + +Core Framework Changes +~~~~~~~~~~~~~~~~~~~~~~ + +When changes are detected in core framework files: + +- ``watermark/auto_watermark.py`` +- ``watermark/base.py`` +- ``watermark/auto_config.py`` +- ``watermark/__init__.py`` + +The system runs initialization tests for **all algorithms** (``--skip-generation --skip-detection``), ensuring +the framework logic remains correct and compatible across all watermarking methods without running the +time-consuming generation and detection processes. + +Algorithm-Specific Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When changes are detected in: + +- Specific algorithm folders (e.g., ``watermark/tr/``, ``watermark/gs/``) +- Algorithm configuration files in ``config/`` (e.g., ``TR.json``, ``GS.json``) + +The system automatically identifies the affected algorithms and runs initialization tests for **only those +specific algorithms**, ensuring efficient testing while maintaining quality assurance. + +Evaluation and Test Module Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When changes are detected in: + +- ``evaluation/`` module +- ``tests_ci/`` directories + +The system runs fast evaluation tests (``tests_ci/test_pipelines.py``) to verify that the evaluation +pipelines and tools function correctly. + +Workflow Triggers +----------------- + +The CI/CD workflow is triggered on: + +- **Pull Requests**: Automatically runs on all pull requests +- **Push to Main**: Runs when code is pushed to the main branch +- **Manual Dispatch**: Can be manually triggered, which forces full test scope (all algorithms + evaluation) + +Benefits +-------- + +This targeted testing approach provides several advantages: + +- **Fast Feedback**: Developers receive quick feedback on their changes +- **Resource Efficiency**: Only necessary tests are run, saving computational resources +- **Comprehensive Coverage**: Critical changes trigger appropriate test coverage +- **Quality Assurance**: Ensures that framework modifications don't break existing functionality +- **Intelligent Detection**: Automatically identifies which algorithms are affected by changes + + +For the complete test suite with full coverage (including generation and detection tests), +see :doc:`comprehensive_test`. diff --git a/docs/test_system/comprehensive_test.rst b/docs/test_system/comprehensive_test.rst new file mode 100644 index 0000000..79d5569 --- /dev/null +++ b/docs/test_system/comprehensive_test.rst @@ -0,0 +1,72 @@ +Comprehensive Testing +==================== + +MarkDiffusion provides a complete testing suite that covers all functionality in the repository. +This comprehensive test module is located in the ``test/`` directory. + +Test Coverage Statistics +------------------------ + +- **Total Test Cases**: 454 unit tests +- **Code Coverage**: Approximately 90% +- **Coverage Scope**: Nearly all functional modules +- **Uncovered Code**: Primarily exception handling and edge case logic + +The test suite is designed to ensure the reliability and correctness of all components in the MarkDiffusion toolkit. + +Test Structure +-------------- + +The test suite is organized into several test files: + +Core Functionality Tests +~~~~~~~~~~~~~~~~~~~~~~~~ + +**test_watermark_algorithms.py** + Tests for initialization, generation, detection, inversion, and visualization of all watermark algorithms. + +**test_pipelines.py** + Tests for evaluation pipelines including detection and quality analysis. + +**test_dataset.py** + Tests for dataset classes (StableDiffusionPromptsDataset, MSCOCODataset, VBenchDataset). + +Utility and Module Tests +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**test_utils.py** + Tests for utility functions used throughout the toolkit. + +**test_exceptions.py** + Tests for custom exception classes. + +**test_image_editor.py** + Tests for image editing modules (rotation, cropping, noise, etc.). + +**test_video_editor.py** + Tests for video editing modules. + +Running the Tests +----------------- + +.. note:: + + For detailed instructions on running tests, including specific test commands, markers, + and advanced usage options, please refer to the ``test/README.md`` file in the repository. + + This document provides only a high-level overview of the testing system. + +Quick Start +~~~~~~~~~~~ + +Install test dependencies and run the complete test suite: + +.. code-block:: bash + + pip install -r test/requirements-test.txt + pytest test -v --cov=. --cov-report=html --cov-report=term-missing --html=report.html + + +For more detailed information about running tests, please refer to the ``test/README.md`` file +in the repository. + diff --git a/docs/tutorial.rst b/docs/tutorial.rst deleted file mode 100644 index 1eaf158..0000000 --- a/docs/tutorial.rst +++ /dev/null @@ -1,367 +0,0 @@ -Tutorial -======== - -This tutorial provides step-by-step examples for using MarkDiffusion's main features. - -Tutorial 1: Basic Image Watermarking -------------------------------------- - -Learn how to watermark images using different algorithms. - -Using Tree-Ring Watermark -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tree-Ring is a pattern-based watermarking method that embeds invisible patterns in the frequency domain. - -.. code-block:: python - - import torch - from watermark.auto_watermark import AutoWatermark - from utils.diffusion_config import DiffusionConfig - from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler - - # Setup - device = 'cuda' if torch.cuda.is_available() else 'cpu' - model_id = "stabilityai/stable-diffusion-2-1" - - scheduler = DPMSolverMultistepScheduler.from_pretrained(model_id, subfolder="scheduler") - pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler).to(device) - - diffusion_config = DiffusionConfig( - scheduler=scheduler, - pipe=pipe, - device=device, - image_size=(512, 512), - num_inference_steps=50, - guidance_scale=7.5, - gen_seed=42, - inversion_type="ddim" - ) - - # Load Tree-Ring watermark - tr_watermark = AutoWatermark.load( - 'TR', - algorithm_config='config/TR.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - prompt = "A majestic mountain landscape with snow peaks" - watermarked_img = tr_watermark.generate_watermarked_media(prompt) - watermarked_img.save("tr_watermarked.png") - - # Detect watermark - result = tr_watermark.detect_watermark_in_media(watermarked_img) - print(f"Tree-Ring Detection: {result}") - -Using Gaussian-Shading Watermark -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Gaussian-Shading is a key-based method that provides provable performance-lossless watermarking. - -.. code-block:: python - - # Load Gaussian-Shading watermark - gs_watermark = AutoWatermark.load( - 'GS', - algorithm_config='config/GS.json', - diffusion_config=diffusion_config - ) - - # Generate watermarked image - watermarked_img = gs_watermark.generate_watermarked_media(prompt) - watermarked_img.save("gs_watermarked.png") - - # Detect watermark - result = gs_watermark.detect_watermark_in_media(watermarked_img) - print(f"Gaussian-Shading Detection: {result}") - -Tutorial 2: Visualizing Watermark Mechanisms ---------------------------------------------- - -Learn how to visualize watermarking mechanisms using the same approach as in ``MarkDiffusion_demo.ipynb``. - -Tree-Ring Visualization -~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - # Get visualization data - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - - # Load visualizer - visualizer = AutoVisualizer.load('TR', data_for_visualization=data_for_visualization) - - # Create visualization with specific methods and channels - method_kwargs = [{}, {"channel": 0}, {}, {"channel": 0}, {}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image', - 'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'], - method_kwargs=method_kwargs, - save_path='TR_watermark_visualization.pdf' - ) - -Gaussian-Shading Visualization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - visualizer = AutoVisualizer.load('GS', data_for_visualization=data_for_visualization) - - method_kwargs = [{"channel": 0}, {"channel": 0}, {}, {"channel": 0}, {"channel": 0}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_watermark_bits', 'draw_orig_latents', 'draw_watermarked_image', - 'draw_inverted_latents', 'draw_reconstructed_watermark_bits'], - method_kwargs=method_kwargs, - save_path='GS_watermark_visualization.pdf' - ) - -ROBIN Visualization -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from visualize.auto_visualization import AutoVisualizer - - data_for_visualization = mywatermark.get_data_for_visualize(watermarked_image) - visualizer = AutoVisualizer.load('ROBIN', data_for_visualization=data_for_visualization) - - method_kwargs = [{}, {"channel": 3}, {}, {"channel": 3}, {}] - fig = visualizer.visualize( - rows=1, - cols=5, - methods=['draw_pattern_fft', 'draw_orig_latents_fft', 'draw_watermarked_image', - 'draw_inverted_latents_fft', 'draw_inverted_pattern_fft'], - method_kwargs=method_kwargs, - save_path='ROBIN_watermark_visualization.pdf' - ) - -Tutorial 3: Evaluating Watermark Quality ------------------------------------------ - -Assess the quality and robustness of watermarks. - -Image Quality Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import StableDiffusionPromptsDataset - from evaluation.pipelines.image_quality_analysis import ComparedImageQualityAnalysisPipeline - from evaluation.tools.image_quality_analyzer import PSNRAnalyzer, SSIMAnalyzer - from evaluation.pipelines.image_quality_analysis import QualityPipelineReturnType - - # Create dataset - dataset = StableDiffusionPromptsDataset(max_samples=50) - - # Setup quality analysis pipeline - pipeline = ComparedImageQualityAnalysisPipeline( - dataset=dataset, - watermarked_image_editor_list=[], - unwatermarked_image_editor_list=[], - analyzers=[PSNRAnalyzer(), SSIMAnalyzer()], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - # Evaluate watermark quality - results = pipeline.evaluate(gs_watermark) - print(f"PSNR: {results['PSNR']:.2f} dB") - print(f"SSIM: {results['SSIM']:.4f}") - -Robustness Evaluation -~~~~~~~~~~~~~~~~~~~~~~ - -Test watermark robustness against various attacks: - -.. code-block:: python - - from evaluation.tools.image_editor import ( - JPEGCompression, GaussianBlurring, GaussianNoise, Rotation - ) - from evaluation.pipelines.detection import WatermarkedMediaDetectionPipeline - from evaluation.pipelines.detection import DetectionPipelineReturnType - from evaluation.tools.success_rate_calculator import DynamicThresholdSuccessRateCalculator - - # Test against JPEG compression - dataset = StableDiffusionPromptsDataset(max_samples=100) - - # Create detection pipeline with JPEG attack - detection_pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[JPEGCompression(quality=75)], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - - # Evaluate detection after attack - detection_kwargs = { - "num_inference_steps": 50, - "guidance_scale": 1.0, - } - - scores = detection_pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs) - print(f"Detection scores after JPEG compression: {scores}") - -Multiple Attacks Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Test robustness against multiple attacks - attacks = { - 'JPEG-75': JPEGCompression(quality=75), - 'JPEG-50': JPEGCompression(quality=50), - 'Blur': GaussianBlurring(kernel_size=3), - 'Noise': GaussianNoise(std=0.05), - 'Rotation': Rotation(angle=15) - } - - results = {} - for attack_name, attack_editor in attacks.items(): - pipeline = WatermarkedMediaDetectionPipeline( - dataset=dataset, - media_editor_list=[attack_editor], - show_progress=True, - return_type=DetectionPipelineReturnType.SCORES - ) - scores = pipeline.evaluate(gs_watermark, detection_kwargs=detection_kwargs) - results[attack_name] = scores - - # Print results - print("\n=== Robustness Results ===") - for attack, scores in results.items(): - avg_score = sum(scores) / len(scores) if scores else 0 - print(f"{attack}: Average Score = {avg_score:.4f}") - -Tutorial 4: Video Watermarking -------------------------------- - -Learn how to watermark videos using VideoShield or VideoMark. - -Basic Video Watermarking -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from diffusers import DiffusionPipeline - - # Setup for video generation - video_model_id = "cerspense/zeroscope_v2_576w" - video_pipe = DiffusionPipeline.from_pretrained( - video_model_id, - torch_dtype=torch.float16 - ).to(device) - - video_diffusion_config = DiffusionConfig( - scheduler=video_pipe.scheduler, - pipe=video_pipe, - device=device, - image_size=(576, 320), - num_inference_steps=40, - guidance_scale=7.5, - gen_seed=42, - num_frames=16 - ) - - # Load VideoShield watermark - video_watermark = AutoWatermark.load( - 'VideoShield', - algorithm_config='config/VideoShield.json', - diffusion_config=video_diffusion_config - ) - - # Generate watermarked video - prompt = "A drone flying over a beach at sunset" - video_frames = video_watermark.generate_watermarked_media(prompt) - - # Save frames - import os - os.makedirs("output_video", exist_ok=True) - for i, frame in enumerate(video_frames): - frame.save(f"output_video/frame_{i:04d}.png") - - # Detect watermark in video - detection_result = video_watermark.detect_watermark_in_media(video_frames) - print(f"Video watermark detection: {detection_result}") - -Video Quality Evaluation -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from evaluation.dataset import VBenchDataset - from evaluation.pipelines.video_quality_analysis import DirectVideoQualityAnalysisPipeline - from evaluation.tools.video_quality_analyzer import ( - SubjectConsistencyAnalyzer, - MotionSmoothnessAnalyzer - ) - - # Create video dataset - video_dataset = VBenchDataset(max_samples=20, dimension='subject_consistency') - - # Setup video quality pipeline - video_pipeline = DirectVideoQualityAnalysisPipeline( - dataset=video_dataset, - watermarked_video_editor_list=[], - unwatermarked_video_editor_list=[], - watermarked_frame_editor_list=[], - unwatermarked_frame_editor_list=[], - analyzers=[SubjectConsistencyAnalyzer(device=device)], - show_progress=True, - return_type=QualityPipelineReturnType.MEAN_SCORES - ) - - # Evaluate video quality - results = video_pipeline.evaluate(video_watermark) - print(f"Subject consistency: {results}") - -Tutorial 5: Custom Configuration ---------------------------------- - -Customize watermarking parameters for your specific needs. - -Batch Processing -~~~~~~~~~~~~~~~~ - -.. code-block:: python - - prompts = [ - "A serene lake at dawn", - "A bustling city street at night", - "A colorful flower garden", - "A snowy mountain peak", - "A tropical beach paradise" - ] - - output_dir = "batch_output" - os.makedirs(output_dir, exist_ok=True) - - for i, prompt in enumerate(prompts): - print(f"Processing {i+1}/{len(prompts)}: {prompt}") - - # Generate watermarked image - img = gs_watermark.generate_watermarked_media(prompt) - img.save(f"{output_dir}/watermarked_{i:03d}.png") - - # Detect watermark - result = gs_watermark.detect_watermark_in_media(img) - print(f" Detection: {result}") - -Next Steps ----------- - -Explore more advanced topics: - -- :doc:`user_guide/algorithms` - Detailed algorithm documentation -- :doc:`api/watermark` - Complete API reference - diff --git a/img/A_Quick_Example.png b/img/A_Quick_Example.png new file mode 100644 index 0000000..a09b551 Binary files /dev/null and b/img/A_Quick_Example.png differ