- dimension_qc_checker.py: rules-based QC for dimension chains, overlaps, crowding - generate_dzi.py: Deep Zoom Image tile pyramid generator for OpenSeadragon - generate_web_viewer.py: OpenSeadragon viewer with SVG overlays and issue feedback buttons - rag_query.py: fix LightRAG remove_think_tags crash on None response from LLM - .gitignore: add *.pdf, *.db, backend/uploads/, backend/outputs/
115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Генератор Deep Zoom Image (DZI) тайлов из PNG для быстрого просмотра больших чертежей.
|
||
|
||
Использует PIL — не требует внешних зависимостей.
|
||
|
||
Использование:
|
||
python generate_dzi.py <png_file> [--tile-size 256] [--format png]
|
||
|
||
Результат:
|
||
<png_stem>.dzi — XML-дескриптор
|
||
<png_stem>_files/ — папка с тайлами level/col_row.png
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import math
|
||
from pathlib import Path
|
||
from PIL import Image
|
||
|
||
|
||
def generate_dzi(png_path: Path, tile_size: int = 256, fmt: str = "png"):
|
||
"""Генерирует DZI тайлы из PNG."""
|
||
png_path = Path(png_path)
|
||
if not png_path.exists():
|
||
print(f"[ERR] Файл не найден: {png_path}")
|
||
sys.exit(1)
|
||
|
||
base = png_path.stem
|
||
files_dir = png_path.parent / f"{base}_files"
|
||
files_dir.mkdir(exist_ok=True)
|
||
|
||
print(f"[INFO] Открываем {png_path}...")
|
||
img = Image.open(png_path)
|
||
orig_w, orig_h = img.size
|
||
print(f"[INFO] Размер: {orig_w}x{orig_h}")
|
||
|
||
# DZI uses power-of-2 levels, starting from 1x1 at level 0
|
||
max_dim = max(orig_w, orig_h)
|
||
max_level = math.ceil(math.log2(max_dim))
|
||
|
||
print(f"[INFO] Уровней: {max_level + 1} (0..{max_level})")
|
||
|
||
# Process from largest to smallest (build pyramid top-down)
|
||
current = img.convert("RGBA" if fmt == "png" else "RGB")
|
||
|
||
for level in range(max_level, -1, -1):
|
||
# Calculate size at this level
|
||
scale = 2 ** (max_level - level)
|
||
level_w = math.ceil(orig_w / scale)
|
||
level_h = math.ceil(orig_h / scale)
|
||
|
||
# Resize current image to level size
|
||
if current.size != (level_w, level_h):
|
||
current = current.resize((level_w, level_h), Image.LANCZOS)
|
||
|
||
# Save tiles
|
||
cols = math.ceil(level_w / tile_size)
|
||
rows = math.ceil(level_h / tile_size)
|
||
|
||
level_dir = files_dir / str(level)
|
||
level_dir.mkdir(exist_ok=True)
|
||
|
||
for row in range(rows):
|
||
for col in range(cols):
|
||
x = col * tile_size
|
||
y = row * tile_size
|
||
w = min(tile_size, level_w - x)
|
||
h = min(tile_size, level_h - y)
|
||
|
||
tile = current.crop((x, y, x + w, y + h))
|
||
tile_path = level_dir / f"{col}_{row}.{fmt}"
|
||
|
||
if fmt == "png":
|
||
tile.save(tile_path, "PNG", compress_level=3)
|
||
else:
|
||
tile.save(tile_path, "JPEG", quality=85)
|
||
|
||
print(f" Level {level}: {level_w}x{level_h}, {cols}x{rows} tiles")
|
||
|
||
# Write DZI descriptor
|
||
dzi_path = png_path.parent / f"{base}.dzi"
|
||
dzi_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||
<Image xmlns="http://schemas.microsoft.com/deepzoom/2008"
|
||
Format="{fmt}"
|
||
Overlap="0"
|
||
TileSize="{tile_size}">
|
||
<Size Width="{orig_w}"
|
||
Height="{orig_h}"/>
|
||
</Image>'''
|
||
dzi_path.write_text(dzi_xml, encoding="utf-8")
|
||
|
||
print(f"[OK] DZI создан: {dzi_path}")
|
||
print(f" Тайлы: {files_dir}")
|
||
print(f" Уровней: {max_level + 1}, TileSize: {tile_size}")
|
||
|
||
return dzi_path, files_dir
|
||
|
||
|
||
def main():
|
||
if len(sys.argv) < 2:
|
||
print("Usage: python generate_dzi.py <png_file> [tile_size] [format]")
|
||
sys.exit(1)
|
||
|
||
png = Path(sys.argv[1])
|
||
tile_size = int(sys.argv[2]) if len(sys.argv) > 2 else 256
|
||
fmt = sys.argv[3] if len(sys.argv) > 3 else "png"
|
||
|
||
generate_dzi(png, tile_size, fmt)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|