Images from CIFAR-10 dataset¶

In [1]:
%reset -f
import time

start = time.time()

from IPython.display import display, HTML

display(HTML('''
<script id="MathJax-script"
        async
        src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js">
</script>
'''))

import warnings
import matplotlib.pyplot as plt
from pathlib import Path
import plotly.graph_objects as go
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import torch
import numpy as np
import plotly
from plotly.subplots import make_subplots
import os

from dyned import (
    sigma_delta_quantisation,
    generate_step_signal,
    identify_peaks_and_troughs,
    create_spike_plot,
    create_rle_spike_plot,
    create_lzw_spike_plot,
    create_huffman_spike_plot,
    create_delta_spike_plot,
    create_bwt_spike_plot,
    create_lz77_spike_plot,
    create_dynedc_v4_spike_plot,
    compression_summary,
    print_compression_table,
)

plotly.offline.init_notebook_mode(connected=True)

import plotly.io as pio
if os.environ.get("theme", "light") == "dark":
    pio.templates.default = "plotly_dark"
else:
    pio.templates.default = "plotly"
In [2]:
HERE = Path.cwd()

if torch.cuda.is_available():
    device = torch.device("cuda")
    print("Using GPU")
else:
    device = torch.device("cpu")
    print("Using CPU")

def get_mean_std(loader):
    # Var[X] = E[X^2] - E[X]^2
    channels_sum, channels_squared_sum, num_batches = 0, 0, 0

    for data, _ in loader:
        channels_sum += torch.mean(data, dim=[0, 2, 3])
        channels_squared_sum += torch.mean(data ** 2, dim=[0, 2, 3])
        num_batches += 1

    _mean = channels_sum / num_batches
    _std = (channels_squared_sum / num_batches - _mean ** 2) ** 0.5

    return _mean, _std

train_transform = transforms.Compose([transforms.ToTensor()])
train_dataset = datasets.CIFAR10(f"{HERE}/assets", train=True, download=True, transform=train_transform)
train_dataloader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
train_mean, train_std = get_mean_std(train_dataloader)
print(f"Train Mean: {train_mean}, Train Standard Deviation: {train_std}")

test_transform = transforms.Compose([transforms.ToTensor()])
test_dataset = datasets.CIFAR10(f"{HERE}/assets", train=False, download=True, transform=test_transform)
test_dataloader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)
test_mean, test_std = get_mean_std(test_dataloader)
print(f"Test Mean: {test_mean}, Test Standard Deviation: {test_std}")


transform_train = transforms.Compose([transforms.ToTensor(), transforms.Normalize(train_mean.tolist(), train_std.tolist())])
train_dataset = datasets.CIFAR10(f"{HERE}/assets", train=True, download=True, transform=transform_train)

transform_test = transforms.Compose([transforms.ToTensor(), transforms.Normalize(test_mean.tolist(), test_std.tolist())])
test_dataset = datasets.CIFAR10(f"{HERE}/assets", train=False, download=True, transform=transform_test)

dataset = test_dataset

# # Find the mean and standard deviation of the ImageNet dataset
# transform = transforms.Compose([transforms.ToTensor()])
# dataset = datasets.ImageNet(f"{HERE}/assets", train=True, download=True, transform=transform, split="balanced")
# dataloader = DataLoader(dataset, batch_size=64, shuffle=False, num_workers=0)
# mean, std = get_mean_std(dataloader)
# print(f"Mean: {mean}, Standard Deviation: {std}")

# # Normalize the dataset
# transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean, std)])
# dataset = datasets.ImageNet(f"{HERE}/assets", train=True, download=True, transform=transform, split="balanced")
# dataloader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)
Using GPU
/home/akshay/code/personal/head-tracker/.venv/lib/python3.13/site-packages/torchvision/datasets/cifar.py:83: VisibleDeprecationWarning: dtype(): align should be passed as Python or NumPy boolean but got `align=0`. Did you mean to pass a tuple to create a subarray type? (Deprecated NumPy 2.4)
  entry = pickle.load(f, encoding="latin1")
