Skip to content

N98134015/1140918

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

1140918

論文原始數據加密、解密及分析程式碼

================================================

Converted Python Script: 1140918.py

================================================

This script is the converted version of the original Google Colab notebook "1140918.ipynb".

It has been adapted for local execution in VSCode on a Windows machine.

Base path: D:\highway\Scripts\1140918

Execution Instructions:

1. Save this file as "1140918.py" in "D:\highway\Scripts\1140918".

2. Ensure Python 3.12+ is installed (matches the original environment).

3. Install dependencies via pip (create a virtual environment recommended):

- Run in terminal: pip install numpy matplotlib scikit-image scipy

- Full requirements: numpy, matplotlib, scikit-image, scipy

4. Run the script in VSCode: Right-click > Run Python File in Terminal, or use python 1140918.py in terminal.

5. Plots will display using matplotlib's default backend; ensure a GUI backend is available (e.g., TkAgg).

Notes from the Expert (Info Engineering Professor & Cryptography Scholar):

- This code demonstrates image encryption using chaotic Logistic maps and Sum-Preserving Encryption (SPE) variants (3-pixel and 4-pixel).

- Cryptographic Analysis: The SPE methods aim to preserve pixel sums for diffusion, combined with permutation via Logistic chaos for confusion.

However, in practice, such schemes should be evaluated for resistance to chosen-plaintext attacks, differential attacks (NPCR/UACI), and key space size.

The Logistic map with R=3.9999999 and x0=0.42 provides a large key space (~10^15 precision), but floating-point sensitivity must be handled carefully.

- Optimizations: Added error handling in key generation and block processing to prevent division-by-zero and out-of-range values.

Ensured type safety (e.g., uint8 for images, int64 for intermediate sums to avoid overflow).

Removed redundant computations and added comments for clarity.

- Security Best Practices: This is for educational purposes; do not use in production without formal cryptanalysis (e.g., against linear/differential cryptanalysis).

- Local Adaptations: No Google Drive mounts or Colab-specific imports were present, so no changes needed. All data uses skimage's built-in datasets.

- Error Handling: Added checks for file paths (though none used), array sizes, and division errors.

- Efficiency: The code runs efficiently on local hardware; for large images, consider parallelizing block processing if needed.

import numpy as np import matplotlib.pyplot as plt from skimage import io, util, color, data from skimage.metrics import structural_similarity as ssim import time from scipy.fftpack import dct import math # For ceil from scipy.stats import pearsonr # For correlation from skimage.color import rgb2gray # Explicit import from skimage import img_as_float, img_as_ubyte # For type handling import os # For local path handling (though not used for IO here) import sys # For potential sys.exit on errors

Set working directory to base path for consistency (optional but recommended)

os.chdir(r"D:\highway\Scripts\1140918")

--- Image Loading (Using built-in skimage data; no file IO needed) ---

img_gray_orig = util.img_as_ubyte(data.camera())

img_color_orig = util.img_as_ubyte(data.chelsea()) # Commented in original

img_color_orig = util.img_as_ubyte(data.astronaut())

img_color_orig = util.img_as_ubyte(data.rocket()) # Commented in original

all_results_list = [] # Used to collect all metric results

--- Global Constant ---

e_const = 256 # Renamed to avoid conflict with exception variable 'e'

--- Helper Functions (Logistic, Keys, Permutation - From 3px Code) ---

def generate_logistic_sequence(x0, R, total): """Generate chaotic Logistic sequence""" seq = [] x = x0 for _ in range(100): # Initial iterations x = R * x * (1 - x) for _ in range(total): x = R * x * (1 - x) seq.append(x) return np.array(seq)

def logistic_map_key_sequence(x0, R, num_keys, key_min=1, key_max=255): """Generate integer key sequence using Logistic Map""" print(f"Generating {num_keys} keys with x0={x0}, R={R}") seq = [] x = x0 # Initial iterations for _ in range(100): x = R * x * (1 - x) # Generate sequence values raw_seq = [] for _ in range(num_keys): x = R * x * (1 - x) raw_seq.append(x)

min_val, max_val = min(raw_seq), max(raw_seq)
if max_val == min_val:  # Prevent division by zero
    scaled_seq = [key_min] * num_keys
else:
    scaled_seq = [
        key_min + int(((val - min_val) / (max_val - min_val)) * (key_max - key_min))
        for val in raw_seq
    ]
keys = np.maximum(key_min, np.array(scaled_seq)).tolist()
return keys

def get_sort_indices(chaos_values): """Get sort and reverse sort indices based on chaos sequence""" indices = np.argsort(chaos_values) reverse_indices = np.argsort(indices) return indices, reverse_indices

--- SPE (3-pixel) Encryption ---

def encrypt_block_3pixel(block, key): a, b, c = block.astype(np.int32) d = 255 s = a + b + c a_p, b_p, c_p = 0, 0, 0 # Initialize try: # Add error handling if 0 < s < d: divisor_ac1 = a + c + 1 if divisor_ac1 == 0: a_p, b_p, c_p = a, b, c else: a_p = (a + key) % divisor_ac1 divisor_b1 = s - a_p + 1 if divisor_b1 == 0: b_p = b c_p = s - a_p - b_p else: b_p = (b + key) % divisor_b1 c_p = s - a_p - b_p elif d <= s <= 2 * d: term_ac = a + c if term_ac < d: divisor_ap2a = term_ac + 1 if divisor_ap2a == 0: a_p = a else: a_p = (a + key) % divisor_ap2a else: divisor_ap2b = (2 * d - term_ac + 1) if divisor_ap2b == 0: a_p = a else: a_p = (term_ac - d) + ((d - a + key) % divisor_ap2b)

        term_sa = s - a_p
        if term_sa < d:
            divisor_bp2a = term_sa + 1
            if divisor_bp2a == 0:
                b_p = b
            else:
                b_p = (b + key) % divisor_bp2a
        else:
            divisor_bp2b = (2 * d - term_sa + 1)
            if divisor_bp2b == 0:
                b_p = b
            else:
                b_p = d - ((d - b + key) % divisor_bp2b)
        c_p = s - a_p - b_p
    elif s > 2 * d:
        term_ab = a + b
        divisor_ap3 = (2 * d - term_ab + 1)
        if divisor_ap3 == 0:
            a_p, b_p, c_p = a, b, c
        else:
            a_p = (term_ab - d) + ((d - a + key) % divisor_ap3)
            term_sa_3 = s - a_p
            divisor_bp3 = (2 * d - term_sa_3 + 1)
            if divisor_bp3 == 0:
                b_p = b
                c_p = s - a_p - b_p
            else:
                b_p = d - ((d - c + key) % divisor_bp3)
                c_p = s - a_p - b_p
    else:
        a_p, b_p, c_p = a, b, c
except Exception as e:
    print(f"Error during encryption for block {block} with key {key}: {e}")
    a_p, b_p, c_p = a, b, c
return np.array([max(0, min(255, a_p)), max(0, min(255, b_p)), max(0, min(255, c_p))], dtype=np.uint8)

--- SPE (3-pixel) Decryption ---

def decrypt_block_3pixel(block_enc, key): a_p, b_p, c_p = block_enc.astype(np.int32) d = 255 s = a_p + b_p + c_p a, b, c = 0, 0, 0 try: if 0 < s < d: b = -1 divisor_b = s - a_p + 1 if divisor_b != 0: for Kb in range(256): temp_b = (divisor_b) * Kb + b_p - key if 0 <= temp_b <= 255: b = temp_b break if b == -1: b = b_p # Fallback

        a = -1
        divisor_a = s - b + 1
        if divisor_a != 0:
            for Ka in range(256):
                temp_a = (divisor_a) * Ka + a_p - key
                if 0 <= temp_a <= 255:
                    a = temp_a
                    break
        if a == -1:
            a = a_p  # Fallback
        c = s - a - b
    elif d <= s <= 2 * d:
        b = -1
        term_sa = s - a_p
        divisor_b1 = term_sa + 1
        divisor_b2 = 2 * d - term_sa + 1
        for K in range(256):
            temp_b = -1
            if term_sa < d:
                if divisor_b1 != 0:
                    temp_b = divisor_b1 * K + b_p - key
                else:
                    temp_b = b_p
                    break  # Fallback
            else:
                if divisor_b2 != 0:
                    temp_b = key + b_p - divisor_b2 * K
                else:
                    temp_b = b_p
                    break  # Fallback
            if 0 <= temp_b <= 255:
                b = temp_b
                break
        if b == -1:
            b = b_p  # Fallback

        a = -1
        term_sb = s - b
        divisor_a1 = term_sb + 1
        divisor_a2 = 2 * d - term_sb + 1
        for K in range(256):
            temp_a = -1
            if term_sb < d:
                if divisor_a1 != 0:
                    temp_a = divisor_a1 * K + a_p - key
                else:
                    temp_a = a_p
                    break  # Fallback
            else:
                if divisor_a2 != 0:
                    temp_a = (key - a_p + term_sb) - divisor_a2 * K
                else:
                    temp_a = a_p
                    break  # Fallback
            if 0 <= temp_a <= 255:
                a = temp_a
                break
        if a == -1:
            a = a_p  # Fallback
        c = s - a - b
    elif s > 2 * d:
        c = -1
        term_sa_3 = s - a_p
        divisor_c3 = 2 * d - term_sa_3 + 1
        for K in range(256):
            temp_c = -1
            if divisor_c3 != 0:
                temp_c = key + b_p - divisor_c3 * K
            else:
                temp_c = c_p
                break  # Fallback
            if 0 <= temp_c <= 255:
                c = temp_c
                break
        if c == -1:
            c = c_p  # Fallback

        a = -1
        term_sc = s - c
        divisor_a3 = 2 * d - term_sc + 1
        for K in range(256):
            temp_a = -1
            if divisor_a3 != 0:
                temp_a = (key - a_p + term_sc) - divisor_a3 * K
            else:
                temp_a = a_p
                break  # Fallback
            if 0 <= temp_a <= 255:
                a = temp_a
                break
        if a == -1:
            a = a_p  # Fallback
        b = s - a - c
    else:
        a, b, c = a_p, b_p, c_p
