renoir.color.analysis

Color analysis functions for statistical and color space analysis.

This module provides tools for analyzing color distributions, converting between color spaces, and computing color statistics from artworks.

class renoir.color.analysis.ColorAnalyzer[source]

Bases: object

Analyze color distributions and relationships in artworks.

This class provides methods for statistical analysis of colors, color space conversions, and comparative analysis across artworks. Designed for teaching color theory and computational analysis to art and design students.

rgb_to_hsv(rgb)[source]

Convert RGB color to HSV (Hue, Saturation, Value) color space.

HSV is often more intuitive for artists and designers as it separates color into hue (color type), saturation (intensity), and value (brightness).

Parameters:

rgb (Tuple[int, int, int]) – Tuple of (R, G, B) values (0-255)

Returns:

H: Hue in degrees (0-360) S: Saturation as percentage (0-100) V: Value as percentage (0-100)

Return type:

Tuple of (H, S, V) where

Example

>>> analyzer = ColorAnalyzer()
>>> hsv = analyzer.rgb_to_hsv((255, 87, 51))
>>> print(f"Hue: {hsv[0]}°, Saturation: {hsv[1]}%, Value: {hsv[2]}%")
hsv_to_rgb(hsv)[source]

Convert HSV color to RGB color space.

Parameters:

hsv (Tuple[float, float, float]) – Tuple of (H, S, V) where: H: Hue in degrees (0-360) S: Saturation as percentage (0-100) V: Value as percentage (0-100)

Return type:

Tuple[int, int, int]

Returns:

Tuple of (R, G, B) values (0-255)

Example

>>> analyzer = ColorAnalyzer()
>>> rgb = analyzer.hsv_to_rgb((10, 80, 100))
>>> print(f"RGB: {rgb}")
rgb_to_hsl(rgb)[source]

Convert RGB color to HSL (Hue, Saturation, Lightness) color space.

Parameters:

rgb (Tuple[int, int, int]) – Tuple of (R, G, B) values (0-255)

Returns:

H: Hue in degrees (0-360) S: Saturation as percentage (0-100) L: Lightness as percentage (0-100)

Return type:

Tuple of (H, S, L) where

hsl_to_rgb(hsl)[source]

Convert HSL color to RGB.

Parameters:

hsl (Tuple[float, float, float]) – Tuple of (H, S, L) where: H: Hue in degrees (0-360) S: Saturation as percentage (0-100) L: Lightness as percentage (0-100)

Return type:

Tuple[int, int, int]

Returns:

Tuple of (R, G, B) values (0-255)

analyze_palette_statistics(colors)[source]

Compute statistical measures for a color palette.

Educational method for teaching students about color data analysis.

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Returns:

  • mean_rgb: Average RGB values

  • std_rgb: Standard deviation of RGB values

  • hsv_values: HSV representation of each color

  • mean_hue: Average hue

  • mean_saturation: Average saturation

  • mean_value: Average brightness/value

Return type:

Dictionary containing

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(255, 87, 51), (100, 200, 150), (50, 100, 200)]
>>> stats = analyzer.analyze_palette_statistics(colors)
>>> print(f"Average hue: {stats['mean_hue']:.1f}°")
calculate_color_diversity(colors)[source]

Calculate color diversity using hue distribution entropy.

Higher values indicate more diverse color usage. Useful for comparing artistic styles quantitatively.

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Return type:

float

Returns:

Diversity score (0-1, higher = more diverse)

Example

>>> analyzer = ColorAnalyzer()
>>> monochrome = [(100, 100, 100), (110, 110, 110), (120, 120, 120)]
>>> diverse = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
>>> print(analyzer.calculate_color_diversity(monochrome))  # Low score
>>> print(analyzer.calculate_color_diversity(diverse))     # High score
calculate_saturation_score(colors)[source]

Calculate average saturation score for a palette.

Useful for characterizing artistic styles: - High saturation: Bold, vibrant (Fauvism, Pop Art) - Low saturation: Muted, subtle (Impressionism, Realism)

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Return type:

float

Returns:

Average saturation (0-100)

Example

>>> analyzer = ColorAnalyzer()
>>> vibrant = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
>>> muted = [(200, 180, 170), (150, 140, 130)]
>>> print(analyzer.calculate_saturation_score(vibrant))  # ~100
>>> print(analyzer.calculate_saturation_score(muted))    # ~20
calculate_brightness_score(colors)[source]

Calculate average brightness/value score for a palette.

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Return type:

float

Returns:

Average brightness (0-100)

compare_palettes(palette1, palette2)[source]

Compare two color palettes statistically.

Educational method for teaching comparative color analysis.

Parameters:
  • palette1 (List[Tuple[int, int, int]]) – First list of RGB tuples

  • palette2 (List[Tuple[int, int, int]]) – Second list of RGB tuples

Return type:

Dict

Returns:

Dictionary with comparative statistics

Example

