image_print.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. # LICENSE HEADER MANAGED BY add-license-header
  2. #
  3. # Copyright 2018 Kornia Team
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """Convert an image tensor to an ANSI text string (xterm-256color).
  18. Nice long listing of all 256 colors and their codes.
  19. Taken from https://gist.github.com/klange/1687427
  20. """
  21. import re
  22. from typing import Tuple, Union
  23. import torch
  24. from torch import float16, float32, float64
  25. import kornia
  26. from kornia.core import Tensor
  27. from kornia.core.check import KORNIA_CHECK_IS_IMAGE, KORNIA_CHECK_SHAPE
  28. from kornia.io import ImageLoadType
  29. # color look-up table
  30. # 8-bit, RGB hex
  31. CLUT = [
  32. # Primary 3-bit (8 colors). Unique representation!
  33. ("00", "000000"),
  34. ("01", "800000"),
  35. ("02", "008000"),
  36. ("03", "808000"),
  37. ("04", "000080"),
  38. ("05", "800080"),
  39. ("06", "008080"),
  40. ("07", "c0c0c0"),
  41. # Equivalent "bright" versions of original 8 colors.
  42. ("08", "808080"),
  43. ("09", "ff0000"),
  44. ("10", "00ff00"),
  45. ("11", "ffff00"),
  46. ("12", "0000ff"),
  47. ("13", "ff00ff"),
  48. ("14", "00ffff"),
  49. ("15", "ffffff"),
  50. # Strictly ascending.
  51. ("16", "000000"),
  52. ("17", "00005f"),
  53. ("18", "000087"),
  54. ("19", "0000af"),
  55. ("20", "0000d7"),
  56. ("21", "0000ff"),
  57. ("22", "005f00"),
  58. ("23", "005f5f"),
  59. ("24", "005f87"),
  60. ("25", "005faf"),
  61. ("26", "005fd7"),
  62. ("27", "005fff"),
  63. ("28", "008700"),
  64. ("29", "00875f"),
  65. ("30", "008787"),
  66. ("31", "0087af"),
  67. ("32", "0087d7"),
  68. ("33", "0087ff"),
  69. ("34", "00af00"),
  70. ("35", "00af5f"),
  71. ("36", "00af87"),
  72. ("37", "00afaf"),
  73. ("38", "00afd7"),
  74. ("39", "00afff"),
  75. ("40", "00d700"),
  76. ("41", "00d75f"),
  77. ("42", "00d787"),
  78. ("43", "00d7af"),
  79. ("44", "00d7d7"),
  80. ("45", "00d7ff"),
  81. ("46", "00ff00"),
  82. ("47", "00ff5f"),
  83. ("48", "00ff87"),
  84. ("49", "00ffaf"),
  85. ("50", "00ffd7"),
  86. ("51", "00ffff"),
  87. ("52", "5f0000"),
  88. ("53", "5f005f"),
  89. ("54", "5f0087"),
  90. ("55", "5f00af"),
  91. ("56", "5f00d7"),
  92. ("57", "5f00ff"),
  93. ("58", "5f5f00"),
  94. ("59", "5f5f5f"),
  95. ("60", "5f5f87"),
  96. ("61", "5f5faf"),
  97. ("62", "5f5fd7"),
  98. ("63", "5f5fff"),
  99. ("64", "5f8700"),
  100. ("65", "5f875f"),
  101. ("66", "5f8787"),
  102. ("67", "5f87af"),
  103. ("68", "5f87d7"),
  104. ("69", "5f87ff"),
  105. ("70", "5faf00"),
  106. ("71", "5faf5f"),
  107. ("72", "5faf87"),
  108. ("73", "5fafaf"),
  109. ("74", "5fafd7"),
  110. ("75", "5fafff"),
  111. ("76", "5fd700"),
  112. ("77", "5fd75f"),
  113. ("78", "5fd787"),
  114. ("79", "5fd7af"),
  115. ("80", "5fd7d7"),
  116. ("81", "5fd7ff"),
  117. ("82", "5fff00"),
  118. ("83", "5fff5f"),
  119. ("84", "5fff87"),
  120. ("85", "5fffaf"),
  121. ("86", "5fffd7"),
  122. ("87", "5fffff"),
  123. ("88", "870000"),
  124. ("89", "87005f"),
  125. ("90", "870087"),
  126. ("91", "8700af"),
  127. ("92", "8700d7"),
  128. ("93", "8700ff"),
  129. ("94", "875f00"),
  130. ("95", "875f5f"),
  131. ("96", "875f87"),
  132. ("97", "875faf"),
  133. ("98", "875fd7"),
  134. ("99", "875fff"),
  135. ("100", "878700"),
  136. ("101", "87875f"),
  137. ("102", "878787"),
  138. ("103", "8787af"),
  139. ("104", "8787d7"),
  140. ("105", "8787ff"),
  141. ("106", "87af00"),
  142. ("107", "87af5f"),
  143. ("108", "87af87"),
  144. ("109", "87afaf"),
  145. ("110", "87afd7"),
  146. ("111", "87afff"),
  147. ("112", "87d700"),
  148. ("113", "87d75f"),
  149. ("114", "87d787"),
  150. ("115", "87d7af"),
  151. ("116", "87d7d7"),
  152. ("117", "87d7ff"),
  153. ("118", "87ff00"),
  154. ("119", "87ff5f"),
  155. ("120", "87ff87"),
  156. ("121", "87ffaf"),
  157. ("122", "87ffd7"),
  158. ("123", "87ffff"),
  159. ("124", "af0000"),
  160. ("125", "af005f"),
  161. ("126", "af0087"),
  162. ("127", "af00af"),
  163. ("128", "af00d7"),
  164. ("129", "af00ff"),
  165. ("130", "af5f00"),
  166. ("131", "af5f5f"),
  167. ("132", "af5f87"),
  168. ("133", "af5faf"),
  169. ("134", "af5fd7"),
  170. ("135", "af5fff"),
  171. ("136", "af8700"),
  172. ("137", "af875f"),
  173. ("138", "af8787"),
  174. ("139", "af87af"),
  175. ("140", "af87d7"),
  176. ("141", "af87ff"),
  177. ("142", "afaf00"),
  178. ("143", "afaf5f"),
  179. ("144", "afaf87"),
  180. ("145", "afafaf"),
  181. ("146", "afafd7"),
  182. ("147", "afafff"),
  183. ("148", "afd700"),
  184. ("149", "afd75f"),
  185. ("150", "afd787"),
  186. ("151", "afd7af"),
  187. ("152", "afd7d7"),
  188. ("153", "afd7ff"),
  189. ("154", "afff00"),
  190. ("155", "afff5f"),
  191. ("156", "afff87"),
  192. ("157", "afffaf"),
  193. ("158", "afffd7"),
  194. ("159", "afffff"),
  195. ("160", "d70000"),
  196. ("161", "d7005f"),
  197. ("162", "d70087"),
  198. ("163", "d700af"),
  199. ("164", "d700d7"),
  200. ("165", "d700ff"),
  201. ("166", "d75f00"),
  202. ("167", "d75f5f"),
  203. ("168", "d75f87"),
  204. ("169", "d75faf"),
  205. ("170", "d75fd7"),
  206. ("171", "d75fff"),
  207. ("172", "d78700"),
  208. ("173", "d7875f"),
  209. ("174", "d78787"),
  210. ("175", "d787af"),
  211. ("176", "d787d7"),
  212. ("177", "d787ff"),
  213. ("178", "d7af00"),
  214. ("179", "d7af5f"),
  215. ("180", "d7af87"),
  216. ("181", "d7afaf"),
  217. ("182", "d7afd7"),
  218. ("183", "d7afff"),
  219. ("184", "d7d700"),
  220. ("185", "d7d75f"),
  221. ("186", "d7d787"),
  222. ("187", "d7d7af"),
  223. ("188", "d7d7d7"),
  224. ("189", "d7d7ff"),
  225. ("190", "d7ff00"),
  226. ("191", "d7ff5f"),
  227. ("192", "d7ff87"),
  228. ("193", "d7ffaf"),
  229. ("194", "d7ffd7"),
  230. ("195", "d7ffff"),
  231. ("196", "ff0000"),
  232. ("197", "ff005f"),
  233. ("198", "ff0087"),
  234. ("199", "ff00af"),
  235. ("200", "ff00d7"),
  236. ("201", "ff00ff"),
  237. ("202", "ff5f00"),
  238. ("203", "ff5f5f"),
  239. ("204", "ff5f87"),
  240. ("205", "ff5faf"),
  241. ("206", "ff5fd7"),
  242. ("207", "ff5fff"),
  243. ("208", "ff8700"),
  244. ("209", "ff875f"),
  245. ("210", "ff8787"),
  246. ("211", "ff87af"),
  247. ("212", "ff87d7"),
  248. ("213", "ff87ff"),
  249. ("214", "ffaf00"),
  250. ("215", "ffaf5f"),
  251. ("216", "ffaf87"),
  252. ("217", "ffafaf"),
  253. ("218", "ffafd7"),
  254. ("219", "ffafff"),
  255. ("220", "ffd700"),
  256. ("221", "ffd75f"),
  257. ("222", "ffd787"),
  258. ("223", "ffd7af"),
  259. ("224", "ffd7d7"),
  260. ("225", "ffd7ff"),
  261. ("226", "ffff00"),
  262. ("227", "ffff5f"),
  263. ("228", "ffff87"),
  264. ("229", "ffffaf"),
  265. ("230", "ffffd7"),
  266. ("231", "ffffff"),
  267. # Gray-scale range.
  268. ("232", "080808"),
  269. ("233", "121212"),
  270. ("234", "1c1c1c"),
  271. ("235", "262626"),
  272. ("236", "303030"),
  273. ("237", "3a3a3a"),
  274. ("238", "444444"),
  275. ("239", "4e4e4e"),
  276. ("240", "585858"),
  277. ("241", "626262"),
  278. ("242", "6c6c6c"),
  279. ("243", "767676"),
  280. ("244", "808080"),
  281. ("245", "8a8a8a"),
  282. ("246", "949494"),
  283. ("247", "9e9e9e"),
  284. ("248", "a8a8a8"),
  285. ("249", "b2b2b2"),
  286. ("250", "bcbcbc"),
  287. ("251", "c6c6c6"),
  288. ("252", "d0d0d0"),
  289. ("253", "dadada"),
  290. ("254", "e4e4e4"),
  291. ("255", "eeeeee"),
  292. ]
  293. SHORT2RGB_DICT = dict(CLUT)
  294. RGB2SHORT_DICT = {v: k for k, v in SHORT2RGB_DICT.items()}
  295. def _str2hex(hexstr: str) -> int:
  296. return int(hexstr, 16)
  297. def _strip_hash(rgb: str) -> str:
  298. return rgb.removeprefix("#")
  299. def short2rgb(short: str) -> str:
  300. """Convert short to RGB code."""
  301. return SHORT2RGB_DICT[short]
  302. def rgb2short(rgb: str) -> Tuple[str, str]:
  303. """Find the closest xterm-256 approximation to the given RGB value.
  304. Args:
  305. rgb: Hex code representing an RGB value, eg, 'abcdef'.
  306. Returns:
  307. String between 0 and 255, compatible with xterm.
  308. Example:
  309. >>> rgb2short('123456')
  310. ('23', '005f5f')
  311. >>> rgb2short('ffffff')
  312. ('231', 'ffffff')
  313. >>> rgb2short('0DADD6') # vimeo logo
  314. ('38', '00afd7')
  315. """
  316. rgb = _strip_hash(rgb)
  317. incs = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF)
  318. # Break 6-char RGB code into 3 integer vals.
  319. parts = [int(h, 16) for h in re.split(r"(..)(..)(..)", rgb)[1:4]]
  320. res = []
  321. for part in parts:
  322. i = 0
  323. while i < len(incs) - 1:
  324. s, b = incs[i], incs[i + 1] # smaller, bigger
  325. if s <= part <= b:
  326. s1 = abs(s - part)
  327. b1 = abs(b - part)
  328. if s1 < b1:
  329. closest = s
  330. else:
  331. closest = b
  332. res.append(closest)
  333. break
  334. i += 1
  335. _res = "".join([f"{i:02x}" for i in res])
  336. equiv = RGB2SHORT_DICT[_res]
  337. return equiv, _res
  338. def image_to_string(image: torch.Tensor, max_width: int = 256) -> str:
  339. """Obtain the closest xterm-256 approximation string from an image tensor.
  340. The tensor shall be either 0~1 float type or 0~255 long type.
  341. Args:
  342. image: an RGB image with shape :math:`3HW`.
  343. max_width: maximum width of the input image.
  344. """
  345. KORNIA_CHECK_IS_IMAGE(image, None, raises=True)
  346. KORNIA_CHECK_SHAPE(image, ["C", "H", "W"])
  347. if image.dtype not in [float16, float32, float64]:
  348. image = image / 255.0
  349. if image.shape[-1] > max_width:
  350. new_h = image.size(-2) * max_width // image.size(-1)
  351. image = kornia.geometry.resize(image, (new_h, max_width))
  352. image = (image * 255).clamp(0, 255).long()
  353. H, W = image.shape[-2:]
  354. flat = image.permute(1, 2, 0).reshape(-1, 3)
  355. rgb2short_fn = rgb2short
  356. lines = []
  357. idx = 0
  358. for _ in range(H):
  359. row_parts = []
  360. for _ in range(W):
  361. r, g, b = flat[idx].tolist()
  362. h = f"{r:02x}{g:02x}{b:02x}"
  363. short, _ = rgb2short_fn(h)
  364. row_parts.append(f"\033[48;5;{short}m ")
  365. idx += 1
  366. row_parts.append("\033[0m\n")
  367. lines.append("".join(row_parts))
  368. return "".join(lines)
  369. def print_image(image: Union[str, Tensor], max_width: int = 96) -> None:
  370. """Print an image to the terminal.
  371. .. image:: https://github.com/kornia/data/blob/main/print_image.png?raw=true
  372. Args:
  373. image: path to a valid image file or a tensor.
  374. max_width: maximum width to print to terminal.
  375. Note:
  376. Need to use `print_image(...)`.
  377. """
  378. if isinstance(image, str):
  379. img = kornia.io.load_image(image, ImageLoadType.RGB8)
  380. elif isinstance(image, Tensor):
  381. img = image
  382. else:
  383. raise RuntimeError(f"Expect image type to be either Tensor or str. Got {type(image)}.")
  384. print(image_to_string(img, max_width))