Train Mean: tensor([0.4913, 0.4821, 0.4465]), Train Standard Deviation: tensor([0.2470, 0.2435, 0.2616])
Test Mean: tensor([0.4942, 0.4851, 0.4504]), Test Standard Deviation: tensor([0.2467, 0.2430, 0.2616])
In [3]:
img, label = dataset[3]
class_name = dataset.classes[label]

# 1. Cleaner PyTorch-native unnormalize function
def unnormalize(tensor, mean, std):
    """Reverse normalization strictly in PyTorch before returning."""
    # Reshape 1D mean/std tensors to (3, 1, 1) for broadcasting
    m = mean.view(3, 1, 1)
    s = std.view(3, 1, 1)

    # Unnormalize and clip to [0, 1]
    unnorm_tensor = torch.clamp(tensor * s + m, 0, 1)
    return unnorm_tensor

# 2. Unnormalize using the TRAIN mean and std
img_unnorm = unnormalize(img, train_mean, train_std)

# 3. Calculate grayscale on the UNNORMALIZED tensor
img_gray = 0.2989 * img_unnorm[0] + 0.5870 * img_unnorm[1] + 0.1140 * img_unnorm[2]

# 4. Prepare for Matplotlib (convert to NumPy and permute channels to H, W, C)
img_rgb = img_unnorm.permute(1, 2, 0).numpy()

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(3, 1.5), dpi=150) # Slightly larger for better visibility
fig.suptitle(f"Label: {label}, Class: {class_name}", fontsize=8)

axes[0].imshow(img_rgb, interpolation='bilinear')
axes[0].set_title("RGB", fontsize=7)
axes[0].axis('off')

axes[1].imshow(img_gray.numpy(), cmap='gray', interpolation='bilinear')
axes[1].set_title("Grayscale", fontsize=7)
axes[1].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [4]:
# Flatten the image and compute FFT
flatten_img = torch.flatten(img_gray)
fft_result = torch.fft.fft2(img_gray)
fft_shifted = torch.fft.fftshift(fft_result)
fft_shifted_flatten = torch.flatten(fft_shifted)
# magnitude_spectrum = 20 * torch.log10(torch.abs(fft_shifted_flatten) + 1e-6)
magnitude_spectrum = torch.abs(fft_shifted_flatten)
phase_spectrum = torch.angle(fft_shifted_flatten)

magnitude_spectrum = torch.log1p(magnitude_spectrum)
magnitude_spectrum_np = magnitude_spectrum.numpy()

# Calculate frequency bins
N = len(magnitude_spectrum_np)
sample_rate = 1 # Unknown sample rate, so we assume it is 1 Hz
freqs = np.fft.fftshift(np.fft.fftfreq(N, d=1/sample_rate))

# Create Plotly subplots
fig = make_subplots(rows=2, cols=1, subplot_titles=("Flattened Image Data", "1D FFT Magnitude Spectrum"), vertical_spacing=0.3)

# Add flattened image data
fig.add_trace(
    go.Scatter(x=np.arange(len(flatten_img)), y=flatten_img.numpy(), mode='lines', name='Flattened Image Data'),
    row=1, col=1
)
fig.update_xaxes(title_text="Index", row=1, col=1)
fig.update_yaxes(title_text="Pixel Value", row=1, col=1)

# Add FFT magnitude spectrum
fig.add_trace(
    go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Magnitude Spectrum', line=dict(color='blue')),
    row=2, col=1
)
fig.update_xaxes(title_text="Frequency (Hz)", row=2, col=1)
fig.update_yaxes(title_text="Magnitude (dB)", row=2, col=1)

fig.update_xaxes(title_text="Frequency (Hz)", row=3, col=1)
fig.update_yaxes(title_text="Magnitude (dB) Peaks", row=3, col=1)