except Exception as e:
    print(f"Error during decryption for block_enc {block_enc} with key {key}: {e}")
    a, b, c = a_p, b_p, c_p
return np.array([max(0, min(255, a)), max(0, min(255, b)), max(0, min(255, c))], dtype=np.uint8)

--- ★ MODIFIED Core SPE Encryption (Returns int64/int16) ★ ---

def encrypt_block_4pixel(block, key): """ Encrypts a uint8 block using 4-pixel SPE, returns raw int64 result to avoid clamping loss. """ if not isinstance(block, np.ndarray): block = np.array(block) if block.size != 4: print(f"Warning: encrypt_block_4pixel size {block.size} != 4. Returning as int64.") return block.astype(np.int64) # Return input cast to expected type on error

if not np.all((block >= 0) & (block <= 255)):
    print(f"Warning: encrypt_block_4pixel input {block} out of range. Clamping input.")
    block = np.clip(block, 0, 255)

a, b, c, d_val = block.astype(np.int64)
r = int(key)
e = 256
a_, b_, c_, d_ = a, b, c, d_val

try:
    s = a + b + c + d_val
    # --- Core SPE Encryption Logic ---
    if 0 <= s < e:
        s1 = a + b
        divisor1 = s1 + 1
        if divisor1 == 0:  # Added check
            raise ValueError("Divisor1 is zero")
        a_ = (a + r) % divisor1
        b_ = s1 - a_
        s2 = s - s1
        divisor2 = s2 + 1
        if divisor2 == 0:  # Added check
            raise ValueError("Divisor2 is zero")
        c_ = (c + r) % divisor2
        d_ = s - a_ - b_ - c_
    elif e <= s <= 2 * e:
        s1 = a + b
        if s1 < e:
            a_ = (a + r) % (s1 + 1)
        else:
            divisor1b = 2 * e - s1 + 1
            if divisor1b == 0:  # Added check
                raise ValueError("Divisor1b is zero")
            a_ = (s1 - e) + ((e - a + r) % divisor1b)
        b_ = s1 - a_
        s2 = s - s1
        if s2 < e:
            c_ = (c + r) % (s2 + 1)
        else:
            divisor2b = 2 * e - s2 + 1
            if divisor2b == 0:  # Added check
                raise ValueError("Divisor2b is zero")
            c_ = e - ((e - c + r) % divisor2b)
        d_ = s - a_ - b_ - c_
    elif 2 * e < s <= 3 * e:
        s1 = a + c  # Use a+c
        if s1 < e:
            a_ = (a + r) % (s1 + 1)
        else:
            divisor1b = 2 * e - s1 + 1
            if divisor1b == 0:  # Added check
                raise ValueError("Divisor1b is zero")
            a_ = (s1 - e) + ((e - a + r) % divisor1b)
        c_ = s1 - a_
        s2 = s - s1  # Use b+d
        if s2 < e:
            b_ = (b + r) % (s2 + 1)  # Encrypt b
        else:
            divisor2b = 2 * e - s2 + 1
            if divisor2b == 0:  # Added check
                raise ValueError("Divisor2b is zero")
            b_ = e - ((e - b + r) % divisor2b)  # Encrypt b
        d_ = s - a_ - b_ - c_
    else:  # s > 3 * e
        s1 = b + c  # Use b+c
        if s1 < e:
            b_ = (b + r) % (s1 + 1)  # Encrypt b
        else:
            divisor1b = 2 * e - s1 + 1
            if divisor1b == 0:  # Added check
                raise ValueError("Divisor1b is zero")
            b_ = (s1 - e) + ((e - b + r) % divisor1b)  # Encrypt b
        c_ = s1 - b_
        s2 = s - s1  # Use a+d
        if s2 < e:
            a_ = (a + r) % (s2 + 1)  # Encrypt a
        else:
            divisor2b = 2 * e - s2 + 1
            if divisor2b == 0:  # Added check
                raise ValueError("Divisor2b is zero")
            a_ = e - ((e - a + r) % divisor2b)  # Encrypt a
        d_ = s - a_ - b_ - c_
except Exception as err:
    print(f"Error during 4-pixel int encryption for block {block} key {r}: {err}")
    # Fallback to initial values (originals)
    pass

res = np.array([a_, b_, c_, d_], dtype=np.int64)
return res

--- ★ MODIFIED Core SPE Decryption (Accepts int64/int16, Returns uint8) ★ ---

def decrypt_block_4pixel(block_enc_int, key): """ Decrypts an int64/int16 block using 4-pixel SPE, returns uint8 result. Assumes block_enc_int contains the raw (unclamped) encrypted values. """ if not isinstance(block_enc_int, np.ndarray): block_enc_int = np.array(block_enc_int) if block_enc_int.size != 4: print(f"Warning: decrypt_block_4pixel size {block_enc_int.size} != 4. Returning as uint8.") # Attempt reasonable cast on error, maybe clip? return np.clip(block_enc_int, 0, 255).astype(np.uint8)

# Input is expected to be int64 or int16 - ensure it's int64 for calculations
a_, b_, c_, d_ = block_enc_int.astype(np.int64)
r = int(key)
e = 256
a, b, c, d = a_, b_, c_, d_  # Initialize output

try:
    s = a_ + b_ + c_ + d_  # Sum from potentially out-of-range values
    # --- Core SPE Decryption Logic ---
    if 0 <= s < e:
        s1 = a_ + b_
        divisor1 = s1 + 1
        a = (a_ - r % divisor1 + divisor1) % divisor1
        b = s1 - a
        s2 = s - s1
        divisor2 = s2 + 1
        c = (c_ - r % divisor2 + divisor2) % divisor2
        d = s - a - b - c
    elif e <= s <= 2 * e:
        s1 = a_ + b_
        if s1 < e:
            a = (a_ - r % (s1 + 1) + (s1 + 1)) % (s1 + 1)
        else:
            divisor1b = 2 * e - s1 + 1
            temp_mod_arg = a_ - s1 + e - r
            a = e - ((temp_mod_arg % divisor1b + divisor1b) % divisor1b)
        b = s1 - a
        s2 = s - s1
        if s2 < e:
            c = (c_ - r % (s2 + 1) + (s2 + 1)) % (s2 + 1)
        else:
            divisor2b = 2 * e - s2 + 1
            temp_mod_arg = e - c_ - r
            c = e - ((temp_mod_arg % divisor2b + divisor2b) % divisor2b)
        d = s - a - b - c
    elif 2 * e < s <= 3 * e:
        s1 = a_ + c_  # Use a_+c_
        if s1 < e:
            a = (a_ - r % (s1 + 1) + (s1 + 1)) % (s1 + 1)
        else:
            divisor1b = 2 * e - s1 + 1
            temp_mod_arg = a_ - s1 + e - r
            a = e - ((temp_mod_arg % divisor1b + divisor1b) % divisor1b)
        c = s1 - a
        s2 = s - s1  # Use b_+d_
        if s2 < e:
            b = (b_ - r % (s2 + 1) + (s2 + 1)) % (s2 + 1)  # Decrypt b
        else:
            divisor2b = 2 * e - s2 + 1
            temp_mod_arg = e - b_ - r
            b = e - ((temp_mod_arg % divisor2b + divisor2b) % divisor2b)  # Decrypt b
        d = s - a - b - c
    else:  # s > 3 * e
        s1 = b_ + c_  # Use b_+c_
        if s1 < e:
            b = (b_ - r % (s1 + 1) + (s1 + 1)) % (s1 + 1)  # Decrypt b
        else:
            divisor1b = 2 * e - s1 + 1
            temp_mod_arg = b_ - s1 + e - r
            b = e - ((temp_mod_arg % divisor1b + divisor1b) % divisor1b)  # Decrypt b
        c = s1 - b
        s2 = s - s1  # Use a_+d_
        if s2 < e:
            a = (a_ - r % (s2 + 1) + (s2 + 1)) % (s2 + 1)  # Decrypt a
        else:
            divisor2b = 2 * e - s2 + 1
            temp_mod_arg = e - a_ - r
            a = e - ((temp_mod_arg % divisor2b + divisor2b) % divisor2b)  # Decrypt a
        d = s - a - b - c
except Exception as err:
    print(f"Error during 4-pixel int decryption for block {block_enc_int} key {r}: {err}")
    # Fallback to initial values (encrypted input) cast to uint8 after clip
    a, b, c, d = np.clip(block_enc_int, 0, 255)  # Clip before return if error
    pass  # Continue to final clipping/casting

