ingest.py 3.3 KB

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