fig.update_layout(height=500, width=1400, title_text=f"Analysis of CIFAR-10 Class: {class_name}", showlegend=False)
fig.write_html(f"{HERE}/{class_name}_{label}_EMNIST_FFT.html")
fig.show()
In [5]:
# Quantise the magnitude spectrum using DyNED encoder
quantised_magnitude_spectrum, errors = sigma_delta_quantisation(magnitude_spectrum_np, levels=20)
step_signal = generate_step_signal(quantised_magnitude_spectrum)
peaks, troughs = identify_peaks_and_troughs(quantised_magnitude_spectrum)

fig = make_subplots(rows=5, cols=1, vertical_spacing=0.09, subplot_titles=(
    "Magnitude Spectrum", "Quantised Signals", "Spikes", "Peaks and Troughs", "Quantisation Error"))

fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum_np, name="Magnitude Spectrum"), row=1, col=1)

fig.add_trace(go.Scatter(x=freqs, y=quantised_magnitude_spectrum, mode='lines', name='Quantised Magnitude Spectrum'), row=2, col=1)

fig.add_trace(create_spike_plot(freqs, step_signal), row=3, col=1)

fig.add_trace(go.Scatter(x=freqs[peaks], y=quantised_magnitude_spectrum[peaks], mode='markers', marker=dict(color='green', size=3), name='Peaks'), row=4, col=1)
fig.add_trace(go.Scatter(x=freqs[troughs], y=quantised_magnitude_spectrum[troughs], mode='markers', marker=dict(color='red', size=3), name='Troughs'), row=4, col=1)

fig.add_trace(go.Scatter(x=np.arange(len(errors)), y=errors, mode='lines', name='Quantisation Errors', line=dict(color='red')), row=5, col=1)

fig.update_layout(height=1080, width=1400, title='Quantised FFT Magnitude Spectrum', xaxis_title='Frequency (Hz)', yaxis_title='Magnitude (dB)')
fig.show()
Number of spikes: 732

Reconstruction of the Image¶

In [6]:
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from scipy.interpolate import RBFInterpolator, CubicSpline, Rbf, KroghInterpolator

def split_data(x, y, num_segments):
    """Split the data into approximately equal segments."""
    # Determine the size of each segment
    segment_size = len(x) // num_segments
    segments = []

    for i in range(num_segments):
        start = i * segment_size
        # Ensure the last segment grabs all remaining data
        if i == num_segments - 1:
            end = len(x)
        else:
            end = start + segment_size
        segments.append((x[start:end], y[start:end]))

    return segments

def interpolate_segments(segments):
    """Interpolate each segment and return interpolators."""
    interpolators = []
    for x_seg, y_seg in segments:
        interpolator = KroghInterpolator(x_seg, y_seg)
        interpolators.append(interpolator)
    return interpolators

def merge_interpolations(interpolators, x_full):
    """Evaluate interpolations on the full x range, merging them smoothly."""
    y_full = np.zeros_like(x_full)
    segment_length = len(x_full) // len(interpolators)

    for i, interpolator in enumerate(interpolators):
        if i == len(interpolators) - 1:
            # Last segment goes up to the end
            x_range = x_full[i * segment_length:]
        else:
            x_range = x_full[i * segment_length: (i + 1) * segment_length]
        y_full[i * segment_length: i * segment_length + len(x_range)] = interpolator(x_range)

    return y_full

# Assuming quantised_magnitude_spectrum is already defined
quantised_magnitude_spectrum_reshaped = quantised_magnitude_spectrum.reshape(-1, 1)
freqs_reshaped = freqs.reshape(-1, 1)  # Ensure this is a 2D array as required by RBFInterpolator

freqs_interpolated = np.linspace(freqs.min(), freqs.max(), len(quantised_magnitude_spectrum))  # Smooth interpolation across the frequency range

# RBF interpolation
start_time = time.time()
rbfi = RBFInterpolator(freqs_reshaped, quantised_magnitude_spectrum_reshaped, kernel="linear")
interpolated_values = rbfi(freqs_interpolated[:, None]).flatten()
rbf_time = time.time() - start_time

# Cubic spline interpolation
start_time = time.time()
cube_spline = CubicSpline(freqs, quantised_magnitude_spectrum, bc_type='clamped')
interpolated_values_cubic = cube_spline(freqs_interpolated)
cubic_time = time.time() - start_time

