ingest.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. #!/usr/bin/env python3
  2. from collections import namedtuple
  3. import numpy as np
  4. from scipy.cluster import vq
  5. from PIL import Image
  6. from colorspacious import cspace_convert
  7. verbose = False
  8. seed = 20220211
  9. def is_outline(r: int, g: int, b: int, a: int) -> bool:
  10. # returns true if a pixel is transparent or pure black
  11. return a == 0 or (r, g, b) == (0, 0, 0)
  12. def x_metric(pixels: np.array) -> float:
  13. # X metric - the mean squared Euclidean norm
  14. # computed as the sum of the squares of the components of the pixels,
  15. # normalized by the number of pixels
  16. if verbose:
  17. print(" Computing X...")
  18. return sum(sum(pixels ** 2)) / len(pixels)
  19. def y_metric(pixels: np.array) -> np.array:
  20. # Y metric - the mean pixel of the image
  21. if verbose:
  22. print(" Computing Y...")
  23. return pixels.mean(0)
  24. def z_metric(pixels: np.array) -> tuple[np.array, np.array]:
  25. # Z metric - run k-means, and return the mean of each cluster
  26. # k chosen somewhat arbitrarily to be 3
  27. if verbose:
  28. print(" Computing Z...")
  29. means, labels = vq.kmeans2(pixels.astype(float), 3, minit="++", seed=seed)
  30. return means, np.unique(labels, return_counts=True)[1] / len(pixels)
  31. ImageInfo = namedtuple(
  32. "ImageInfo", ["name", "xrgb", "xjab", "yrgb", "yjab", "zrgb", "zjab"]
  33. )
  34. def ingest_png(file_name: str) -> ImageInfo:
  35. print(f"Ingesting {file_name}")
  36. # image name - strip leading path and trailing extension
  37. name = file_name.rsplit("/", maxsplit=1)[1].split(".", maxsplit=1)[0]
  38. # read non-outline pixels of image
  39. rgb_pixels = np.array([
  40. (r, g, b)
  41. for r, g, b, a in Image.open(file_name).convert("RGBA").getdata()
  42. if not is_outline(r, g, b, a)
  43. ])
  44. # convert RGB pixels to CAM02 values
  45. jab_pixels = cspace_convert(rgb_pixels, "sRGB255", "CAM02-UCS")
  46. # compute and return metrics
  47. return ImageInfo(
  48. name=name,
  49. xrgb=x_metric(rgb_pixels),
  50. xjab=x_metric(jab_pixels),
  51. yrgb=y_metric(rgb_pixels),
  52. yjab=y_metric(jab_pixels),
  53. zrgb=z_metric(rgb_pixels),
  54. zjab=z_metric(jab_pixels),
  55. )
  56. if __name__ == "__main__":
  57. import csv
  58. import os
  59. import sys
  60. dir = "pngs" if len(sys.argv) < 2 else sys.argv[1]
  61. data = [
  62. ingest_png(dir + "/" + fn)
  63. for f in os.listdir(dir)
  64. if (fn := os.fsdecode(f)).endswith(".png")
  65. ]
  66. with open("database.csv", "w") as outfile:
  67. writer = csv.writer(outfile, delimiter=",", quotechar="'")
  68. writer.writerows([d.name, d.xrgb, *d.yrgb] for d in data)
  69. with open("database-cam02.csv", "w") as outfile:
  70. writer = csv.writer(outfile, delimiter=",", quotechar="'")
  71. writer.writerows([d.name, d.xjab, *d.yjab] for d in data)
  72. def write_array(contents):
  73. return f"[ {', '.join(str(c) for c in contents)} ]"
  74. with open("database.js", "w") as outfile:
  75. outfile.write("const database = [\n")
  76. for info in data:
  77. fields = ", ".join(
  78. (
  79. f'name: "{info.name}"',
  80. f"xRGB: {info.xrgb}",
  81. f"xJAB: {info.xjab}",
  82. f"yRGB: {write_array(info.yrgb)}",
  83. f"yJAB: {write_array(info.yjab)}",
  84. f"zRGB: {write_array([write_array(z) for z in info.zrgb[0]] + [write_array(info.zjab[1])])}",
  85. f"zJAB: {write_array([write_array(z) for z in info.zjab[0]] + [write_array(info.zjab[1])])}",
  86. )
  87. )
  88. outfile.write(f" {{ {fields} }},\n")
  89. outfile.write("];\n")