Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions pyqpanda-algorithm/example/QJEPG/demo_QJPEG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Demo: Quantum JPEG (QJPEG) image downsampling.

The given image is down-sampled at several compression levels and compared
with classical box-averaging.

Run:
python demo_QJPEG.py (default -P equals -S, no tiling)
python demo_QJPEG.py -P 256 -S 512 (large canvas with small patch, force tiling)
"""

from pathlib import Path
from argparse import ArgumentParser
from time import perf_counter

import numpy as np
import matplotlib.pyplot as plt

import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from utils import load_img, psnr, box_downsample, QJPEG, IMG_DIR, OUT_DIR


def save_filename(args):
I: Path = args.input
S = args.img_size
P = args.patch_size
sfx = ''
sfx += f'_{P}' if S == P else f'_P{P}_S{S}'
if args.no_H_layer is False:
sfx += '_noH'
return f'{I.stem}-QJPEG{sfx}.png'


def run(args):
img = load_img(args.input, args.img_size)
print(f'Input img_shape: {img.shape}')

qjpeg = QJPEG(patch_size=args.patch_size, wrap_H_layer=args.no_H_layer)
print(f'QJPEG patch_size: {qjpeg.patch_size}')

results: dict[int, np.ndarray] = {}
for n_discard in (1, 2, 3):
ts_start = perf_counter()
out = qjpeg(img, n_discard=n_discard)
ref = box_downsample(img, 2 ** n_discard)
ts_end = perf_counter()
results[n_discard] = out
print(f'n_discard={n_discard}: {img.shape} -> {out.shape} | PSNR={psnr(out, ref):.2f} dB ({ts_end - ts_start:.3f}s)')

N = 1 + len(results)
fig, axes = plt.subplots(1, N, figsize=(3 * N, 3))
axes[0].imshow(img, vmin=0, vmax=1)
axes[0].set_title(f'original {img.shape[0]}x{img.shape[1]}')
axes[0].axis('off')
for ax, (nd, out) in zip(axes[1:], results.items()):
ax.imshow(np.clip(out, 0, 1), vmin=0, vmax=1)
ax.set_title(f'QJPEG {out.shape[0]}x{out.shape[1]}\nn_discard={nd}')
ax.axis('off')
fig.tight_layout()
OUT_DIR.mkdir(exist_ok=True)
fp = OUT_DIR / save_filename(args)
fig.savefig(fp, dpi=300, bbox_inches='tight')
print(f'Saved comparison figure to {fp}')


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('-I', '--input', default=IMG_DIR / 'Border Collie.webp', type=Path)
parser.add_argument('-S', '--img_size', default=256, type=int)
parser.add_argument('-P', '--patch_size', default=None, type=int)
parser.add_argument('-nH', '--no_H_layer', action='store_false', default='disable H layer, only experimental use')
args = parser.parse_args()

args.patch_size = args.patch_size or args.img_size

if args.img_size > 256:
print(f'>> [WARN] the first image size will exceed {args.img_size * 2**2}, might be VERY SLOW!! :(')

run(args)
88 changes: 88 additions & 0 deletions pyqpanda-algorithm/example/QJEPG/demo_cyclic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Demo: QJPEG & unQJPEG forward-inverse cyclic.

Down-samples the given image with QJPEG, then up-samples it back with the unQJPEG
quantum circuit (spectral zero-padding / sinc interpolation) and reports how well
the original resolution is recovered. Also demonstrates plain up-sampling of a
low-resolution image.

Run:
python demo_cyclic.py
python demo_cyclic.py -S 512
"""

from pathlib import Path
from argparse import ArgumentParser
from time import perf_counter

import numpy as np
import matplotlib.pyplot as plt

import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from utils import load_img, psnr, bilinear_upsample, QJPEG, unQJPEG, IMG_DIR, OUT_DIR


def save_filename(args):
I: Path = args.input
S = args.size
sfx = ''
sfx += f'_{S}'
return f'{I.stem}-cyclic{sfx}.png'


def run(args):
size = args.size
half = size // 2
orig = load_img(args.input, size)
print(f'Input img_shape: {orig.shape}')

# 1) downsample to half resolution with QJPEG
ts_start = perf_counter()
lowres = QJPEG(patch_size=size)(orig, n_discard=1)
ts_end = perf_counter()
print(f'Downsampled with QJPEG: {orig.shape} -> {lowres.shape} ({ts_end - ts_start:.3f}s)')

# 2) upsample back to full resolution with unQJPEG
ts_start = perf_counter()
recon = unQJPEG()(lowres, n_append=1)
ts_end = perf_counter()
psnr_recon = psnr(recon, orig)
print(f'Upsampled with unQJPEG: {lowres.shape} -> {recon.shape} | PSNR={psnr_recon:.2f} dB ({ts_end - ts_start:.3f}s)')

# 3) bilinear upscaling of the same lowres image
ts_start = perf_counter()
bilinear = bilinear_upsample(lowres, 2)
ts_end = perf_counter()
psnr_bilinear = psnr(bilinear, orig)
print(f'Upsampled with BILINEAR: {lowres.shape} -> {bilinear.shape} | PSNR={psnr_bilinear:.2f} dB ({ts_end - ts_start:.3f}s)')

imgs = [orig, np.clip(lowres, 0, 1), np.clip(recon, 0.0, 1.0), bilinear]
titles = [
f'original {size}x{size}',
f'QJPEG {half}x{half}',
f'unQJPEG {size}x{size}\nPSNR={psnr_recon:.2f}',
f'bilinear {size}x{size}\nPSNR={psnr_bilinear:.2f}',
]