# Radial basis function interpolation
start_time = time.time()
rbf = Rbf(freqs, quantised_magnitude_spectrum, function='cubic')
interpolated_values_rbf = rbf(freqs_interpolated)
rbf_classic_time = time.time() - start_time

# Krogh interpolation
start_time = time.time()
segments = split_data(freqs, quantised_magnitude_spectrum, num_segments=30)
interpolators = interpolate_segments(segments)
interpolated_values_krogh = merge_interpolations(interpolators, freqs)
krogh_time = time.time() - start_time

# Plot using Plotly
fig = make_subplots(rows=4, cols=1, subplot_titles=("RBF Interpolation", "Cubic Interpolation", "RBF Interpolation (Classic)", "Krogh Interpolation"), vertical_spacing=0.09)
fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Quantized Magnitude Spectrum'), row=1, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values, mode='lines',line = dict(dash='dot'), name='Interpolated Quantized Magnitude Spectrum'), row=1, col=1)

fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Quantized Magnitude Spectrum'), row=2, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_cubic, mode='lines', line = dict(dash='dot'), name='Interpolated Quantized Magnitude Spectrum'), row=2, col=1)

fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Quantized Magnitude Spectrum'), row=3, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_rbf, mode='lines', line = dict(dash='dot'), name='Interpolated Quantized Magnitude Spectrum'), row=3, col=1)

fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Quantized Magnitude Spectrum'), row=4, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_krogh, mode='lines', line = dict(dash='dot'), name='Interpolated Quantized Magnitude Spectrum'), row=4, col=1)
fig.update_layout(title='Interpolated Quantized FFT Magnitude Spectrum', xaxis_title='Frequency (Hz)', yaxis_title='Magnitude (dB)', height=1080, width=1400, showlegend=False)
fig.show()

original_values_at_interpolated_freqs = np.interp(freqs_interpolated, freqs, magnitude_spectrum)

# Compute interpolation method
# RBF Interpolation Error Calculation
mse_rbfi = mean_squared_error(original_values_at_interpolated_freqs, interpolated_values)
rmse_rbfi = np.sqrt(mse_rbfi)
mae_rbfi = mean_absolute_error(original_values_at_interpolated_freqs, interpolated_values)
r2_rbfi = r2_score(original_values_at_interpolated_freqs, interpolated_values)

# Cubic Spline Interpolation Error Calculation
mse_cubic = mean_squared_error(original_values_at_interpolated_freqs, interpolated_values_cubic)
rmse_cubic = np.sqrt(mse_cubic)
mae_cubic = mean_absolute_error(original_values_at_interpolated_freqs, interpolated_values_cubic)
r2_cubic = r2_score(original_values_at_interpolated_freqs, interpolated_values_cubic)

# Radial Basis Function Interpolation Error Calculation
mse_rbf = mean_squared_error(original_values_at_interpolated_freqs, interpolated_values_rbf)
rmse_rbf = np.sqrt(mse_rbf)
mae_rbf = mean_absolute_error(original_values_at_interpolated_freqs, interpolated_values_rbf)
r2_rbf = r2_score(original_values_at_interpolated_freqs, interpolated_values_rbf)

# Krogh Interpolation Error Calculation
mse_krogh = mean_squared_error(original_values_at_interpolated_freqs, interpolated_values_krogh)
rmse_krogh = np.sqrt(mse_krogh)
mae_krogh = mean_absolute_error(original_values_at_interpolated_freqs, interpolated_values_krogh)
r2_krogh = r2_score(original_values_at_interpolated_freqs, interpolated_values_krogh)

