Документация Engee

ImageAxes.jl

Хотя изображения зачастую можно представить в виде обычных массивов Array, иногда требуется дополнительная информация о «значении» каждой оси массива. Например, на трехмерном МРТ-снимке расстояние между вокселами по оси z может быть не таким, как по осям x и y, и это следует учитывать при отображении и (или) анализе таких изображений. Аналогичным образом, фильм имеет две пространственные оси и одну временную — этот факт может иметь значение при обработке изображений.

Пакет ImageAxes (входящий в состав Images) объединяет в себе функции из AxisArrays и SimpleTraits, обеспечивая удобное представление и парадигму программирования для работы с такими изображениями.

Установка

Чтобы использовать ImageAxes напрямую, добавьте (add) его через диспетчер пакетов.

Использование

Имена и расположения

Самое простое, что можно сделать, — это присвоить имена осям изображения:

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 в скрипте верхнего уровня.