|
1 | 1 | ```@meta
|
2 |
| -CurrentModule = FluorophoreColors |
| 2 | +CurrentModule = MultiChannelColors |
3 | 3 | ```
|
4 | 4 |
|
5 |
| -# FluorophoreColors |
| 5 | +# MultiChannelColors |
6 | 6 |
|
7 |
| -Documentation for [FluorophoreColors](https://github.com/JuliaImages/FluorophoreColors.jl). |
| 7 | +[MultiChannelColors](https://github.com/JuliaImages/MultiChannelColors.jl) aims to support "unconventional colors," such as might arise in applications like multichannel fluorescence microscopy and hyperspectral imaging. Consistent with the philosophy of the [JuliaImages ecosystem](https://juliaimages.org/latest/), this package allows you to bundle together the different color channels into a "color object," and many color objects can be stored in an array. Having each entry of the array represent a complete pixel or voxel makes it much easier to write generic code supporting a wide range of image types. |
8 | 8 |
|
9 |
| -```@index |
| 9 | +## Installation |
| 10 | + |
| 11 | +Install the package with `add MultiChannelColors` from the `pkg>` prompt, which you access by typing `]` from the `julia>` prompt. See the [Pkg documentation](https://pkgdocs.julialang.org/v1/getting-started/) for more information. |
| 12 | + |
| 13 | +## Usage |
| 14 | + |
| 15 | +Use the package interactively or in code with |
| 16 | + |
| 17 | +```jldoctest demo |
| 18 | +julia> using MultiChannelColors |
| 19 | +``` |
| 20 | + |
| 21 | +In addition to giving access to specific types defined below, this will import the namespaces of [FixedPointNumbers](https://github.com/JuliaMath/FixedPointNumbers.jl) (which harmonizes the interpretation of "integer" and "floating-point" pixel-encodings) and [ColorTypes](https://github.com/JuliaGraphics/ColorTypes.jl) (which defines core color types and low-level manipulation). It will also define arithmetic for colors such as RGB (see [ColorVectorSpace](https://github.com/JuliaGraphics/ColorVectorSpace.jl)). |
| 22 | + |
| 23 | +The color types in this package support two fundamental categories of operations: |
| 24 | + |
| 25 | +- arithmetic operations such as `+` and `-` and multiplying or dividing by a scalar. You can also scale each color channel independently with `⊙` (obtained with `\odotTAB`) or its synonym `hadamard`, e.g., `g ⊙ c` where `c` is a color object defined in this package and `g` is a tuple of real numbers (the "gains"). |
| 26 | +- extracting the independent channel intensities as a tuple with `Tuple(c)`. |
| 27 | + |
| 28 | +When creating `c`, you have two choices which primarily affect visualization: |
| 29 | + |
| 30 | +- to use "bare" colors that store the multichannel data but lack any default conversion to other color spaces. This might be most appropriate if you have more than 3 channels, for which there may be many different ways to visualize the data they encode. |
| 31 | +- to use colors with built-in conversion to RGB, making them work automatically in standard visualization tools. This may be most appropriate when you have 3 or fewer channels. |
| 32 | + |
| 33 | +Both options will be discussed below. See the [JuliaImages documentation on visualization](https://juliaimages.org/latest/install/#sec_visualization) for more information about tools for viewing images. |
| 34 | + |
| 35 | +### "Bare" colors: `MultiChannelColor` |
| 36 | + |
| 37 | +A `MultiChannelColor` object is essentially a glorified tuple, one that can be recognized as a [`Colorant`](https://github.com/JuliaGraphics/ColorTypes.jl#the-type-hierarchy-and-abstract-types) but with comparatively few automatic behaviors. For example, if you're working with [Landsat 8](https://en.wikipedia.org/wiki/Landsat_8) data with |
| 38 | +[11 wavelength bands](https://landsat.gsfc.nasa.gov/satellites/landsat-8/landsat-8-bands/), one might create a pixel this way: |
| 39 | + |
| 40 | +```jldoctest demo |
| 41 | +julia> c = MultiChannelColor{N4f12}(0.2, 0.1, 0.2, 0.2, 0.25, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2) |
| 42 | +(0.2N4f12₀₁, 0.1001N4f12₀₂, 0.2N4f12₀₃, 0.2N4f12₀₄, 0.2501N4f12₀₅, 0.2N4f12₀₆, 0.2N4f12₀₇, 0.2N4f12₀₈, 0.2N4f12₀₉, 0.2N4f12₁₀, 0.2N4f12₁₁) |
| 43 | +``` |
| 44 | + |
| 45 | +See the [FixedPointNumbers](https://github.com/JuliaMath/FixedPointNumbers.jl) package for information about the 16-bit data type `N4f12` (Landsat 8 quantizes with 12 bits). |
| 46 | + |
| 47 | +The usual way to visualize such an object is to define a custom function that converts such colors to more conventional colors (`RGB` or `Gray`). For example, we might compute the [Enhanced Vegetation Index](https://www.usgs.gov/landsat-missions/landsat-enhanced-vegetation-index) |
| 48 | +and render positive values in green and negative values in magenta: |
| 49 | + |
| 50 | +```jldoctest demo |
| 51 | +julia> function evi(c::MultiChannelColor{T,11}) where T<:FixedPoint |
| 52 | + # Valid for Landsat 8 with 11 spectral bands |
| 53 | + b = Tuple(c) # extract the bands |
| 54 | + evi = 2.5f0 * (b[5] - b[4]) / (b[5] + 6*b[4] - 7.5f0*b[2] + eps(T)) |
| 55 | + return evi > 0 ? RGB(0, evi, 0) : RGB(-evi, 0, -evi) |
| 56 | + end; |
| 57 | +
|
| 58 | +julia> evi(c) |
| 59 | +RGB{Float32}(0.0f0,0.17894554f0,0.0f0) |
| 60 | +``` |
| 61 | + |
| 62 | +If `img` is a whole image of such pixels, `evi.(img)` converts the entire array to RGB. For large data, you might prefer to use the [MappedArrays package](https://github.com/JuliaArrays/MappedArrays.jl) to do such conversions "lazily" (on an as-needed basis) to avoid exhausting computer memory: |
| 63 | + |
| 64 | +```julia |
| 65 | +julia> using MappedArrays |
| 66 | + |
| 67 | +julia> imgrgb = mappedarray(evi, img); |
| 68 | +``` |
| 69 | + |
| 70 | +### RGB-convertible colors: `ColorMixture` |
| 71 | + |
| 72 | +`ColorMixture` objects are like `MultiChannelColor` objects except they have a built-in conversion to RGB. Each channel gets assigned a specific RGB color, say `rgbⱼ` for the `j`th channel, along with an intensity `iⱼ`. |
| 73 | +`rgbⱼ` is a feature of the *type* (shared by all objects of the same type) whereas `iⱼ` is a property of *objects*. |
| 74 | + |
| 75 | +`ColorMixture` objects are converted to RGB with intensity-weighting, |
| 76 | + |
| 77 | +`` |
| 78 | +c_{rgb} = \sum_j i_j \mathrm{rgb}_j |
| 79 | +`` |
| 80 | + |
| 81 | +Depending on the the `rgbⱼ` and `iⱼ`, values may exceed the 0-to-1 colorscale of RGBs. |
| 82 | +Conversion to `RGB{Float32}` may be safer than `RGB{T}` where `T` is limited to 0-to-1. |
| 83 | +It is also faster, as the result does not have to be checked for whether it exceeds the bounds of the type. |
| 84 | +(To prevent overflow, all internal operations are performed using floating-point intermediates even if you want a `FixedPoint` output.) |
| 85 | + |
| 86 | +!!! note |
| 87 | + While `ColorMixture` objects can be converted to RGB, they are *not* AbstractRGB |
| 88 | + colors: `red(c)`, `green(c)`, and `blue(c)` are not defined for `c::ColorMixture`, and low-level utilities |
| 89 | + like `mapc` operate on the raw channel intensities rather than the RGB values. |
| 90 | + |
| 91 | + |
| 92 | +There are several ways you can create these colors. An easy approach is to define the type through a "template" object: |
| 93 | + |
| 94 | +```jldoctest demo |
| 95 | +julia> ctemplate = ColorMixture{Float32}((RGB(0,1,0), RGB(1,0,0))) |
| 96 | +(0.0₁, 0.0₂) |
10 | 97 | ```
|
11 | 98 |
|
12 |
| -```@autodocs |
13 |
| -Modules = [FluorophoreColors] |
| 99 | +`ctemplate` is an all-zeros `ColorMixture` object, but can be used to construct arbitrary `c` with specified intensities: |
| 100 | + |
| 101 | +```jldoctest demo |
| 102 | +julia> typeof(ctemplate) |
| 103 | +ColorMixture{Float32, 2, (RGB{N0f8}(0.0,1.0,0.0), RGB{N0f8}(1.0,0.0,0.0))} |
| 104 | +
|
| 105 | +julia> c = ctemplate(0.2, 0.4) |
| 106 | +(0.2₁, 0.4₂) |
| 107 | +
|
| 108 | +julia> Tuple(c) |
| 109 | +(0.2f0, 0.4f0) |
| 110 | +``` |
| 111 | + |
| 112 | +You can also create them with a single call `ColorMixture(rgbs, intensities)`: |
| 113 | + |
| 114 | +```jldoctest demo |
| 115 | +julia> c = ColorMixture{Float32}((RGB(0,1,0), RGB(1,0,0)), (0.2, 0.4)) |
| 116 | +(0.2₁, 0.4₂) |
| 117 | +``` |
| 118 | + |
| 119 | +or even by explicit type construction: |
| 120 | + |
| 121 | +```jldoctest demo |
| 122 | +julia> ColorMixture{Float32, 2, (RGB{N0f8}(0.0,1.0,0.0), RGB{N0f8}(1.0,0.0,0.0))}(0.2, 0.4) |
| 123 | +(0.2₁, 0.4₂) |
14 | 124 | ```
|
| 125 | + |
| 126 | +!!! tip |
| 127 | + All but the last form require [constant propagation](https://en.wikipedia.org/wiki/Constant_folding) for inferrability. |
| 128 | + Julia 1.7 and higher can use "aggressive" constant propagation to solve inference problems that may reduce performance on Julia 1.6. |
| 129 | + |
| 130 | +### Importing external data |
| 131 | + |
| 132 | +When objects are not created by code but instead loaded from an external source such as a file, you have several avenues for creating arrays of multichannel color objects. There are two particularly common cases: |
| 133 | + |
| 134 | +1. If the imported data are an array `A` of size `(nc, m, n)`, where `nc` is the number of color channels (i.e., color is the fastest dimension), then use `reinterpret(reshape, C, A)` where `C` is the color type you want to use (e.g., `MultiChannelColor{T,nc}` or `ColorMixture{T,nc,rgbs}`). For instance, Landsat 8 data might look something like this: |
| 135 | + |
| 136 | + ```julia |
| 137 | + A = rand(0x0000:0x0fff, 11, 100, 100); |
| 138 | + img = reinterpret(reshape, MultiChannelColor{N4f12,11}, A); |
| 139 | + ``` |
| 140 | + |
| 141 | +2. If the imported data have the color channel last, or use separate arrays for each channel, use the [StructArrays package](https://github.com/JuliaArrays/StructArrays.jl). For example: |
| 142 | + |
| 143 | + ```julia |
| 144 | + A = rand(0x0000:0x0fff, 100, 100, 11); |
| 145 | + img = StructArray{MultiChannelColor{N4f12,11}}(A; dims=3) |
| 146 | + ``` |
| 147 | + |
| 148 | +## Additional features |
| 149 | + |
| 150 | +### Fluorophores |
| 151 | + |
| 152 | +This package also exports a lookup table for common [fluorophores](https://en.wikipedia.org/wiki/Fluorophore). If desired, these can be used as the `rgbⱼ` values for `ColorMixture` channels. For example: |
| 153 | + |
| 154 | +```jldoctest demo |
| 155 | +julia> channels = (fluorophore_rgb["EGFP"], fluorophore_rgb["tdTomato"]) |
| 156 | +(RGB{N0f8}(0.0,0.925,0.365), RGB{N0f8}(1.0,0.859,0.0)) |
| 157 | +
|
| 158 | +julia> ctemplate = ColorMixture{N0f16}(channels) |
| 159 | +(0.0N0f16₁, 0.0N0f16₂) |
| 160 | +``` |
| 161 | + |
| 162 | +If you'll be hard-coding the name of the fluorophore, consider using a slightly different syntax: |
| 163 | + |
| 164 | +```jldoctest demo |
| 165 | +julia> channels = (fluorophore_rgb"EGFP", fluorophore_rgb"tdTomato") |
| 166 | +(RGB{N0f8}(0.0,0.925,0.365), RGB{N0f8}(1.0,0.859,0.0)) |
| 167 | +``` |
| 168 | + |
| 169 | +Note the absence of `[]` brackets around the fluorophore names. This form creates types inferrably, but the fluorophore name must be a literal string constant. |
| 170 | + |
| 171 | +The RGB values are computed from the peak emission wavelength of each fluorophore; note, however, that the perceptual appearance is often more red-shifted due to the asymmetric shape of emission spectra. |
| 172 | + |
| 173 | +### Green/magenta coloration |
| 174 | + |
| 175 | +For good separability in two-color imaging, the `GreenMagenta{T}` and `MagentaGreen{T}` types are convenient: |
| 176 | + |
| 177 | +```jldoctest demo |
| 178 | +julia> c = GreenMagenta{N0f8}(0.2, 0.4) |
| 179 | +(0.2N0f8₁, 0.4N0f8₂) |
| 180 | +
|
| 181 | +julia> convert(RGB, c) |
| 182 | +RGB{N0f8}(0.4,0.2,0.4) |
| 183 | +``` |
| 184 | + |
| 185 | +Green and magenta are distinguishable even by individuals with common forms of color blindness, and is thus a good default for two-color imaging. |
0 commit comments