|
| 1 | +import json |
1 | 2 | from typing import Literal |
2 | 3 |
|
3 | 4 | import numpy as np |
4 | 5 | from PIL import Image |
5 | 6 |
|
6 | 7 | from Pylette.src.color import Color |
7 | | -from Pylette.src.types import ExtractionParams, ImageInfo, PaletteMetaData, ProcessingStats, SourceType |
| 8 | +from Pylette.src.types import ColorSpace, ExtractionParams, ImageInfo, PaletteMetaData, ProcessingStats, SourceType |
8 | 9 |
|
9 | 10 |
|
10 | 11 | class Palette: |
@@ -92,7 +93,7 @@ def to_csv( |
92 | 93 | self, |
93 | 94 | filename: str | None = None, |
94 | 95 | frequency: bool = True, |
95 | | - colorspace: Literal["rgb", "hsv", "hls"] = "rgb", |
| 96 | + colorspace: ColorSpace = ColorSpace.RGB, |
96 | 97 | stdout: bool = True, |
97 | 98 | ): |
98 | 99 | """ |
@@ -143,6 +144,125 @@ def random_color(self, N: int, mode: str = "frequency") -> list[Color]: |
143 | 144 | else: |
144 | 145 | raise ValueError(f"Invalid mode: {mode}. Must be 'frequency' or 'uniform'.") |
145 | 146 |
|
| 147 | + def to_json( |
| 148 | + self, |
| 149 | + filename: str | None = None, |
| 150 | + colorspace: ColorSpace = ColorSpace.RGB, |
| 151 | + include_metadata: bool = True, |
| 152 | + stdout: bool = True, |
| 153 | + ) -> dict[str, object] | None: |
| 154 | + """ |
| 155 | + Exports the palette to JSON format. |
| 156 | +
|
| 157 | + Parameters: |
| 158 | + filename (str | None): File to save to. If None, returns the dictionary. |
| 159 | + colorspace (Literal["rgb", "hsv", "hls"]): Color space to use. |
| 160 | + include_metadata (bool): Whether to include palette metadata. |
| 161 | + stdout (bool): Whether to print to stdout. |
| 162 | +
|
| 163 | + Returns: |
| 164 | + dict | None: The palette data as a dictionary if filename is None. |
| 165 | + """ |
| 166 | + |
| 167 | + # Build the palette data |
| 168 | + palette_data: dict[str, object] = { |
| 169 | + "colors": [], |
| 170 | + "palette_size": self.number_of_colors, |
| 171 | + "colorspace": colorspace, |
| 172 | + } |
| 173 | + |
| 174 | + colors_list = [] |
| 175 | + # Add color data |
| 176 | + for color in self.colors: |
| 177 | + color_values = color.get_colors(colorspace) |
| 178 | + color_data: dict[str, object] = { |
| 179 | + "frequency": float(color.freq), |
| 180 | + } |
| 181 | + |
| 182 | + # Add colorspace-specific field |
| 183 | + colorspace_field = colorspace.value.lower() # "rgb", "hsv", "hls" |
| 184 | + if colorspace == ColorSpace.RGB: |
| 185 | + # RGB values should be integers |
| 186 | + color_data[colorspace_field] = [int(v) if isinstance(v, np.integer) else v for v in color_values] |
| 187 | + else: |
| 188 | + # HSV/HLS values should be floats |
| 189 | + color_data[colorspace_field] = [ |
| 190 | + float(v) if isinstance(v, (np.integer, np.floating)) else v for v in color_values |
| 191 | + ] |
| 192 | + |
| 193 | + # Add hex (always present, derived from RGB) |
| 194 | + color_data["hex"] = color.hex |
| 195 | + |
| 196 | + # Add RGB reference if colorspace is not RGB |
| 197 | + if colorspace != ColorSpace.RGB: |
| 198 | + color_data["rgb"] = [int(v) if isinstance(v, np.integer) else v for v in color.rgb] |
| 199 | + |
| 200 | + colors_list.append(color_data) |
| 201 | + |
| 202 | + palette_data["colors"] = colors_list |
| 203 | + |
| 204 | + # Add metadata if requested and available |
| 205 | + if include_metadata and self.metadata: |
| 206 | + metadata_dict: dict[str, object] = {} |
| 207 | + |
| 208 | + if "image_source" in self.metadata: |
| 209 | + metadata_dict["image_source"] = self.metadata["image_source"] |
| 210 | + if "source_type" in self.metadata: |
| 211 | + metadata_dict["source_type"] = self.metadata["source_type"] |
| 212 | + if "extraction_params" in self.metadata: |
| 213 | + metadata_dict["extraction_params"] = self.metadata["extraction_params"] |
| 214 | + if "image_info" in self.metadata: |
| 215 | + metadata_dict["image_info"] = self.metadata["image_info"] |
| 216 | + if "processing_stats" in self.metadata: |
| 217 | + metadata_dict["processing_stats"] = self.metadata["processing_stats"] |
| 218 | + |
| 219 | + palette_data["metadata"] = metadata_dict |
| 220 | + |
| 221 | + # Print to stdout if requested |
| 222 | + if stdout: |
| 223 | + print(json.dumps(palette_data, indent=2)) |
| 224 | + |
| 225 | + # Save to file if filename provided |
| 226 | + if filename is not None: |
| 227 | + with open(filename, "w") as f: |
| 228 | + json.dump(palette_data, f, indent=2) |
| 229 | + return None |
| 230 | + |
| 231 | + # Return data if no filename provided |
| 232 | + return palette_data |
| 233 | + |
| 234 | + def export( |
| 235 | + self, |
| 236 | + filename: str, |
| 237 | + format: Literal["json", "csv"] = "json", |
| 238 | + colorspace: ColorSpace = ColorSpace.RGB, |
| 239 | + include_frequency: bool = True, |
| 240 | + include_metadata: bool = True, |
| 241 | + stdout: bool = False, |
| 242 | + ) -> None: |
| 243 | + """ |
| 244 | + General export method that supports multiple formats with JSON as default. |
| 245 | +
|
| 246 | + 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). |
| 252 | + stdout (bool): Whether to print to stdout. |
| 253 | + """ |
| 254 | + |
| 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'") |
| 265 | + |
146 | 266 | def __str__(self): |
147 | 267 | return "".join(["({}, {}, {}, {}) \n".format(c.rgb[0], c.rgb[1], c.rgb[2], c.freq) for c in self.colors]) |
148 | 268 |
|
|
0 commit comments