Tidy a bit and remove support for tunable parameters that are no longer needed

This commit is contained in:
kris 2021-11-26 10:36:39 +00:00
parent cf63a35797
commit 1075ff0136
2 changed files with 16 additions and 32 deletions

View File

@ -135,7 +135,7 @@ class ClusterPalette:
# print("Perfect image error:", total_image_error) # print("Perfect image error:", total_image_error)
return image_rgb return image_rgb
def _dither_image(self, palettes_cam, penalty): def _dither_image(self, palettes_cam):
# Suppress divide by zero warning, # Suppress divide by zero warning,
# https://github.com/colour-science/colour/issues/900 # https://github.com/colour-science/colour/issues/900
with colour.utilities.suppress_warnings(python_warnings=True): with colour.utilities.suppress_warnings(python_warnings=True):
@ -145,7 +145,7 @@ class ClusterPalette:
output_4bit, line_to_palette, total_image_error, palette_line_errors = \ output_4bit, line_to_palette, total_image_error, palette_line_errors = \
dither_pyx.dither_shr( dither_pyx.dither_shr(
self._image_rgb, palettes_cam, palettes_linear_rgb, self._image_rgb, palettes_cam, palettes_linear_rgb,
self._rgb24_to_cam16ucs, float(penalty)) self._rgb24_to_cam16ucs)
# Update map of palettes to image lines for which the palette was the # Update map of palettes to image lines for which the palette was the
# best match # best match
@ -159,7 +159,7 @@ class ClusterPalette:
return (output_4bit, line_to_palette, palettes_linear_rgb, return (output_4bit, line_to_palette, palettes_linear_rgb,
total_image_error) total_image_error)
def iterate(self, penalty: float, max_inner_iterations: int, def iterate(self, max_inner_iterations: int,
max_outer_iterations: int): max_outer_iterations: int):
total_image_error = 1e9 total_image_error = 1e9
@ -176,10 +176,8 @@ class ClusterPalette:
# Recompute image with proposed palettes and check whether it # Recompute image with proposed palettes and check whether it
# has lower total image error than our previous best. # has lower total image error than our previous best.
(output_4bit, line_to_palette, palettes_linear_rgb, (output_4bit, line_to_palette, palettes_linear_rgb,
new_total_image_error) = self._dither_image( new_total_image_error) = self._dither_image(new_palettes_cam)
new_palettes_cam, penalty)
# TODO: check for unused colours within a palette
self._reassign_unused_palettes( self._reassign_unused_palettes(
line_to_palette, new_palettes_rgb12_iigs) line_to_palette, new_palettes_rgb12_iigs)
@ -444,7 +442,6 @@ def main():
gamma=args.gamma_correct)).astype(np.float32) / 255 gamma=args.gamma_correct)).astype(np.float32) / 255
# TODO: flags # TODO: flags
penalty = 1 # 1e18 # TODO: is this needed any more?
inner_iterations = 10 inner_iterations = 10
outer_iterations = 20 outer_iterations = 20
@ -466,7 +463,7 @@ def main():
seq = 0 seq = 0
for (new_total_image_error, output_4bit, line_to_palette, for (new_total_image_error, output_4bit, line_to_palette,
palettes_rgb12_iigs, palettes_linear_rgb) in cluster_palette.iterate( palettes_rgb12_iigs, palettes_linear_rgb) in cluster_palette.iterate(
penalty, inner_iterations, outer_iterations): inner_iterations, outer_iterations):
if args.verbose and total_image_error is not None: if args.verbose and total_image_error is not None:
print("Improved quality +%f%% (%f)" % ( print("Improved quality +%f%% (%f)" % (

View File

@ -163,6 +163,7 @@ cdef inline float[::1] convert_rgb_to_cam16ucs(float[:, ::1] rgb_to_cam16ucs, fl
cdef unsigned int rgb_24bit = (<unsigned int>(r*255) << 16) + (<unsigned int>(g*255) << 8) + <unsigned int>(b*255) cdef unsigned int rgb_24bit = (<unsigned int>(r*255) << 16) + (<unsigned int>(g*255) << 8) + <unsigned int>(b*255)
return rgb_to_cam16ucs[rgb_24bit] return rgb_to_cam16ucs[rgb_24bit]
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cdef inline float fabs(float value) nogil: cdef inline float fabs(float value) nogil:
@ -175,12 +176,6 @@ cdef inline double colour_distance_squared(float[::1] colour1, float[::1] colour
return (colour1[0] - colour2[0]) ** 2 + (colour1[1] - colour2[1]) ** 2 + (colour1[2] - colour2[2]) ** 2 return (colour1[0] - colour2[0]) ** 2 + (colour1[1] - colour2[1]) ** 2 + (colour1[2] - colour2[2]) ** 2
@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline float colour_distance(float[::1] colour1, float[::1] colour2) nogil:
return fabs(colour1[0] - colour2[0]) + fabs(colour1[1] - colour2[1]) + fabs(colour1[2] - colour2[2])
# Perform error diffusion to a single image row. # Perform error diffusion to a single image row.
# #
# Args: # Args:
@ -353,7 +348,6 @@ def dither_shr_perfect(
cdef int palette_size = full_palette_rgb.shape[0] cdef int palette_size = full_palette_rgb.shape[0]
cdef float decay = 0.5 cdef float decay = 0.5
cdef float min_quant_error = 0.0 # 0.02
cdef int floyd_steinberg = 1 cdef int floyd_steinberg = 1
total_image_error = 0.0 total_image_error = 0.0
@ -373,13 +367,11 @@ def dither_shr_perfect(
if distance < best_distance: if distance < best_distance:
best_distance = distance best_distance = distance
best_colour_idx = idx best_colour_idx = idx
best_colour_rgb = full_palette_rgb[best_colour_idx] best_colour_rgb = full_palette_rgb[best_colour_idx, :]
total_image_error += best_distance total_image_error += best_distance
for i in range(3): for i in range(3):
quant_error = working_image[y, x, i] - best_colour_rgb[i] quant_error = working_image[y, x, i] - best_colour_rgb[i]
if abs(quant_error) <= min_quant_error:
quant_error = 0
working_image[y, x, i] = best_colour_rgb[i] working_image[y, x, i] = best_colour_rgb[i]
if floyd_steinberg: if floyd_steinberg:
@ -455,7 +447,7 @@ def dither_shr_perfect(
@cython.wraparound(False) @cython.wraparound(False)
def dither_shr( def dither_shr(
float[:, :, ::1] input_rgb, float[:, :, ::1] palettes_cam, float[:, :, ::1] palettes_rgb, float[:, :, ::1] input_rgb, float[:, :, ::1] palettes_cam, float[:, :, ::1] palettes_rgb,
float[:,::1] rgb_to_cam16ucs, float penalty): float[:,::1] rgb_to_cam16ucs):
cdef int y, x, idx, best_colour_idx, best_palette, i cdef int y, x, idx, best_colour_idx, best_palette, i
cdef double best_distance, distance, total_image_error cdef double best_distance, distance, total_image_error
cdef float[::1] best_colour_rgb, pixel_cam cdef float[::1] best_colour_rgb, pixel_cam
@ -471,7 +463,6 @@ def dither_shr(
cdef PaletteSelection palette_line cdef PaletteSelection palette_line
cdef float decay = 0.5 cdef float decay = 0.5
cdef float min_quant_error = 0.0 # 0.02
cdef int floyd_steinberg = 1 cdef int floyd_steinberg = 1
best_palette = -1 best_palette = -1
@ -481,7 +472,7 @@ def dither_shr(
line_cam[x, :] = convert_rgb_to_cam16ucs( line_cam[x, :] = convert_rgb_to_cam16ucs(
rgb_to_cam16ucs, working_image[y,x,0], working_image[y,x,1], working_image[y,x,2]) rgb_to_cam16ucs, working_image[y,x,0], working_image[y,x,1], working_image[y,x,2])
palette_line = best_palette_for_line(line_cam, palettes_cam, best_palette, penalty) palette_line = best_palette_for_line(line_cam, palettes_cam, best_palette)
best_palette = palette_line.palette_idx best_palette = palette_line.palette_idx
palette_line_errors[y] = palette_line.total_error palette_line_errors[y] = palette_line.total_error
@ -506,8 +497,6 @@ def dither_shr(
for i in range(3): for i in range(3):
quant_error = working_image[y, x, i] - best_colour_rgb[i] quant_error = working_image[y, x, i] - best_colour_rgb[i]
if abs(quant_error) <= min_quant_error:
quant_error = 0
working_image[y, x, i] = best_colour_rgb[i] working_image[y, x, i] = best_colour_rgb[i]
if floyd_steinberg: if floyd_steinberg:
@ -586,7 +575,7 @@ cdef struct PaletteSelection:
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :, ::1] palettes_cam, int last_palette_idx, float last_penalty) nogil: cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :, ::1] palettes_cam, int last_palette_idx) nogil:
cdef int palette_idx, best_palette_idx, palette_entry_idx, pixel_idx cdef int palette_idx, best_palette_idx, palette_entry_idx, pixel_idx
cdef double best_total_dist, total_dist, best_pixel_dist, pixel_dist cdef double best_total_dist, total_dist, best_pixel_dist, pixel_dist
cdef float[:, ::1] palette_cam cdef float[:, ::1] palette_cam
@ -594,17 +583,15 @@ cdef PaletteSelection best_palette_for_line(float [:, ::1] line_cam, float[:, :,
best_total_dist = 1e9 best_total_dist = 1e9
best_palette_idx = -1 best_palette_idx = -1
cdef float penalty
cdef int line_size = line_cam.shape[0] cdef int line_size = line_cam.shape[0]
for palette_idx in range(16): for palette_idx in range(16):
palette_cam = palettes_cam[palette_idx, :, :] palette_cam = palettes_cam[palette_idx, :, :]
penalty = last_penalty if palette_idx == last_palette_idx else 1.0
total_dist = 0 total_dist = 0
for pixel_idx in range(line_size): for pixel_idx in range(line_size):
pixel_cam = line_cam[pixel_idx] pixel_cam = line_cam[pixel_idx]
best_pixel_dist = 1e9 best_pixel_dist = 1e9
for palette_entry_idx in range(16): for palette_entry_idx in range(16):
pixel_dist = colour_distance_squared(pixel_cam, palette_cam[palette_entry_idx, :]) * penalty pixel_dist = colour_distance_squared(pixel_cam, palette_cam[palette_entry_idx, :])
if pixel_dist < best_pixel_dist: if pixel_dist < best_pixel_dist:
best_pixel_dist = pixel_dist best_pixel_dist = pixel_dist
total_dist += best_pixel_dist total_dist += best_pixel_dist
@ -650,13 +637,12 @@ cdef (unsigned char)[:, ::1] _convert_cam16ucs_to_rgb12_iigs(float[:, ::1] point
cdef float[:, ::1] rgb cdef float[:, ::1] rgb
cdef (float)[:, ::1] rgb12_iigs cdef (float)[:, ::1] rgb12_iigs
# Convert CAM16UCS input to RGB # Convert CAM16UCS input to RGB. Even though this dynamically constructs a path on the graph of colour conversions
# TODO: this dynamically constructs a path on the graph of colour conversions every time, which is # every time, in practise this seems to have a negligible overhead compared to the actual conversion functions.
# presumably not very efficient. However, colour.convert doesn't provide a way to cache the composed conversion
# function so we'd have to build it ourselves (https://github.com/colour-science/colour/issues/905)
with colour.utilities.suppress_warnings(python_warnings=True): with colour.utilities.suppress_warnings(python_warnings=True):
rgb = colour.convert(point_cam, "CAM16UCS", "RGB").astype(np.float32) rgb = colour.convert(point_cam, "CAM16UCS", "RGB").astype(np.float32)
# TODO: precompute this conversion matrix since it's static. This accounts for about 10% of the CPU time here.
rgb12_iigs = np.clip( rgb12_iigs = np.clip(
# Convert to Rec.601 R'G'B' # Convert to Rec.601 R'G'B'
colour.YCbCr_to_RGB( colour.YCbCr_to_RGB(
@ -675,7 +661,8 @@ def convert_cam16ucs_to_rgb12_iigs(float[:, ::1] point_cam):
@cython.wraparound(False) @cython.wraparound(False)
@cython.cdivision(True) @cython.cdivision(True)
def k_means_with_fixed_centroids( def k_means_with_fixed_centroids(
int n_clusters, int n_fixed, float[:, ::1] samples, (unsigned char)[:, ::1] initial_centroids, int max_iterations, float [:, ::1] rgb12_iigs_to_cam16ucs): int n_clusters, int n_fixed, float[:, ::1] samples, (unsigned char)[:, ::1] initial_centroids, int max_iterations,
float [:, ::1] rgb12_iigs_to_cam16ucs):
cdef double error, best_error, total_error, last_total_error cdef double error, best_error, total_error, last_total_error
cdef int centroid_idx, closest_centroid_idx, i, point_idx cdef int centroid_idx, closest_centroid_idx, i, point_idx