Skip to content

Commit

Permalink
[bugfix] Fixes median cut frequencies not summing to one (#39)
Browse files Browse the repository at this point in the history
* Fix median cut frequencies not summing to one

* Bump version
  • Loading branch information
qTipTip authored Jul 7, 2024
1 parent 2781630 commit a3d48f9
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 43 deletions.
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# Unreleased



# Released

## [3.0.1] 07/07/2024

### Fixed
- Fixed a bug where the color frequencies of colors in a palette were not summing to one when using the Median Cut algorithm.

## [3.0.0] 02/07/2024

Expand All @@ -15,10 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- An URL to an image file, as a string.
- A byte stream of an image file, as a bytes object.
- A numpy array of an image file, as a numpy array.


# Released

-
## [2.3.0] 19/06/2024

- Added `image_array` to the `extract_color` function, allowing the user to specify an image as a numpy array.
Expand Down
4 changes: 2 additions & 2 deletions Pylette/src/color_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ def median_cut_extraction(arr: np.ndarray, height: int, width: int, palette_size

arr = arr.reshape((width * height, -1))
c = [ColorBox(arr)]
full_box_size = c[0].size

# Each iteration, find the largest box, split it, remove original box from list of boxes, and add the two new boxes.
while len(c) < palette_size:
largest_c_idx = np.argmax(c)
# add the two new boxes to the list, while removing the split box.
c = c[:largest_c_idx] + c[largest_c_idx].split() + c[largest_c_idx + 1 :]

colors = [Color(tuple(map(int, box.average)), box.size / full_box_size) for box in c]
total_pixels = width * height
colors = [Color(tuple(map(int, box.average)), box.pixel_count / total_pixels) for box in c]

return colors

Expand Down
10 changes: 10 additions & 0 deletions Pylette/src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,13 @@ def split(self) -> list["ColorBox"]:
ColorBox(self.colors[:median_index]),
ColorBox(self.colors[median_index:]),
]

@property
def pixel_count(self) -> int:
"""
Returns the number of pixels in the ColorBox.
Returns:
int: The number of pixels in the ColorBox.
"""
return len(self.colors)
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pylette"
version = "3.0.0"
version = "3.0.1"
description = "A Python library for extracting color palettes from images."
authors = ["Ivar Stangeby"]
license = "MIT"
Expand Down Expand Up @@ -45,5 +45,6 @@ build-backend = "poetry.core.masonry.api"




[tool.poetry.scripts]
pylette = "Pylette.cmd:main"
40 changes: 5 additions & 35 deletions tests/integration/test_colorspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ def test_kmean_extracted_palette(test_image_path_as_str):
@pytest.mark.parametrize("palette_size", [1, 5, 10, 100])
@pytest.mark.parametrize(
"extraction_mode",
[
"KM",
pytest.param(
"MC",
marks=pytest.mark.skip("Currently a bug in the MC algorithm, causing frequencies not summing to one"),
),
],
["KM", "MC"],
)
def test_palette_invariants_with_image_path(test_image_path_as_str, palette_size, extraction_mode):
palette = extract_colors(
Expand Down Expand Up @@ -92,13 +86,7 @@ def test_palette_invariants_with_image_path(test_image_path_as_str, palette_size
@pytest.mark.parametrize("palette_size", [1, 5, 10, 100])
@pytest.mark.parametrize(
"extraction_mode",
[
"KM",
pytest.param(
"MC",
marks=pytest.mark.skip("Currently a bug in the MC algorithm, causing frequencies not summing to one"),
),
],
["KM", "MC"],
)
def test_palette_invariants_with_image_pathlike(test_image_path_as_pathlike, palette_size, extraction_mode):
palette = extract_colors(
Expand Down Expand Up @@ -131,13 +119,7 @@ def test_palette_invariants_with_image_pathlike(test_image_path_as_pathlike, pal
@pytest.mark.parametrize("palette_size", [1, 5, 10, 100])
@pytest.mark.parametrize(
"extraction_mode",
[
"KM",
pytest.param(
"MC",
marks=pytest.mark.skip("Currently a bug in the MC algorithm, causing frequencies not summing to one"),
),
],
["KM", "MC"],
)
def test_palette_invariants_with_image_bytes(test_image_as_bytes, palette_size, extraction_mode):
palette = extract_colors(
Expand Down Expand Up @@ -170,13 +152,7 @@ def test_palette_invariants_with_image_bytes(test_image_as_bytes, palette_size,
@pytest.mark.parametrize("palette_size", [1, 5, 10, 100])
@pytest.mark.parametrize(
"extraction_mode",
[
"KM",
pytest.param(
"MC",
marks=pytest.mark.skip("Currently a bug in the MC algorithm, causing frequencies not summing to one"),
),
],
["KM", "MC"],
)
def test_palette_invariants_with_opencv(test_image_from_opencv, palette_size, extraction_mode):
palette = extract_colors(
Expand Down Expand Up @@ -209,13 +185,7 @@ def test_palette_invariants_with_opencv(test_image_from_opencv, palette_size, ex
@pytest.mark.parametrize("palette_size", [1, 5, 10, 100])
@pytest.mark.parametrize(
"extraction_mode",
[
"KM",
pytest.param(
"MC",
marks=pytest.mark.skip("Currently a bug in the MC algorithm, causing frequencies not summing to one"),
),
],
["KM", "MC"],
)
def test_palette_invariants_with_image_url(test_image_as_url, palette_size, extraction_mode):
palette = extract_colors(
Expand Down

0 comments on commit a3d48f9

Please # to comment.