# Print Error Metrics
print(f"RBFInterpolator - RMSE: {rmse_rbfi}, MAE: {mae_rbfi}, R2: {r2_rbfi}, Time: {rbf_time:.4f}s")
print(f"Cubic Spline - RMSE: {rmse_cubic}, MAE: {mae_cubic}, R2: {r2_cubic}, Time: {cubic_time:.4f}s")
print(f"Radial Basis Function - RMSE: {rmse_rbf}, MAE: {mae_rbf}, R2: {r2_rbf}, Time: {rbf_classic_time:.4f}s")
print(f"Krogh Interpolator - RMSE: {rmse_krogh}, MAE: {mae_krogh}, R2: {r2_krogh}, Time: {krogh_time:.4f}s")

fig = make_subplots(rows=1, cols=1)
fig.add_trace(go.Scatter(x=freqs, y=magnitude_spectrum, mode='lines', name='Original Data Points'), row=1, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values, mode='lines', name='RBF Interpolation', line=dict(dash='dot')), row=1, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_cubic, name='Cubic Spline', line=dict(dash='dot')), row=1, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_rbf, name='RBF', line=dict(dash='dot')), row=1, col=1)
fig.add_trace(go.Scatter(x=freqs_interpolated, y=interpolated_values_krogh, name='Krogh', line=dict(dash='dot')), row=1, col=1)
fig.update_layout(title='Comparison of Interpolation Methods', xaxis_title='Frequency (Hz)', yaxis_title='Magnitude (dB)', height=500, width=1400)
fig.show()
/tmp/ipykernel_2067169/1621631898.py:25: UserWarning: 34 degrees provided, degrees higher than about thirty cause problems with numerical instability with 'KroghInterpolator'
  interpolator = KroghInterpolator(x_seg, y_seg)
/tmp/ipykernel_2067169/1621631898.py:25: UserWarning: 38 degrees provided, degrees higher than about thirty cause problems with numerical instability with 'KroghInterpolator'
  interpolator = KroghInterpolator(x_seg, y_seg)
RBFInterpolator - RMSE: 0.13884961178192629, MAE: 0.11323501172777736, R2: 0.9642931858134081, Time: 0.0230s
Cubic Spline - RMSE: 0.13884961178159766, MAE: 0.1132350117275947, R2: 0.964293185813577, Time: 0.0005s
Radial Basis Function - RMSE: 0.13884955534723653, MAE: 0.11323491069651936, R2: 0.9642932148390934, Time: 0.0864s
Krogh Interpolator - RMSE: 0.13888957497235452, MAE: 0.11335826740113117, R2: 0.9642726288446478, Time: 0.0395s
In [7]:
def reconstruct_image(interpolated_magnitude, original_phase, original_shape):
    # Combine interpolated magnitude with original phase
    complex_spectrum = interpolated_magnitude * torch.exp(1j * original_phase)

    # Reshape back to 2D
    complex_spectrum_2d = complex_spectrum.reshape(original_shape)

    # Perform inverse FFT shift and then inverse FFT
    ifft_result = torch.fft.ifft2(torch.fft.ifftshift(complex_spectrum_2d))

    # Take the real part
    reconstructed_image = torch.real(ifft_result)

    return reconstructed_image

interpolated_magnitude_2d = interpolated_values_cubic.reshape(magnitude_spectrum.shape)
reconstructed_img = reconstruct_image(torch.exp(torch.from_numpy(interpolated_magnitude_2d)) - 1, phase_spectrum, img_gray.shape)

img_np = img_gray.cpu().numpy()          # (32, 32) grayscale
magnitude_spectrum_np = magnitude_spectrum.cpu().numpy()
reconstructed_img_np = reconstructed_img.cpu().numpy()

fig, axes = plt.subplots(1, 2, figsize=(3, 1.5), dpi=150)
fig.suptitle("Image Reconstruction", fontsize=7)

axes[0].imshow(img_np, cmap='gray', interpolation='bilinear')
axes[0].set_title("Original Image", fontsize=5)
axes[0].axis('off')

axes[1].imshow(reconstructed_img_np, cmap='gray', interpolation='bilinear')
axes[1].set_title("Reconstructed Image", fontsize=5)
axes[1].axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Structural Similarity Index (SSI)¶

In [8]:
from skimage.metrics import structural_similarity as ssim