>>> analyzer = ColorAnalyzer()
>>> monet_colors = [(120, 150, 180), (200, 220, 230)]
>>> picasso_colors = [(255, 50, 50), (50, 50, 200)]
>>> comparison = analyzer.compare_palettes(monet_colors, picasso_colors)
>>> print(f"Saturation difference: {comparison['saturation_diff']:.1f}%")
classify_color_temperature(rgb)[source]

Classify a color as warm or cool based on hue.

Educational method for teaching color theory concepts.

Parameters:

rgb (Tuple[int, int, int]) – RGB tuple

Return type:

str

Returns:

‘warm’, ‘cool’, or ‘neutral’

Example

>>> analyzer = ColorAnalyzer()
>>> print(analyzer.classify_color_temperature((255, 0, 0)))    # 'warm'
>>> print(analyzer.classify_color_temperature((0, 0, 255)))    # 'cool'
>>> print(analyzer.classify_color_temperature((128, 128, 128))) # 'neutral'
analyze_color_temperature_distribution(colors)[source]

Analyze the distribution of warm vs. cool colors in a palette.

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Return type:

Dict

Returns:

Dictionary with temperature distribution statistics

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(255, 0, 0), (0, 0, 255), (0, 255, 0)]
>>> temp_dist = analyzer.analyze_color_temperature_distribution(colors)
>>> print(temp_dist)
detect_complementary_colors(colors, tolerance=30)[source]

Detect complementary color pairs in a palette.

Complementary colors are opposite on the color wheel (180° apart). Educational method for teaching color harmony.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • tolerance (float) – Hue difference tolerance in degrees (default: 30)

Return type:

List[Tuple[Tuple[int, int, int], Tuple[int, int, int]]]

Returns:

List of complementary color pairs

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(255, 0, 0), (0, 255, 255), (128, 0, 128)]
>>> pairs = analyzer.detect_complementary_colors(colors)
calculate_contrast_ratio(color1, color2)[source]

Calculate WCAG contrast ratio between two colors.

Useful for teaching accessibility in design. Ratio of 4.5:1 is minimum for normal text (WCAG AA).

Parameters:
  • color1 (Tuple[int, int, int]) – First RGB tuple

  • color2 (Tuple[int, int, int]) – Second RGB tuple

Return type:

float

Returns:

Contrast ratio (1-21)

Example

>>> analyzer = ColorAnalyzer()
>>> ratio = analyzer.calculate_contrast_ratio((0, 0, 0), (255, 255, 255))
>>> print(f"Contrast ratio: {ratio:.2f}:1")  # 21.00:1
detect_triadic_harmony(colors, tolerance=30)[source]

Detect triadic color harmonies in a palette.

Triadic harmonies are three colors equally spaced on the color wheel (120° apart). Used by masters like Mondrian and in vibrant designs.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • tolerance (float) – Hue difference tolerance in degrees (default: 30)

Return type:

List[Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]]

Returns:

List of triadic color triplets

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]  # R, G, B
>>> triads = analyzer.detect_triadic_harmony(colors)
>>> print(f"Found {len(triads)} triadic harmonies")
detect_analogous_harmony(colors, max_hue_range=60)[source]

Detect analogous color schemes in a palette.

Analogous colors are adjacent on the color wheel (within 60° typically). Creates harmonious, serene color schemes. Common in nature and landscapes.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • max_hue_range (float) – Maximum hue range in degrees (default: 60)

Return type:

List[List[Tuple[int, int, int]]]

Returns:

List of analogous color groups (groups of 2+ colors)

Example

>>> analyzer = ColorAnalyzer()
>>> # Blues and greens (analogous)
>>> colors = [(0, 100, 255), (0, 200, 200), (0, 255, 100)]
>>> groups = analyzer.detect_analogous_harmony(colors)
detect_split_complementary(colors, tolerance=30)[source]

Detect split-complementary color schemes.

Split-complementary uses a base color and two colors adjacent to its complement (instead of the direct complement). Provides high contrast while being more subtle than complementary. Popular in Renaissance art.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • tolerance (float) – Hue difference tolerance in degrees (default: 30)

Return type:

List[Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]]

Returns:

List of split-complementary triplets (base, complement1, complement2)

Example

>>> analyzer = ColorAnalyzer()
>>> # Red with blue-green and yellow-green (instead of pure green)
>>> colors = [(255, 0, 0), (0, 200, 100), (100, 200, 0)]
>>> splits = analyzer.detect_split_complementary(colors)
detect_tetradic_harmony(colors, tolerance=30)[source]

Detect tetradic (double complementary) color harmonies.

Tetradic uses two complementary pairs, forming a rectangle on the color wheel. Creates rich, diverse palettes. Used in complex compositions and modern art.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • tolerance (float) – Hue difference tolerance in degrees (default: 30)

Return type:

List[Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]]

Returns:

List of tetradic color quartets

Example

>>> analyzer = ColorAnalyzer()
>>> # Two complementary pairs
>>> colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
>>> tetrads = analyzer.detect_tetradic_harmony(colors)
analyze_color_harmony(colors)[source]

Comprehensive analysis of color harmonies present in a palette.

Analyzes all major harmony types and provides statistics. Educational method for teaching color theory in practice.

Parameters:

colors (List[Tuple[int, int, int]]) – List of RGB tuples

Returns:

  • complementary_pairs: List of complementary color pairs

  • triadic_sets: List of triadic harmonies

  • analogous_groups: List of analogous color groups

  • split_complementary_sets: List of split-complementary schemes

  • tetradic_sets: List of tetradic harmonies

  • harmony_score: Overall harmony score (0-1)

  • dominant_harmony: Most prevalent harmony type

Return type:

Dictionary containing

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
>>> analysis = analyzer.analyze_color_harmony(colors)
>>> print(f"Dominant harmony: {analysis['dominant_harmony']}")
palette_earth_movers_distance(palette1, palette2)[source]

Calculate Palette Earth Mover’s Distance (PEMD) between two palettes.

Uses CIEDE2000 as the perceptual ground distance and colour proportions as weights, solved via optimal transport. This provides a structurally aware comparison that accounts for both colour similarity and proportion differences.

Parameters:
  • palette1 (List[Tuple[Tuple[int, int, int], float]]) – List of (RGB tuple, proportion) pairs. Proportions should sum to 1.0.

  • palette2 (List[Tuple[Tuple[int, int, int], float]]) – List of (RGB tuple, proportion) pairs. Proportions should sum to 1.0.

Return type:

float

Returns:

PEMD distance (lower = more similar). Scale depends on CIEDE2000 units (typically 0–100+, where <2 is imperceptible).

Raises:
  • ImportError – If scipy is not installed.

  • ValueError – If palettes are empty.

Example

>>> analyzer = ColorAnalyzer()
>>> p1 = [((255, 0, 0), 0.6), ((0, 0, 255), 0.4)]
>>> p2 = [((250, 10, 5), 0.5), ((10, 0, 250), 0.5)]
>>> dist = analyzer.palette_earth_movers_distance(p1, p2)
>>> print(f"PEMD: {dist:.2f}")
calculate_color_complexity(colors, proportions=None, weights=None)[source]

Calculate the Colour Complexity Index (CCI) for a palette.

A multi-dimensional information-theoretic measure combining: - Hue entropy (spread across the colour wheel) - Perceptual spread (mean pairwise CIEDE2000 distance) - Proportion evenness (1 - Gini coefficient) - Harmony penalty (lower complexity if colours follow harmony rules)

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples

  • proportions (Optional[List[float]]) – Optional list of colour proportions (should sum to 1). If None, equal proportions are assumed.

  • weights (Optional[Dict[str, float]]) – Optional dict of component weights with keys: ‘hue_entropy’, ‘perceptual_spread’, ‘proportion_evenness’, ‘harmony_penalty’. Defaults to equal weighting.

Returns:

  • cci: Composite Colour Complexity Index (0-1)

  • hue_entropy: Normalised hue entropy (0-1)

  • perceptual_spread: Normalised mean pairwise CIEDE2000 (0-1)

  • proportion_evenness: 1 - Gini coefficient (0-1)

  • harmony_penalty: Harmony score (0-1, subtracted)

  • components: Dict of weighted sub-scores

Return type:

Dictionary containing

Example

>>> analyzer = ColorAnalyzer()
>>> mondrian = [(255, 0, 0), (0, 0, 255), (255, 255, 0),
...             (255, 255, 255), (0, 0, 0)]
>>> result = analyzer.calculate_color_complexity(mondrian)
>>> print(f"CCI: {result['cci']:.3f}")
colour_provenance_score(colors, year, proportions=None)[source]

Calculate Colour Provenance Score (CPS) for a palette and attributed date.

Estimates how consistent a palette is with historically available pigments at the given date. Low scores may indicate anachronistic colour usage.

Requires the artist_pigments vocabulary with historical date fields.

Parameters:
  • colors (List[Tuple[int, int, int]]) – List of RGB tuples from the artwork

  • year (int) – Attributed year of the artwork

  • proportions (Optional[List[float]]) – Optional colour proportions. If None, equal weights used.

Returns:

  • score: Overall provenance score (0–1, higher = more consistent)

  • per_color: List of per-colour assessments

  • flagged: Colours flagged as potentially anachronistic

Return type:

Dictionary containing

Example

>>> analyzer = ColorAnalyzer()
>>> colors = [(0, 50, 200), (255, 0, 0), (255, 255, 0)]
>>> result = analyzer.colour_provenance_score(colors, year=1780)
>>> print(f"Provenance: {result['score']:.2f}")
>>> for flag in result['flagged']:
...     print(f"  ⚠ {flag['color']}: {flag['reason']}")