Colorizing a UInt8 matrix

Hi!

I have a UInt8 matrix of 512 lines by 2048 columns where the elements can only take on the values 0x00, 0x01, 0x02, 0x03, 0x04, 0xfd, 0xfe or 0xff. I’d like to visualize this matrix as a color map, ideally in PNG or PDF format, where

matrix elements == 0x00 become red pixels
matrix elements == 0x01 become white pixels
matrix elements == 0x02 become light gray pixels
matrix elements == 0x03 become aqua pixels
matrix elements == 0x04 become blue pixels
matrix elements == 0xfd become gold pixels
matrix elements == 0xfe become black pixels
matrix elements == 0xff become red pixels

I’ve explored imaging options but find learning the PGFPlotsX.jl and PGF/TikZ packages overwhelming and probably overkill for such a simple task, though I recognize that this approach might allow for later additions of axes, a color scale legend, or a caption.

Could someone either help me jump start on setting up those tools or suggest a simpler approach? Thanks in advance.

julia> using ImageCore, IndirectArrays

julia> colormap = zeros(RGB{N0f8}, 0:255);

julia> colormap[[0,1,2,3,4,0xfd,0xfe,0xff]] = [colorant"red", colorant"white", colorant"light gray", colorant"aqua", colorant"blue", colorant"gold", colorant"black", colorant"red"]
8-element Array{RGB{N0f8},1} with eltype RGB{N0f8}:
 RGB{N0f8}(1.0,0.0,0.0)
 RGB{N0f8}(1.0,1.0,1.0)
 RGB{N0f8}(0.827,0.827,0.827)
 RGB{N0f8}(0.0,1.0,1.0)
 RGB{N0f8}(0.0,0.0,1.0)
 RGB{N0f8}(1.0,0.843,0.0)
 RGB{N0f8}(0.0,0.0,0.0)
 RGB{N0f8}(1.0,0.0,0.0)

julia> img = IndirectArray(mymatrix, colormap);

should do the trick.

4 Likes

Thanks a lot, @tim.holy, for your quick response.

I have integrated those lines in my code, and it looks like the object img is created (no error message). I would now like to visualize and/or save that image in a file. I found the package Plots.jl which appears to be able to save a plot to a file, though installing it led to warnings about outdated packages that cannot be upgraded automatically:

(JMRCCM) pkg> add Plots
   Resolving package versions...
   Installed JSON ──── v0.21.4
   Installed DataAPI ─ v1.15.0
   Installed Parsers ─ v2.5.10
    Updating `~/Projects/MISR/MISR_RCCM/JMRCCM/Project.toml`
  [91a5bcdd] + Plots v1.38.12
    Updating `~/Projects/MISR/MISR_RCCM/JMRCCM/Manifest.toml`
  [d1d4a3ce] + BitFlags v0.1.7
...
  [82ae8749] + StatsAPI v1.6.0
⌅ [2913bbd2] + StatsBase v0.33.21
  [3bb67fe8] + TranscodingStreams v0.9.13
...
  [dd4b983a] + LZO_jll v2.10.1+0
⌅ [e9f186c6] + Libffi_jll v3.2.2+1
  [d4300ac3] + Libgcrypt_jll v1.8.7+0
...
  [3161d3a3] + Zstd_jll v1.5.5+0
⌅ [214eeab7] + fzf_jll v0.29.0+0
  [a4ae2306] + libaom_jll v3.4.0+0
...
  [efcefdf7] + PCRE2_jll v10.40.0+0
        Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
Precompiling project...
  8 dependencies successfully precompiled in 63 seconds. 148 already precompiled.

Furthermore, inserting the statement using Plots in the main module of my application, and even explicitly in REPL, leads to an error message stating the ERROR: UndefVarError: Plots not defined, if I call the function Plots.savefig(map_fspec), or ERROR: UndefVarError: savefig not defined is I use savefig(map_fspec) instead.

I’m not sure whether this is the right approach, or whether all those packages are compatible with each other…

So how do I save the image img in a PNG file, then? And is it possible to visualize that image within Julia, for instance even before the PNG file is created?

Thanks again for your help.

You should read the JuliaImages documentation, it will walk you through all these steps. It describes several visualization packages as well as the use of FileIO & ImageIO to save PNG files.

3 Likes

Thanks a lot!

Hi @tim.holy,

Sorry to bother you again, but after reading the documentation and searching the Web for examples (could not find any, in fact), I am still unable to save the image in a PNG file. Here is the code snippet which causes problems:

colormap = zeros(RGB{N0f8}, 0:255)
colormap[[0, 1, 2, 3, 4, 0xfd, 0xfe, 0xff]] = [
    colorant"red", colorant"white",
    colorant"light gray", colorant"aqua",
    colorant"blue", colorant"gold",
    colorant"black", colorant"red"]
img = IndirectArray(rccm[cam, :, :], colormap)
FileIO.save(File(format"PNG", map_fspecs[cam]), colormap[img])

which consistently generates the error

exception =
│    LoadError: UndefVarError: @format_str not defined
│    in expression starting at /Users/michel/Projects/MISR/MISR_RCCM/JMRCCM/src/map_rccm.jl:97

where line 97 is the one starting with FileIO.save. I suspect it’s a trivial issue, but I can’t see it… Thanks for any suggestion on this matter.

using FileIO, ImageIO should solve that.

1 Like

I’m using a whole set of packages:

MicMac3 ~/Projects/MISR/MISR_RCCM/JMRCCM % cat Project.toml
name = "JMRCCM"
uuid = "dac6d2b8-d9c8-4f24-98c6-71f41ffbee16"
...

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959"
JMTools = "7ad2ddfe-0423-4906-9772-0d2d9ace2845"
JMtk15 = "6c71635d-e68b-418f-880a-c8128ca9dc0a"
NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
julia = "1.8"

and when I start from scratch (i.e., from a fresh Julia session), I still get the error message as soon as I am trying to use the main module of my application:

julia> using JMRCCM
[ Info: Precompiling JMRCCM [dac6d2b8-d9c8-4f24-98c6-71f41ffbee16]
ERROR: LoadError: UndefVarError: @format_str not defined
Stacktrace:
  [1] top-level scope
    @ :0
  [2] #macroexpand#61
    @ ./expr.jl:115 [inlined]
  [3] macroexpand
    @ ./expr.jl:113 [inlined]
  [4] docm(source::LineNumberNode, mod::Module, meta::Any, ex::Any, define::Bool) (repeats 2 times)
    @ Base.Docs ./docs/Docs.jl:537
  [5] (::DocStringExtensions.var"#35#36"{typeof(DocStringExtensions.template_hook)})(::LineNumberNode, ::Vararg{Any})
    @ DocStringExtensions ~/.julia/packages/DocStringExtensions/JVu77/src/templates.jl:11
  [6] var"@doc"(::LineNumberNode, ::Module, ::String, ::Vararg{Any})
    @ Core ./boot.jl:519
  [7] include(mod::Module, _path::String)
    @ Base ./Base.jl:419
  [8] include(x::String)
    @ JMRCCM ~/Projects/MISR/MISR_RCCM/JMRCCM/src/JMRCCM.jl:32
  [9] top-level scope
    @ ~/Projects/MISR/MISR_RCCM/JMRCCM/src/JMRCCM.jl:62
 [10] include
    @ ./Base.jl:419 [inlined]
 [11] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt64}}, source::Nothing)
    @ Base ./loading.jl:1554
 [12] top-level scope
    @ stdin:1
in expression starting at /Users/michel/Projects/MISR/MISR_RCCM/JMRCCM/src/map_rccm.jl:1
in expression starting at /Users/michel/Projects/MISR/MISR_RCCM/JMRCCM/src/map_rccm.jl:1
in expression starting at /Users/michel/Projects/MISR/MISR_RCCM/JMRCCM/src/JMRCCM.jl:1
in expression starting at stdin:1
ERROR: Failed to precompile JMRCCM [dac6d2b8-d9c8-4f24-98c6-71f41ffbee16] to /Users/michel/.julia/compiled/v1.8/JMRCCM/jl_kcLk1H.
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool)
   @ Base ./loading.jl:1707
 [3] compilecache
   @ ./loading.jl:1651 [inlined]
 [4] _require(pkg::Base.PkgId)
   @ Base ./loading.jl:1337
 [5] _require_prelocked(uuidkey::Base.PkgId)
   @ Base ./loading.jl:1200
 [6] macro expansion
   @ ./loading.jl:1180 [inlined]
 [7] macro expansion
   @ ./lock.jl:223 [inlined]
 [8] require(into::Module, mod::Symbol)
   @ Base ./loading.jl:1144