res = np.array([a, b, c, d], dtype=np.int64)
# Final clip and cast back to uint8, as the original should be in this range
decrypted_block = np.clip(res, 0, 255).astype(np.uint8)
return decrypted_block

--- Block Processing Wrappers (3-pixel, uint8) ---

def spe_encrypt_3px_block(flat_block_uint8, key): # Expects uint8, returns uint8 encrypted_block = np.zeros_like(flat_block_uint8) n = len(flat_block_uint8) for i in range(0, n, 3): p_group = flat_block_uint8[i : min(i + 3, n)] if len(p_group) == 3: # Call core 3px function encrypted_block[i : i + 3] = encrypt_block_3pixel(p_group, key) elif len(p_group) > 0: encrypted_block[i : i + len(p_group)] = p_group # Keep leftovers return encrypted_block

def spe_decrypt_3px_block(flat_encrypted_block_uint8, key): # Expects uint8, returns uint8 decrypted_block = np.zeros_like(flat_encrypted_block_uint8) n = len(flat_encrypted_block_uint8) for i in range(0, n, 3): c_group = flat_encrypted_block_uint8[i : min(i + 3, n)] if len(c_group) == 3: # Call core 3px function decrypted_block[i : i + 3] = decrypt_block_3pixel(c_group, key) elif len(c_group) > 0: decrypted_block[i : i + len(c_group)] = c_group # Keep leftovers return decrypted_block

--- Block Processing Wrappers (4-pixel, uint8 -> int16 / int16 -> uint8) ---

def spe_encrypt_4px_block(flat_block_uint8, key): # Expects uint8, returns int16 n = len(flat_block_uint8) encrypted_block_int16 = np.zeros(n, dtype=np.int16) # Output is int16 for i in range(0, n, 4): p_group_uint8 = flat_block_uint8[i: min(i + 4, n)] if len(p_group_uint8) == 4: # Call core function returning int64 encrypted_group_int64 = encrypt_block_4pixel(p_group_uint8, key) # Store as int16 (potential clipping/overflow if values > 32767 or < -32768) # Check range before casting if necessary, though SPE results likely fit encrypted_block_int16[i : i + 4] = encrypted_group_int64.astype(np.int16) elif len(p_group_uint8) > 0: # Copy leftovers, cast to int16 encrypted_block_int16[i : i + len(p_group_uint8)] = p_group_uint8.astype(np.int16) return encrypted_block_int16

def spe_decrypt_4px_block(flat_encrypted_block_int16, key): # Expects int16, returns uint8 n = len(flat_encrypted_block_int16) decrypted_block_uint8 = np.zeros(n, dtype=np.uint8) # Output is uint8 for i in range(0, n, 4): c_group_int16 = flat_encrypted_block_int16[i : min(i + 4, n)] if len(c_group_int16) == 4: # Call core function accepting int, returning uint8 decrypted_group_uint8 = decrypt_block_4pixel(c_group_int16, key) decrypted_block_uint8[i : i + 4] = decrypted_group_uint8 elif len(c_group_int16) > 0: # Copy leftovers, clip and cast back to uint8 safely decrypted_block_uint8[i : i + len(c_group_int16)] = np.clip(c_group_int16, 0, 255).astype(np.uint8) return decrypted_block_uint8

--- ★ Modified: Main Image Processing Functions (Generalized Name) ★ ---

def process_image_permutation_spe(image, block_size, mode, key_seq, spe_encrypt_func, spe_decrypt_func, R=3.92, x0=0.82, output_dtype=np.uint8): """Processes image using block permutation and specified SPE functions.""" # Input type handling if mode == 'encrypt': if image.dtype != np.uint8: # print(f"Warning: Encrypt expected uint8, got {image.dtype}. Casting.") image_uint8 = img_as_ubyte(image) # Use skimage's safe cast else: image_uint8 = image img_proc = image_uint8 result_dtype = output_dtype # Allow specifying output type (uint8 for 3px, int16 for 4px) elif mode == 'decrypt': # Decryption input type depends on which SPE was used (uint8 for 3px, int16 for 4px) # Let's assume the input image has the correct type for the decrypt_func img_proc = image result_dtype = np.uint8 # Decryption always results in uint8 else: raise ValueError("Invalid mode.")

# Dimension handling
if img_proc.ndim == 2:
    h, w = img_proc.shape
    c = 1
    img_proc_3d = img_proc[:, :, np.newaxis]
elif img_proc.ndim == 3:
    h, w, c = img_proc.shape
    img_proc_3d = img_proc
else:
    raise ValueError("Unsupported image dimension.")

# Initialization
result = np.zeros_like(img_proc_3d, dtype=result_dtype)
num_blocks_h = math.ceil(h / block_size)
num_blocks_w = math.ceil(w / block_size)
total_blocks_needed = num_blocks_h * num_blocks_w  # Adjusted for channels if needed, but keys are per block
if len(key_seq) < total_blocks_needed:
    raise ValueError(f"key_seq is too short. Need {total_blocks_needed}, got {len(key_seq)}.")

block_key_index = 0
for r in range(0, h, block_size):
    for col in range(0, w, block_size):
        if block_key_index >= len(key_seq):
            break
        current_key = key_seq[block_key_index]

        for ch in range(c):
            r_end = min(r + block_size, h)
            c_end = min(col + block_size, w)
            block_h = r_end - r
            block_w = c_end - col
            patch = img_proc_3d[r:r_end, col:c_end, ch]
            flat_patch_in = patch.flatten()  # Type depends on mode
            num_pixels_in_block = flat_patch_in.size
            if num_pixels_in_block == 0:
                continue

            chaos = generate_logistic_sequence(x0, R, num_pixels_in_block)
            sort_idx, reverse_idx = get_sort_indices(chaos)

            if mode == 'encrypt':
                # Input is uint8
                permuted = flat_patch_in[sort_idx]  # uint8
                # Call the provided SPE encryption wrapper
                processed_block = spe_encrypt_func(permuted, current_key)  # Returns uint8 or int16
            else:  # mode == 'decrypt'
                # Input is uint8 or int16
                # Call the provided SPE decryption wrapper
                decrypted_spe = spe_decrypt_func(flat_patch_in, current_key)  # Returns uint8
                processed_block = decrypted_spe[reverse_idx]  # uint8 output

            # Reshape and Store Result
            target_shape = (block_h, block_w)
            if processed_block.size == target_shape[0] * target_shape[1]:
                result[r:r_end, col:c_end, ch] = processed_block.reshape(target_shape)
            else:
                # Handle potential size mismatch (e.g., if SPE func didn't handle leftovers)
                # print(f"Warning: Size mismatch at ({r},{c}), ch {ch}. Resizing/padding.")
                padded = np.zeros(target_shape[0] * target_shape[1], dtype=result_dtype)
                common_size = min(processed_block.size, padded.size)
                padded[:common_size] = processed_block[:common_size]
                result[r:r_end, col:c_end, ch] = padded.reshape(target_shape)

        block_key_index += 1
    if block_key_index >= len(key_seq):
        break

if img_proc.ndim == 2:
    return result.squeeze(axis=2)
else:
    return result

--- ★ Modified: Check block sum preservation (Generalized) ★ ---

def check_block_sum_preservation(img_channel, block_size, core_enc_func, key_seq, pixel_group_size): """Check block sum differences for single-channel image using specified core encryption function (3px or 4px)""" if img_channel.dtype != np.uint8: img_channel = img_as_ubyte(img_channel) # Ensure uint8 input

h, w = img_channel.shape
diffs = []
k = 0
num_blocks_h = math.ceil(h / block_size)
num_blocks_w = math.ceil(w / block_size)
total_blocks_needed = num_blocks_h * num_blocks_w
if len(key_seq) < total_blocks_needed:
    raise ValueError(f"check_block_sum_preservation: key_seq too short. Need {total_blocks_needed}, got {len(key_seq)}.")

for r in range(0, h, block_size):
    for c in range(0, w, block_size):
        if k >= len(key_seq):
            break
        block_h = min(block_size, h - r)
        block_w = min(block_size, w - c)

        # Option: Only check full blocks for simplicity, or handle partial blocks
        # Here, we process the block regardless of full size, but SPE might only work on full groups
        patch = img_channel[r:r+block_h, c:c+block_w]
        if patch.size == 0:
            continue  # Skip empty patches

        block_uint8 = patch.flatten()  # Original pixels as uint8
        block_int64 = block_uint8.astype(np.int64)  # For accurate sum

        # Simulate block encryption using only the core SPE function
        enc_block_sim = np.zeros_like(block_int64)  # Store results as int64 for sum
        current_key = key_seq[k]
        n_block = len(block_uint8)

        for i in range(0, n_block, pixel_group_size):  # Use group size parameter
            p_uint8 = block_uint8[i : min(i + pixel_group_size, n_block)]
            if len(p_uint8) == pixel_group_size:
                # Call the core encryption function (3px or 4px)
                enc_result = core_enc_func(p_uint8, current_key)  # Returns uint8 or int64
                enc_block_sim[i : i + pixel_group_size] = enc_result.astype(np.int64)  # Store as int64
            elif len(p_uint8) > 0:
                # Handle leftovers: copy original pixels (as int64)
                enc_block_sim[i : i + len(p_uint8)] = p_uint8.astype(np.int64)

        diffs.append(np.sum(block_int64) - np.sum(enc_block_sim))
        k += 1
    if k >= len(key_seq):
        break
