110 | ```
111 | The latter method is useful if you want to generate and preview the palette before caching it. There are many other cache commands like `load`, `clear`, `rename`, etc.
112 |
113 | **See [Caching](https://github.com/EmperorEntropy/PaletteSnap/wiki/Caching) for further information.**
114 |
115 | ### Color Palette
116 | Based on Everforest's palette usage,
117 | | Identifier | Usages |
118 | |---|---|
119 | | `bg` | Default Background |
120 | | `bg1` | Unused |
121 | | `bg2` | Unused |
122 | | `bg3` | Comment color |
123 | | `bg4` | Selection background color |
124 | | `bg5` | Unused |
125 | | `foreground` | Default Foreground, [_Treesitter_: Constants, Variables, Function Parameters, Properties, Symbol Identifiers] |
126 | | `red` | Conditional Keywords, Loop Keywords, Exception Keywords, Inclusion Keywords, Uncategorised Keywords, Diff Deleted Signs, Error Messages, Error Signs |
127 | | `orange` | Operator Keywords, Operators, Labels, Storage Classes, Composite Types, Enumerated Types, Tags, Title, Debugging Statements |
128 | | `yellow` | Types, Special Characters, Warning Messages, Warning Signs, [_Treesitter_: Modules, Namespaces] |
129 | | `green` | Function Names, Method Names, Strings, Characters, Hint Messages, Hint Signs, Search Highlights, [_Treesitter_: Constructors, Function Calls, Built-In Functions, Macro Functions, String Escapes, Regex Literals, Tag Delimiters, Non-Structured Text] |
130 | | `cyan` | Constants, Macros, [_Treesitter_: Strings, Characters] |
131 | | `blue` | Identifiers, Uncategorised Special Symbols, Diff Changed Text Background, Info Messages, Info Signs, [_Treesitter_: Fields, Special Punctuation, Math Environments] |
132 | | `magenta` | Booleans, Numbers, Preprocessors, [_Treesitter_: Built-In Constants, Built-In Variables, Macro-Defined Constants, Attributes/Annotations] |
133 | | `violet` | Unused. Can be used to replace `magenta` if wanted. |
134 | | `white` | ANSI white |
135 | | `black` | ANSI black |
136 |
137 | `cusor_bg` is usually just the foreground. `cursor_fg` is usually just the background.
138 |
139 | **See [Color Roles](https://github.com/EmperorEntropy/PaletteSnap/wiki/Color-Roles) for further information.**
140 |
141 | ## To Do
142 | - Add option for users to define number of bg gradients.
143 | - Add more flexibility to determine bg.
144 | - Add check for templating that returns list of variables not replaced.
145 | - Add wallpaper setting support for more Linux distributions.
146 | - Add image preview support for more terminals.
147 |
148 | ## Acknowledgments
149 | - [pywal](https://github.com/dylanaraps/pywal) for the inspiration that led me to create PaletteSnap.
150 | - Contributors in pull requests for adding or fixing features.
151 | - [ozwaldorf](https://github.com/ozwaldorf/) for helpful suggestions and advice during PaletteSnap's initial development. Check out [lutgen](https://github.com/ozwaldorf/lutgen-rs), a program that themes any image to a desktop colorscheme.
152 | - [Everforest colorscheme](https://github.com/sainnhe/everforest/) for its colorscheme usage.
153 | - [Solarized](https://github.com/altercation/solarized) and [Selenized](https://github.com/jan-warchol/selenized) colorschemes for giving me valuable insights on what a palette should be like.
154 |
--------------------------------------------------------------------------------
/palettesnap/PaletteSnap.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # Internal Modules
6 | from . import setup
7 | from .colorOptimize import performOptimal
8 | from .console import console
9 | from .colorClass import Color, rgbColor, hexColor, lchColor
10 | from .background import findBgGradient
11 |
12 | # External Modules
13 | import numpy as np
14 | import numpy.typing as npt
15 | #import sklearn
16 | from pykdtree.kdtree import KDTree
17 | from kmedoids import fasterpam
18 | from sklearn.cluster import KMeans
19 | from sklearn.metrics import pairwise_distances
20 | from colour import read_image, sRGB_to_XYZ, XYZ_to_Oklab
21 | import concurrent.futures
22 | import mixbox
23 | import os
24 | import toml
25 |
26 | # Use python3 -m pip
27 | # We use the Oklab colorspace
28 |
29 | ###
30 | # Config Functions
31 | ###
32 |
33 | def isValidHex(hexString : str) -> bool:
34 | '''returns true if string is valid hex'''
35 | # expand on this later
36 | return len(hexString) == 7
37 |
38 | def parseConfig() -> dict[str, Color]:
39 | '''parses palsnap.toml'''
40 | console.log("Defined accent colors:")
41 | accentDict = toml.load(setup.palsnapFile)
42 | # double check values
43 | for key, value in accentDict.items():
44 | if not isValidHex(value):
45 | raise Exception("palsnap.toml is wrong")
46 | accentDict = {key : hexColor(value) for key, value in accentDict.items()}
47 | console.log(accentDict)
48 | return accentDict
49 |
50 | ###
51 | # Finder Functions
52 | ##
53 | def findBackground(labColors : npt.NDArray[any], mode : str, dominant : int) -> Color:
54 | '''returns the background color for the palette'''
55 | if mode == "auto":
56 | # Apply KMeans clustering
57 | kmeans = KMeans(n_clusters=1)
58 | kmeans.fit(labColors)
59 | bgColors = kmeans.cluster_centers_
60 | bgColors = tuple(bgColors[0])
61 | return Color(*bgColors)
62 | else:
63 | # Apply KMeans clustering
64 | console.log(f"Using {dominant} dominant colors to find {mode} background color.")
65 | kmeans = KMeans(n_clusters=dominant)
66 | kmeans.fit(labColors)
67 | bgColors = kmeans.cluster_centers_
68 | bgColors = [tuple(labColor) for labColor in bgColors]
69 | bgColors = sorted(bgColors, key=lambda x: x[0])
70 | if mode == "dark":
71 | return Color(*bgColors[0])
72 | else:
73 | return Color(*bgColors[-1])
74 |
75 | def findForeground(bgColor : Color, labColors : npt.NDArray[any]) -> Color:
76 | '''returns the foreground color for the palette'''
77 | L = bgColor.L
78 | threshold = 0.33
79 | # Filter out the bad colors
80 | filterArray = np.where(abs(labColors[:,0]-L) > threshold, True, False)
81 | goodColors = labColors[filterArray]
82 | if goodColors.size == 0:
83 | raise Exception("No foreground found. Please lower threshold.")
84 | # Apply KMeans clustering
85 | kmeans = KMeans(n_clusters=1)
86 | kmeans.fit(goodColors)
87 | fgColor = kmeans.cluster_centers_
88 | fgColor = tuple(fgColor[0])
89 | fgColor = Color(*fgColor)
90 | return fgColor
91 |
92 | def filterColors(labColors : npt.NDArray[any], specialColor : tuple[int | float, int | float, int | float], numSample : int) -> Color:
93 | '''filter colors in image based on a color'''
94 | L, a, b = specialColor
95 | specialColor = np.asarray([[L, a, b]])
96 | # Filter to find a certain number of the most similar colors
97 | kd_tree = KDTree(labColors)
98 | dist, idx = kd_tree.query(specialColor, k=numSample)
99 | filteredColors = labColors[idx]
100 | # Eliminate channels
101 | height, width, channels = filteredColors.shape
102 | filteredColors = filteredColors.reshape((height * width, channels))
103 | # Use KMedoids to find best one out of the sampled colors
104 | distMatrix = pairwise_distances(filteredColors, filteredColors)
105 | dominantIdx = fasterpam(distMatrix, 1)
106 | finalColor = tuple(filteredColors[dominantIdx.medoids][0])
107 | # Convert to Color
108 | return Color(*finalColor)
109 |
110 | def findAccentColors(labColors : npt.NDArray[any], extraDict : dict[str, Color], numSample : int) -> dict[str, Color]:
111 | '''finds the accent colors'''
112 | resDict = dict()
113 | values = [(key, value.oklab) for key, value in extraDict.items()]
114 | # Get the extra colors in parallel
115 | futureDict = dict()
116 | with concurrent.futures.ThreadPoolExecutor() as executor:
117 | # Find the futures
118 | for key, value in values:
119 | future = executor.submit(filterColors, labColors, value, numSample)
120 | futureDict[future] = key
121 | # Process result
122 | for future in concurrent.futures.as_completed(futureDict):
123 | key = futureDict[future]
124 | result = future.result()
125 | resDict[key] = result
126 | # Rearrange key ordering
127 | keyOrder = list(extraDict.keys())
128 | resDict = {key : resDict[key] for key in keyOrder}
129 | return resDict
130 |
131 | def findColorHarmony(givenColor : Color) -> dict[str, Color]:
132 | '''finds colors that are harmonious with the given color'''
133 | # use Oklch color space
134 | light, chroma, hue = givenColor.oklch
135 | harmonyColors = dict()
136 | # complementary
137 | comHue = (hue + 180) % 360
138 | complementaryColor = (light, chroma, comHue)
139 | harmonyColors["complementary"] = lchColor(complementaryColor)
140 | # analogous
141 | angHue1 = (hue + 30) % 360
142 | angHue2 = (hue - 30) % 360
143 | ang1 = (light, chroma, angHue1)
144 | ang2 = (light, chroma, angHue2)
145 | harmonyColors["analogous 1"] = lchColor(ang1)
146 | harmonyColors["analogous 2"] = lchColor(ang2)
147 | # split complementary
148 | splitHue1 = (hue + 150) % 360
149 | splitHue2 = (hue + 210) % 360
150 | split1 = (light, chroma, splitHue1)
151 | split2 = (light, chroma, splitHue2)
152 | harmonyColors["split complementary 1"] = lchColor(split1)
153 | harmonyColors["split complementary 2"] = lchColor(split2)
154 | # triadic
155 | triHue1 = (hue + 120) % 360
156 | triHue2 = (hue + 240) % 360
157 | tri1 = (light, chroma, triHue1)
158 | tri2 = (light, chroma, triHue2)
159 | harmonyColors["triadic 1"] = lchColor(tri1)
160 | harmonyColors["triadic 2"] = lchColor(tri2)
161 | # square (og, 90, 180, 270)
162 | squareHue1 = (hue + 90) % 360
163 | squareHue2 = (hue + 270) % 360
164 | sq1 = (light, chroma, squareHue1)
165 | sq2 = (light, chroma, squareHue2)
166 | harmonyColors["square 1"] = lchColor(sq1)
167 | harmonyColors["square 2"] = lchColor(sq2)
168 | # tetradic (og, 60, 180, 240)
169 | tetraHue1 = (hue + 60) % 360
170 | tetraHue2 = (hue + 240) % 360
171 | tetra1 = (light, chroma, tetraHue1)
172 | tetra2 = (light, chroma, tetraHue2)
173 | harmonyColors["tetradic 1"] = lchColor(tetra1)
174 | harmonyColors["tetradic 2"] = lchColor(tetra2)
175 | return harmonyColors
176 |
177 | def findMode(bgColor : Color) -> str:
178 | '''finds the mode of the palette if given mode is auto'''
179 | if bgColor.L >= 0.5:
180 | return "light"
181 | else:
182 | return "dark"
183 |
184 | ###
185 | # Manipulation Functions
186 | ###
187 | def processImage(imgPath : str) -> npt.NDArray[any]:
188 | '''processes the image and returns an array of Oklab colors'''
189 | # Get the colors
190 | console.log(f"Reading image [u]{imgPath}[/u].")
191 | imgPath = os.path.expanduser(imgPath)
192 | rgbColors = read_image(imgPath, method="Imageio")
193 | rgbColors = rgbColors[..., 0:3]
194 | # Convert colors to Oklab color space
195 | console.log("Converting image colors to [cyan]Oklab[/cyan] color space.")
196 | xyzColors = sRGB_to_XYZ(rgbColors)
197 | labColors = XYZ_to_Oklab(xyzColors)
198 | labColors = labColors.reshape((-1,3))
199 | return labColors
200 |
201 | def adjustAccents(colorDict : dict[str, Color], iterations : int, weight : int) -> dict[str, Color]:
202 | '''adjusts lightness of accents based on background'''
203 | # based on https://github.com/jan-warchol/selenized/blob/master/balancing-lightness-of-colors.md
204 | bgLightness = colorDict["bg"].cielab[0]
205 | # adjust the lightness
206 | newAccents = performOptimal(bgLightness, colorDict, iterations, weight)
207 | return newAccents
208 |
209 | def findClosestColor(colorDict : dict[str, Color], labColor : Color) -> str:
210 | '''finds the color name in colorDict that labColor is closest to'''
211 | # find the best color name
212 | bestColor = None
213 | bestDist = float("inf")
214 | for (key, otherColor) in colorDict.items():
215 | distance = Color.findDist(labColor, otherColor)
216 | if distance < bestDist:
217 | bestDist = distance
218 | bestColor = key
219 | return bestColor
220 |
221 | def tweakColor(paletteColor : Color, orgColor : Color, hueThreshold, hueFactor, chromaThreshold, chromaFactor) -> Color:
222 | '''tweaks the chroma and hue of the color so that it is closer to the accent colors'''
223 | orgHue = orgColor.oklch[2]
224 | paletteHue = paletteColor.oklch[2]
225 | orgChroma = orgColor.oklch[1]
226 | paletteChroma = paletteColor.oklch[1]
227 | # change hue
228 | if abs(orgHue - paletteHue) > hueThreshold:
229 | newHue = orgHue * hueFactor
230 | else:
231 | newHue = paletteHue
232 | # change chroma
233 | if paletteChroma * chromaThreshold <= orgChroma:
234 | newChroma = orgChroma * chromaFactor
235 | else:
236 | newChroma = paletteChroma
237 | return lchColor((paletteColor.oklch[0], newChroma, newHue))
238 |
239 | ###
240 | # Palette Functions
241 | ###
242 | def pickColors(imgPath : str, accentColors : dict[str, Color], mode : str, dominant : int, numSample : int) -> dict[str, Color]:
243 | '''start picking colors from the image'''
244 | palette = dict()
245 | labColors = processImage(imgPath)
246 | # Background
247 | console.log("Finding background color.")
248 | bg = findBackground(labColors, mode, dominant)
249 | palette["bg"] = bg
250 | # Foreground
251 | console.log("Finding foreground color.")
252 | fg = findForeground(bg, labColors)
253 | palette["fg"] = fg
254 | # Accents
255 | console.log(f"Finding accents by sampling [magenta]{numSample} colors[/magenta].")
256 | accentColors = findAccentColors(labColors, accentColors, numSample)
257 | palette |= accentColors
258 | return palette
259 |
260 | def expandPalette(accentColors : dict[str, Color], givenColor : Color, palette : dict[str, list[Color]]) -> dict[str, list[Color]]:
261 | '''expand the modified palette with a given color by finding the corresponding label via accentColors'''
262 | harmonyColors = findColorHarmony(givenColor)
263 | # find closest color for each of the harmony colors and expand the palette
264 | for key in harmonyColors:
265 | # find the closest label
266 | currColor = harmonyColors[key]
267 | label = findClosestColor(accentColors, currColor)
268 | # expand the palette
269 | palette[label].append(currColor)
270 | return palette
271 |
272 | def exportPalette(colorDict : dict[str, Color]) -> None:
273 | '''exports the palette'''
274 | strDict = {key: value for key, value in colorDict.items() if key == "image" or key == "mode"}
275 | colorDict : dict[str, str] = {key : value.hex for key, value in colorDict.items() if key != "image" and key != "mode"}
276 | colorDict = strDict | colorDict
277 | exportPath = os.path.join(setup.cache, "palette.toml")
278 | with open(exportPath, "w") as file:
279 | toml.dump(colorDict, file)
280 | file.close()
281 |
282 | def mixPalette(accentColors : dict[str, Color], palette : dict[str, Color], mixAmount : float, mixThreshold : float) -> dict[str, Color]:
283 | '''mixes colors in palette with accent colors'''
284 | console.log(f"Mixing colors to increase color variety.")
285 | console.log(f"Mixing colors by {mixAmount * 100}% each iteration with threshold distance {mixThreshold}.")
286 | numNeeded = len(accentColors.keys())
287 | passed = set()
288 | counter = 0
289 | while len(passed) != numNeeded:
290 | for key in accentColors:
291 | counter += 1
292 | paletteColor = palette[key].rgb
293 | accentColor = accentColors[key].rgb
294 | distance = Color.findDist(palette[key], accentColors[key])
295 | # only mix by 10% if distance > 0.16
296 | if distance > mixThreshold:
297 | mixedColor = mixbox.lerp(paletteColor, accentColor, mixAmount)
298 | # set mixed colors in palette
299 | mixedColor = rgbColor(mixedColor)
300 | palette[key] = mixedColor
301 | else:
302 | passed.add(key)
303 | console.log(f"Mixed a total of {counter} times.")
304 | return palette
305 |
306 | ###
307 | # Main Function
308 | ###
309 | def extractPalette(imgPath : str, mode : str, dominant : int, extraFlag : bool, mixFlag : bool, tweakFlag : bool,
310 | numSample : int, mixAmount : float, mixThreshold : float, iterations : int, weight : int,
311 | hueThreshold, hueFactor, chromaThreshold, chromaFactor, adjust : bool) -> dict[str, Color]:
312 | '''extracts the palette from the image'''
313 | # Get the accent values
314 | console.log("Reading [u]palsnap.toml[/u].")
315 | accentColors = parseConfig()
316 |
317 | console.log("Extracting palette.")
318 | palette = pickColors(imgPath, accentColors, mode, dominant, numSample)
319 |
320 | # harmony colors
321 | if extraFlag:
322 | console.log("Finding extra colors to improve color variety.")
323 | bgColor = palette["bg"]
324 | fgColor = palette["fg"]
325 | palette = {key: [value] for key, value in palette.items()}
326 | console.log("Getting colors harmonious to background.")
327 | paletteExpanded = expandPalette(accentColors, bgColor, palette)
328 | console.log("Getting colors harmonious to foreground.")
329 | paletteExpanded = expandPalette(accentColors, fgColor, paletteExpanded)
330 | # pick the color in the expanded palette with the least difference
331 | for key in paletteExpanded:
332 | possibleColors = paletteExpanded[key]
333 | if len(possibleColors) == 1:
334 | paletteExpanded[key] = possibleColors[0]
335 | else:
336 | orgColor = accentColors[key]
337 | # find the best lab color
338 | bestColor = None
339 | bestDist = float("inf")
340 | for currColor in possibleColors:
341 | distance = Color.findDist(orgColor, currColor)
342 | if distance < bestDist:
343 | bestColor = currColor
344 | bestDist = distance
345 | paletteExpanded[key] = bestColor
346 | # overwrite palette
347 | palette : dict[str, Color] = paletteExpanded
348 |
349 | # mixing
350 | if mixFlag:
351 | palette = mixPalette(accentColors, palette, mixAmount, mixThreshold)
352 |
353 | # tweak colors
354 | if tweakFlag:
355 | console.log("Tweaking colors.")
356 | for key in accentColors:
357 | newColor = tweakColor(palette[key], accentColors[key], hueThreshold, hueFactor, chromaThreshold, chromaFactor)
358 | palette[key] = newColor
359 |
360 | # adjusting
361 | if adjust:
362 | console.log(f"Adjusting palette with uniqueness weight {weight}.")
363 | newPalette = adjustAccents(palette, iterations, weight)
364 | palette = {**palette, **newPalette}
365 |
366 | # add image path and mode
367 | palette["image"] = imgPath
368 | if mode == "auto":
369 | palette["mode"] = findMode(palette["bg"])
370 | else:
371 | palette["mode"] = mode
372 |
373 | # generate background gradient
374 | console.log("Generating background gradient.")
375 | bgGradient = findBgGradient(palette["bg"], palette["fg"])
376 | palette |= bgGradient
377 |
378 | # reorder palette
379 | bgOrder = ['image', 'mode', 'bg', 'fg', 'bg1', 'bg2', 'bg3', 'bg4', 'bg5']
380 | reorderPalette = {key: palette[key] for key in bgOrder}
381 | reorderPalette.update({key: value for key, value in palette.items() if key not in reorderPalette})
382 | palette = reorderPalette
383 |
384 | # display palette
385 | console.log("Final palette:")
386 | console.log(palette)
387 |
388 | # export palette for preview
389 | console.log("Exporting palette.")
390 | exportPalette(palette)
391 |
392 | return palette
--------------------------------------------------------------------------------
/palettesnap/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EmperorEntropy/PaletteSnap/f5f3ebc1716fe1085d7090dde07dd5a1cee5ba57/palettesnap/__init__.py
--------------------------------------------------------------------------------
/palettesnap/background.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # Internal Modules
6 | from .colorClass import Color
7 |
8 | # External Modules
9 | import numpy as np
10 |
11 | def findBgGradient(bgColor : Color, fgColor : Color) -> dict[str, Color]:
12 | '''returns a spectrum'''
13 | '''determines background accent'''
14 | bgDict = dict()
15 | # Get 5 gradient colors from background to foreground
16 | gradient = np.linspace(bgColor.oklab, fgColor.oklab, 7)
17 | gradient = [Color(*tuple(labColor)) for labColor in gradient]
18 | for i in range(1, 6):
19 | bgDict[f"bg{i}"] = gradient[i]
20 | return bgDict
--------------------------------------------------------------------------------
/palettesnap/cache.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import os
7 | import toml
8 | from typer import confirm, Exit
9 | from random import choice
10 |
11 | # Internal modules
12 | from . import setup
13 | from .console import console
14 | from .colorClass import Color, hexColor
15 | from .preview import readPalette
16 | from .wallpaper import setWallpaper
17 | from .templating import exportAll
18 |
19 | ###
20 | # Cache functions
21 | ###
22 | def cacheSet(name : str) -> None:
23 | '''caches the current palette by creating the file palette in the cache dir'''
24 | console.log(f"Caching current palette to {name}.toml.")
25 | if name in ["palette"] or '.' in name:
26 | console.log("Illegal name. Please try a different name.")
27 | raise Exit()
28 | else:
29 | # check if name already exists
30 | filePath= os.path.join(setup.cache, f"{name}.toml")
31 | if os.path.exists(filePath):
32 | console.log(f"Palette with name {name} already exists in cached directory.")
33 | overwrite = confirm("Do you want to overwrite it?")
34 | if overwrite:
35 | palette = readPalette()
36 | with open(filePath, "w") as file:
37 | toml.dump(palette, file)
38 | file.close()
39 | console.log("Cache operation [green]completed[/green].")
40 | else:
41 | console.log("Cache operation [red]canceled[/red].")
42 | raise Exit()
43 | else:
44 | palette = readPalette()
45 | with open(filePath, "w") as file:
46 | toml.dump(palette, file)
47 | file.close()
48 | console.log("Cache operation [green]completed[/green].")
49 |
50 | def loadCache(name : str) -> None:
51 | '''loads the palette from the file path in the cache dir'''
52 | cacheDir = setup.cache
53 | if name == "palette":
54 | console.log("Illegal name. Please try a different name.")
55 | raise Exit()
56 | else:
57 | console.log(f"Loading cached {name}.toml.")
58 | filePath = os.path.join(cacheDir, f"{name}.toml")
59 | # check if palette exists
60 | if not os.path.exists(filePath):
61 | console.log(f"No palette with name {name} exists in cache.")
62 | console.log("[red]Failed[/red] to load cached palette.")
63 | raise Exit()
64 | else:
65 | # get the palette
66 | textPalette : dict[str, str] = toml.load(filePath)
67 | imgDict = {"image" : textPalette["image"], "mode" : textPalette["mode"]}
68 | palette : dict[str, Color] = {key: hexColor(value) for key, value in textPalette.items() if key != "image" and key != "mode"}
69 | palette = imgDict | palette
70 | console.log("Cached file [green]succesfully[/green] loaded.")
71 | # overwrite palette.toml for previewing
72 | currPalette = os.path.join(setup.cache, "palette.toml")
73 | with open(currPalette, "w") as file:
74 | toml.dump(textPalette, file)
75 | file.close()
76 | # set wallpaper background
77 | imgPath = palette["image"]
78 | setWallpaper(imgPath)
79 | # export templates
80 | exportAll(palette)
81 |
82 | def loadRandomCache():
83 | '''loads a random cached palette'''
84 | cacheDir = setup.cache
85 | console.log("Randomly picking a cached palette.")
86 | # pick a random cached palette
87 | exclude = ["palette.toml", "PaletteTest.html", "styles.css"]
88 | allFiles = os.listdir(cacheDir)
89 | cachedPalettes = [file for file in allFiles if file not in exclude]
90 | fileName = choice(cachedPalettes)
91 | console.log(f"Cached palette {fileName} chosen.")
92 | # load it
93 | name = os.path.splitext(fileName)[0]
94 | loadCache(name)
95 |
96 |
97 | def removeCache(names : list[str]) -> None:
98 | '''removes cache palettes'''
99 | cacheDir = setup.cache
100 | if "palette" in names:
101 | console.log("palette is an illegal name. It cannot be removed.")
102 | else:
103 | for name in names:
104 | filePath= os.path.join(cacheDir, f"{name}.toml")
105 | os.remove(filePath)
106 | console.log(f"Cached file {name} has been [green]succesfully[/green] cleared.")
107 |
108 | def clearCache() -> None:
109 | '''clears the cache'''
110 | cacheDir = setup.cache
111 | exclude = ["palette.toml", "PaletteTest.html", "styles.css"]
112 | for fileName in os.listdir(cacheDir):
113 | filePath = os.path.join(cacheDir, f"{fileName}")
114 | if os.path.isfile(filePath) and fileName not in exclude:
115 | os.remove(filePath)
116 | console.log("Cache directory has been [green]succesfully[/green] cleared.")
117 |
118 | def listCache() -> None:
119 | '''counts and lists the cached palettes'''
120 | cacheDir = setup.cache
121 | exclude = ["palette.toml", "PaletteTest.html", "styles.css"]
122 | allFiles = os.listdir(cacheDir)
123 | cachedPalettes = [file for file in allFiles if file not in exclude]
124 | console.log(f"There are {len(cachedPalettes)} cached palettes:")
125 | for file in cachedPalettes:
126 | console.log(f"{file}")
127 | return None
128 |
129 | def renameCache(oldName : str, newName : str) -> None:
130 | '''renames cached palette'''
131 | cacheDir = setup.cache
132 | if oldName == "palette" or newName == "palette" or '.' in oldName or '.' in newName:
133 | console.log("Illegal name. Please use different name.")
134 | raise Exit()
135 | else:
136 | oldPath = os.path.join(cacheDir, f"{oldName}.toml")
137 | newPath = os.path.join(cacheDir, f"{newName}.toml")
138 | os.rename(oldPath, newPath)
139 | console.log(f"Renamed {oldName}.toml to {newName}.toml.")
140 |
141 | def checkCache(name : str) -> bool:
142 | '''checks a cached palette and makes sure it is legal'''
143 | currPalette = list(readPalette().keys())
144 | palettePath = os.path.join(setup.cache, f"{name}")
145 | cachedPalette = list(toml.load(palettePath).keys())
146 | if currPalette == cachedPalette:
147 | return True
148 | else:
149 | return False
150 |
151 | def checkAll() -> None:
152 | '''checks all cached palettes and returns list of illegal ones'''
153 | # Get all cached palettes
154 | cacheDir = setup.cache
155 | exclude = ["palette.toml", "PaletteTest.html", "styles.css"]
156 | allFiles = os.listdir(cacheDir)
157 | cachedPalettes = [file for file in allFiles if file not in exclude]
158 | illegalPalettes = [palette for palette in cachedPalettes if not checkCache(palette)]
159 | count = len(illegalPalettes)
160 | if len(illegalPalettes) == 0:
161 | console.log("All palettes are well-defined.")
162 | else:
163 | console.log(f"[red]{count}[/red] palettes with invaild palette variable names found.")
164 | console.log(f"{illegalPalettes}")
165 |
166 |
--------------------------------------------------------------------------------
/palettesnap/cli.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # Internal Modules
6 | from . import setup
7 | from .PaletteSnap import extractPalette
8 | from .wallpaper import setWallpaper
9 | from .templating import exportAll
10 | from .console import console
11 | from .preview import previewPalette
12 | from .cache import cacheSet, loadCache, loadRandomCache, removeCache, clearCache, listCache, renameCache, checkAll
13 | from .outdatedCheck import outdatedCheck
14 |
15 | # External Modules
16 | import typer
17 | from typing_extensions import Annotated
18 | import time
19 |
20 | ###
21 | # Functions
22 | ###
23 | app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
24 | cache_app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}, help="Manipulates the cache.")
25 | app.add_typer(cache_app, name="cache")
26 |
27 | # gen command
28 | @app.command()
29 | def gen(
30 | path: Annotated[
31 | str,
32 | typer.Argument(help="Path to wallpaper image.")
33 | ],
34 | skip: Annotated[
35 | bool,
36 | typer.Option(
37 | help="Skip creating templates and setting wallpaper.", rich_help_panel="Options"
38 | ),
39 | ] = False,
40 | mode : Annotated[
41 | str,
42 | typer.Option(
43 | help="Theme mode. light, dark, or auto.", rich_help_panel="Options"
44 | ),
45 | ] = "auto",
46 | dominant : Annotated[
47 | int,
48 | typer.Option(
49 | help="Number of dominant colors to pick from image for background. Must be >= 2.", rich_help_panel="Options"
50 | ),
51 | ] = 5,
52 | extra: Annotated[
53 | bool,
54 | typer.Option(
55 | help="Toggles extra colors.", rich_help_panel="Variety"
56 | ),
57 | ] = False,
58 | mix: Annotated[
59 | bool,
60 | typer.Option(
61 | help="Toggles extra colors.", rich_help_panel="Variety"
62 | ),
63 | ] = False,
64 | tweak: Annotated[
65 | bool,
66 | typer.Option(
67 | help="Toggles extra colors.", rich_help_panel="Variety"
68 | ),
69 | ] = False,
70 | sample : Annotated[
71 | int,
72 | typer.Option(
73 | help="Number of colors to sample from images to find accent colors.",
74 | rich_help_panel="Options"
75 | ),
76 | ] = 10000,
77 | mixAmount: Annotated[
78 | float,
79 | typer.Option(
80 | "--mixAmount", "-ma",
81 | help="Percentage amount accent color should be mixed with picked color. 0 to 1.", rich_help_panel="Mix Settings"
82 | ),
83 | ] = 0.1,
84 | mixThreshold : Annotated[
85 | float,
86 | typer.Option(
87 | "--mixThreshold", "-mt",
88 | help="Distance threshold for mixing.", rich_help_panel="Mix Settings"
89 | ),
90 | ] = 0.16,
91 | iterations : Annotated[
92 | int,
93 | typer.Option(
94 | "--iterations", "-i",
95 | help="Number of iterations for optimization to run.",
96 | rich_help_panel="Options"
97 | ),
98 | ] = 10000,
99 | weight : Annotated[
100 | int,
101 | typer.Option(
102 | help="Uniqueness weight for optimization. Larger means more unique.",
103 | rich_help_panel="Options"
104 | ),
105 | ] = 100,
106 | cache : Annotated[
107 | str,
108 | typer.Option(
109 | help="Name of palette to be cached as.",
110 | rich_help_panel="Options"
111 | ),
112 | ] = None,
113 | hueThreshold : Annotated[
114 | float,
115 | typer.Option(
116 | "--hueThreshold", "-ht",
117 | help="Distance threshold for hue.", rich_help_panel="Tweak Settings"
118 | ),
119 | ] = 10.0,
120 | hueFactor : Annotated[
121 | float,
122 | typer.Option(
123 | "--hueFactor", "-hf",
124 | help="Factor of defined accent color hue. 0 to 1.", rich_help_panel="Tweak Settings"
125 | ),
126 | ] = 1.0,
127 | chromaThreshold : Annotated[
128 | float,
129 | typer.Option(
130 | "--chromaThreshold", "-ct",
131 | help="Threshold for chroma.", rich_help_panel="Tweak Settings"
132 | ),
133 | ] = 3.0,
134 | chromaFactor : Annotated[
135 | float,
136 | typer.Option(
137 | "--chromaFactor", "-cf",
138 | help="Factor of defined accent color chroma. 0 to 1.", rich_help_panel="Tweak Settings"
139 | ),
140 | ] = 0.25,
141 | ):
142 | '''
143 | Generates color palette given path to image and optional arguments.
144 | '''
145 | outdatedCheck()
146 | # precheck
147 | if mode not in ["auto", "light", "dark"]:
148 | raise typer.BadParameter(f"{mode} is not a valid mode. Allowed values are auto, light, and dark.")
149 | if dominant <= 1:
150 | console.log("Illegal number of dominant colors.")
151 | if mode == "auto" and dominant != 5:
152 | console.log("Mode must not be auto for dominant option to be used.")
153 | # functionality
154 | if skip:
155 | start = time.time()
156 | # Only create palette
157 | palette = extractPalette(path, mode, dominant, extra, mix, tweak, sample, mixAmount, mixThreshold, iterations, weight, hueThreshold, hueFactor, chromaThreshold, chromaFactor, True)
158 | # cache
159 | if cache is not None:
160 | cacheSet(cache)
161 | end = time.time()
162 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
163 | else:
164 | start = time.time()
165 | # extract palette
166 | palette = extractPalette(path, mode, dominant, extra, mix, tweak, sample, mixAmount, mixThreshold, iterations, weight, hueThreshold, hueFactor, chromaThreshold, chromaFactor, True)
167 | # set wallpaper background
168 | setWallpaper(path)
169 | # export templates
170 | exportAll(palette)
171 | # cache
172 | if cache is not None:
173 | cacheSet(cache)
174 | end = time.time()
175 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
176 |
177 | # preview command
178 | @app.command()
179 | def preview(
180 | image : Annotated[
181 | bool,
182 | typer.Option(
183 | help="Flag for image in preview output.",
184 | rich_help_panel="Customization"
185 | ),
186 | ] = True,
187 | ):
188 | '''Previews current color palette.'''
189 | outdatedCheck()
190 | previewPalette(image)
191 |
192 | ###
193 | # cache command
194 | ###
195 |
196 | @cache_app.command("set")
197 | def cache_set(
198 | name: Annotated[
199 | str,
200 | typer.Argument(help="Name of cached palette to set to.")
201 | ],
202 | ):
203 | '''Caches the current color palette with the given name.'''
204 | start = time.time()
205 | outdatedCheck()
206 | cacheSet(name)
207 | end = time.time()
208 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
209 |
210 | @cache_app.command("load")
211 | def cache_load(
212 | name: Annotated[
213 | str,
214 | typer.Argument(help="Name of cached palette.")
215 | ],
216 | ):
217 | '''Loads the cached palette given a name.'''
218 | start = time.time()
219 | outdatedCheck()
220 | loadCache(name)
221 | end = time.time()
222 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
223 |
224 | @cache_app.command("random")
225 | def cache_random():
226 | '''Loads a random cached palette.'''
227 | start = time.time()
228 | outdatedCheck()
229 | loadRandomCache()
230 | end = time.time()
231 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
232 |
233 | @cache_app.command("remove")
234 | def cache_remove(
235 | names: Annotated[
236 | list[str],
237 | typer.Argument(help="Names of cached palette.")
238 | ],
239 | ):
240 | '''Removes cached palettes.'''
241 | start = time.time()
242 | outdatedCheck()
243 | removeCache(names)
244 | end = time.time()
245 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
246 |
247 | @cache_app.command("clear")
248 | def cache_clear(
249 | skip: Annotated[
250 | bool,
251 | typer.Option(help="Skips the confirmation prompt.")
252 | ] = False,
253 | ):
254 | '''Clears all cached palettes.'''
255 | start = time.time()
256 | outdatedCheck()
257 | if not skip:
258 | prompt = typer.confirm("Are you sure you want to clear the entire cache?")
259 | if prompt:
260 | clearCache()
261 | else:
262 | console.log()
263 | raise typer.Exit()
264 | else:
265 | clearCache()
266 | end = time.time()
267 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
268 |
269 | @cache_app.command("list")
270 | def cache_list():
271 | '''Lists all cached palettes.'''
272 | start = time.time()
273 | outdatedCheck()
274 | listCache()
275 | end = time.time()
276 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
277 |
278 | @cache_app.command("rename")
279 | def cache_rename(
280 | oldName: Annotated[
281 | str,
282 | typer.Argument(help="Present name of cached palette.")
283 | ],
284 | newName : Annotated[
285 | str,
286 | typer.Argument(help="New name for cached palette.")
287 | ],
288 | ):
289 | '''Renames a cached palette.'''
290 | start = time.time()
291 | outdatedCheck()
292 | renameCache(oldName, newName)
293 | end = time.time()
294 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
295 |
296 | @cache_app.command("check")
297 | def cache_check():
298 | '''Checks all the cached palettes.'''
299 | start = time.time()
300 | outdatedCheck()
301 | checkAll()
302 | end = time.time()
303 | console.log(f"Process [green]completed[/green] in {end-start} seconds.")
304 |
--------------------------------------------------------------------------------
/palettesnap/colorClass.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 | import numpy as np
5 | from colour import Oklab_to_XYZ, XYZ_to_sRGB, RGB_to_HSL, sRGB_to_XYZ, XYZ_to_Lab, XYZ_to_Oklab, Lab_to_XYZ, HSL_to_RGB
6 | from colour.models import JCh_to_Jab, Jab_to_JCh
7 | from PIL import ImageColor
8 |
9 | ###
10 | # Helper functions
11 | ###
12 | def okToNormalRgb(okColor):
13 | '''oklab color to rgb normalized'''
14 | xyzColor = Oklab_to_XYZ(list(okColor))
15 | rgbColor = XYZ_to_sRGB(xyzColor)
16 | red, green, blue = tuple(rgbColor)
17 | # Clamp color values to eliminate impossible colors
18 | [red, green, blue] = np.clip([red, green, blue], 0, 1)
19 | return (red, green, blue)
20 |
21 | def normalRgbToRgb(rgbColor):
22 | '''normalized rgb to rgb'''
23 | red, green, blue = rgbColor
24 | red = round(red * 255)
25 | green = round(green * 255)
26 | blue = round(blue * 255)
27 | return (red, green, blue)
28 |
29 | def normalRgbToHex(rgbColor):
30 | '''normalized rgb to hex'''
31 | red, green, blue = normalRgbToRgb(rgbColor)
32 | return "#{:02x}{:02x}{:02x}".format(red,green,blue)
33 |
34 | def normalRgbToHsl(rgbColor):
35 | '''normalized rgb to hsl'''
36 | hslColor = RGB_to_HSL(rgbColor)
37 | hue, saturation, light = tuple(hslColor)
38 | hue = round(hue * 360)
39 | saturation = round(saturation * 100)
40 | light = round(light * 100)
41 | return (hue, saturation, light)
42 |
43 |
44 | def normalRgbToCIE(rgbColor):
45 | '''normalized rgb to cielab'''
46 | xyzColor = sRGB_to_XYZ(list(rgbColor))
47 | cieColor = XYZ_to_Lab(xyzColor)
48 | return cieColor
49 |
50 | # used for palette extraction
51 | def hexToLab(hexColor):
52 | '''converts hex to lab color'''
53 | red, green, blue = ImageColor.getcolor(hexColor, "RGB")
54 | red /= 255
55 | green /= 255
56 | blue /= 255
57 | xyzColors = sRGB_to_XYZ([red, green, blue])
58 | labColors = XYZ_to_Oklab(xyzColors)
59 | return tuple(labColors)
60 |
61 | # used for palette extraction
62 | def hslColor(hslColor):
63 | hue, saturation, light = hslColor
64 | hue /= 360
65 | saturation /= 100
66 | light /= 100
67 | rgbColor = HSL_to_RGB([hue, saturation, light])
68 | xyzColor = sRGB_to_XYZ(rgbColor)
69 | labColor = tuple(XYZ_to_Oklab(xyzColor))
70 | return Color(*labColor)
71 |
72 | # used for optimization
73 | def cieColor(currColor):
74 | '''CIE Lab color as tuple to Color class'''
75 | xyzColor = Lab_to_XYZ(list(currColor))
76 | okLab = tuple(XYZ_to_Oklab(xyzColor))
77 | okColor = Color(*okLab)
78 | return okColor
79 |
80 | # used for palette extraction
81 | def rgbColor(rgbColor):
82 | '''rgb color to Color'''
83 | red, green, blue = rgbColor
84 | red /= 255
85 | green /= 255
86 | blue /= 255
87 | xyzColor = sRGB_to_XYZ([red, green, blue])
88 | labColor = tuple(XYZ_to_Oklab(xyzColor))
89 | return Color(*labColor)
90 |
91 | # use for palette extraction
92 | def hexColor(hexColor):
93 | '''hex color to Color'''
94 | red, green, blue = ImageColor.getcolor(hexColor, "RGB")
95 | red /= 255
96 | green /= 255
97 | blue /= 255
98 | xyzColors = sRGB_to_XYZ([red, green, blue])
99 | labColors = tuple(XYZ_to_Oklab(xyzColors))
100 | return Color(*labColors)
101 |
102 | # used for previewing
103 | def hexToRgb(hexColor):
104 | red, green, blue = ImageColor.getcolor(hexColor, "RGB")
105 | return (red, green, blue)
106 |
107 | # used for palette extraction
108 | def lchColor(lchColor):
109 | '''Oklch to Color'''
110 | labColor = tuple(JCh_to_Jab(list(lchColor)))
111 | return Color(*labColor)
112 |
113 | ###
114 | # Color class
115 | ###
116 | class Color():
117 |
118 | def __init__(self, L, a, b):
119 | '''Initialize Oklab color'''
120 | self.L = L
121 | self.a = a
122 | self.b = b
123 | # different color spaces
124 | self.oklab = (L, a, b)
125 | self.oklch = tuple(Jab_to_JCh(list(self.oklab)))
126 | self.normalRgb = okToNormalRgb(self.oklab)
127 | self.rgb = normalRgbToRgb(self.normalRgb)
128 | # we use rgb since rgb eliminates impossible colors
129 | self.hex = normalRgbToHex(self.normalRgb)
130 | self.hsl = normalRgbToHsl(self.normalRgb)
131 | self.cielab = normalRgbToCIE(self.normalRgb)
132 |
133 | def __repr__(self):
134 | '''string representation'''
135 | red, green, blue = self.rgb
136 | return f"{self.hex} \033[48;2;{red};{green};{blue}m \033[0m"
137 |
138 | def findDist(self, other):
139 | '''computes distance between two colors'''
140 | return ((self.L - other.L)**2+(self.a - other.a)**2+(self.b - other.b)**2)**0.5
141 |
142 | def lighten(self, factor : float):
143 | '''lightens color by factor percentage'''
144 | factor = 1 + factor
145 | L, a, b = self.oklab
146 | L *= factor
147 | return Color(L, a, b)
148 |
149 | def darken(self, factor : float):
150 | '''darkens color by factor percentage'''
151 | factor = 1 - factor
152 | L, a, b = self.oklab
153 | L *= factor
154 | return Color(L, a, b)
--------------------------------------------------------------------------------
/palettesnap/colorOptimize.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import numpy as np
7 | import math
8 | from scipy.optimize import minimize, Bounds
9 | import warnings
10 |
11 | # Internal modules
12 | from .console import console
13 | from .colorClass import Color, cieColor
14 |
15 |
16 | ###
17 | # Helper Functions
18 | ###
19 |
20 | # Penalty function
21 | def penalty_function(a, original, weight):
22 | # Distances
23 | deviation_penalty = math.sqrt(np.sum((a - original) ** 2))
24 | # Uniqueness cost
25 | uniqueness_penalty = np.sum([max(0, 0.1 - abs(a[i] - a[j]))**2 for i in range(len(a)) for j in range(i + 1, len(a))])
26 | return deviation_penalty + uniqueness_penalty * weight # Higher means more uniqueness
27 |
28 | def no_penalty_function(a, original):
29 | return 0
30 |
31 | # Constraints:
32 | def constraint_1(x):
33 | def inner_constraint(a):
34 | return np.abs(a - x) - 33 # Ensure abs(a_i - x) >= 33
35 | return inner_constraint
36 |
37 | def constraint_2(a):
38 | # Each pair of a_i must differ by at most 20
39 | n = len(a)
40 | constraints = []
41 | for i in range(n):
42 | for j in range(i + 1, n):
43 | constraints.append(20 - abs(a[i] - a[j]))
44 | return np.array(constraints)
45 |
46 | ###
47 | # Primary Function
48 | ###
49 |
50 | # Ignore warnings
51 | warnings.filterwarnings("ignore", message="delta_grad == 0.0. Check if the approximated function is linear.")
52 | warnings.filterwarnings("ignore", message="Singular Jacobian matrix. Using SVD decomposition to perform the factorizations.")
53 |
54 | def performOptimal(bgLight : int | float, palette : dict[str, Color], iterations : int, weight : int) -> dict[str, Color]:
55 | '''performs optimization on the lightness of the accent colors'''
56 | # set up initial values
57 | lightDict = {key: value for key, value in palette.items() if "bg" not in key}
58 | lightList = [lightDict[key].cielab[0] for key in lightDict]
59 | original_values = lightList.copy()
60 | length = len(original_values)
61 | bounds = Bounds([0] * length, [100] * length) # bounds between 0 and 100
62 | # Constraints
63 | cons = [{'type': 'ineq', 'fun': constraint_1(bgLight)},
64 | {'type': 'ineq', 'fun': constraint_2}]
65 | # extract and print result
66 | console.log(f"Optimizing palette colors for good contrast with {iterations} as maximum number of iterations.")
67 | result = minimize(penalty_function, lightList, args=(original_values, weight), bounds=bounds, constraints=cons, method='trust-constr', options={'disp': False, 'maxiter': iterations})
68 | if result.success:
69 | console.log(f"Optimization [green]succeed[/green].")
70 | optimized_values = result.x
71 | # return new accent colors
72 | newLightDict = dict(zip(lightDict.keys(), optimized_values))
73 | newAccents = dict()
74 | for key in lightDict:
75 | newColor = (newLightDict[key], lightDict[key].cielab[1], lightDict[key].cielab[2])
76 | newAccents[key] = cieColor(newColor)
77 | return newAccents
78 | else:
79 | console.log("Current optimization method [red]failed[/red].")
80 | console.log("Trying new optimization method.")
81 | # Redo bounds and constraints
82 | fgLight = palette["fg"].cielab[0]
83 | if fgLight > bgLight:
84 | lightThreshold = bgLight + 33
85 | newBounds = Bounds([lightThreshold] * length, [100] * length)
86 | else:
87 | lightThreshold = bgLight - 33
88 | newBounds = Bounds([0] * length, [lightThreshold] * length)
89 | newCons = [{'type': 'ineq', 'fun': constraint_2}]
90 | # Try it again
91 | result = minimize(penalty_function, lightList, args=(original_values, weight), bounds=newBounds, constraints=newCons, method='trust-constr', options={'disp': False, 'maxiter': iterations})
92 | if result.success:
93 | console.log("Backup optimization [green]succeed[/green].")
94 | optimized_values = result.x
95 | # return new accent colors
96 | newLightDict = dict(zip(lightDict.keys(), optimized_values))
97 | newAccents = dict()
98 | for key in lightDict:
99 | newColor = (newLightDict[key], lightDict[key].cielab[1], lightDict[key].cielab[2])
100 | newAccents[key] = cieColor(newColor)
101 | return newAccents
102 | else:
103 | console.log("Backup optimization [red]failed[/red].")
104 | console.log("Manual optimization process started.")
105 | console.log("Palette results from manual operation will not be as good as automatic optimization.")
106 | console.log("Please adjust your settings to prevent manual optimization.")
107 | console.log("Defining the [b]mode[/b] of your image usually prevents manual optimization.")
108 | fgLight = palette["fg"].cielab[0]
109 | if fgLight > bgLight:
110 | lightThreshold = bgLight + 33
111 | else:
112 | lightThreshold = bgLight - 33
113 | optimized_values = [lightThreshold] * length
114 | # return new accent colors
115 | newLightDict = dict(zip(lightDict.keys(), optimized_values))
116 | newAccents = dict()
117 | for key in lightDict:
118 | newColor = (newLightDict[key], lightDict[key].cielab[1], lightDict[key].cielab[2])
119 | newAccents[key] = cieColor(newColor)
120 | return newAccents
--------------------------------------------------------------------------------
/palettesnap/console.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Console Setup
3 | ###
4 | from rich.console import Console
5 |
6 | console = Console()
7 | console._log_render.omit_repeated_times = False
--------------------------------------------------------------------------------
/palettesnap/outdatedCheck.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import subprocess
7 | import requests
8 |
9 | # Internal modules
10 | from .console import console
11 |
12 | ###
13 | # Main functions
14 | ###
15 | def findCurrVersion() -> str | None:
16 | '''returns user-installed version'''
17 | proc = subprocess.run(['pip', 'show', 'palettesnap'], capture_output=True)
18 | if proc.returncode != 0:
19 | # pip failed to find palettesnap
20 | return None
21 | res = proc.stdout.decode("utf-8")
22 | version = res.splitlines()[1]
23 | version = version.split(" ")[1]
24 | return version
25 |
26 | def findLatestVersion() -> str | None:
27 | '''returns latest version on PyPi'''
28 | url = "https://pypi.org/pypi/palettesnap/json"
29 | try:
30 | response = requests.get(url)
31 | response.raise_for_status()
32 | version = response.json()["info"]["version"]
33 | return version
34 | except requests.exceptions.HTTPError:
35 | return None
36 |
37 | def outdatedCheck() -> None:
38 | '''check if user-installed version is outdated or not'''
39 | current = findCurrVersion()
40 | latest = findLatestVersion()
41 | if latest is None or current is None:
42 | console.log("Failed to check for palettesnap updates.")
43 | elif current != latest:
44 | console.log("Your version of palettesnap is [red]outdated[/red].")
45 | console.log(f"Current version: {current}.")
46 | console.log(f"Latest version: {latest}.")
47 | else:
48 | console.log("Your version of palettesnap is [green]up-to-date[/green].")
49 |
--------------------------------------------------------------------------------
/palettesnap/preview.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import os
7 | import subprocess
8 | import psutil
9 | import toml
10 |
11 | # Internal modules
12 | from . import setup
13 | from .console import console
14 | from .colorClass import hexToRgb
15 |
16 | ###
17 | # Terminal function
18 | ###
19 | def iTermCheck() -> bool:
20 | '''checks if terminal is iTerm2 terminal'''
21 | result = os.getenv("ITERM_SESSION_ID")
22 | return result != None
23 |
24 | def getTerminal() -> str:
25 | '''identify the terminal type'''
26 | # python script's ppid is terminal shell's pid
27 | # terminal type is shell's ppid
28 | # Get the shell's PPID
29 | shellPID = os.getppid()
30 | terminalPID = psutil.Process(shellPID).ppid()
31 | # Get the terminal type
32 | cmd = f"ps -p {terminalPID} | awk 'NR==2 {{print $NF}}'"
33 | result = subprocess.run(cmd, shell=True, capture_output=True, text=True).stdout
34 | result = result[:-1]
35 | # parse the result
36 | if "wezterm" in result or "WezTerm" in result:
37 | return "wezterm"
38 | elif "kitty" in result or "Kitty" in result:
39 | return "kitty"
40 | elif iTermCheck():
41 | return "iterm"
42 | else:
43 | return "NA"
44 |
45 | ###
46 | # Image function
47 | ###
48 |
49 | def showImage(path : str, terminal : str) -> None:
50 | '''prints the image in the terminal'''
51 | if terminal == "wezterm":
52 | subprocess.run(["wezterm", "imgcat", path, "--height", "45%"])
53 | elif terminal == "kitty":
54 | subprocess.run(["kitty", "+kitten", "icat", path])
55 | elif terminal == "iterm":
56 | subprocess.run(["imgcat", path])
57 | else:
58 | console.log("Terminal type not supported for displaying images.")
59 | console.log("Skipping set to display the image.")
60 |
61 | ###
62 | # Color function
63 | ###
64 | def readPalette() -> dict[str, str]:
65 | '''reads palette.toml and returns'''
66 | path = os.path.join(setup.cache, "palette.toml")
67 | return toml.load(path)
68 |
69 | def rgbToAnsi(fg : tuple[int, int, int], bg : tuple[int, int, int], text : str) -> str:
70 | '''returns text with fg and bg color'''
71 | fgRed, fgGreen, fgBlue = fg
72 | bgRed, bgGreen, bgBlue = bg
73 | res = f"\033[38;2;{fgRed};{fgGreen};{fgBlue}m\033[48;2;{bgRed};{bgGreen};{bgBlue}m"
74 | res = res + text + "\033[0m"
75 | return res
76 |
77 | def colorRow(label : str, hexColor : str, colorDict : dict[str, str]) -> str:
78 | '''creates a row for the colors'''
79 | maxLen = max([len(key) for key in colorDict])
80 | text = "PaletteSnap"[:maxLen]
81 | row = label + " " * (maxLen - len(label)) + "|"
82 | fgColor = hexToRgb(hexColor)
83 | for key in colorDict:
84 | bgColor = hexToRgb(colorDict[key])
85 | row = row + rgbToAnsi(fgColor, bgColor, text) + "|"
86 | row = row[:-1] + "\n"
87 | return row
88 |
89 | def printColors(colorDict : dict[str, str]) -> None:
90 | '''prints the colors in the terminal'''
91 | maxLen = max([len(key) for key in colorDict])
92 | # first row
93 | firstRow = " " * maxLen + "|"
94 | for key in colorDict:
95 | firstRow = firstRow + key + " " * (maxLen - len(key)) + "|"
96 | firstRow = firstRow[:-1] + "\n" # remove last |
97 | finalRes = firstRow
98 | # do other rows
99 | for key in colorDict:
100 | row = colorRow(key, colorDict[key], colorDict)
101 | finalRes += row
102 | print(finalRes)
103 | return None
104 |
105 | ###
106 | # Main function
107 | ###
108 | def previewPalette(imageFlag) -> None:
109 | # Get image path and colors
110 | console.log("Terminal must support [blue]24-bit / true color[/blue] for previewing to work.")
111 | if imageFlag:
112 | palette = readPalette()
113 | imgPath = palette["image"]
114 | palette.pop("image")
115 | palette.pop("mode")
116 | # Get terminal type and print image
117 | terminal = getTerminal()
118 | showImage(imgPath, terminal)
119 | # Print colors
120 | printColors(palette)
--------------------------------------------------------------------------------
/palettesnap/refresh.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 | # External modules
5 | import subprocess
6 | from os import chdir
7 | from shlex import split
8 |
9 | # Internal modules
10 | from .setup import home
11 | from .console import console
12 |
13 | ###
14 | # Helper Function
15 | ###
16 | def isProgramOpen(name):
17 | '''checks if program is currently open or not'''
18 | output = subprocess.run(f"pgrep -a {name}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
19 | if output.returncode == 0:
20 | return True
21 | else:
22 | return False
23 |
24 | ###
25 | # Main Function
26 | ###
27 | def refreshProgram(program, cmd):
28 | '''refresh opened programs'''
29 | if isProgramOpen(program):
30 | console.log(f"Refreshing [u]{program}[/u]")
31 | cmdList = split(cmd)
32 | subprocess.run(cmdList)
--------------------------------------------------------------------------------
/palettesnap/setup.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 | import os
5 | import platform
6 | import toml
7 |
8 | ###
9 | # Identify the OS
10 | ###
11 | osType = platform.system()
12 |
13 | ###
14 | # Define program directories
15 | ###
16 | # .config stores config files for PaletteSnap
17 | # .cache stores the output
18 |
19 | # Set up the environment
20 | home = os.getenv('HOME', os.getenv('USERPROFILE'))
21 | xdgCache = os.getenv('XDG_CACHE_HOME', os.path.join(home, '.cache'))
22 | xdgConfig = os.getenv('XDG_CONFIG_HOME', os.path.join(home, '.config'))
23 |
24 | # Define the directories
25 | cache = os.path.join(xdgCache, 'palsnap')
26 | config = os.path.join(xdgConfig, 'palsnap')
27 | #module = os.path.dirname(__file__)
28 |
29 | # Create the directories
30 | os.makedirs(config, exist_ok=True)
31 | os.makedirs(cache, exist_ok=True)
32 |
33 | # Create palsnap.toml
34 | defaultAccents = {
35 | "black": "#000000",
36 | "red": "#ff0000",
37 | "orange": "#ffa500",
38 | "yellow": "#ffff00",
39 | "green": "#008000",
40 | "blue": "#0000ff",
41 | "cyan": "#00ffff",
42 | "magenta": "#ff00ff",
43 | "violet": "#7f00ff",
44 | "white": "#ffffff"
45 | }
46 | palsnapFile = os.path.join(config, 'palsnap.toml')
47 | if not os.path.isfile(palsnapFile):
48 | # create the file
49 | with open(palsnapFile, 'w') as file:
50 | # default accent colors
51 | toml.dump(defaultAccents, file)
52 | file.close()
53 |
54 | # Create template.toml
55 | templateFile = os.path.join(config, 'templates.toml')
56 | if not os.path.isfile(templateFile):
57 | # create the file
58 | with open(templateFile, 'w') as file:
59 | pass
60 | file.close()
61 |
62 | # Create the template folder
63 | os.makedirs(os.path.join(config, 'templates'), exist_ok=True)
64 | templateDir = os.path.join(config, 'templates')
--------------------------------------------------------------------------------
/palettesnap/templating.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import os
7 | import toml
8 | from string import Template
9 | from re import sub, findall
10 |
11 | # Internal modules
12 | from . import setup
13 | from .refresh import refreshProgram
14 | from .console import console
15 | from .colorClass import Color
16 |
17 | ###
18 | # PaletteSnap built-in export functions
19 | ###
20 | def exportHTML():
21 | '''exports HTML template file for viewing'''
22 | path = os.path.join(setup.cache, "PaletteTest.html")
23 | htmlRes = '''
24 |
25 |
26 |
27 |
28 |
35 | PaletteSnap Test
36 |
37 |
38 | Test Terminal:
39 | from PIL import ImageColor
40 |
41 |
42 | def distance(p1, p2):
43 | x1, y1, z1 = p1
44 | x2, y2, z2 = p2
45 | return ((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2)**2
46 |
47 |
48 | class Color:
49 | def __init__(self, hex):
50 | self.hex = hex
51 | self.hexDigits = self.hex[1:]
52 | self.rgb = ImageColor.getrgb(self.hex)
53 |
54 | def __repr__(self):
55 | return "Hex string: " + self.hex
56 |
57 |
58 |
59 | '''
60 | # Overwrite file at path
61 | output = open(path, "w")
62 | output.write(htmlRes)
63 | output.close()
64 |
65 |
66 | def exportCSS(colorDict : dict[str, Color]) -> None:
67 | '''exports palette as CSS file'''
68 | path = os.path.join(setup.cache, "styles.css")
69 | cssTemplate = Template('''
70 | div {
71 | color: $fg;
72 | background-color: $bg;
73 | border: 3px solid $fg;
74 | }
75 | ::-moz-selection { /* Code for Firefox */
76 | background: $selection;
77 | }
78 | ::selection {
79 | background: $selection;
80 | }
81 | #comment {
82 | color: $comment !important;
83 | }
84 | #red {
85 | color: $red !important;
86 | }
87 | #green {
88 | color: $green !important;
89 | }
90 | #orange {
91 | color: $orange !important;
92 | }
93 | #magenta {
94 | color: $magenta !important;
95 | }
96 | #yellow {
97 | color: $yellow !important;
98 | }
99 | #blue {
100 | color: $blue !important;
101 | }
102 | #cyan {
103 | color: $cyan !important;
104 | }
105 | ''')
106 | # comment and selection color
107 | commentColor = colorDict["bg3"].hex
108 | selectionColor = colorDict["bg4"].hex
109 |
110 | cssRes = cssTemplate.substitute(
111 | fg = colorDict["fg"].hex,
112 | bg = colorDict["bg"].hex,
113 | comment = commentColor,
114 | selection = selectionColor,
115 | red = colorDict["red"].hex,
116 | green = colorDict["green"].hex,
117 | orange = colorDict["orange"].hex,
118 | magenta = colorDict["magenta"].hex,
119 | yellow = colorDict["yellow"].hex,
120 | blue = colorDict["blue"].hex,
121 | cyan = colorDict["cyan"].hex
122 | )
123 | # Overwrite file at path
124 | output = open(path, "w")
125 | output.write(cssRes)
126 | output.close()
127 |
128 | ###
129 | # Template export functions
130 | ###
131 | def replaceVar(tempContents : str, palette : dict[str, Color]) -> str:
132 | '''replaces variable in template with colors in palette'''
133 | # replace vars with colors
134 | modePattern = r"\{\{mode \| (.*?) \| (.*?)\}\}"
135 | for key in palette:
136 | if key == "image" or key == "mode":
137 | # image/mode
138 | tempContents = tempContents.replace("{{" + key + "}}", palette[key])
139 | # special case for image
140 | tempContents = tempContents.replace("{{image.name}}", os.path.basename(palette[key]))
141 | # special case for mode
142 | mode = palette["mode"]
143 | tempContents = sub(modePattern, lambda match: match.group(1) if mode == 'light' else match.group(2), tempContents)
144 | else:
145 | # hex
146 | tempContents = tempContents.replace("{{" + key + "}}", palette[key].hex)
147 | tempContents = tempContents.replace("{{" + key + ".digits}}", (palette[key].hex)[1:])
148 | # rgb
149 | tempContents = tempContents.replace("{{" + key + ".r}}", str(palette[key].rgb[0]))
150 | tempContents = tempContents.replace("{{" + key + ".g}}", str(palette[key].rgb[1]))
151 | tempContents = tempContents.replace("{{" + key + ".b}}", str(palette[key].rgb[2]))
152 | # normalized rgb
153 | tempContents = tempContents.replace("{{" + key + ".nr}}", str(palette[key].normalRgb[0]))
154 | tempContents = tempContents.replace("{{" + key + ".ng}}", str(palette[key].normalRgb[1]))
155 | tempContents = tempContents.replace("{{" + key + ".nb}}", str(palette[key].normalRgb[2]))
156 | # hsl
157 | tempContents = tempContents.replace("{{" + key + ".h}}", str(palette[key].hsl[0]))
158 | tempContents = tempContents.replace("{{" + key + ".s}}", str(palette[key].hsl[1]))
159 | tempContents = tempContents.replace("{{" + key + ".l}}", str(palette[key].hsl[2]))
160 | # lighten, darken, and mode inverse
161 | lightenPattern = r"\{\{(.*?).lighten\(([0-9.\d]+)\)\}\}"
162 | darkenPattern = r"\{\{(.*?).darken\(([0-9.\d]+)\)\}\}"
163 | modeInversePattern = r"\{\{(.*?).modeInverse\(([0-9.\d]+)\)\}\}"
164 | lightenMatches = findall(lightenPattern, tempContents)
165 | darkenMatches = findall(darkenPattern, tempContents)
166 | modeInverseMatches = findall(modeInversePattern, tempContents)
167 | for match in lightenMatches:
168 | name = match[0]
169 | value = float(match[1])
170 | if name in palette:
171 | currColor = palette[name]
172 | tempContents = tempContents.replace("{{" + name + ".lighten(" + str(value) + ")}}", Color.lighten(currColor, value).hex)
173 | else:
174 | console.log(f"Cannot lighten {name} with value {value}")
175 | for match in darkenMatches:
176 | name = match[0]
177 | value = float(match[1])
178 | if name in palette:
179 | currColor = palette[name]
180 | tempContents = tempContents.replace("{{" + name + ".darken(" + str(value) + ")}}", Color.darken(currColor, value).hex)
181 | else:
182 | console.log(f"Cannot darken {name} with value {value}")
183 | for match in modeInverseMatches:
184 | mode = palette["mode"]
185 | name = match[0]
186 | value = float(match[1])
187 | # mode inverse darkens a color if the mode is light
188 | # mode inverse lightens a color if the mode is dark
189 | if name in palette:
190 | currColor = palette[name]
191 | if mode == "light":
192 | newColor = Color.darken(currColor, value).hex
193 | else:
194 | newColor = Color.lighten(currColor, value).hex
195 | tempContents = tempContents.replace("{{" + name + ".modeInverse(" + str(value) + ")}}", newColor)
196 | else:
197 | console.log(f"Cannot apply mode inverse to {name} with value {value}")
198 | return tempContents
199 |
200 | def exportTemplates(palette : dict[str, Color]) -> None:
201 | '''export templates'''
202 | console.log("Generating templates.")
203 | templateInfo = toml.load(setup.templateFile)
204 | for program in templateInfo:
205 | programInfo : list[dict[str, str]] = templateInfo[program]
206 | for tempDict in programInfo:
207 | # extract information
208 | for key, value in tempDict.items():
209 | if key == "name":
210 | name = value
211 | elif key == "alias":
212 | alias = value
213 | elif key == "dir":
214 | dir = os.path.expanduser(value)
215 | else:
216 | cmd = value
217 | # handle empty information
218 | if alias == "":
219 | alias = name
220 | if dir == "":
221 | dir = setup.cache
222 | # replace variables for template
223 | tempLoc = os.path.join(setup.templateDir, name)
224 | exportLoc = os.path.join(dir, alias)
225 | # check if template file exists
226 | if os.path.isfile(tempLoc):
227 | file = open(tempLoc, "r")
228 | template = file.read()
229 | file.close()
230 | # generate the file based on the template
231 | newContents = replaceVar(template, palette)
232 | with open(exportLoc, "w") as newFile:
233 | newFile.write(newContents)
234 | newFile.close()
235 | console.log(f"Template {name} succesfully generated.")
236 | else:
237 | console.log(f"Template {name} does not exist.")
238 | # refresh the program
239 | if cmd != "":
240 | refreshProgram(program, cmd)
241 |
242 | ###
243 | # Export all
244 | ###
245 |
246 | def exportAll(colors : dict[str, Color]) -> None:
247 | exportHTML()
248 | #exportCSS(colors)
249 | exportTemplates(colors)
--------------------------------------------------------------------------------
/palettesnap/wallpaper.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Modules
3 | ###
4 |
5 | # External modules
6 | import os
7 | import subprocess
8 |
9 | # Interal modules
10 | from . import setup
11 | from .console import console
12 |
13 | ###
14 | # Helper functions
15 | ###
16 | def untestWarn() -> None:
17 | '''prints the untested warning message to console'''
18 | console.log("You are using one of the untested OS or Linux environments.")
19 | console.log("Please let the creator know if it works or not.")
20 | return None
21 |
22 | def findEnv():
23 | '''return the desktop environment'''
24 | env = os.getenv('XDG_CURRENT_DESKTOP', '')
25 | if "GNOME" in env:
26 | return "GNOME"
27 | elif "KDE" in env:
28 | return "KDE"
29 | elif "XFCE" in env:
30 | return "XFCE"
31 | elif "MATE" in env:
32 | return "MATE"
33 | else:
34 | return None
35 |
36 | def setMacWallpaper(imgPath):
37 | '''set the wallpaper for macOS'''
38 | subprocess.run(['osascript', '-e', 'tell application \"System Events\" to tell every desktop to set picture to \"%s\" as POSIX file' % imgPath])
39 |
40 | # based off pywal's https://raw.githubusercontent.com/dylanaraps/pywal/master/pywal/wallpaper.py
41 | def setLinuxWallpaper(env, imgPath):
42 | '''set the wallpaper for Linux'''
43 | if env == "GNOME":
44 | untestWarn()
45 | subprocess.run(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', f'file://{imgPath}'])
46 | elif env == "KDE":
47 | kdeCommand = [
48 | "qdbus",
49 | "org.kde.plasmashell",
50 | "/PlasmaShell",
51 | "org.kde.PlasmaShell.evaluateScript",
52 | f'var allDesktops = desktops();print (allDesktops);for (i=0;i=7.2)", "sphinx-copybutton"]
214 | mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"]
215 | test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
216 | test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"]
217 |
218 | [[package]]
219 | name = "cycler"
220 | version = "0.12.1"
221 | description = "Composable style cycles"
222 | optional = false
223 | python-versions = ">=3.8"
224 | files = [
225 | {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
226 | {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
227 | ]
228 |
229 | [package.extras]
230 | docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
231 | tests = ["pytest", "pytest-cov", "pytest-xdist"]
232 |
233 | [[package]]
234 | name = "fonttools"
235 | version = "4.53.1"
236 | description = "Tools to manipulate font files"
237 | optional = false
238 | python-versions = ">=3.8"
239 | files = [
240 | {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"},
241 | {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"},
242 | {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"},
243 | {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"},
244 | {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"},
245 | {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"},
246 | {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"},
247 | {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"},
248 | {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"},
249 | {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"},
250 | {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"},
251 | {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"},
252 | {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"},
253 | {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"},
254 | {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"},
255 | {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"},
256 | {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"},
257 | {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"},
258 | {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"},
259 | {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"},
260 | {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"},
261 | {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"},
262 | {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"},
263 | {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"},
264 | {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"},
265 | {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"},
266 | {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"},
267 | {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"},
268 | {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"},
269 | {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"},
270 | {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"},
271 | {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"},
272 | {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"},
273 | {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"},
274 | {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"},
275 | {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"},
276 | {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"},
277 | {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"},
278 | {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"},
279 | {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"},
280 | {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"},
281 | {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"},
282 | ]
283 |
284 | [package.extras]
285 | all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
286 | graphite = ["lz4 (>=1.7.4.2)"]
287 | interpolatable = ["munkres", "pycairo", "scipy"]
288 | lxml = ["lxml (>=4.0)"]
289 | pathops = ["skia-pathops (>=0.5.0)"]
290 | plot = ["matplotlib"]
291 | repacker = ["uharfbuzz (>=0.23.0)"]
292 | symfont = ["sympy"]
293 | type1 = ["xattr"]
294 | ufo = ["fs (>=2.2.0,<3)"]
295 | unicode = ["unicodedata2 (>=15.1.0)"]
296 | woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
297 |
298 | [[package]]
299 | name = "idna"
300 | version = "3.7"
301 | description = "Internationalized Domain Names in Applications (IDNA)"
302 | optional = false
303 | python-versions = ">=3.5"
304 | files = [
305 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
306 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
307 | ]
308 |
309 | [[package]]
310 | name = "imageio"
311 | version = "2.34.2"
312 | description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
313 | optional = false
314 | python-versions = ">=3.8"
315 | files = [
316 | {file = "imageio-2.34.2-py3-none-any.whl", hash = "sha256:a0bb27ec9d5bab36a9f4835e51b21d2cb099e1f78451441f94687ff3404b79f8"},
317 | {file = "imageio-2.34.2.tar.gz", hash = "sha256:5c0c0ee8faa018a1c42f649b90395dd4d3bb6187c09053a0cd6f1fdd51bbff5e"},
318 | ]
319 |
320 | [package.dependencies]
321 | numpy = "*"
322 | pillow = ">=8.3.2"
323 |
324 | [package.extras]
325 | all-plugins = ["astropy", "av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"]
326 | all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"]
327 | build = ["wheel"]
328 | dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"]
329 | docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"]
330 | ffmpeg = ["imageio-ffmpeg", "psutil"]
331 | fits = ["astropy"]
332 | full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"]
333 | gdal = ["gdal"]
334 | itk = ["itk"]
335 | linting = ["black", "flake8"]
336 | pillow-heif = ["pillow-heif"]
337 | pyav = ["av"]
338 | test = ["fsspec[github]", "pytest", "pytest-cov"]
339 | tifffile = ["tifffile"]
340 |
341 | [[package]]
342 | name = "joblib"
343 | version = "1.4.2"
344 | description = "Lightweight pipelining with Python functions"
345 | optional = false
346 | python-versions = ">=3.8"
347 | files = [
348 | {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"},
349 | {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
350 | ]
351 |
352 | [[package]]
353 | name = "kiwisolver"
354 | version = "1.4.5"
355 | description = "A fast implementation of the Cassowary constraint solver"
356 | optional = false
357 | python-versions = ">=3.7"
358 | files = [
359 | {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"},
360 | {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"},
361 | {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"},
362 | {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"},
363 | {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"},
364 | {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"},
365 | {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"},
366 | {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"},
367 | {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"},
368 | {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"},
369 | {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"},
370 | {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"},
371 | {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"},
372 | {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"},
373 | {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"},
374 | {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"},
375 | {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"},
376 | {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"},
377 | {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"},
378 | {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"},
379 | {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"},
380 | {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"},
381 | {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"},
382 | {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"},
383 | {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"},
384 | {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"},
385 | {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"},
386 | {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"},
387 | {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"},
388 | {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"},
389 | {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"},
390 | {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"},
391 | {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"},
392 | {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"},
393 | {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"},
394 | {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"},
395 | {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"},
396 | {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"},
397 | {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"},
398 | {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"},
399 | {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"},
400 | {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"},
401 | {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"},
402 | {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"},
403 | {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"},
404 | {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"},
405 | {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"},
406 | {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"},
407 | {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"},
408 | {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"},
409 | {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"},
410 | {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"},
411 | {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"},
412 | {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"},
413 | {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"},
414 | {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"},
415 | {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"},
416 | {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"},
417 | {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"},
418 | {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"},
419 | {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"},
420 | {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"},
421 | {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"},
422 | {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"},
423 | {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"},
424 | {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"},
425 | {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"},
426 | {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"},
427 | {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"},
428 | {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"},
429 | {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"},
430 | {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"},
431 | {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"},
432 | {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"},
433 | {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"},
434 | {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"},
435 | {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"},
436 | {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"},
437 | {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"},
438 | {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"},
439 | {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"},
440 | {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"},
441 | {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"},
442 | {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"},
443 | {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"},
444 | {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"},
445 | {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"},
446 | {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"},
447 | {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"},
448 | {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"},
449 | {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"},
450 | {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"},
451 | {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"},
452 | {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"},
453 | {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"},
454 | {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"},
455 | {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"},
456 | {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"},
457 | {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"},
458 | {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"},
459 | {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"},
460 | {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"},
461 | {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"},
462 | {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"},
463 | ]
464 |
465 | [[package]]
466 | name = "kmedoids"
467 | version = "0.5.1"
468 | description = "k-Medoids Clustering in Python with FasterPAM"
469 | optional = false
470 | python-versions = "*"
471 | files = [
472 | {file = "kmedoids-0.5.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2a5c6bac64d82adf29d4aa4989938789895eb8a1fee1c51d696db17a6d81f944"},
473 | {file = "kmedoids-0.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:731a2b2ef2ee2ee169b299a341d1cc678da31e31567d3120712f2cf9a5b3361b"},
474 | {file = "kmedoids-0.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c39559ab88d7fdc205a20bf99ad973cc0700dea33679ba1283b1d290babbfd7"},
475 | {file = "kmedoids-0.5.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c589080866b61680712563d92998ac54bf5fd9bffe5f37d311cb490ca1fdba8b"},
476 | {file = "kmedoids-0.5.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:453ba9336bb0f30be65b8e8a9a421d8cff0feadc1751880be18d66bba835dd46"},
477 | {file = "kmedoids-0.5.1-cp310-none-win_amd64.whl", hash = "sha256:4215466abbafae42e107b115562acbd9ace8b760e8f347ef59316dc9e7b3a6e2"},
478 | {file = "kmedoids-0.5.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:78d5bf0d6f8d72c402f5f4df643a4bba0df5ad3f915028408db97523ea771cdf"},
479 | {file = "kmedoids-0.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761ea521dd1d90ca64c6a63fa9d2ff88fe83b7ac85c82a76b52795ca8d20441d"},
480 | {file = "kmedoids-0.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:763e5af82194127f7e84c2753b9beccc1bcc7962b37cca9ba0ece777759a6658"},
481 | {file = "kmedoids-0.5.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3b1297600886d50512a7d923633820698ef2d68d10b985a70ae32ea5e7cd8c37"},
482 | {file = "kmedoids-0.5.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:91eb445825c7f59b338f227b88cc52c4ee1589a821b31c21c1f37f8723a154da"},
483 | {file = "kmedoids-0.5.1-cp311-none-win_amd64.whl", hash = "sha256:4935cbc691396bb5b6e9eb905d298a33f027fa6e81ab9fe7735bf5c56531b14b"},
484 | {file = "kmedoids-0.5.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:105aff65add39542524d121bf66b48909bef5dd06fbe7b6209f697eea1820058"},
485 | {file = "kmedoids-0.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eba433df1639bbf8cdf73c5c284811d118576f341d37e35982d68d4e7ff94cf1"},
486 | {file = "kmedoids-0.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b775c73055543d88915286f9833cb579ac2a19c6e4f766539457350b682ce779"},
487 | {file = "kmedoids-0.5.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3d1e5137421c5c4f1e9cbeb50653fa2411db33d59a3edc3c54b6f136b7071ef4"},
488 | {file = "kmedoids-0.5.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2949ddf69cd1ed610ac411cd1a2351c86f66bcda343aa956343fcf75efa95dd1"},
489 | {file = "kmedoids-0.5.1-cp312-none-win_amd64.whl", hash = "sha256:fe96d9f072537670f370d7eb2352e98c23a2d65374889701e375b612e549a4ac"},
490 | {file = "kmedoids-0.5.1-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e52eb9f5b6ed48c8972cfb75f59bf0f64045208ab86fec549e18cca9c675f0c"},
491 | {file = "kmedoids-0.5.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bae9f473cce6952add4ec8596ce3c559d20156c54a62af2a5a32967d20019667"},
492 | {file = "kmedoids-0.5.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f9b66be7a5dcca6c49815bf450424e1aa4a012abafdb28f84def5c882a0f241c"},
493 | {file = "kmedoids-0.5.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:a64e7a64edd25d62400e882a9276ea0a144d86f2b5662b3af312ca779507d94c"},
494 | {file = "kmedoids-0.5.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:eb98d48bb68c0683ed6a22ddac84db988199ce517652a0c12e1c83400f21ef04"},
495 | {file = "kmedoids-0.5.1-cp38-none-win_amd64.whl", hash = "sha256:5d233e7b354f1e7e293b413ff65a248c16dbd58a67e4c9bd8d23117ea682ebf5"},
496 | {file = "kmedoids-0.5.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6b39d64e3938e8f6dcca8ac1c76f71b0884c62a4eaaee86519cf1a63c0a46763"},
497 | {file = "kmedoids-0.5.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:802be8fd10cee053f74a612cd5146d80f02e8d4d683c889db5f8e6d6fcc32774"},
498 | {file = "kmedoids-0.5.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b4cfbc10b71a98f64fb17a0932c42ef866b14c702b4f9f0552512ce027afe6e"},
499 | {file = "kmedoids-0.5.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f6774988d2ca249ec8a0c9539ebbedd7f32e853b6b20fd390d22e3741792ac59"},
500 | {file = "kmedoids-0.5.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ff94bf47e9948ff6b34144e00bd91286af9cc2c78092f2adefa24a016b42fdbc"},
501 | {file = "kmedoids-0.5.1-cp39-none-win_amd64.whl", hash = "sha256:bcf25bb9b04c63cc21df36514e726892301c2ab5c2ae8a0fd93be5034b751567"},
502 | ]
503 |
504 | [[package]]
505 | name = "markdown-it-py"
506 | version = "3.0.0"
507 | description = "Python port of markdown-it. Markdown parsing, done right!"
508 | optional = false
509 | python-versions = ">=3.8"
510 | files = [
511 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
512 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
513 | ]
514 |
515 | [package.dependencies]
516 | mdurl = ">=0.1,<1.0"
517 |
518 | [package.extras]
519 | benchmarking = ["psutil", "pytest", "pytest-benchmark"]
520 | code-style = ["pre-commit (>=3.0,<4.0)"]
521 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
522 | linkify = ["linkify-it-py (>=1,<3)"]
523 | plugins = ["mdit-py-plugins"]
524 | profiling = ["gprof2dot"]
525 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
526 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
527 |
528 | [[package]]
529 | name = "matplotlib"
530 | version = "3.9.0"
531 | description = "Python plotting package"
532 | optional = false
533 | python-versions = ">=3.9"
534 | files = [
535 | {file = "matplotlib-3.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56"},
536 | {file = "matplotlib-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b"},
537 | {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241"},
538 | {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d"},
539 | {file = "matplotlib-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4"},
540 | {file = "matplotlib-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463"},
541 | {file = "matplotlib-3.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38"},
542 | {file = "matplotlib-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152"},
543 | {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85"},
544 | {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb"},
545 | {file = "matplotlib-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674"},
546 | {file = "matplotlib-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be"},
547 | {file = "matplotlib-3.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382"},
548 | {file = "matplotlib-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84"},
549 | {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5"},
550 | {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db"},
551 | {file = "matplotlib-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7"},
552 | {file = "matplotlib-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf"},
553 | {file = "matplotlib-3.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956"},
554 | {file = "matplotlib-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a"},
555 | {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321"},
556 | {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89"},
557 | {file = "matplotlib-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b"},
558 | {file = "matplotlib-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888"},
559 | {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0"},
560 | {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03"},
561 | {file = "matplotlib-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd"},
562 | {file = "matplotlib-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e"},
563 | {file = "matplotlib-3.9.0.tar.gz", hash = "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a"},
564 | ]
565 |
566 | [package.dependencies]
567 | contourpy = ">=1.0.1"
568 | cycler = ">=0.10"
569 | fonttools = ">=4.22.0"
570 | kiwisolver = ">=1.3.1"
571 | numpy = ">=1.23"
572 | packaging = ">=20.0"
573 | pillow = ">=8"
574 | pyparsing = ">=2.3.1"
575 | python-dateutil = ">=2.7"
576 |
577 | [package.extras]
578 | dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"]
579 |
580 | [[package]]
581 | name = "mdurl"
582 | version = "0.1.2"
583 | description = "Markdown URL utilities"
584 | optional = false
585 | python-versions = ">=3.7"
586 | files = [
587 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
588 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
589 | ]
590 |
591 | [[package]]
592 | name = "numpy"
593 | version = "1.26.4"
594 | description = "Fundamental package for array computing in Python"
595 | optional = false
596 | python-versions = ">=3.9"
597 | files = [
598 | {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
599 | {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
600 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
601 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
602 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
603 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
604 | {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
605 | {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
606 | {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
607 | {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
608 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
609 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
610 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
611 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
612 | {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
613 | {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
614 | {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
615 | {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
616 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
617 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
618 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
619 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
620 | {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
621 | {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
622 | {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
623 | {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
624 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
625 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
626 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
627 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
628 | {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
629 | {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
630 | {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
631 | {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
632 | {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
633 | {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
634 | ]
635 |
636 | [[package]]
637 | name = "packaging"
638 | version = "24.1"
639 | description = "Core utilities for Python packages"
640 | optional = false
641 | python-versions = ">=3.8"
642 | files = [
643 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
644 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
645 | ]
646 |
647 | [[package]]
648 | name = "pillow"
649 | version = "10.4.0"
650 | description = "Python Imaging Library (Fork)"
651 | optional = false
652 | python-versions = ">=3.8"
653 | files = [
654 | {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
655 | {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
656 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
657 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
658 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
659 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
660 | {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
661 | {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
662 | {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
663 | {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
664 | {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
665 | {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
666 | {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
667 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
668 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
669 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
670 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
671 | {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
672 | {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
673 | {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
674 | {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
675 | {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
676 | {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
677 | {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
678 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
679 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
680 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
681 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
682 | {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
683 | {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
684 | {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
685 | {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
686 | {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
687 | {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"},
688 | {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"},
689 | {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"},
690 | {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"},
691 | {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"},
692 | {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"},
693 | {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"},
694 | {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"},
695 | {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"},
696 | {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"},
697 | {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"},
698 | {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"},
699 | {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"},
700 | {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"},
701 | {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"},
702 | {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"},
703 | {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"},
704 | {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"},
705 | {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"},
706 | {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"},
707 | {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"},
708 | {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
709 | {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
710 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
711 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
712 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
713 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
714 | {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
715 | {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
716 | {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
717 | {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
718 | {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
719 | {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
720 | {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
721 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
722 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
723 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
724 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
725 | {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
726 | {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
727 | {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
728 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
729 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
730 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
731 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
732 | {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
733 | {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
734 | ]
735 |
736 | [package.extras]
737 | docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
738 | fpx = ["olefile"]
739 | mic = ["olefile"]
740 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
741 | typing = ["typing-extensions"]
742 | xmp = ["defusedxml"]
743 |
744 | [[package]]
745 | name = "psutil"
746 | version = "6.0.0"
747 | description = "Cross-platform lib for process and system monitoring in Python."
748 | optional = false
749 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
750 | files = [
751 | {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
752 | {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
753 | {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
754 | {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
755 | {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
756 | {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
757 | {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
758 | {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
759 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
760 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
761 | {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
762 | {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
763 | {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
764 | {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
765 | {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
766 | {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
767 | {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
768 | ]
769 |
770 | [package.extras]
771 | test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
772 |
773 | [[package]]
774 | name = "pygments"
775 | version = "2.18.0"
776 | description = "Pygments is a syntax highlighting package written in Python."
777 | optional = false
778 | python-versions = ">=3.8"
779 | files = [
780 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
781 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
782 | ]
783 |
784 | [package.extras]
785 | windows-terminal = ["colorama (>=0.4.6)"]
786 |
787 | [[package]]
788 | name = "pykdtree"
789 | version = "1.3.12"
790 | description = "Fast kd-tree implementation with OpenMP-enabled queries"
791 | optional = false
792 | python-versions = ">=3.9"
793 | files = [
794 | {file = "pykdtree-1.3.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a072b845cc2983eb79fbfa9baeda4f4b3c3e6db0a54e1c89434d353246e66104"},
795 | {file = "pykdtree-1.3.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76d7f9ba21864701ac1206038abaf7613f35e96312731df770327e763ebb8d3a"},
796 | {file = "pykdtree-1.3.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:431194955fd4f2be298a778643e36392bda9c43101dcb719146a050f3455d8cc"},
797 | {file = "pykdtree-1.3.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7437e854b976c51fb1781392d148d6d9244945e1504e49bf490370464848260"},
798 | {file = "pykdtree-1.3.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:046efd4607208d6edeedee3d67754c60118086c6ca6a83c33ee01ec70749ea51"},
799 | {file = "pykdtree-1.3.12-cp310-cp310-win_amd64.whl", hash = "sha256:e3ef09d033e4af683b08cde01a4f3c96d4a96329aa6e3135095325befac28007"},
800 | {file = "pykdtree-1.3.12-cp310-cp310-win_arm64.whl", hash = "sha256:3ebab60fa3dab12791fd04987aef53315b8acae196f45e6bad58a2086c1b3436"},
801 | {file = "pykdtree-1.3.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83263e154871934423850d3b6eaaba6eb38463806d76313cbfa8798ecd7ca0a3"},
802 | {file = "pykdtree-1.3.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8079f357d2358d4c20b2cb779d36add251626ebab1aaf2e03ce4e70355eff49"},
803 | {file = "pykdtree-1.3.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7771f1151e97080c3efb38e444ed6912dede203ca7e1d415f9b43f880cf92bcb"},
804 | {file = "pykdtree-1.3.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7882706b092b501dd4e74d7658d9ab06d1eac2f88e819877a52cd7441b21a806"},
805 | {file = "pykdtree-1.3.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f64e2dcc603c436ec7fd00c975f38537930cf1097e6ae4d9a80e1e900de528"},
806 | {file = "pykdtree-1.3.12-cp311-cp311-win_amd64.whl", hash = "sha256:1b9473cc5c62f1a7e61f3cabe5d4098b9ec61a63040e1bbd74e5d4482c3603d9"},
807 | {file = "pykdtree-1.3.12-cp311-cp311-win_arm64.whl", hash = "sha256:199585ff5a41c5f383bef3a68cff5c651b342045a8c7409576e3b17fd8a57f4e"},
808 | {file = "pykdtree-1.3.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d20a8f54d29d255fad4bac26e80231067d9331bb9aacc2ee6743e4325b346fa"},
809 | {file = "pykdtree-1.3.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f985d1b742452ff597fe9c9a0ea2c89cd85b298bcf3f6a565fa0d7ce66eb313f"},
810 | {file = "pykdtree-1.3.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41020443270ef8aee5caf513d8a060126743a0aee19d33d35a6684fa2d37c13a"},
811 | {file = "pykdtree-1.3.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88f52541f094ada031cee6ab44864d25d41ede97dbdb78cf55f5e9919e366841"},
812 | {file = "pykdtree-1.3.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3fd826f886bff82b4c25cdc61df5142e309a45421388de2922bd315995d7fe8f"},
813 | {file = "pykdtree-1.3.12-cp312-cp312-win_amd64.whl", hash = "sha256:c79d6db009714c1b2092366c78c5ffc645cfe5b7103ed4f94eabba3833b70088"},
814 | {file = "pykdtree-1.3.12-cp312-cp312-win_arm64.whl", hash = "sha256:67a87b1a4f9ea484a3269cb7f468e3993bc03bc984ce63215302280061e5b691"},
815 | {file = "pykdtree-1.3.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c72357382c37e8d9af7e6b0257340a4c37bc20c9034559af11527034ba3e7b2"},
816 | {file = "pykdtree-1.3.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9ba445012613a5284bfb57e119019138b15b363d39246a618d2e9994505baa2e"},
817 | {file = "pykdtree-1.3.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfe19bab0e7bf92e69657d5c03b3fabc89c2afed64ae394700d09e00b86b07f"},
818 | {file = "pykdtree-1.3.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba7d8dbd5976a029b59d5c9893fe0448f9c452ca3b08aaf9be4d8262fbeab42f"},
819 | {file = "pykdtree-1.3.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e171cc813c54ff4cffce844230575334c7298aea0c78c1b33a533995608fce5"},
820 | {file = "pykdtree-1.3.12-cp39-cp39-win_amd64.whl", hash = "sha256:b6cc13f4cc1a5ec967d47db088b6423a629127c43184e5857c631ccc64a8c590"},
821 | {file = "pykdtree-1.3.12-cp39-cp39-win_arm64.whl", hash = "sha256:2e23bdaff080affc415b614f2cb230616fc4c4095b56bbb614c56b67f11f3ed1"},
822 | {file = "pykdtree-1.3.12.tar.gz", hash = "sha256:cc20b2a67c64056485a314d2c2b6dba354af7ee1c8fb8dae1be6f2936a374341"},
823 | ]
824 |
825 | [package.dependencies]
826 | numpy = "*"
827 |
828 | [[package]]
829 | name = "pymixbox"
830 | version = "2.0.0"
831 | description = "Pigment-Based Color Mixing"
832 | optional = false
833 | python-versions = "*"
834 | files = [
835 | {file = "pymixbox-2.0.0-py2.py3-none-any.whl", hash = "sha256:ac645ee27989ee96622579250ccc057ede2cc57847c56c73082eae2e47341aa4"},
836 | {file = "pymixbox-2.0.0.tar.gz", hash = "sha256:7f1fc8e950778efedfc20bbeeaf240576baca06726b3ce83a97ce507f226aee4"},
837 | ]
838 |
839 | [[package]]
840 | name = "pyparsing"
841 | version = "3.1.2"
842 | description = "pyparsing module - Classes and methods to define and execute parsing grammars"
843 | optional = false
844 | python-versions = ">=3.6.8"
845 | files = [
846 | {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
847 | {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
848 | ]
849 |
850 | [package.extras]
851 | diagrams = ["jinja2", "railroad-diagrams"]
852 |
853 | [[package]]
854 | name = "python-dateutil"
855 | version = "2.9.0.post0"
856 | description = "Extensions to the standard Python datetime module"
857 | optional = false
858 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
859 | files = [
860 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
861 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
862 | ]
863 |
864 | [package.dependencies]
865 | six = ">=1.5"
866 |
867 | [[package]]
868 | name = "requests"
869 | version = "2.32.3"
870 | description = "Python HTTP for Humans."
871 | optional = false
872 | python-versions = ">=3.8"
873 | files = [
874 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
875 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
876 | ]
877 |
878 | [package.dependencies]
879 | certifi = ">=2017.4.17"
880 | charset-normalizer = ">=2,<4"
881 | idna = ">=2.5,<4"
882 | urllib3 = ">=1.21.1,<3"
883 |
884 | [package.extras]
885 | socks = ["PySocks (>=1.5.6,!=1.5.7)"]
886 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
887 |
888 | [[package]]
889 | name = "rich"
890 | version = "13.7.1"
891 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
892 | optional = false
893 | python-versions = ">=3.7.0"
894 | files = [
895 | {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
896 | {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
897 | ]
898 |
899 | [package.dependencies]
900 | markdown-it-py = ">=2.2.0"
901 | pygments = ">=2.13.0,<3.0.0"
902 |
903 | [package.extras]
904 | jupyter = ["ipywidgets (>=7.5.1,<9)"]
905 |
906 | [[package]]
907 | name = "scikit-learn"
908 | version = "1.5.1"
909 | description = "A set of python modules for machine learning and data mining"
910 | optional = false
911 | python-versions = ">=3.9"
912 | files = [
913 | {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"},
914 | {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"},
915 | {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"},
916 | {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"},
917 | {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"},
918 | {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"},
919 | {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"},
920 | {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"},
921 | {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"},
922 | {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"},
923 | {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"},
924 | {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"},
925 | {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"},
926 | {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"},
927 | {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"},
928 | {file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"},
929 | {file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"},
930 | {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"},
931 | {file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"},
932 | {file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"},
933 | {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"},
934 | ]
935 |
936 | [package.dependencies]
937 | joblib = ">=1.2.0"
938 | numpy = ">=1.19.5"
939 | scipy = ">=1.6.0"
940 | threadpoolctl = ">=3.1.0"
941 |
942 | [package.extras]
943 | benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"]
944 | build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"]
945 | docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"]
946 | examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"]
947 | install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"]
948 | maintenance = ["conda-lock (==2.5.6)"]
949 | tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"]
950 |
951 | [[package]]
952 | name = "scipy"
953 | version = "1.14.0"
954 | description = "Fundamental algorithms for scientific computing in Python"
955 | optional = false
956 | python-versions = ">=3.10"
957 | files = [
958 | {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"},
959 | {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"},
960 | {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"},
961 | {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"},
962 | {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"},
963 | {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"},
964 | {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"},
965 | {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"},
966 | {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"},
967 | {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"},
968 | {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"},
969 | {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"},
970 | {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"},
971 | {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"},
972 | {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"},
973 | {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"},
974 | {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"},
975 | {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"},
976 | {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"},
977 | {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"},
978 | {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"},
979 | {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"},
980 | {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"},
981 | {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"},
982 | {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"},
983 | ]
984 |
985 | [package.dependencies]
986 | numpy = ">=1.23.5,<2.3"
987 |
988 | [package.extras]
989 | dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
990 | doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"]
991 | test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
992 |
993 | [[package]]
994 | name = "shellingham"
995 | version = "1.5.4"
996 | description = "Tool to Detect Surrounding Shell"
997 | optional = false
998 | python-versions = ">=3.7"
999 | files = [
1000 | {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
1001 | {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
1002 | ]
1003 |
1004 | [[package]]
1005 | name = "six"
1006 | version = "1.16.0"
1007 | description = "Python 2 and 3 compatibility utilities"
1008 | optional = false
1009 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
1010 | files = [
1011 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
1012 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
1013 | ]
1014 |
1015 | [[package]]
1016 | name = "threadpoolctl"
1017 | version = "3.5.0"
1018 | description = "threadpoolctl"
1019 | optional = false
1020 | python-versions = ">=3.8"
1021 | files = [
1022 | {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"},
1023 | {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"},
1024 | ]
1025 |
1026 | [[package]]
1027 | name = "toml"
1028 | version = "0.10.2"
1029 | description = "Python Library for Tom's Obvious, Minimal Language"
1030 | optional = false
1031 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
1032 | files = [
1033 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
1034 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
1035 | ]
1036 |
1037 | [[package]]
1038 | name = "typer"
1039 | version = "0.12.3"
1040 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
1041 | optional = false
1042 | python-versions = ">=3.7"
1043 | files = [
1044 | {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
1045 | {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
1046 | ]
1047 |
1048 | [package.dependencies]
1049 | click = ">=8.0.0"
1050 | rich = ">=10.11.0"
1051 | shellingham = ">=1.3.0"
1052 | typing-extensions = ">=3.7.4.3"
1053 |
1054 | [[package]]
1055 | name = "typing-extensions"
1056 | version = "4.12.2"
1057 | description = "Backported and Experimental Type Hints for Python 3.8+"
1058 | optional = false
1059 | python-versions = ">=3.8"
1060 | files = [
1061 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
1062 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
1063 | ]
1064 |
1065 | [[package]]
1066 | name = "urllib3"
1067 | version = "2.2.2"
1068 | description = "HTTP library with thread-safe connection pooling, file post, and more."
1069 | optional = false
1070 | python-versions = ">=3.8"
1071 | files = [
1072 | {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
1073 | {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
1074 | ]
1075 |
1076 | [package.extras]
1077 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
1078 | h2 = ["h2 (>=4,<5)"]
1079 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
1080 | zstd = ["zstandard (>=0.18.0)"]
1081 |
1082 | [metadata]
1083 | lock-version = "2.0"
1084 | python-versions = "~3.12"
1085 | content-hash = "9481cfb08b4e7d8d22e27e11f6b870a575a3440f2f8cf1015a55ca522d23b09b"
1086 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "palettesnap"
3 | version = "1.2.0"
4 | description = "A color palette generator"
5 | authors = ["EmperorEntropy"]
6 | readme = "README.md"
7 | license = "MIT"
8 | repository = "https://github.com/EmperorEntropy/PaletteSnap"
9 |
10 | [tool.poetry.scripts]
11 | palsnap = "palettesnap.cli:app"
12 |
13 | [tool.poetry.dependencies]
14 | python = "~3.12"
15 | toml = "^0.10.2"
16 | typer = "^0.12.3"
17 | scikit-learn = "^1.5.1"
18 | pykdtree = "^1.3.12"
19 | kmedoids = "^0.5.1"
20 | colour-science = "^0.4.4"
21 | rich = "^13.7.1"
22 | matplotlib = "^3.9.0"
23 | psutil = "^6.0.0"
24 | pymixbox = "^2.0.0"
25 | requests = "^2.32.3"
26 |
27 | [build-system]
28 | requires = ["poetry-core"]
29 | build-backend = "poetry.core.masonry.api"
30 |
--------------------------------------------------------------------------------