julia> using JMRCCM
[ Info: Precompiling JMRCCM [dac6d2b8-d9c8-4f24-98c6-71f41ffbee16]

julia> 

However, as soon as I comment out that FileIO.save statement, the pre-compilation of the module JMRCCM completes without error, as seen in the last couple of lines above.

…and you have to have that using statement in your JMRCCM package. There’s nothing FileIO-specific about this, it’s just general Julia usage. The only FileIO-specific “gotcha” is that it does not itself support any file formats, it just knows which packages are needed to support them. So you need both FileIO (for that @format_str) and a format-specific package like ImageIO.

1 Like

Hi! Thanks for your quick response. Indeed, I had included the using ImageIO in the main module, but not the FileIO… My mistake, sorry about this.

I now encountered a different error message:

julia> rccm, n_miss = fix_rccm(168, 92981, 102;
           stats_it = true, save_it = true, map_it = true);
ERROR: ArgumentError: unable to check bounds for indices of type ColorTypes.RGB{FixedPointNumbers.N0f8}
Stacktrace:
 [1] checkindex(#unused#::Type{Bool}, inds::OffsetArrays.IdOffsetRange{Int64, Base.OneTo{Int64}}, i::ColorTypes.RGB{FixedPointNumbers.N0f8})
   @ Base ./abstractarray.jl:725
 [2] checkindex
   @ ./abstractarray.jl:740 [inlined]
 [3] checkbounds
   @ ./abstractarray.jl:653 [inlined]
 [4] checkbounds
   @ ./abstractarray.jl:668 [inlined]
 [5] _getindex
   @ ./multidimensional.jl:874 [inlined]
 [6] getindex
   @ ./abstractarray.jl:1241 [inlined]
 [7] map_rccm(misr_path::Int64, misr_orbit::Int64, misr_block::Int64, rccm::Array{UInt8, 3}, step::Int64; user::Nothing, project::Nothing, misr_version::String, map_folder::Nothing)
   @ JMRCCM ~/Projects/MISR/MISR_RCCM/JMRCCM/src/map_rccm.jl:97
 [8] fix_rccm(misr_path::Int64, misr_orbit::Int64, misr_block::Int64; misr_version::Nothing, edge::Nothing, user::Nothing, project::Nothing, stats_it::Bool, stats_folder::Nothing, save_it::Bool, save_folder::Nothing, map_it::Bool, map_folder::Nothing)
   @ JMRCCM ~/Projects/MISR/MISR_RCCM/JMRCCM/src/fix_rccm.jl:147
 [9] top-level scope
   @ REPL[7]:1

I’m not sure what that error message refers to…

map_rccm is your code, presumably, so I’m afraid that without more info you’re on your own on this one. Note the text of the error message suggests you’re trying to use colors to index an array, which is not a supported operation.

Yes, every element of the the array rccm has to take one of the values [0, 1, 2, 3, 4, 0xfd, 0xfe, 0xff]. I saw examples of coloring indirect arrays here

https://juliaimages.org/latest/examples/color_channels/indexed_image/#demo_indexed_image

so I guess it must be possible to associate a small set of colors to a discrete set of values in an array. This process is conceptually equivalent to drawing the US flag, where every pixel is either white, blue or red. Any further idea?

Haven’t we gone full-circle? Colorizing a UInt8 matrix - #2 by tim.holy That seems to work, so it seems likely you have a bug in some of the code you’re not showing us.

No, I don’t think so: what I learned is to generate the image, and your help was instrumental for that. The current issue is that I don’t seem to be able to save it in a file…

To recap, in post #10 above, the Julia code does not complain about creating img, but issues the message ArgumentError: unable to check bounds for indices of type ColorTypes. I don’t know how to interpret that error message because I’m just starting to learn about graphics in this language.

Coming back to the current situation: what exactly is Julia complaining about indices of type ColorTypes? Should I assign a color to all intermediary values of colormap between 5 and 0xfc (inclusive), even though they never occur in the data array? Should I specify the dimensions of the individual digital maps contained in the array rccm[9, 512, 128]?

For reference, here is the entire mapping function, which is supposed to image and save 9 maps, each of 512 by 128 pixels, where every pixel can only take one of 8 possible UInt8 values:

function map_rccm(
    misr_path::Integer,
    misr_orbit::Integer,
    misr_block::Integer,
    rccm::Array{UInt8, 3},
    step::Integer;
    user::Union{AbstractString, Nothing} = nothing,
    project::Union{AbstractString, Nothing} = nothing,
    misr_version::Union{AbstractString, Nothing} = nothing,
    map_folder::Union{AbstractString, Nothing} = nothing
    )::Vector{AbstractString}

    # Retrieve the technical specifications of the MISR instrument:
    misr_specs = set_misr_specs()

    # Retrieve the acquisition date of the specified MISR Orbit and extract the year-month-date string needed to set the filename:
    orbit_date, orbit_date_string = orbit2date(misr_orbit)

    map_fspecs = Vector{AbstractString}(undef, misr_specs.ncameras)
    for cam = 1:misr_specs.ncameras

    # Set the specification of the save file:
        qualifier = "Step" * string(step)
        map_fspecs[cam] = mk_post_fspec("Map", "L1RCCMMR", "cldm", qualifier;
            user = user, project = project,
            misr_path1 = misr_path,
            misr_orbit1 = misr_orbit,
            misr_block1 = misr_block,
            misr_camera1 = misr_specs.camera_names[cam],
            misr_resolution = 1100,
            from_date = orbit_date,
            misr_version = misr_version,
            ext = ".png", call_f = "fix_rccm", out_folder = map_folder)

    # Generate a map of the cloud mask for the current camera:
        colormap = zeros(RGB{N0f8}, 0:255)
        colormap[[0, 1, 2, 3, 4, 0xfd, 0xfe, 0xff]] = [
            colorant"red", colorant"white",
            colorant"light gray", colorant"aqua",
            colorant"blue", colorant"gold",
            colorant"black", colorant"red"]
        img = IndirectArray(rccm[cam, :, :], colormap)
        FileIO.save(File(format"PNG", map_fspecs[cam]), colormap[img])
    end

    return map_fspecs

end

It’s pretty simple, really: the array rccm is an input argument, and the purpose of function mk_post_fspec is only to generate a path and name for the output file. The statement that causes problems is FileIO.save(File(format"PNG", map_fspecs[cam]), colormap[img]).

Perhaps a first ‘next step’ could be to visualize those maps, just to make sure they look reasonable, and then to find a way to save them.

I really appreciate your patience in this matter.

UPDATE: I think I found one issue: the colormap statement should probably be written colormap[[0x00, 0x01, 0x02, 0x03, 0x04, 0xfd, 0xfe, 0xff]], not colormap[[0, 1, 2, 3, 4, 0xfd, 0xfe, 0xff]]. I assumed integer representations as decimals or hexadecimal constants could be mixed… In any case, I now don’t get any error messages, but no PNG file is saved either, so the writing of the file is still a problem…

UPDATE to the UPDATE: no cigar yet… After recompiling and executing, I still get the same error message as before:

julia> rccm, n_miss = fix_rccm(168, 1412, 108;
                  stats_it = true, save_it = true, map_it = true);
map_fspecs[cam] = /Users/michel/Projects/MISR/Scrap/Dev/Test/P168+O001412+B108/fix_rccm/L1RCCMMR/Map/Map_L1RCCMMR_cldm_Step1_P168+O001412+B108+DF_R1100_2000-03-24+2023-05-24_F04_0025.png
ERROR: ArgumentError: unable to check bounds for indices of type ColorTypes.RGB{FixedPointNumbers.N0f8}
Stacktrace:
 [1] checkindex(#unused#::Type{Bool}, inds::OffsetArrays.IdOffsetRange{Int64, Base.OneTo{Int64}}, i::ColorTypes.RGB{FixedPointNumbers.N0f8})
   @ Base ./abstractarray.jl:725
 [2] checkindex
   @ ./abstractarray.jl:740 [inlined]
 [3] checkbounds
   @ ./abstractarray.jl:653 [inlined]
 [4] checkbounds
   @ ./abstractarray.jl:668 [inlined]
 [5] _getindex
   @ ./multidimensional.jl:874 [inlined]
 [6] getindex
   @ ./abstractarray.jl:1241 [inlined]
 [7] map_rccm(misr_path::Int64, misr_orbit::Int64, misr_block::Int64, rccm::Array{UInt8, 3}, step::Int64; user::Nothing, project::Nothing, misr_version::String, map_folder::Nothing)
   @ JMRCCM ~/Projects/MISR/MISR_RCCM/JMRCCM/src/map_rccm.jl:99
 [8] fix_rccm(misr_path::Int64, misr_orbit::Int64, misr_block::Int64; misr_version::Nothing, edge::Nothing, user::Nothing, project::Nothing, stats_it::Bool, stats_folder::Nothing, save_it::Bool, save_folder::Nothing, map_it::Bool, map_folder::Nothing)
   @ JMRCCM ~/Projects/MISR/MISR_RCCM/JMRCCM/src/fix_rccm.jl:150
 [9] top-level scope
   @ REPL[12]:1

You don’t need to do the index the colormap again, IndirectArray already does that for you.

FileIO.save(File(format"PNG", map_fspecs[cam]), img)

Or even

save("filename.png", img)
1 Like

Wow! Thanks a lot, @contradict: that was indeed simple and very efficient. It works!