similarity_index = ssim(img_np, reconstructed_img_np, data_range=reconstructed_img_np.max() - reconstructed_img_np.min())
print(f"Structural Similarity Index: {similarity_index}")
Structural Similarity Index: 0.9841686132181577

Convolutions¶

In [9]:
import torch
from torch.nn import Conv2d
import numpy as np

def apply_convolution(_img, _kernel):
    if not isinstance(_img, torch.Tensor):
        _img = torch.tensor(_img, dtype=torch.float32)
    if _img.dim() == 2:
        _img = _img.unsqueeze(0).unsqueeze(0)
    elif _img.dim() == 3:
        _img = _img.unsqueeze(0)

    conv_layer = Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1, bias=False)
    conv_layer.weight.data = _kernel.reshape(1, 1, 3, 3)

    with torch.no_grad():
        output = conv_layer(_img)

    return output.squeeze().numpy()

def visualize_multiple_convolutions(original, images: dict[str, np.ndarray]):
    n = len(images) + 1
    cols = 3
    rows = (n + cols - 1) // cols

    fig, axes = plt.subplots(rows, cols, figsize=(4, rows * 1.3), dpi=150)
    axes = axes.flatten()

    axes[0].imshow(original, cmap='gray', interpolation='bilinear')
    axes[0].set_title("Original Image", fontsize=5)
    axes[0].axis('off')

    for i, (name, _img) in enumerate(images.items()):
        axes[i + 1].imshow(_img, cmap='gray', interpolation='bilinear')
        axes[i + 1].set_title(name, fontsize=5)
        axes[i + 1].axis('off')

    # Hide unused subplots
    for j in range(n, len(axes)):
        axes[j].axis('off')

    fig.suptitle("Multiple Convolutions Visualization", fontsize=7)
    plt.tight_layout()
    plt.show()

