ImageAxes.jl
Хотя изображения зачастую можно представить в виде обычных массивов Array
, иногда требуется дополнительная информация о «значении» каждой оси массива. Например, на трехмерном МРТ-снимке расстояние между вокселами по оси z может быть не таким, как по осям x и y, и это следует учитывать при отображении и (или) анализе таких изображений. Аналогичным образом, фильм имеет две пространственные оси и одну временную — этот факт может иметь значение при обработке изображений.
Пакет ImageAxes (входящий в состав Images) объединяет в себе функции из AxisArrays и SimpleTraits, обеспечивая удобное представление и парадигму программирования для работы с такими изображениями.
Использование
Имена и расположения
Самое простое, что можно сделать, — это присвоить имена осям изображения:
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
Как более подробно описано в документации по AxisArrays, теперь можно делать срезы следующим образом:
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
Можно также задать единицы измерения для осей:
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
Это означает, что интервал по осям x
и y
равен 1 мм, а по оси z
— 3 мм. Кроме того, задается местоположение центра каждого воксела.
Временные оси
Любой массив, имеющий ось Axis{:time}
, распознается как имеющий временное измерение. Если дан массив 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
его временную ось можно получить следующим образом:
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)
а обращаться к ней по индексам можно следующим образом:
img[ax(4)] # возвращает 4-й «временной срез»
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
Кроме того, можно специализировать методы следующим образом:
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
где предварительно определенный типаж HasTimeAxis
ограничивает действие метода массивами, имеющими ось времени. Вот более сложный пример:
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
Если применимо, будет возвращаться средняя интенсивность на каждом временном срезе.
Пользовательские временные оси
С помощью макроса @traitimpl
из пакета SimpleTraits
можно добавить Axis{:t}
, Axis{:scantime}
или любое другое имя в список осей, имеющих временное измерение:
using ImageAxes, SimpleTraits
@traitimpl TimeAxis{Axis{:t}}
Имейте в виду, что это объявление влияет на все массивы на протяжении сеанса. Кроме того, это следует сделать до вызова любых функций применительно к массивам, у которых есть такие оси. Лучше всего сделать это сразу после директивы using ImageAxes
в скрипте верхнего уровня.