return np.array(diffs)

def pixel_correlation(image, sample_size=5000): """Calculate correlations for randomly sampled adjacent pixel pairs (horizontal, vertical, diagonal)""" if image.dtype != np.uint8: # Ensure uint8 for calculation consistency try: image = img_as_ubyte(image) except Exception as e: print(f"Warning: Error during uint8 conversion: {e}. Continuing with original dtype.") # Proceed, but results may vary

h, w = image.shape[:2]
is_color = image.ndim == 3

# Randomly sample starting points
np.random.seed(0)  # for reproducibility
max_possible_points = (h - 1) * (w - 1)
if max_possible_points <= 0:  # Handle tiny images
    print("Warning: Image too small to sample points.")
    return np.array([]), np.array([]), np.nan

actual_sample_size = min(sample_size, max_possible_points)
if actual_sample_size == 0:
    actual_sample_size = 1

valid_indices = np.arange(max_possible_points)
chosen_flat_indices = np.random.choice(valid_indices, size=actual_sample_size, replace=True)
row_indices, col_indices = np.unravel_index(chosen_flat_indices, (h - 1, w - 1))

x_h, y_h = [], []  # Horizontal
x_v, y_v = [], []  # Vertical
x_d, y_d = [], []  # Diagonal

if is_color:
    for r, c in zip(row_indices, col_indices):
        x_h.extend(image[r, c, :])  # Add all channels for the pixel
        y_h.extend(image[r, c + 1, :])
        x_v.extend(image[r, c, :])
        y_v.extend(image[r + 1, c, :])
        x_d.extend(image[r, c, :])
        y_d.extend(image[r + 1, c + 1, :])
else:  # Grayscale
    for r, c in zip(row_indices, col_indices):
        x_h.append(image[r, c])
        y_h.append(image[r, c + 1])
        x_v.append(image[r, c])
        y_v.append(image[r + 1, c])
        x_d.append(image[r, c])
        y_d.append(image[r + 1, c + 1])

# Combine pairs - choose one direction (e.g., Horizontal) or combine all
# Let's use horizontal for consistency with the plots shown previously
x_combined = np.array(x_h)
y_combined = np.array(y_h)

# Calculate correlation if enough points
if x_combined.size > 1 and y_combined.size > 1 and len(x_combined) == len(y_combined):
    try:
        corr, p_val = pearsonr(x_combined, y_combined)
        if np.isnan(corr):  # Handle cases where pearsonr returns NaN (e.g., constant input)
            corr = 0.0 if np.all(x_combined == x_combined[0]) and np.all(y_combined == y_combined[0]) else np.nan
    except ValueError:  # Catches constant input error in older scipy versions
        corr = 0.0 if np.all(x_combined == x_combined[0]) and np.all(y_combined == y_combined[0]) else np.nan
else:
    corr = np.nan  # Not enough points or mismatched lengths

return x_combined, y_combined, corr  # Return points and correlation value

def compute_entropy(image): # Ensure uint8 grayscale for histogram if image.ndim == 3: image_gray = img_as_ubyte(rgb2gray(image)) elif image.dtype != np.uint8: image_gray = img_as_ubyte(image) else: image_gray = image

hist, _ = np.histogram(image_gray.ravel(), bins=256, range=(0, 256))
hist = hist[hist > 0]  # Avoid log2(0)
if hist.size == 0:
    return 0  # Handle case of uniform image
prob = hist / hist.sum()
return -np.sum(prob * np.log2(prob))

def compute_dct(image): # Convert to grayscale float for DCT if image.ndim == 3: image = util.img_as_ubyte(color.rgb2gray(image)) elif image.dtype != np.float64 and image.dtype != np.float32: image = image.astype(np.float64) # Apply DCT return dct(dct(image.T, norm='ortho').T, norm='ortho')

=== Main Program Flow ===

--- Parameter Settings ---

block_size_main = 8 R_chaos = 3.9999999 # Logistic map R value x0_chaos = 0.42 delta_key_sens = 1e-15 # For key sensitivity test x0_mod = x0_chaos + delta_key_sens

--- Dimensions (for key generation) ---

h_gray, w_gray = img_gray_orig.shape num_blocks_gray = math.ceil(h_gray / block_size_main) * math.ceil(w_gray / block_size_main) print(f"Grayscale: Requesting {num_blocks_gray} keys.") key_sequence_gray = logistic_map_key_sequence(x0_chaos, R_chaos, num_blocks_gray)

h_color, w_color, _ = img_color_orig.shape num_blocks_color = math.ceil(h_color / block_size_main) * math.ceil(w_color / block_size_main) print(f"Color: Requesting {num_blocks_color} keys.") key_sequence_color = logistic_map_key_sequence(x0_chaos, R_chaos, num_blocks_color)

--- Modified keys for sensitivity test ---

key_seq_gray_mod = logistic_map_key_sequence(x0_mod, R_chaos, num_blocks_gray) key_seq_color_mod = logistic_map_key_sequence(x0_mod, R_chaos, num_blocks_color)

--- Encryption/Decryption (Grayscale) ---

print("\n=== Grayscale Processing (3px) ===") start_time_g_enc_3px = time.time() img_gray_enc_3px = process_image_permutation_spe( img_gray_orig, block_size_main, 'encrypt', key_sequence_gray, spe_encrypt_3px_block, None, R_chaos, x0_chaos, output_dtype=np.uint8) print(f"Encrypt Time (3px): {time.time() - start_time_g_enc_3px:.2f}s")

start_time_g_dec_3px = time.time() img_gray_dec_3px = process_image_permutation_spe( img_gray_enc_3px, block_size_main, 'decrypt', key_sequence_gray, None, spe_decrypt_3px_block, R_chaos, x0_chaos) print(f"Decrypt Time (3px): {time.time() - start_time_g_dec_3px:.2f}s")

