Skip to content

Commit 1db2a8c

Browse files
add an introduction demo for indexed image (#166)
add an introduction demo for the indexed image Co-authored-by: Tim Holy <[email protected]>
1 parent 2e1e30c commit 1db2a8c

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ImageMorphology = "787d08f9-d448-5407-9aad-5290dd7ab264"
2121
ImageSegmentation = "80713f31-8817-5129-9cf8-209ff8fb23e1"
2222
ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
2323
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
24+
IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959"
2425
MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
2526
MosaicViews = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
2627
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
@@ -53,6 +54,7 @@ ImageMorphology = "0.2"
5354
ImageSegmentation = "1"
5455
ImageShow = "0"
5556
Images = "0.23"
57+
IndirectArrays = "0"
5658
MappedArrays = "0"
5759
MosaicViews = "0"
5860
OffsetArrays = "1.0"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# ---
2+
# cover: assets/indexed_image.png
3+
# title: Indexed image in 5 minutes
4+
# author: Johnny Chen
5+
# date: 2020-11-23
6+
# ---
7+
8+
# This demonstration shows you how to work with [indexed
9+
# image](https://en.wikipedia.org/wiki/Indexed_color) using
10+
# [IndirectArrays.jl](https://github.com/JuliaArrays/IndirectArrays.jl)
11+
12+
# An indexed image consists of two parts: the indices and the palatte. The palatte keeps track of
13+
# all possible pixel values of an image, while the `indices` consists of the palatte index of each
14+
# pixel.
15+
16+
using Images
17+
18+
# The following is a normally used representation of image, i.e., an image as an array of
19+
# `Colorant`s.
20+
21+
img = [
22+
RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0)
23+
RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0)
24+
RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0)
25+
RGB(0.0, 0.0, 1.0) RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0)
26+
RGB(1.0, 1.0, 1.0) RGB(0.0, 0.0, 0.0) RGB(1.0, 0.0, 0.0) RGB(0.0, 1.0, 0.0) RGB(0.0, 0.0, 1.0)]
27+
28+
# Alternatively, we could save it in the indexed image format:
29+
30+
indices = [1 2 3 4 5; 2 3 4 5 1; 3 4 5 1 2; 4 5 1 2 3; 5 1 2 3 4] # `i` records the pixel value `palatte[i]`
31+
palatte = [RGB(0.0, 0.0, 0.0), RGB(1.0, 0.0, 0.0), RGB(0.0, 1.0, 0.0), RGB(0.0, 0.0, 1.0), RGB(1.0, 1.0, 1.0)]
32+
33+
palatte[indices] == img
34+
35+
# This is doable because it follows the Julia [indexing
36+
# rules](https://docs.julialang.org/en/v1/manual/arrays/#man-supported-index-types) that `indices`
37+
# is an array of scalar indices, and the dimensionality of the output is the dimensionality of the
38+
# `indices`. For example, the following equivalence holds:
39+
40+
palatte[[1, 2]] == [palatte[1], palatte[2]]
41+
palatte[[1 2; 2 1]] == [palatte[1] palatte[2]; palatte[2] palatte[1]]
42+
#md nothing #hide
43+
44+
# Now, let's analyze the resource usage of these two approaches. For this example, we are storing
45+
# the data using type `Float64`, where each `Float64` number requires 8 bytes (1 byte=8bits). Hence
46+
# the normal representation format requires `5*5*3*8 = 600` bytes storage in total. As a comparison,
47+
# we only need `5*5*8 + 5*3*8 = 320` bytes storage if we use the indexed image format. This is why
48+
# images with few distinct values can often be encoded more compactly as an indexed image.
49+
#
50+
# Although it does compress the data in this example, in real world applications, it is not always
51+
# clear whether you should or should not use indexed image format. There are two main drawbacks of
52+
# it:
53+
#
54+
# - indexed images can require more memory if `length(unique(img))` is too large.
55+
# - indexing into an indexed image requires two `getindex` operations, so using it can be slower
56+
# than a direct image representation (and not amenable to SIMD vectorization). This is a typical
57+
# [space-time tradeoff](https://en.wikipedia.org/wiki/Space%E2%80%93time_tradeoff) case.
58+
#
59+
# Benchmarks are always recommended before you choose to use the indexed image format.
60+
61+
# Using indexed image format with two seperate arrays can be inconvinient, hence
62+
# [`IndirectArrays.jl`](https://github.com/JuliaArrays/IndirectArrays.jl) provides an array
63+
# abstraction to union these two data:
64+
using IndirectArrays
65+
66+
indexed_img = IndirectArray(indices, palatte)
67+
img == indexed_img
68+
69+
# Under the hook, it is just a simple struct that subtypes `AbstractArray`:
70+
#
71+
# ```julia
72+
# # no need to run this
73+
# struct IndirectArray{T,N,A,V} <: AbstractArray{T,N}
74+
# index::A
75+
# values::V
76+
# end
77+
# ```
78+
79+
indexed_img.index === indices, indexed_img.values === palatte
80+
81+
# Since `IndirectArray` is just an array, common image operations are applicable to this type, for
82+
# example:
83+
84+
imresize(indexed_img; ratio=2) # no longer an IndirectArray
85+
86+
# --- save covers --- #src
87+
save("assets/indexed_image.png", repeat(indexed_img, inner=(20, 20))) #src

0 commit comments

Comments
 (0)