# Define 5 different kernels
kernels = {
    "Laplacian": torch.tensor([[0, 1, 0], [1, -4, 1], [0, 1, 0]], dtype=torch.float32),  # Laplacian
    "Sharpen": torch.tensor([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], dtype=torch.float32),  # Sharpen
    "Gaussian Blur": torch.tensor([[1, 2, 1], [2, 4, 2], [1, 2, 1]], dtype=torch.float32) / 16,  # Gaussian Blur
    "Sobel X": torch.tensor([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=torch.float32),  # Sobel X
    "Sobel Y": torch.tensor([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=torch.float32) # Sobel Y
}

convolved_images = {name: apply_convolution(img_np, kernel) for name, kernel in kernels.items()}
visualize_multiple_convolutions(img_np, convolved_images)

# Print statistics
print(f"Original Image - Min: {img.min():.4f}, Max: {img.max():.4f}, Mean: {img.mean():.4f}")
for i, (name, convolved) in enumerate(convolved_images.items()):
    print(f"Convolution using {name} - Min: {convolved.min():.4f}, Max: {convolved.max():.4f}, Mean: {convolved.mean():.4f}")
No description has been provided for this image
Original Image - Min: -1.4641, Max: 1.7859, Mean: 0.5171
Convolution using Laplacian - Min: -1.3483, Max: 0.6665, Mean: -0.0691
Convolution using Sharpen - Min: -1.7263, Max: 3.3967, Mean: 0.2054
Convolution using Gaussian Blur - Min: 0.1440, Max: 0.8453, Mean: 0.5771
Convolution using Sobel X - Min: -3.3525, Max: 3.3836, Mean: 0.0036
Convolution using Sobel Y - Min: -2.1869, Max: 3.2668, Mean: -0.0558
In [10]:
end = time.time()
print(f"Time taken: {end - start:.4f} seconds")
Time taken: 8.8956 seconds

Spike Compression¶

In [11]:
# Compression classes are imported from dyned.py
# (RunLengthEncoding, LZWCompressor, HuffmanCompressor, DeltaCompressor,
#  BWTransform, LZ77Compressor, DyNEDcCompressor)
# See imports in the first cell.
In [12]:
plt1, cr1, sa1 = create_rle_spike_plot(freqs, step_signal)
plt2, cr2, sa2 = create_lzw_spike_plot(freqs, step_signal)
plt3, cr3, sa3 = create_huffman_spike_plot(freqs, step_signal)
plt4, cr4, sa4 = create_delta_spike_plot(freqs, step_signal)
plt5, cr5, sa5 = create_bwt_spike_plot(freqs, step_signal)
plt6, cr6, sa6 = create_lz77_spike_plot(freqs, step_signal)
plt7, cr7, sa7 = create_dynedc_v4_spike_plot(freqs, step_signal)

fig = make_subplots(rows=8, cols=1, subplot_titles=(
    f"Original Spikes",
    f"Run-Length Encoding ({cr1}, {sa1})",
    f"LZW Compression ({cr2}, {sa2})",
    f"Huffman Compression ({cr3}, {sa3})",
    f"Delta Compression ({cr4}, {sa4})",
    f"BWT Compression ({cr5}, {sa5})",
    f"LZ77 Compression ({cr6}, {sa6})",
    f"$\\text{{DyNED}}_{{c}}\\text{{ Compression ({cr7}, {sa7})}}$"
    ), vertical_spacing=0.09)

fig.add_trace(create_spike_plot(freqs, step_signal), row=1, col=1)

fig.add_trace(plt1, row=2, col=1)
fig.add_trace(plt2, row=3, col=1)
fig.add_trace(plt3, row=4, col=1)
fig.add_trace(plt4, row=5, col=1)
fig.add_trace(plt5, row=6, col=1)
fig.add_trace(plt6, row=7, col=1)
fig.add_trace(plt7, row=8, col=1)

fig.update_layout(height=1080, width=1400, title='Spike Compression Techniques - <i>r</i> image', xaxis_title='Frequency (Hz)', yaxis_title='Magnitude (dB)', showlegend=False)
fig.show()

# Compression summary table with bits-per-spike comparison
summary = compression_summary(step_signal)
print_compression_table(summary)
Number of spikes: 732
Run-Length Encoding:
    Original data size: 1024 bits
    Compressed data size: 2217 bits
    Compression ratio: 2.165
    Space saving: -116.5%
Number of spikes: 732
LZW Compression:
    Original data size: 1024 bits
    Compressed data size: 1456 bits
    Compression ratio: 1.422
    Space saving: -42.2%
Number of spikes: 732
Huffman Compression:
    Original data size: 1024 bits
    Compressed data size: 871 bits
    Compression ratio: 0.851
    Space saving: 14.9%
Number of spikes: 732
Delta Compression:
    Original data size: 1024 bits
    Compressed data size: 1680 bits
    Compression ratio: 1.641
    Space saving: -64.1%
Number of spikes: 732
Burrows-Wheeler Transform:
    Original data size: 1024 bits
    Compressed data size: 1031 bits
    Compression ratio: 1.007
    Space saving: -0.7%
Number of spikes: 732
LZ77:
    Original data size: 1024 bits
    Compressed data size: 2409 bits
    Compression ratio: 2.353
    Space saving: -135.3%
Number of spikes: 732
    Mode selected: huffman
DyNEDc Compression:
    Original data size: 1024 bits
    Compressed data size: 871 bits
    Compression ratio: 0.851
    Space saving: 14.9%
Number of spikes: 732
Spike Train: 1024 bits, 732 spikes
Method         Compressed    Ratio   Bits/Spike   Saving
--------------------------------------------------------
RLE                  2217   2.1650         3.03  -116.5%
LZW                  1456   1.4219         1.99   -42.2%
Huffman               871   0.8506         1.19    14.9%
Delta                1680   1.6406         2.30   -64.1%
BWT                  1031   1.0068         1.41    -0.7%
LZ77                 2409   2.3525         3.29  -135.3%
DyNEDc                951   0.9287         1.30     7.1%
DyNEDc V2            1110   1.0840         1.52    -8.4%
DyNEDc V3             871   0.8506         1.19    14.9%
DyNEDc V4             871   0.8506         1.19    14.9%