# Handling colors in new \`Makie.voxels\` function

**URL:** <https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251>\
**Category:** Visualization\
**Tags:** question, plotting, makie\
**Created:** [June 5, 2024, 6:10pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251 "2024-06-05T18:10:26Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [June 5, 2024, 6:10pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/1 "2024-06-05T18:10:26Z")

</div>

The new `voxels` function was introduced in Makie v0.21 to plot 3D Cartesian grids more efficiently. However, it assumes `Array{<:Real,3}` for the colors. Can it sill be used with general Colors.jl colors? It is easy to convert `Gray` to `Real`, but multi-channel colors (e.g. RGB) cannot be converted to `Gray` without loss.

---

<div class="post-metadata">

**Author:** ![ffreyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffreyer/32/21569_2.png) [@ffreyer](https://discourse.julialang.org/u/ffreyer)\
**Post date:** [June 8, 2024, 11:40am UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/2 "2024-06-08T11:40:42Z")

</div>

As it is now you can’t pass RGB colors directly. You either have to make a colormap and pass float values or pass an array of RGB colors as `color` and use `UInt8` voxel ids to index that array.

The assumption with voxels is that you want to plot large amount of them, so each individual voxel carries as little data as possible. Regardless of the input you use they get compressed to UInt8. If you value color depth over quantity you can always just use a meshscatter with a `Rect3`.

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [June 8, 2024, 11:48am UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/3 "2024-06-08T11:48:30Z")

</div>

Assuming we have a color vector with colors from Colors.jl how would you call Makie.voxels to get a good approximation of the colors? Any MWE to get started? 🙂

---

<div class="post-metadata">

**Author:** ![ffreyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffreyer/32/21569_2.png) [@ffreyer](https://discourse.julialang.org/u/ffreyer)\
**Post date:** [June 8, 2024, 3:14pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/4 "2024-06-08T15:14:26Z")

</div>

If you have a function that converts an `Array{RGB, 3}` to an `Array{UInt8, 3}` of indices (with 0 being invisible) and a vector of (up to 255) colors then you just need to call `voxels(indices, color = colors)`.

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [June 8, 2024, 3:56pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/5 "2024-06-08T15:56:00Z")

</div>

I think my question is how to write such function? Do you have a specific transformation of color spaces in mind?

---

<div class="post-metadata">

**Author:** ![ffreyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffreyer/32/21569_2.png) [@ffreyer](https://discourse.julialang.org/u/ffreyer)\
**Post date:** [June 8, 2024, 4:53pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/6 "2024-06-08T16:53:24Z")

</div>

Yea, that’s the hard part. I don’t know any good way to do that, though I’m wondering if the Image and Color libraries have something useful for that. Some older gifs have a heavily compressed color space so I’d assume there is a algorithm for it out there

---

<div class="post-metadata">

**Author:** ![ffreyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffreyer/32/21569_2.png) [@ffreyer](https://discourse.julialang.org/u/ffreyer)\
**Post date:** [June 10, 2024, 4:33pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/7 "2024-06-10T16:33:45Z")

</div>

I did some searching. The keywords for this are color palette, color quantization and color depth rather than color space.

One relatively simple option would be to use Clustering:

```julia
using Colors
using Clustering
using GLMakie

# Sample input
N = 30
cs = [RGB(r^2, g^2, b^2) for 
    r in range(0.2, 1, length=N), 
    g in range(0, 0.6, length=N), 
    b in range(0.3, 1, length=N)]
        
begin
    X = Matrix{Float32}(undef, 3, length(cs))
    for (i, c) in enumerate(cs)
        lab = Lab(c)
        X[1, i] = lab.l
        X[2, i] = lab.a
        X[3, i] = lab.b
    end

    @time result = kmeans(X, 255)
    colors = [RGB(Lab(result.centers[:, i]...)) for i in 1:255]

    ids = map(cs) do c
        _, idx = findmin(ref -> colordiff(c, ref), colors)
        UInt8(idx)
    end
end;

voxels(ids, color = colors)

```

![image](https://global.discourse-cdn.com/julialang/original/3X/0/5/05531f9749bacc285ef29d9d247998e6b1388665.png)

---

<div class="post-metadata">

**Author:** ![asinghvi17](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asinghvi17/32/8272_2.png) [@asinghvi17](https://discourse.julialang.org/u/asinghvi17)\
**Post date:** [June 10, 2024, 6:53pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/8 "2024-06-10T18:53:48Z")

</div>

This could be implement as a `convert_arguments(::Type{Voxels}, xs, ys, zs, vs::AbstractArray{3, <: Colors.Colorant})` that returns a PlotSpec, if I understand what happened here correctly?

If so, it would be cool to push this to some `MakieClusteringExt` extension package, so that it’s isolated from the user!

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [June 10, 2024, 6:56pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/9 "2024-06-10T18:56:34Z")

</div>

I wouldn’t place it in an extension with Clustering.jl as a dependency. The K-means algorithm is super easy to implement, and a basic implementation can live in Makie.jl itself to provide this fallback.

---

<div class="post-metadata">

**Author:** ![ffreyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffreyer/32/21569_2.png) [@ffreyer](https://discourse.julialang.org/u/ffreyer)\
**Post date:** [June 11, 2024, 2:04pm UTC](https://discourse.julialang.org/t/handling-colors-in-new-makie-voxels-function/115251/10 "2024-06-11T14:04:03Z")

</div>

I don’t think this solution is fast enough to be used in general. It already takes on the order of 1s for 30^3 voxels, 1min for 100^3. Following some crude benchmarks that would end up taking 15h for a 1000^3 voxel plot, which is around the upper limit that can be handled by voxels atm.`colordiff` is also fairly slow here, but should scale better.  
 ![Screenshot 2024-06-11 152851](https://global.discourse-cdn.com/julialang/original/3X/c/2/c206379f4ca0406856ddb95ca2cd845eb62a7b35.png)

I also tried a naive Octree approach with RGB colors instead of LAB. It’s much faster (~1s for 100^3, depends on breadth of colors) but doesn’t do as good of a job. With `colordiff` and a direct euclidean distance:

> <https://gist.github.com/ffreyer/970bfe61327dd114364e0c5d3a9601b7>

![Screenshot 2024-06-11 153857](https://global.discourse-cdn.com/julialang/original/3X/1/b/1b8dc022cab174b7723c1dc718d515a28538ff4f.png) ![Screenshot 2024-06-11 154035](https://global.discourse-cdn.com/julialang/original/3X/f/d/fd2791193af8252db22c241e6ae25643e3a5cf9f.png)

There is also the option to used a predefined colormap, like [List of software palettes - Wikipedia](https://en.wikipedia.org/wiki/List_of_software_palettes#6-7-6_levels_RGB). These will look pretty bad if you don’t use a wide color spectrum though.

```julia
begin
    rgb676 = [RGB(r/0xff, g/0xff, b/0xff) 
        for r in (0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF)
        for g in (0x00, 0x2A, 0x55, 0x80, 0xAA, 0xD4, 0xFF)
        for b in (0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF)
    ]
    ids = map(cs) do c
        _, idx = findmin(ref -> colordiff(c, ref), rgb676)
        UInt8(idx)
    end
end
voxels(ids, color = rgb676, gap = 0.0)

```

![Screenshot 2024-06-11 154735](https://global.discourse-cdn.com/julialang/original/3X/6/5/6572e24dcc429f107209b27b04ff2c2e210c4ad5.png)
