ImageAxes.jl
While images can often be represented as plain Array
s, sometimes additional information about the "meaning" of each axis of the array is needed. For example, in a 3-dimensional MRI scan, the voxels may not have the same spacing along the z-axis that they do along the x- and y-axes, and this fact should be accounted for during the display and/or analysis of such images. Likewise, a movie has two spatial axes and one temporal axis; this fact may be relevant for how one performs image processing.
The ImageAxes package (which is incorporated into Images) combines features from AxisArrays and SimpleTraits to provide a convenient representation and programming paradigm for dealing with such images.
Installation
If you want to directly use ImageAxes
, add
it via the package manager.
Usage
Names and locations
The simplest thing you can do is to provide names to your image axes:
using ImageAxes
img = AxisArray(reshape(1:192, (8,8,3)), :x, :y, :z)
3-dimensional AxisArray{Int64,3,...} with axes:
:x, Base.OneTo(8)
:y, Base.OneTo(8)
:z, Base.OneTo(3)
And data, a 8×8×3 reshape(::UnitRange{Int64}, 8, 8, 3) with eltype Int64:
[:, :, 1] =
1 9 17 25 33 41 49 57
2 10 18 26 34 42 50 58
3 11 19 27 35 43 51 59
4 12 20 28 36 44 52 60
5 13 21 29 37 45 53 61
6 14 22 30 38 46 54 62
7 15 23 31 39 47 55 63
8 16 24 32 40 48 56 64
[:, :, 2] =
65 73 81 89 97 105 113 121
66 74 82 90 98 106 114 122
67 75 83 91 99 107 115 123
68 76 84 92 100 108 116 124
69 77 85 93 101 109 117 125
70 78 86 94 102 110 118 126
71 79 87 95 103 111 119 127
72 80 88 96 104 112 120 128
[:, :, 3] =
129 137 145 153 161 169 177 185
130 138 146 154 162 170 178 186
131 139 147 155 163 171 179 187
132 140 148 156 164 172 180 188
133 141 149 157 165 173 181 189
134 142 150 158 166 174 182 190
135 143 151 159 167 175 183 191
136 144 152 160 168 176 184 192
As described in more detail in the AxisArrays documentation, you can now take slices like this:
slz = img[Axis{:z}(2)]
2-dimensional AxisArray{Int64,2,...} with axes:
:x, Base.OneTo(8)
:y, Base.OneTo(8)
And data, a 8×8 Matrix{Int64}:
65 73 81 89 97 105 113 121
66 74 82 90 98 106 114 122
67 75 83 91 99 107 115 123
68 76 84 92 100 108 116 124
69 77 85 93 101 109 117 125
70 78 86 94 102 110 118 126
71 79 87 95 103 111 119 127
72 80 88 96 104 112 120 128
slx = img[Axis{:x}(2)]
2-dimensional AxisArray{Int64,2,...} with axes:
:y, Base.OneTo(8)
:z, Base.OneTo(3)
And data, a 8×3 Matrix{Int64}:
2 66 130
10 74 138
18 82 146
26 90 154
34 98 162
42 106 170
50 114 178
58 122 186
sly = img[Axis{:y}(2)]
2-dimensional AxisArray{Int64,2,...} with axes:
:x, Base.OneTo(8)
:z, Base.OneTo(3)
And data, a 8×3 Matrix{Int64}:
9 73 137
10 74 138
11 75 139
12 76 140
13 77 141
14 78 142
15 79 143
16 80 144
You can also give units to the axes:
using ImageAxes, Unitful
const mm = u"mm"
img = AxisArray(reshape(1:192, (8,8,3)),
Axis{:x}(1mm:1mm:8mm),
Axis{:y}(1mm:1mm:8mm),
Axis{:z}(2mm:3mm:8mm))
3-dimensional AxisArray{Int64,3,...} with axes:
:x, (1:8) mm
:y, (1:8) mm
:z, (2:3:8) mm
And data, a 8×8×3 reshape(::UnitRange{Int64}, 8, 8, 3) with eltype Int64:
[:, :, 1] =
1 9 17 25 33 41 49 57
2 10 18 26 34 42 50 58
3 11 19 27 35 43 51 59
4 12 20 28 36 44 52 60
5 13 21 29 37 45 53 61
6 14 22 30 38 46 54 62
7 15 23 31 39 47 55 63
8 16 24 32 40 48 56 64
[:, :, 2] =
65 73 81 89 97 105 113 121
66 74 82 90 98 106 114 122
67 75 83 91 99 107 115 123
68 76 84 92 100 108 116 124
69 77 85 93 101 109 117 125
70 78 86 94 102 110 118 126
71 79 87 95 103 111 119 127
72 80 88 96 104 112 120 128
[:, :, 3] =
129 137 145 153 161 169 177 185
130 138 146 154 162 170 178 186
131 139 147 155 163 171 179 187
132 140 148 156 164 172 180 188
133 141 149 157 165 173 181 189
134 142 150 158 166 174 182 190
135 143 151 159 167 175 183 191
136 144 152 160 168 176 184 192
which specifies that x
and y
have spacing of 1mm and z
has a spacing of 3mm, as well as the location of the center of each voxel.
Temporal axes
Any array possessing an axis Axis{:time}
will be recognized as having a temporal dimension. Given an array A
,
using ImageAxes, Unitful
const s = u"s"
img = AxisArray(reshape(1:9*300, (3,3,300)),
Axis{:x}(1:3),
Axis{:y}(1:3),
Axis{:time}(1s/30:1s/30:10s))
3-dimensional AxisArray{Int64,3,...} with axes:
:x, 1:3
:y, 1:3
:time, (0.03333333333333333:0.03333333333333333:10.0) s
And data, a 3×3×300 reshape(::UnitRange{Int64}, 3, 3, 300) with eltype Int64:
[:, :, 1] =
1 4 7
2 5 8
3 6 9
[:, :, 2] =
10 13 16
11 14 17
12 15 18
[:, :, 3] =
19 22 25
20 23 26
21 24 27
;;; …
[:, :, 298] =
2674 2677 2680
2675 2678 2681
2676 2679 2682
[:, :, 299] =
2683 2686 2689
2684 2687 2690
2685 2688 2691
[:, :, 300] =
2692 2695 2698
2693 2696 2699
2694 2697 2700
you can retrieve its temporal axis with
ax = timeaxis(img)
Axis{:time, StepRangeLen{Unitful.Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}, Base.TwicePrecision{Unitful.Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}}, Base.TwicePrecision{Unitful.Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}}, Int64}}((0.03333333333333333:0.03333333333333333:10.0) s)
and index it like
img[ax(4)] # returns the 4th "timeslice"
2-dimensional AxisArray{Int64,2,...} with axes:
:x, 1:3
:y, 1:3
And data, a 3×3 Matrix{Int64}:
28 31 34
29 32 35
30 33 36
You can also specialize methods like this:
using ImageAxes, SimpleTraits
@traitfn nimages(img::AA) where {AA<:AxisArray; HasTimeAxis{AA}} = length(timeaxis(img))
@traitfn nimages(img::AA) where {AA<:AxisArray; !HasTimeAxis{AA}} = 1
nimages (generic function with 3 methods)
where the pre-defined HasTimeAxis
trait will restrict that method to arrays that have a timeaxis. A more complex example is
using ImageAxes, SimpleTraits, Statistics
@traitfn meanintensity(img::AA) where {AA<:AxisArray; !HasTimeAxis{AA}} = mean(img)
@traitfn function meanintensity(img::AA) where {AA<:AxisArray; HasTimeAxis{AA}}
ax = timeaxis(img)
n = length(ax)
intensity = zeros(eltype(img), n)
for ti in 1:n
sl = view(img, ax(ti))
intensity[ti] = mean(sl)
end
intensity
end
and, when appropriate, it will return the mean intensity at each timeslice.
Custom temporal axes
Using SimpleTraits
's @traitimpl
, you can add Axis{:t}
or Axis{:scantime}
or any other name to the list of axes that have a temporal dimension:
using ImageAxes, SimpleTraits
@traitimpl TimeAxis{Axis{:t}}
Note this declaration affects all arrays throughout your entire session. Moreover, it should be made before calling any functions on array-types that possess such axes; a convenient place to do this is right after you say using ImageAxes
in your top-level script.