Add purple code

This commit is contained in:
Bruce Hill 2025-02-27 17:33:07 -05:00
parent 78f4bd390c
commit 58bf02decb

175
purple.py Normal file
View File

@ -0,0 +1,175 @@
import numpy as np
import matplotlib.pyplot as plt
def gaussian_sensitivity(peak1, peak2, sigma, wavelengths, depth=2):
if depth <= 0: return 0
return (np.exp(-((wavelengths - peak1) ** 2) / (2 * sigma ** 2))
+ .25*np.exp(-((wavelengths - peak2) ** 2) / (2 * sigma ** 2)))
def section_break():
input("\n" + "="*80 + "\n")
wavelengths = np.linspace(300, 700, 500)
# Estimated sigma values for human and bird cones
sigma_human = 40
sigma_starling = 35
# Human cone sensitivities
human_peaks = {(437, 315): 'blue', (533,345): 'green', (564,355): 'red'}
# Starling cone sensitivities (including UV)
starling_peaks = {(362,250): 'violet', (449,320): 'blue', (504,340): 'green', (563,360): 'red'}
ANSI_COLORS = {
"blue": "\033[34m",
"red": "\033[31m",
"green": "\033[32m",
"violet": "\033[38;5;177m",
"purple": "\033[38;5;177m",
}
'''
plt.figure(figsize=(10, 6))
# Plot human sensitivities
plt.subplot(2, 1, 1)
for (peak1, peak2), color in human_peaks.items():
plt.plot(wavelengths, gaussian_sensitivity(peak1, peak2, sigma_human, wavelengths), color=color, label=f'λ_max = {peak1} nm')
plt.title("Human Retinal Cone Sensitivity")
plt.ylabel("Relative Absorption")
plt.legend()
# Plot starling sensitivities
plt.subplot(2, 1, 2)
for (peak1,peak2), color in starling_peaks.items():
plt.plot(wavelengths, gaussian_sensitivity(peak1, peak2, sigma_starling, wavelengths), color=color, label=f'λ_max = {peak1} nm')
plt.title("European Starling Retinal Cone Sensitivity")
plt.xlabel("Wavelength (nm)")
plt.ylabel("Relative Absorption")
plt.legend()
plt.tight_layout()
plt.savefig("retinal_cones.png")
'''
def human_activations(color_name, wavelengths, verbose=False):
raw_activations = {}
for (peak1,peak2), color in human_peaks.items():
raw_activations[color] = gaussian_sensitivity(peak1, peak2, sigma_human, wavelengths)
if verbose:
print(f"\033[1mHumans\033[m see {ANSI_COLORS[color_name]}{color_name}\033[m as:")
activations = []
for color,levels in raw_activations.items():
k = sum(levels)/sum(sum(l) for l in raw_activations.values())
activations.append(k)
if verbose:
print(f"{ANSI_COLORS[color]}{color}: \033[10G {k:%}\033[m")
if verbose:
print("")
vec = np.array(activations)
return vec / np.linalg.norm(vec)
human_activations("violet", np.array([380]), verbose=True)
human_activations("purple", np.array([623, 462]), verbose=True)
print("\nThey look the same to us!")
section_break()
def starling_activations(color_name, wavelengths, verbose=False):
raw_activations = {}
for (peak1,peak2), color in starling_peaks.items():
raw_activations[color] = gaussian_sensitivity(peak1, peak2, sigma_starling, wavelengths)
if verbose:
print(f"\033[1mStarlings\033[m see {ANSI_COLORS[color_name]}{color_name}\033[m as:")
activations = []
for color,levels in raw_activations.items():
k = sum(levels)/sum(sum(l) for l in raw_activations.values())
activations.append(k)
if verbose:
print(f"{ANSI_COLORS[color]}{color}: \033[10G {k:%}\033[m")
if verbose:
print("")
vec = np.array(activations)
return vec / np.linalg.norm(vec)
starling_activations("violet", np.array([380]), verbose=True)
starling_purple = starling_activations("purple", np.array([623, 455]), verbose=True)
print("\nThey look completely differe to a starling!")
section_break()
print("...But what pure wavelength looks like purple?")
def wavelength_to_rgb(wavelength, gamma=0.8):
'''
This converts a given wavelength of light to an
approximate RGB color value. The wavelength must be given
in nanometers in the range from 380 nm through 750 nm
(789 THz through 400 THz).
Based on code by Dan Bruton
http://www.physics.sfasu.edu/astro/color/spectra.html
'''
wavelength = float(wavelength)
if wavelength >= 380 and wavelength <= 440:
attenuation = 0.3 + 0.7 * (wavelength - 380) / (440 - 380)
R = ((-(wavelength - 440) / (440 - 380)) * attenuation) ** gamma
G = 0.0
B = (1.0 * attenuation) ** gamma
elif wavelength >= 440 and wavelength <= 490:
R = 0.0
G = ((wavelength - 440) / (490 - 440)) ** gamma
B = 1.0
elif wavelength >= 490 and wavelength <= 510:
R = 0.0
G = 1.0
B = (-(wavelength - 510) / (510 - 490)) ** gamma
elif wavelength >= 510 and wavelength <= 580:
R = ((wavelength - 510) / (580 - 510)) ** gamma
G = 1.0
B = 0.0
elif wavelength >= 580 and wavelength <= 645:
R = 1.0
G = (-(wavelength - 645) / (645 - 580)) ** gamma
B = 0.0
elif wavelength >= 645 and wavelength <= 750:
attenuation = 0.3 + 0.7 * (750 - wavelength) / (750 - 645)
R = (1.0 * attenuation) ** gamma
G = 0.0
B = 0.0
else:
R = 0.0
G = 0.0
B = 0.0
R *= 255
G *= 255
B *= 255
return (int(R), int(G), int(B))
print("")
closest_wavelength = None
best_dist = None
import time
for wavelength in range(100,700,2):
activations = starling_activations("???", np.array([wavelength]), verbose=False)
# dist = np.linalg.norm(starling_purple - activations)
dist = np.dot(starling_purple, activations)
if closest_wavelength is None or dist > best_dist:
closest_wavelength, best_dist = wavelength, dist
r,g,b = wavelength_to_rgb(wavelength)
#print(f"\033[48;2;{r};{g};{b}m Scanning: {closest_wavelength}nm... \033[m dist = {dist}", end="\033[K\n", flush=True)
section_break()
r,g,b = wavelength_to_rgb(closest_wavelength)
print(f"\033[48;2;{r};{g};{b}m WINNER: {closest_wavelength}nm! (blue) \033[m\n\n")
# import subprocess
# subprocess.run(["sxiv", "retinal_cones.png"])
# plt.show()