Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
__all__ = ["EmbedMode", "ExtractMode", "AnalyzeQuality", "GenerateMode"]
|
||||
|
||||
from .embed_mode import EmbedMode
|
||||
from .extract_mode import ExtractMode
|
||||
from .analyze_quality import AnalyzeQuality
|
||||
from .generate_mode import GenerateMode
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
import numpy as np
|
||||
|
||||
from entities.rgb_image512 import RgbImage512
|
||||
from entities.color_space import ColorSpace
|
||||
from entities.psnr_metric import PsnrMetric
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalyzeQuality:
|
||||
"""CLI-режим расчёта метрик качества."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
original_path: str,
|
||||
stego_path: str,
|
||||
space: str = "ycbcr",
|
||||
metrics: List[str] = None,
|
||||
) -> Dict[str, float]:
|
||||
"""Посчитать метрики между изображениями."""
|
||||
if metrics is None:
|
||||
metrics = ["psnr"]
|
||||
metrics = [m.lower() for m in metrics]
|
||||
if any(m != "psnr" for m in metrics):
|
||||
raise ValueError("Поддерживается только метрика PSNR.")
|
||||
|
||||
orig = RgbImage512.from_file(original_path).to_array()
|
||||
steg = RgbImage512.from_file(stego_path).to_array()
|
||||
|
||||
if space.lower() == "ycbcr":
|
||||
a = ColorSpace.rgb_to_ycbcr(orig)
|
||||
b = ColorSpace.rgb_to_ycbcr(steg)
|
||||
elif space.lower() == "rgb":
|
||||
a, b = orig, steg
|
||||
else:
|
||||
raise ValueError("space должен быть 'rgb' или 'ycbcr'.")
|
||||
|
||||
psnr = PsnrMetric().psnr(a, b)
|
||||
return {"psnr": float(psnr)}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
|
||||
from entities.rgb_image512 import RgbImage512
|
||||
from entities.label64 import Label64
|
||||
from entities.sequence8 import Sequence8
|
||||
from steg.embedder import Embedder
|
||||
from steg.extractor import Extractor
|
||||
from steg.component_pipeline import ComponentPipeline
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmbedMode:
|
||||
"""CLI-режим встраивания метки в изображение."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
input_path: str,
|
||||
label_path: str,
|
||||
output_path: str,
|
||||
channel: str,
|
||||
seq0: str,
|
||||
) -> Dict[str, str]:
|
||||
"""Выполнить встраивание и сохранить результат."""
|
||||
rgb = RgbImage512.from_file(input_path)
|
||||
label = Label64.from_image(label_path)
|
||||
seq = Sequence8.from_string(seq0)
|
||||
|
||||
pipeline = ComponentPipeline(channel=channel, embedder=Embedder(), extractor=Extractor())
|
||||
rgb_out = pipeline.embed_rgb(rgb, label, seq)
|
||||
rgb_out.save(output_path)
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"output": output_path,
|
||||
"channel": channel,
|
||||
"seq0": seq0,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
|
||||
from entities.rgb_image512 import RgbImage512
|
||||
from entities.label64 import Label64
|
||||
from entities.sequence8 import Sequence8
|
||||
from steg.embedder import Embedder
|
||||
from steg.extractor import Extractor
|
||||
from steg.component_pipeline import ComponentPipeline
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractMode:
|
||||
"""CLI-режим извлечения метки из изображения."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
input_path: str,
|
||||
output_path: str,
|
||||
channel: str,
|
||||
seq0: str,
|
||||
) -> Dict[str, str]:
|
||||
"""Извлечь метку и сохранить 64x64 PNG."""
|
||||
rgb = RgbImage512.from_file(input_path)
|
||||
seq = Sequence8.from_string(seq0)
|
||||
|
||||
pipeline = ComponentPipeline(channel=channel, embedder=Embedder(), extractor=Extractor())
|
||||
label: Label64 = pipeline.extract_from_rgb(rgb, seq)
|
||||
label.to_image(output_path)
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"output": output_path,
|
||||
"channel": channel,
|
||||
"seq0": seq0,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from generators.gradient_generator import GradientGenerator
|
||||
from generators.chessboard_generator import ChessboardGenerator
|
||||
|
||||
|
||||
@dataclass
|
||||
class GenerateMode:
|
||||
"""CLI-режим генерации изображений: gradient/chess."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
kind: str,
|
||||
size: int,
|
||||
channels: int,
|
||||
output_path: str,
|
||||
tile: Optional[int] = None,
|
||||
) -> Dict[str, str]:
|
||||
"""Сгенерировать изображение и сохранить в файл."""
|
||||
if kind == "gradient":
|
||||
arr = GradientGenerator.make(size=size, channels=channels)
|
||||
elif kind == "chess":
|
||||
t = 32 if tile is None else int(tile)
|
||||
arr = ChessboardGenerator.make(size=size, channels=channels, tile=t)
|
||||
else:
|
||||
raise ValueError("kind должен быть 'gradient' или 'chess'.")
|
||||
|
||||
mode = "L" if channels == 1 else "RGB"
|
||||
if arr.ndim == 2:
|
||||
img = Image.fromarray(arr, mode=mode)
|
||||
else:
|
||||
img = Image.fromarray(arr.astype(np.uint8), mode=mode)
|
||||
img.save(output_path)
|
||||
|
||||
return {"status": "ok", "kind": kind, "size": str(size), "channels": str(channels), "output": output_path}
|
||||
Reference in New Issue
Block a user