fig, axes = plt.subplots(1, 4, figsize=(12, 3))
for ax, im, title in zip(axes, imgs, titles):
ax.imshow(im, vmin=0, vmax=1)
ax.set_title(title)
ax.axis('off')
fig.tight_layout()
OUT_DIR.mkdir(exist_ok=True)
fp = OUT_DIR / save_filename(args)
fig.savefig(fp, dpi=300, bbox_inches='tight')
print(f'Saved comparison figure to {fp}')


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('-I', '--input', default=IMG_DIR / 'Border Collie.webp', type=Path)
parser.add_argument('-S', '--size', default=256, type=int)
args = parser.parse_args()

run(args)
79 changes: 79 additions & 0 deletions pyqpanda-algorithm/example/QJEPG/demo_unQJPEG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Demo: inverse Quantum JPEG (unQJPEG) image upsampling / super-resolution.

The given image is up-sampled at several upscale levels and compared
with classical bilinear interpolation.

Run:
python demo_unQJPEG.py (default -P equals -S, no tiling)
python demo_unQJPEG.py -P 32 -S 64 (large canvas with small patch, force tiling)
"""

from pathlib import Path
from argparse import ArgumentParser
from time import perf_counter

import numpy as np
import matplotlib.pyplot as plt

import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from utils import load_img, psnr, bilinear_upsample, unQJPEG, IMG_DIR, OUT_DIR



def save_filename(args):
I: Path = args.input
S = args.img_size
P = args.patch_size
sfx = ''
sfx += f'_{P}' if S == P else f'_P{P}_S{S}'
return f'{I.stem}-unQJPEG{sfx}.png'


def run(args):
img = load_img(args.input, args.img_size)
print(f'Input img_shape: {img.shape}')

unqjpeg = unQJPEG(separated_QFT=args.no_separated_QFT)
print(f'patch_size: {args.patch_size}')

results: dict[int, np.ndarray] = {}
for n_append in (1, 2, 3):
ts_start = perf_counter()
out = unqjpeg(img, n_append=n_append, patch_size=args.patch_size)
ref = bilinear_upsample(img, 2 ** n_append)
ts_end = perf_counter()
results[n_append] = out
print(f'n_append={n_append}: {img.shape} -> {out.shape} | PSNR={psnr(out, ref):.2f} dB ({ts_end - ts_start:.3f}s)')

N = 1 + len(results)
fig, axes = plt.subplots(1, N, figsize=(3 * N, 3))
axes[0].imshow(img, vmin=0, vmax=1)
axes[0].set_title(f'original {img.shape[0]}x{img.shape[1]}')
axes[0].axis('off')
for ax, (nd, out) in zip(axes[1:], results.items()):
ax.imshow(np.clip(out, 0, 1), vmin=0, vmax=1)
ax.set_title(f'unQJPEG {out.shape[0]}x{out.shape[1]}\nn_append={nd}')
ax.axis('off')
fig.tight_layout()
OUT_DIR.mkdir(exist_ok=True)
fp = OUT_DIR / save_filename(args)
fig.savefig(fp, dpi=300, bbox_inches='tight')
print(f'Saved comparison figure to {fp}')


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('-I', '--input', default=IMG_DIR / 'Border Collie.webp', type=Path)
parser.add_argument('-S', '--img_size', default=64, type=int)
parser.add_argument('-P', '--patch_size', default=None, type=int)
parser.add_argument('-nQFT', '--no_separated_QFT', action='store_false', help='disable separated QFT, only experimental use')
args = parser.parse_args()

if args.img_size > 64:
print(f'>> [WARN] the last image size will exceed {args.img_size * 2**3}, could be VERY SLOW!! :(')

run(args)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions pyqpanda-algorithm/example/QJEPG/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import sys
from pathlib import Path

import numpy as np
from numpy import ndarray
from PIL import Image
from PIL.Image import Resampling
import matplotlib ; matplotlib.use('Agg')

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from pyqpanda_alg.QJEPG import QJPEG, unQJPEG # keep

BASE_PATH = Path(__file__).resolve().parent
IMG_DIR = BASE_PATH / 'img'
OUT_DIR = BASE_PATH / 'output'


def load_img(path: Path, size: int) -> ndarray:
img = Image.open(path).resize((size, size), resample=Resampling.LANCZOS)
return np.asarray(img, dtype=np.float64) / 255.0 # [H, W, C] / [H, W]


def psnr(a: ndarray, b: ndarray) -> float:
mse = np.mean((a - b) ** 2)
return float('inf') if mse == 0 else 10 * np.log10(1.0 / mse)


def nearest_neighbor_upsample(img: ndarray, factor: int = 2) -> ndarray:
return np.repeat(np.repeat(img, factor, axis=0), factor, axis=1)


def bilinear_upsample(img: ndarray, factor: int = 2) -> ndarray:
if len(img.shape) == 3:
H, W, C = img.shape
else:
H, W = img.shape
im = np.asarray(np.clip(img, 0, 1) * 255, dtype=np.uint8)
img = Image.fromarray(im).resize((W * factor, H * factor), resample=Resampling.BILINEAR)
hires = np.asarray(img, dtype=np.float32) / 255.0
return hires


def box_downsample(img: ndarray, factor: int = 2) -> ndarray:
if len(img.shape) == 3:
H, W, C = img.shape
p = H // factor
lowres = img.reshape(p, factor, p, factor, C).mean(axis=(1, 3))
else:
H, W = img.shape
p = H // factor
lowres = img.reshape(p, factor, p, factor).mean(axis=(1, 3))
return lowres
Loading