論文原始數據加密、解密及分析程式碼
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).
- 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.
- 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.
- 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
os.chdir(r"D:\highway\Scripts\1140918")
img_gray_orig = util.img_as_ubyte(data.camera())
img_color_orig = util.img_as_ubyte(data.astronaut())
all_results_list = [] # Used to collect all metric results
e_const = 256 # Renamed to avoid conflict with exception variable 'e'
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
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)
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)
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
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
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
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
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
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')
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
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)
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)
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")
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")
print("\n" + "="*20 + " Analysis " + "="*20)
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)
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}")
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})")
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]")
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)
x_enc_g_4px, y_enc_g_4px, corr_enc_g_4px = pixel_correlation(img_gray_enc_4px_disp)
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)]")
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)
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)
print("\n【Color Image Analysis】")
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}")
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)
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}")
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})")
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}")
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)
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 ---")
print("Calculating correlations for color channels...")
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}")
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}")
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.")
print("\nGenerating Plots...")
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)
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
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()
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()
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)
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)
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()
fig4, axes4 = plt.subplots(1, 3, figsize=(21, 6)) # Added column for 4px fig4.suptitle('Grayscale Pixel Correlation Plots (Horizontal Adjacent)', fontsize=16)
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)
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)
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()
fig5, axes5 = plt.subplots(2, 3, figsize=(18, 10)) # Added column for 4px fig5.suptitle('Histograms', fontsize=16)
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
colors = ['red', 'green', 'blue'] titles = ["R", "G", "B"]
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()
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()
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()
print("\nGenerating Block Disruption Plots...") block_sizes_list = [8, 16, 32, 64] num_vis_blocks = len(block_sizes_list)
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)
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")
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)
axes7[0, 0].imshow(img_color_orig) axes7[0, 0].set_title("Original Color") axes7[0, 0].axis("off") axes7[1, 0].axis("off")
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])
fig8, axes8 = plt.subplots(1, 3, figsize=(21, 6)) fig8.suptitle(f'- - - Grayscale correlation - - - ', fontsize=16)
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)')
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)')
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)')
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)')
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)')
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)')
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)')
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)')
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)')
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)')
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)')
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 ---")