print("\n=== Grayscale Processing (4px) ===") start_time_g_enc_4px = time.time() img_gray_enc_4px = process_image_permutation_spe( # Returns int16 img_gray_orig, block_size_main, 'encrypt', key_sequence_gray, spe_encrypt_4px_block, None, R_chaos, x0_chaos, output_dtype=np.int16) print(f"Encrypt Time (4px): {time.time() - start_time_g_enc_4px:.2f}s")

start_time_g_dec_4px = time.time() img_gray_dec_4px = process_image_permutation_spe( # Accepts int16, returns uint8 img_gray_enc_4px, block_size_main, 'decrypt', key_sequence_gray, None, spe_decrypt_4px_block, R_chaos, x0_chaos) print(f"Decrypt Time (4px): {time.time() - start_time_g_dec_4px:.2f}s")

--- Encryption/Decryption (Color) ---

print("\n=== Color Processing (3px) ===") start_time_c_enc_3px = time.time() img_color_enc_3px = process_image_permutation_spe( img_color_orig, block_size_main, 'encrypt', key_sequence_color, spe_encrypt_3px_block, None, R_chaos, x0_chaos, output_dtype=np.uint8) print(f"Encrypt Time (3px): {time.time() - start_time_c_enc_3px:.2f}s")

start_time_c_dec_3px = time.time() img_color_dec_3px = process_image_permutation_spe( img_color_enc_3px, block_size_main, 'decrypt', key_sequence_color, None, spe_decrypt_3px_block, R_chaos, x0_chaos) print(f"Decrypt Time (3px): {time.time() - start_time_c_dec_3px:.2f}s")

print("\n=== Color Processing (4px) ===") start_time_c_enc_4px = time.time() img_color_enc_4px = process_image_permutation_spe( # Returns int16 img_color_orig, block_size_main, 'encrypt', key_sequence_color, spe_encrypt_4px_block, None, R_chaos, x0_chaos, output_dtype=np.int16) print(f"Encrypt Time (4px): {time.time() - start_time_c_enc_4px:.2f}s")

start_time_c_dec_4px = time.time() img_color_dec_4px = process_image_permutation_spe( # Accepts int16, returns uint8 img_color_enc_4px, block_size_main, 'decrypt', key_sequence_color, None, spe_decrypt_4px_block, R_chaos, x0_chaos) print(f"Decrypt Time (4px): {time.time() - start_time_c_dec_4px:.2f}s")

--- Analysis ---

print("\n" + "="*20 + " Analysis " + "="*20)

Prepare display/analysis versions of 4px encrypted images (clip int16 to uint8)

img_gray_enc_4px_disp = np.clip(img_gray_enc_4px, 0, 255).astype(np.uint8) img_color_enc_4px_disp = np.clip(img_color_enc_4px, 0, 255).astype(np.uint8)

--- Grayscale Analysis ---

print("\n【Grayscale Image Analysis】") ssim_g_enc_3px = ssim(img_gray_orig, img_gray_enc_3px, data_range=255) ssim_g_dec_3px = ssim(img_gray_orig, img_gray_dec_3px, data_range=255) ssim_g_enc_4px = ssim(img_gray_orig, img_gray_enc_4px_disp, data_range=255) # Use display version ssim_g_dec_4px = ssim(img_gray_orig, img_gray_dec_4px, data_range=255)

block_diffs_g_3px = check_block_sum_preservation(img_gray_orig, block_size_main, encrypt_block_3pixel, key_sequence_gray, pixel_group_size=3) block_diffs_g_4px = check_block_sum_preservation(img_gray_orig, block_size_main, encrypt_block_4pixel, key_sequence_gray, pixel_group_size=4)

entropy_g_orig = compute_entropy(img_gray_orig) entropy_g_enc_3px = compute_entropy(img_gray_enc_3px) entropy_g_dec_3px = compute_entropy(img_gray_dec_3px) entropy_g_enc_4px = compute_entropy(img_gray_enc_4px_disp) # Use display version entropy_g_dec_4px = compute_entropy(img_gray_dec_4px)

dct_orig_gray = compute_dct(img_gray_orig) dct_enc_3_gray = compute_dct(img_gray_enc_3px) dct_enc_4_gray = compute_dct(img_gray_enc_4px_disp) # Use display version

print(f"SSIM (Orig vs Enc 3px): {ssim_g_enc_3px:.4f}") print(f"SSIM (Orig vs Enc 4px): {ssim_g_enc_4px:.4f}") print(f"SSIM (Orig vs Dec 3px): {ssim_g_dec_3px:.4f}") print(f"SSIM (Orig vs Dec 4px): {ssim_g_dec_4px:.4f}") print(f"Block Sum Max Diff (3px): {np.max(np.abs(block_diffs_g_3px)) if len(block_diffs_g_3px)>0 else 'N/A'}") print(f"Block Sum Max Diff (4px): {np.max(np.abs(block_diffs_g_4px)) if len(block_diffs_g_4px)>0 else 'N/A'}") print(f"Entropy Original: {entropy_g_orig:.4f}") print(f"Entropy Enc (3px): {entropy_g_enc_3px:.4f}") print(f"Entropy Enc (4px): {entropy_g_enc_4px:.4f}")

--- Grayscale Decryption Validation ---

print("\n[Decryption Validation - Grayscale]") try: assert np.allclose(img_gray_orig, img_gray_dec_3px, atol=1) print(" 3px Decryption: PASSED") except AssertionError: diff_pixels = np.sum(img_gray_orig != img_gray_dec_3px) total_diff = np.sum(np.abs(img_gray_orig.astype(float) - img_gray_dec_3px.astype(float))) print(f" 3px Decryption: FAILED ({diff_pixels}/{img_gray_orig.size} pixels differ, total diff: {total_diff:.2f}, SSIM:{ssim_g_dec_3px:.4f})") # Consider printing min/max of decrypted if fails badly: print(img_gray_dec_3px.min(), img_gray_dec_3px.max())

try: assert np.allclose(img_gray_orig, img_gray_dec_4px, atol=1) print(" 4px Decryption: PASSED") except AssertionError: diff_pixels = np.sum(img_gray_orig != img_gray_dec_4px) total_diff = np.sum(np.abs(img_gray_orig.astype(float) - img_gray_dec_4px.astype(float))) print(f" 4px Decryption: FAILED ({diff_pixels}/{img_gray_orig.size} pixels differ, total diff: {total_diff:.2f}, SSIM:{ssim_g_dec_4px:.4f})")

--- Grayscale Key Sensitivity ---

print("\n[Key Sensitivity - Grayscale]") img_gray_enc_mod_3px = process_image_permutation_spe( img_gray_orig, block_size_main, 'encrypt', key_seq_gray_mod, spe_encrypt_3px_block, None, R_chaos, x0_mod, output_dtype=np.uint8)

img_gray_enc_mod_4px = process_image_permutation_spe( # Returns int16 img_gray_orig, block_size_main, 'encrypt', key_seq_gray_mod, spe_encrypt_4px_block, None, R_chaos, x0_mod, output_dtype=np.int16) img_gray_enc_mod_4px_disp = np.clip(img_gray_enc_mod_4px, 0, 255).astype(np.uint8)

ssim_key_sens_g_3px = ssim(img_gray_enc_3px, img_gray_enc_mod_3px, data_range=255) ssim_key_sens_g_4px = ssim(img_gray_enc_4px_disp, img_gray_enc_mod_4px_disp, data_range=255) print(f" SSIM Enc(K) vs Enc(K') (3px): {ssim_key_sens_g_3px:.4f} (Should be near 0)") print(f" SSIM Enc(K) vs Enc(K') (4px): {ssim_key_sens_g_4px:.4f} (Should be near 0)")

print("\n[Pixel Correlation - Grayscale]")

Correctly receive 3 values from the function return

x_orig_g, y_orig_g, corr_orig_g = pixel_correlation(img_gray_orig) x_enc_g_3px, y_enc_g_3px, corr_enc_g_3px = pixel_correlation(img_gray_enc_3px)

Note: Ensure img_gray_enc_4px_disp is uint8 type passed in

x_enc_g_4px, y_enc_g_4px, corr_enc_g_4px = pixel_correlation(img_gray_enc_4px_disp)

Directly use the correlation coefficient returned by the function

print(f" Correlation Original (Gray): {corr_orig_g:.4f}") print(f" Correlation Enc (Gray, 3px): {corr_enc_g_3px:.4f} (Should be near 0)") print(f" Correlation Enc (Gray, 4px): {corr_enc_g_4px:.4f} (Should be near 0)")

print("\n[Pixel Correlation - Color (Flattened)]")

Correctly receive 3 values from the function return

x_orig_c, y_orig_c, corr_orig_c = pixel_correlation(img_color_orig) x_enc_c_3px, y_enc_c_3px, corr_enc_c_3px = pixel_correlation(img_color_enc_3px)

Note: Ensure img_color_enc_4px_disp is uint8 type passed in

x_enc_c_4px, y_enc_c_4px, corr_enc_c_4px = pixel_correlation(img_color_enc_4px_disp)

Directly use the correlation coefficient returned by the function

print(f" Correlation Original (Color): {corr_orig_c:.4f}") print(f" Correlation Enc (Color, 3px): {corr_enc_c_3px:.4f} (Should be near 0)") print(f" Correlation Enc (Color, 4px): {corr_enc_c_4px:.4f} (Should be near 0)")

print("-" * 60)

--- Color Analysis ---

print("\n【Color Image Analysis】")

SSIM per channel (Orig vs Enc)

ssim_c_r_3px = ssim(img_color_orig[:,:,0], img_color_enc_3px[:,:,0], data_range=255) ssim_c_g_3px = ssim(img_color_orig[:,:,1], img_color_enc_3px[:,:,1], data_range=255) ssim_c_b_3px = ssim(img_color_orig[:,:,2], img_color_enc_3px[:,:,2], data_range=255) ssim_c_r_4px = ssim(img_color_orig[:,:,0], img_color_enc_4px_disp[:,:,0], data_range=255) ssim_c_g_4px = ssim(img_color_orig[:,:,1], img_color_enc_4px_disp[:,:,1], data_range=255) ssim_c_b_4px = ssim(img_color_orig[:,:,2], img_color_enc_4px_disp[:,:,2], data_range=255)

print(f"SSIM Orig vs Enc (3px) - R: {ssim_c_r_3px:.4f}, G: {ssim_c_g_3px:.4f}, B: {ssim_c_b_3px:.4f}") print(f"SSIM Orig vs Enc (4px) - R: {ssim_c_r_4px:.4f}, G: {ssim_c_g_4px:.4f}, B: {ssim_c_b_4px:.4f}")

Block Sum Check per channel

print("\n[Block Sum Check - Color Channels]") max_diffs_3px = [] max_diffs_4px = [] for i, name in enumerate(["Red", "Green", "Blue"]): diffs_3 = check_block_sum_preservation(img_color_orig[:,:,i], block_size_main, encrypt_block_3pixel, key_sequence_color, 3) diffs_4 = check_block_sum_preservation(img_color_orig[:,:,i], block_size_main, encrypt_block_4pixel, key_sequence_color, 4) max_d3 = np.max(np.abs(diffs_3)) if len(diffs_3) > 0 else np.nan max_d4 = np.max(np.abs(diffs_4)) if len(diffs_4) > 0 else np.nan print(f" Max Block Sum Diff ({name}) - 3px: {max_d3:.1f}, 4px: {max_d4:.1f}") max_diffs_3px.append(max_d3) max_diffs_4px.append(max_d4)

Entropy per channel

print("\n[Entropy - Color Channels]") entropy_c_orig = [compute_entropy(img_color_orig[:,:,i]) for i in range(3)] entropy_c_enc_3px = [compute_entropy(img_color_enc_3px[:,:,i]) for i in range(3)] entropy_c_enc_4px = [compute_entropy(img_color_enc_4px_disp[:,:,i]) for i in range(3)] # Use display version print(f" Entropy Orig (R,G,B): {entropy_c_orig[0]:.4f}, {entropy_c_orig[1]:.4f}, {entropy_c_orig[2]:.4f}") print(f" Entropy Enc 3px(R,G,B): {entropy_c_enc_3px[0]:.4f}, {entropy_c_enc_3px[1]:.4f}, {entropy_c_enc_3px[2]:.4f}") print(f" Entropy Enc 4px(R,G,B): {entropy_c_enc_4px[0]:.4f}, {entropy_c_enc_4px[1]:.4f}, {entropy_c_enc_4px[2]:.4f}")

--- Color Decryption Validation ---

print("\n[Decryption Validation - Color]") try: assert np.allclose(img_color_orig, img_color_dec_3px, atol=1) print(" 3px Decryption: PASSED") except AssertionError: diff_pixels = np.sum(img_color_orig != img_color_dec_3px) total_diff = np.sum(np.abs(img_color_orig.astype(float) - img_color_dec_3px.astype(float))) ssim_dec_3px = ssim(img_color_orig, img_color_dec_3px, data_range=255, channel_axis=-1, win_size=3) # Use multichannel SSIM print(f" 3px Decryption: FAILED ({diff_pixels}/{img_color_orig.size} pixels differ, total diff: {total_diff:.2f}, SSIM:{ssim_dec_3px:.4f})")

try: assert np.allclose(img_color_orig, img_color_dec_4px, atol=1) print(" 4px Decryption: PASSED") except AssertionError: diff_pixels = np.sum(img_color_orig != img_color_dec_4px) total_diff = np.sum(np.abs(img_color_orig.astype(float) - img_color_dec_4px.astype(float))) ssim_dec_4px = ssim(img_color_orig, img_color_dec_4px, data_range=255, channel_axis=-1, win_size=3) # Use multichannel SSIM print(f" 4px Decryption: FAILED ({diff_pixels}/{img_color_orig.size} pixels differ, total diff: {total_diff:.2f}, SSIM:{ssim_dec_4px:.4f})")

--- Color Key Sensitivity ---

print("\n[Key Sensitivity - Color]") img_color_enc_mod_3px = process_image_permutation_spe( img_color_orig, block_size_main, 'encrypt', key_seq_color_mod, spe_encrypt_3px_block, None, R_chaos, x0_mod, output_dtype=np.uint8)

img_color_enc_mod_4px = process_image_permutation_spe( # Returns int16 img_color_orig, block_size_main, 'encrypt', key_seq_color_mod, spe_encrypt_4px_block, None, R_chaos, x0_mod, output_dtype=np.int16) img_color_enc_mod_4px_disp = np.clip(img_color_enc_mod_4px, 0, 255).astype(np.uint8)

print(" SSIM Enc(K) vs Enc(K') per channel (Should be near 0):") ssim_k_sens_c_3px = [ssim(img_color_enc_3px[:,:,i], img_color_enc_mod_3px[:,:,i], data_range=255) for i in range(3)] ssim_k_sens_c_4px = [ssim(img_color_enc_4px_disp[:,:,i], img_color_enc_mod_4px_disp[:,:,i], data_range=255) for i in range(3)] print(f" 3px (R,G,B): {ssim_k_sens_c_3px[0]:.4f}, {ssim_k_sens_c_3px[1]:.4f}, {ssim_k_sens_c_3px[2]:.4f}") print(f" 4px (R,G,B): {ssim_k_sens_c_4px[0]:.4f}, {ssim_k_sens_c_4px[1]:.4f}, {ssim_k_sens_c_4px[2]:.4f}")

--- Color Correlation ---

print("\n[Pixel Correlation - Color (Flattened)]") x_orig_c, y_orig_c, corr_orig_c = pixel_correlation(img_color_orig) x_enc_c_3px, y_enc_c_3px, corr_enc_c_3px = pixel_correlation(img_color_enc_3px) x_enc_c_4px, y_enc_c_4px, corr_enc_c_4px = pixel_correlation(img_color_enc_4px_disp) # Use display version

print(f" Correlation Original: {corr_orig_c:.4f}") print(f" Correlation Enc (3px): {corr_enc_c_3px:.4f} (Should be near 0)") print(f" Correlation Enc (4px): {corr_enc_c_4px:.4f} (Should be near 0)")

print("-" * 60)

---------------------------

Calculate Pearson correlation pixel_correlation

---------------------------

Calculate Pearson correlation - grayscale

x_original_gray, y_original_gray, corr_original_gray = pixel_correlation(img_gray_orig) x_enc_img_3_gray, y_enc_img_3_gray, corr_enc_img_3_gray = pixel_correlation(img_gray_enc_3px) x_enc_img_4_gray, y_enc_img_4_gray, corr_enc_img_4_gray = pixel_correlation(img_gray_enc_4px_disp)

print(f"灰階原始影像像素相關係數: {corr_original_gray:.4f}") print(f"3 pixel灰階加密影像像素相關係數: {corr_enc_img_3_gray:.4f}") print(f"4 pixel灰階加密影像像素相關係數: {corr_enc_img_4_gray:.4f}")

print("\n--- Generating Correlation Scatter Plots ---")

--- Calculate correlations for color channels separately ---

print("Calculating correlations for color channels...")

R channel (channel 0)

x_orig_c_r, y_orig_c_r, corr_orig_c_r = pixel_correlation(img_color_orig[:,:,0]) x_enc_c_3px_r, y_enc_c_3px_r, corr_enc_c_3px_r = pixel_correlation(img_color_enc_3px[:,:,0]) x_enc_c_4px_r, y_enc_c_4px_r, corr_enc_c_4px_r = pixel_correlation(img_color_enc_4px_disp[:,:,0]) print(f"彩色原始影像像素相關係數(紅色): {corr_orig_c_r:.4f}") print(f"3 pixel彩色加密影像像素相關係數(紅色): {corr_enc_c_3px_r:.4f}") print(f"4 pixel彩色加密影像像素相關係數(紅色): {corr_enc_c_4px_r:.4f}")

G channel (channel 1)

x_orig_c_g, y_orig_c_g, corr_orig_c_g = pixel_correlation(img_color_orig[:,:,1]) x_enc_c_3px_g, y_enc_c_3px_g, corr_enc_c_3px_g = pixel_correlation(img_color_enc_3px[:,:,1]) x_enc_c_4px_g, y_enc_c_4px_g, corr_enc_c_4px_g = pixel_correlation(img_color_enc_4px_disp[:,:,1]) print(f"彩色原始影像像素相關係數(綠色): {corr_orig_c_g:.4f}") print(f"3 pixel彩色加密影像像素相關係數(綠色): {corr_enc_c_3px_g:.4f}") print(f"4 pixel彩色加密影像像素相關係數(綠色): {corr_enc_c_4px_g:.4f}")

B channel (channel 2)

x_orig_c_b, y_orig_c_b, corr_orig_c_b = pixel_correlation(img_color_orig[:,:,2]) x_enc_c_3px_b, y_enc_c_3px_b, corr_enc_c_3px_b = pixel_correlation(img_color_enc_3px[:,:,2]) x_enc_c_4px_b, y_enc_c_4px_b, corr_enc_c_4px_b = pixel_correlation(img_color_enc_4px_disp[:,:,2]) print(f"彩色原始影像像素相關係數(藍色): {corr_orig_c_b:.4f}") print(f"3 pixel彩色加密影像像素相關係數(藍色): {corr_enc_c_3px_b:.4f}") print(f"4 pixel彩色加密影像像素相關係數(藍色): {corr_enc_c_4px_b:.4f}")

print("Correlation calculation for channels complete.")

=== Plotting ===

print("\nGenerating Plots...")

--- 1. Main Image Results (Original, Enc 3px, Enc 4px, Dec 4px) ---

fig1, axes1 = plt.subplots(2, 5, figsize=(22, 10)) # Increased columns for 4px fig1.suptitle(f'Image Encryption/Decryption Comparison (Block Size: {block_size_main}x{block_size_main})', fontsize=16)

Row 1: Grayscale

axes1[0, 0].imshow(img_gray_orig, cmap='gray') axes1[0, 0].set_title("Grayscale Original") axes1[0, 0].axis("off") axes1[0, 1].imshow(img_gray_enc_3px, cmap='gray') axes1[0, 1].set_title("Grayscale Enc (3px)") axes1[0, 1].axis("off") axes1[0, 2].imshow(img_gray_dec_3px, cmap='gray') axes1[0, 2].set_title("Grayscale Dec (3px)") axes1[0, 2].axis("off") # Show 3px decrypted axes1[0, 3].imshow(img_gray_enc_4px_disp, cmap='gray') axes1[0, 3].set_title("Grayscale Enc (4px)") axes1[0, 3].axis("off") # Show display version axes1[0, 4].imshow(img_gray_dec_4px, cmap='gray') axes1[0, 4].set_title("Grayscale Dec (4px)") axes1[0, 4].axis("off") # Show 4px decrypted

Row 2: Color

axes1[1, 0].imshow(img_color_orig) axes1[1, 0].set_title("Color Original") axes1[1, 0].axis("off") axes1[1, 1].imshow(img_color_enc_3px) axes1[1, 1].set_title("Color Enc (3px)") axes1[1, 1].axis("off") axes1[1, 2].imshow(img_color_dec_3px) axes1[1, 2].set_title("Color Dec (3px)") axes1[1, 2].axis("off") axes1[1, 3].imshow(img_color_enc_4px_disp) axes1[1, 3].set_title("Color Enc (4px)") axes1[1, 3].axis("off") # Show display version axes1[1, 4].imshow(img_color_dec_4px) axes1[1, 4].set_title("Color Dec (4px)") axes1[1, 4].axis("off") # Show 4px decrypted plt.tight_layout(rect=[0.2, 0.1, 1, 0.95]) plt.show()

--- 2. Visualize DCT Spectrums (Gray: Orig, Enc 3px, Enc 4px) ---

fig2, axes2 = plt.subplots(1, 3, figsize=(18, 6)) # Added column for 4px fig2.suptitle('Grayscale DCT Spectrums (Log Scale)', fontsize=14) axes2[0].imshow(np.log(np.abs(dct_orig_gray) + 1), cmap='magma') axes2[0].set_title("Original DCT") axes2[0].axis('off') axes2[1].imshow(np.log(np.abs(dct_enc_3_gray) + 1), cmap='magma') axes2[1].set_title("Encrypted (3px) DCT") axes2[1].axis('off') axes2[2].imshow(np.log(np.abs(dct_enc_4_gray) + 1), cmap='magma') axes2[2].set_title("Encrypted (4px) DCT") axes2[2].axis('off') # Add 4px DCT plt.tight_layout(rect=[0, 0.03, 1, 0.93]) plt.show()

--- 3. Display Key Sensitivity Difference Maps (Gray & Color Red Channel for 3px vs 4px) ---

fig3, axes3 = plt.subplots(2, 3, figsize=(18, 10)) # Keep 3 columns: Enc(K), Enc(K'), Diff fig3.suptitle(f'Key Sensitivity Test (delta={delta_key_sens:.1E}) - Comparing 3px vs 4px', fontsize=16)

Row 1: Grayscale Sensitivity 3px

axes3[0,0].imshow(img_gray_enc_3px, cmap='gray') axes3[0,0].set_title("Gray Enc 3px (Key K)") axes3[0,0].axis('off') axes3[0,1].imshow(img_gray_enc_mod_3px, cmap='gray') axes3[0,1].set_title("Gray Enc 3px (Key K')") axes3[0,1].axis('off') diff_g_3 = np.abs(img_gray_enc_3px.astype(np.int32) - img_gray_enc_mod_3px.astype(np.int32)) im_g3 = axes3[0,2].imshow(diff_g_3, cmap='viridis') axes3[0,2].set_title(f"Gray Diff 3px (SSIM:{ssim_key_sens_g_3px:.3f})") axes3[0,2].axis('off') fig3.colorbar(im_g3, ax=axes3[0,2], fraction=0.046, pad=0.04)

Row 2: Grayscale Sensitivity 4px

axes3[1,0].imshow(img_gray_enc_4px_disp, cmap='gray') axes3[1,0].set_title("Gray Enc 4px (Key K)") axes3[1,0].axis('off') axes3[1,1].imshow(img_gray_enc_mod_4px_disp, cmap='gray') axes3[1,1].set_title("Gray Enc 4px (Key K')") axes3[1,1].axis('off') diff_g_4 = np.abs(img_gray_enc_4px_disp.astype(np.int32) - img_gray_enc_mod_4px_disp.astype(np.int32)) # Diff on display uint8 im_g4 = axes3[1,2].imshow(diff_g_4, cmap='viridis') axes3[1,2].set_title(f"Gray Diff 4px (SSIM:{ssim_key_sens_g_4px:.3f})") axes3[1,2].axis('off') fig3.colorbar(im_g4, ax=axes3[1,2], fraction=0.046, pad=0.04) plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show()

--- 4. Display Correlation Scatter Plots (Gray: Orig, Enc 3px, Enc 4px) ---

fig4, axes4 = plt.subplots(1, 3, figsize=(21, 6)) # Added column for 4px fig4.suptitle('Grayscale Pixel Correlation Plots (Horizontal Adjacent)', fontsize=16)

Orig

axes4[0].scatter(x_orig_g, y_orig_g, s=1, color='black', alpha=0.1) axes4[0].set_title(f'Grayscale Orig (PCC={corr_orig_g:.4f})') axes4[0].set_xlabel('Pixel(x,y)') axes4[0].set_ylabel('Pixel(x+1,y)') axes4[0].axis('square') axes4[0].set_xlim(0, 255) axes4[0].set_ylim(0, 255)

Enc 3px

axes4[1].scatter(x_enc_g_3px, y_enc_g_3px, s=1, color='black', alpha=0.1) axes4[1].set_title(f'Grayscale Enc 3px (PCC={corr_enc_g_3px:.4f})') axes4[1].set_xlabel('Pixel(x,y)') axes4[1].set_ylabel('Pixel(x+1,y)') axes4[1].axis('square') axes4[1].set_xlim(0, 255) axes4[1].set_ylim(0, 255)

Enc 4px

axes4[2].scatter(x_enc_g_4px, y_enc_g_4px, s=1, color='black', alpha=0.1) axes4[2].set_title(f'Grayscale Enc 4px (PCC={corr_enc_g_4px:.4f})') axes4[2].set_xlabel('Pixel(x,y)') axes4[2].set_ylabel('Pixel(x+1,y)') axes4[2].axis('square') axes4[2].set_xlim(0, 255) axes4[2].set_ylim(0, 255) plt.tight_layout(rect=[0, 0.03, 1, 0.93]) plt.show()

--- 5. Display Histograms (Gray: Orig, Enc 3px, Enc 4px / Color: Orig, Enc 3px, Enc 4px) ---

fig5, axes5 = plt.subplots(2, 3, figsize=(18, 10)) # Added column for 4px fig5.suptitle('Histograms', fontsize=16)

Row 1: Grayscale

axes5[0, 0].hist(img_gray_orig.flatten(), bins=256, range=(0, 256), color='black', alpha=0.7, density=True) axes5[0, 0].set_title("Gray Original") axes5[0, 0].set_xlim([0, 256]) axes5[0, 1].hist(img_gray_enc_3px.flatten(), bins=256, range=(0, 256), color='gray', alpha=0.7, density=True) axes5[0, 1].set_title("Gray Enc (3px)") axes5[0, 1].set_xlim([0, 256]) axes5[0, 2].hist(img_gray_enc_4px_disp.flatten(), bins=256, range=(0, 256), color='darkgray', alpha=0.7, density=True) axes5[0, 2].set_title("Gray Enc (4px)") axes5[0, 2].set_xlim([0, 256]) # Add 4px

Row 2: Color (Combined Channels)

colors = ['red', 'green', 'blue'] titles = ["R", "G", "B"]

Orig Color

for i, (co, t) in enumerate(zip(colors, titles)): axes5[1, 0].hist(img_color_orig[:, :, i].flatten(), bins=256, range=(0, 256), color=co, alpha=0.6, density=True, label=t) axes5[1, 0].set_title("Color Original") axes5[1, 0].set_xlim([0, 256]) axes5[1, 0].legend()

Enc 3px Color

for i, (co, t) in enumerate(zip(colors, titles)): axes5[1, 1].hist(img_color_enc_3px[:, :, i].flatten(), bins=256, range=(0, 256), color=co, alpha=0.6, density=True, label=t) axes5[1, 1].set_title("Color Enc (3px)") axes5[1, 1].set_xlim([0, 256]) axes5[1, 1].legend()

Enc 4px Color

for i, (co, t) in enumerate(zip(colors, titles)): axes5[1, 2].hist(img_color_enc_4px_disp[:, :, i].flatten(), bins=256, range=(0, 256), color=co, alpha=0.6, density=True, label=t) # Use display version axes5[1, 2].set_title("Color Enc (4px)") axes5[1, 2].set_xlim([0, 256]) axes5[1, 2].legend() # Add 4px

plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show()

--- 6. Block Disruption Analysis (Comparing 3px vs 4px Encrypted) ---

print("\nGenerating Block Disruption Plots...") block_sizes_list = [8, 16, 32, 64] num_vis_blocks = len(block_sizes_list)

--- Grayscale Block Disruption Figure ---

Create 2 rows (Orig, 3px, 4px) x (1 + N blocks) columns of subplots

fig6, axes6 = plt.subplots(2, num_vis_blocks + 1, figsize=(5 * (num_vis_blocks + 1), 12)) fig6.suptitle(f'Grayscale Block Disruption Comparison (Block Sizes: {block_sizes_list})', fontsize=16)

Display original grayscale in the first column

axes6[0, 0].imshow(img_gray_orig, cmap='gray') axes6[0, 0].set_title("Original Gray") axes6[0, 0].axis("off") # Leave rows 1,2 in first column empty axes6[1, 0].axis("off")

--- Color Block Disruption Figure ---

Create 3 rows (Orig, 3px, 4px) x (1 + N blocks) columns of subplots

fig7, axes7 = plt.subplots(2, num_vis_blocks + 1, figsize=(5 * (num_vis_blocks + 1), 12)) fig7.suptitle(f'Color Block Disruption Comparison (Block Sizes: {block_sizes_list})', fontsize=16)

Display original color in the first column

axes7[0, 0].imshow(img_color_orig) axes7[0, 0].set_title("Original Color") axes7[0, 0].axis("off") axes7[1, 0].axis("off")

Iterate over different block sizes

for i, b in enumerate(block_sizes_list): # b will be 8, 16, 32, 64 in order print(f"\nProcessing Disruption Analysis for Block Size: {b}x{b}")

# --- Generate required number of keys for current block size 'b' ---
# Grayscale keys
num_blocks_needed_gray_disp = math.ceil(h_gray / b) * math.ceil(w_gray / b)
print(f"  Grayscale: Requesting {num_blocks_needed_gray_disp} keys for {b}x{b} blocks.")
key_seq_disp_gray = logistic_map_key_sequence(x0_chaos, R_chaos, num_blocks_needed_gray_disp)
# Color keys
num_blocks_needed_color_disp = math.ceil(h_color / b) * math.ceil(w_color / b)
print(f"  Color: Requesting {num_blocks_needed_color_disp} keys for {b}x{b} blocks.")
key_seq_disp_color = logistic_map_key_sequence(x0_chaos, R_chaos, num_blocks_needed_color_disp)

# --- Process grayscale image (3px and 4px encryption) ---
print("  Encrypting grayscale...")
# 3px Grayscale Encryption
enc_img_gray_3px_disp = process_image_permutation_spe(
    img_gray_orig, b, 'encrypt', key_seq_disp_gray,
    spe_encrypt_3px_block, None,  # Use 3px encryption block handler
    R_chaos, x0_chaos, output_dtype=np.uint8)  # Output uint8

# 4px Grayscale Encryption
enc_img_gray_4px_disp_int16 = process_image_permutation_spe(
    img_gray_orig, b, 'encrypt', key_seq_disp_gray,
    spe_encrypt_4px_block, None,  # Use 4px encryption block handler
    R_chaos, x0_chaos, output_dtype=np.int16)  # Output int16
# Convert 4px result to uint8 for display
enc_img_gray_4px_disp_uint8 = np.clip(enc_img_gray_4px_disp_int16, 0, 255).astype(np.uint8)

# --- Process color image (3px and 4px encryption) ---
print("  Encrypting color...")
# 3px Color Encryption
enc_img_color_3px_disp = process_image_permutation_spe(
    img_color_orig, b, 'encrypt', key_seq_disp_color,
    spe_encrypt_3px_block, None,  # Use 3px encryption block handler
    R_chaos, x0_chaos, output_dtype=np.uint8)  # Output uint8

# 4px Color Encryption
enc_img_color_4px_disp_int16 = process_image_permutation_spe(
    img_color_orig, b, 'encrypt', key_seq_disp_color,
    spe_encrypt_4px_block, None,  # Use 4px encryption block handler
    R_chaos, x0_chaos, output_dtype=np.int16)  # Output int16
# Convert 4px result to uint8 for display
enc_img_color_4px_disp_uint8 = np.clip(enc_img_color_4px_disp_int16, 0, 255).astype(np.uint8)

# --- Plot results to grayscale fig6 at corresponding positions ---
ax_3px_g = axes6[0, i + 1]  # Row 1 for 3px results
ax_4px_g = axes6[1, i + 1]  # Row 2 for 4px results

ax_3px_g.imshow(enc_img_gray_3px_disp, cmap='gray')
ax_3px_g.set_title(f"Gray Enc 3px\nBlock {b}x{b}")
ax_3px_g.axis("off")

ax_4px_g.imshow(enc_img_gray_4px_disp_uint8, cmap='gray')
ax_4px_g.set_title(f"Gray Enc 4px\nBlock {b}x{b}")
ax_4px_g.axis("off")

# --- Plot results to color fig7 at corresponding positions ---
ax_3px_c = axes7[0, i + 1]  # Row 1 for 3px results
ax_4px_c = axes7[1, i + 1]  # Row 2 for 4px results

ax_3px_c.imshow(enc_img_color_3px_disp)
ax_3px_c.set_title(f"Color Enc 3px\nBlock {b}x{b}")
ax_3px_c.axis("off")

ax_4px_c.imshow(enc_img_color_4px_disp_uint8)
ax_4px_c.set_title(f"Color Enc 4px\nBlock {b}x{b}")
ax_4px_c.axis("off")

# --- Adjust overall layout and display images ---
fig6.tight_layout(rect=[0, 0.03, 1, 0.95])
fig7.tight_layout(rect=[0, 0.03, 1, 0.95])

--- 7. Correlation Scatter Plots (Comparing 3px vs 4px Encrypted) ---

fig8, axes8 = plt.subplots(1, 3, figsize=(21, 6)) fig8.suptitle(f'- - - Grayscale correlation - - - ', fontsize=16)

Plot correlation scatter plot (original)

axes8[0].scatter(x_original_gray, y_original_gray, s=1, color='black') axes8[0].set_aspect('equal', adjustable='box') axes8[0].set_title(f'original Grayscale correlation (PCC={corr_original_gray:.4f})') axes8[0].set_xlabel('(x, y)') axes8[0].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (3 pixel grayscale)

axes8[1].scatter(x_enc_img_3_gray, y_enc_img_3_gray, s=1, color='black') axes8[1].set_aspect('equal', adjustable='box') axes8[1].set_title(f'3 pixel Grayscale correlation (PCC={corr_enc_img_3_gray:.4f})') axes8[1].set_xlabel('(x, y)') axes8[1].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (4 pixel grayscale)

axes8[2].scatter(x_enc_img_4_gray, y_enc_img_4_gray, s=1, color='black') axes8[2].set_aspect('equal', adjustable='box') axes8[2].set_title(f'4 pixel Grayscale correlation (PCC={corr_enc_img_4_gray:.4f})') axes8[2].set_xlabel('(x, y)') axes8[2].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (color - red channel)

fig9, axes9 = plt.subplots(1, 3, figsize=(21, 6)) fig9.suptitle(f'- - - Red color correlation - - - ', fontsize=16) axes9[0].scatter(x_orig_c_r, y_orig_c_r, s=1, color='red') axes9[0].set_aspect('equal', adjustable='box') axes9[0].set_title(f'original image correlation (PCC={corr_orig_c_r:.4f})') axes9[0].set_xlabel('(x, y)') axes9[0].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (3 pixel color - red channel)

axes9[1].scatter(x_enc_c_3px_r, y_enc_c_3px_r, s=1, color='red') axes9[1].set_aspect('equal', adjustable='box') axes9[1].set_title(f'3 pixel red correlation (PCC={corr_enc_c_3px_r:.4f})') axes9[1].set_xlabel('(x, y)') axes9[1].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (4 pixel color - red channel)

axes9[2].scatter(x_enc_c_4px_r, y_enc_c_4px_r, s=1, color='red') axes9[2].set_aspect('equal', adjustable='box') axes9[2].set_title(f'4 pixel red correlation (PCC={corr_enc_c_4px_r:.4f})') axes9[2].set_xlabel('(x, y)') axes9[2].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (color - green channel)

fig10, axes10 = plt.subplots(1, 3, figsize=(21, 6)) fig10.suptitle(f'- - - Green color correlation - - - ', fontsize=16) axes10[0].scatter(x_orig_c_g, y_orig_c_g, s=1, color='green') axes10[0].set_aspect('equal', adjustable='box') axes10[0].set_title(f'original image correlation (PCC={corr_orig_c_g:.4f})') axes10[0].set_xlabel('(x, y)') axes10[0].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (3 pixel color - green channel)

axes10[1].scatter(x_enc_c_3px_g, y_enc_c_3px_g, s=1, color='green') axes10[1].set_aspect('equal', adjustable='box') axes10[1].set_title(f'3 pixel green correlation (PCC={corr_enc_c_3px_g:.4f})') axes10[1].set_xlabel('(x, y)') axes10[1].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (4 pixel color - green channel)

axes10[2].scatter(x_enc_c_4px_g, y_enc_c_4px_g, s=1, color='green') axes10[2].set_aspect('equal', adjustable='box') axes10[2].set_title(f'4 pixel green correlation (PCC={corr_enc_c_4px_g:.4f})') axes10[2].set_xlabel('(x, y)') axes10[2].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (color - blue channel)

fig11, axes11 = plt.subplots(1, 3, figsize=(21, 6)) fig11.suptitle(f'- - - Blue color correlation - - - ', fontsize=16) axes11[0].scatter(x_orig_c_b, y_orig_c_b, s=1, color='blue') axes11[0].set_aspect('equal', adjustable='box') axes11[0].set_title(f'original image correlation (PCC={corr_orig_c_b:.4f})') axes11[0].set_xlabel('(x, y)') axes11[0].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (3 pixel color - blue channel)

axes11[1].scatter(x_enc_c_3px_b, y_enc_c_3px_b, s=1, color='blue') axes11[1].set_aspect('equal', adjustable='box') axes11[1].set_title(f'3 pixel blue correlation (PCC={corr_enc_c_3px_b:.4f})') axes11[1].set_xlabel('(x, y)') axes11[1].set_ylabel('(x+1, y+1)')

Plot correlation scatter plot (4 pixel color - blue channel)

axes11[2].scatter(x_enc_c_4px_b, y_enc_c_4px_b, s=1, color='blue') axes11[2].set_aspect('equal', adjustable='box') axes11[2].set_title(f'4 pixel blue correlation (PCC={corr_enc_c_4px_b:.4f})') axes11[2].set_xlabel('(x, y)') axes11[2].set_ylabel('(x+1, y+1)')

plt.tight_layout() plt.show()

print("\n--- Block Disruption Analysis Complete ---")

print("\n--- Analysis Complete ---")

About

論文原始數據加密、解密及分析程式碼

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages