Skip to content

Commit 751ddde

Browse files
authored
Release: Pylette 5.0.0 (#95)
* Changelog and version update * Add better examples * Update documentation * Remove new in 5.0 * Update changelog * Update old CSV-test to check JSON output instead
1 parent 29b5c0e commit 751ddde

10 files changed

Lines changed: 489 additions & 206 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88

9-
## [Unreleased]
9+
# Released
10+
11+
## 5.0.0 - 22/08/2025
1012

1113
### Added
12-
- JSON export functionality for palettes with `to_json()` method
13-
- General `export()` method supporting multiple formats (JSON, CSV) with JSON as default
14-
- CLI `--export-json` flag for JSON output instead of CSV to stdout
15-
- CLI `--output` parameter with auto-detection for individual vs combined file export
16-
- Smart file naming with prefix + index (`palette_001.json`) for directory exports
17-
- Support for combined JSON files containing multiple palettes
18-
- `hex` property on `Color` class, representing RGB-values in hexadecimal
19-
- Pylette JSON format with colorspace-specific field names (e.g., `rgb`, `hsv`, `hls`)
14+
- **JSON Export**: New `to_json()` method on `Palette` class for exporting palettes to JSON format
15+
- **Enhanced CLI Export**:
16+
- `--export-json` flag for JSON output
17+
- `--output` parameter supporting both individual files and combined export
18+
- **Batch Processing**: Process multiple images in a single CLI command with parallel processing
19+
- **Color Hex Representation**: `hex` property on `Color` class providing `#RRGGBB` format
20+
- **Palette Metadata**: Metadata including image source, extraction parameters, and processing statistics
21+
- **Enhanced Progress Display**: CLI shows recently extracted palettes with color previews
22+
- **Rich Table Output**: Clean, colorized table display for CLI showing hex values, RGB/HSV/HLS, and frequencies
23+
24+
### Changed
25+
- **CLI Interface**: Removed `--filename` and `--image-url` parameters in favor of positional image arguments
26+
- **Batch Processing**: CLI now processes multiple image sources by default
27+
28+
### Removed
29+
- **CSV Export**: Removed `to_csv()` method from `Palette` class - use JSON export instead
30+
31+
### Breaking Changes
32+
- CLI parameter changes: `--filename` and `--image-url` removed
33+
- `Palette.to_csv()` method removed
2034

21-
# Released
2235

2336
## 4.4.0 - 20/08/2025
2437

Pylette/cmd.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from typing import Annotated, List
55

66
import typer
7+
from rich.console import Console
8+
from rich.table import Table
79

810
from Pylette.src.cli_utils import PyletteProgress
911
from Pylette.src.color_extraction import batch_extract_colors
@@ -52,8 +54,6 @@ def main(
5254
typer.echo("Error: --output is required when using --export-json", err=True)
5355
raise typer.Exit(1)
5456

55-
output_file_path = str(out_filename) if out_filename is not None else None
56-
5757
# Set up progress bar for CLI
5858
with PyletteProgress(palette_size=n) as progress:
5959
task_id = progress.add_task("Extracting colors...", total=len(image_sources))
@@ -84,11 +84,9 @@ def progress_callback(task_number: int, result: BatchResult):
8484

8585
if export_json and output:
8686
handle_json_export(successful, output, colorspace)
87-
else:
88-
# Original CSV behavior
89-
for success in successful:
90-
if success.palette is not None:
91-
success.palette.to_csv(filename=output_file_path, frequency=True, stdout=stdout, colorspace=colorspace)
87+
elif stdout and successful:
88+
# Show clean palette summary
89+
display_palette_summary(successful, colorspace)
9290

9391
# Display colors if requested
9492
if display_colors:
@@ -151,6 +149,62 @@ def handle_json_export(
151149
typer.echo(f"✓ Exported {len(successful_results)} palettes to {output_path}")
152150

153151

152+
def display_palette_summary(successful_results: list[BatchResult], colorspace: ColorSpace) -> None:
153+
"""Display a clean summary of extracted palettes."""
154+
console = Console()
155+
156+
for result in successful_results:
157+
if result.palette is not None:
158+
palette = result.palette
159+
160+
# Show extraction success message
161+
source = result.source
162+
if isinstance(source, str) and len(source) > 50:
163+
source = "..." + source[-47:] # Truncate long paths
164+
165+
console.print(f"\n✓ Extracted {palette.number_of_colors} colors from [bold]{source}[/bold]")
166+
167+
# Create color table
168+
table = Table(show_header=True, header_style="bold blue")
169+
table.add_column("Hex", style="bold", width=8)
170+
171+
# Add colorspace-specific column
172+
colorspace_label = colorspace.value.upper()
173+
if colorspace == ColorSpace.RGB:
174+
table.add_column(f"{colorspace_label}", width=15)
175+
else:
176+
table.add_column(f"{colorspace_label}", width=20)
177+
table.add_column("RGB", width=15)
178+
179+
table.add_column("Frequency", width=8, justify="right")
180+
181+
# Add color rows
182+
for color in palette.colors:
183+
hex_color = color.hex
184+
frequency = f"{color.freq:.1%}"
185+
186+
# Get colorspace values
187+
color_values = color.get_colors(colorspace)
188+
189+
if colorspace == ColorSpace.RGB:
190+
rgb_str = f"({int(color_values[0])}, {int(color_values[1])}, {int(color_values[2])})"
191+
table.add_row(f"[{hex_color}]{hex_color}[/{hex_color}]", rgb_str, frequency)
192+
else:
193+
# For HSV/HLS, show both the colorspace values and RGB reference
194+
if colorspace == ColorSpace.HSV:
195+
cs_str = f"({color_values[0]:.2f}, {color_values[1]:.2f}, {color_values[2]:.2f})"
196+
else: # HLS
197+
cs_str = f"({color_values[0]:.2f}, {color_values[1]:.2f}, {color_values[2]:.2f})"
198+
199+
rgb_str = f"({color.rgb[0]}, {color.rgb[1]}, {color.rgb[2]})"
200+
table.add_row(f"[{hex_color}]{hex_color}[/{hex_color}]", cs_str, rgb_str, frequency)
201+
202+
console.print(table)
203+
204+
# Show helpful usage message
205+
console.print("\n[dim]Use --export-json for structured data or --no-stdout to suppress output.[/dim]")
206+
207+
154208
def print_extraction_summary(successful: list[BatchResult], failed: list[BatchResult]):
155209
total = len(successful) + len(failed)
156210

Pylette/src/palette.py

Lines changed: 9 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
from typing import Literal
32

43
import numpy as np
54
from PIL import Image
@@ -89,36 +88,6 @@ def __getitem__(self, item: int) -> Color:
8988
def __len__(self) -> int:
9089
return self.number_of_colors
9190

92-
def to_csv(
93-
self,
94-
filename: str | None = None,
95-
frequency: bool = True,
96-
colorspace: ColorSpace = ColorSpace.RGB,
97-
stdout: bool = True,
98-
):
99-
"""
100-
Dumps the palette to stdout. Saves to file if filename is specified.
101-
102-
Parameters:
103-
filename (str | None): File to dump to.
104-
frequency (bool): Whether to dump the corresponding frequency of each color.
105-
colorspace (Literal["rgb", "hsv", "hls"]): Color space to use.
106-
stdout (bool): Whether to dump to stdout.
107-
"""
108-
109-
if stdout:
110-
print(self.metadata)
111-
for color in self.colors:
112-
print(",".join(map(str, color.get_colors(colorspace))))
113-
114-
if filename is not None:
115-
with open(filename, "w") as palette_file:
116-
for color in self.colors:
117-
palette_file.write(",".join(map(str, color.get_colors(colorspace))))
118-
if frequency:
119-
palette_file.write(",{}".format(color.freq))
120-
palette_file.write("\n")
121-
12291
def random_color(self, N: int, mode: str = "frequency") -> list[Color]:
12392
"""
12493
Returns N random colors from the palette, either using the frequency of each color, or choosing uniformly.
@@ -234,34 +203,25 @@ def to_json(
234203
def export(
235204
self,
236205
filename: str,
237-
format: Literal["json", "csv"] = "json",
238206
colorspace: ColorSpace = ColorSpace.RGB,
239-
include_frequency: bool = True,
240207
include_metadata: bool = True,
241208
stdout: bool = False,
242209
) -> None:
243210
"""
244-
General export method that supports multiple formats with JSON as default.
211+
Export palette to JSON format.
245212
246213
Parameters:
247-
filename (str): File to save to (extension will be added automatically).
248-
format (Literal["json", "csv"]): Export format (default: json).
249-
colorspace (Literal["rgb", "hsv", "hls"]): Color space to use.
250-
include_frequency (bool): Whether to include frequency data.
251-
include_metadata (bool): Whether to include metadata (JSON only).
214+
filename (str): File to save to (extension will be added automatically if not present).
215+
colorspace (ColorSpace): Color space to use.
216+
include_metadata (bool): Whether to include metadata.
252217
stdout (bool): Whether to print to stdout.
253218
"""
254219

255-
if format == "json":
256-
json_filename = f"{filename}.json"
257-
self.to_json(
258-
filename=json_filename, colorspace=colorspace, include_metadata=include_metadata, stdout=stdout
259-
)
260-
elif format == "csv":
261-
csv_filename = f"{filename}.csv"
262-
self.to_csv(filename=csv_filename, frequency=include_frequency, colorspace=colorspace, stdout=stdout)
263-
else:
264-
raise ValueError(f"Unsupported format: {format}. Supported formats: 'json', 'csv'")
220+
# Add .json extension if not present
221+
if not filename.endswith(".json"):
222+
filename = f"{filename}.json"
223+
224+
self.to_json(filename=filename, colorspace=colorspace, include_metadata=include_metadata, stdout=stdout)
265225

266226
def __str__(self):
267227
return "".join(["({}, {}, {}, {}) \n".format(c.rgb[0], c.rgb[1], c.rgb[2], c.freq) for c in self.colors])

0 commit comments

Comments
 (0)