Skip to content

Commit 2e02c5a

Browse files
authored
move MultiChannelColors and ImageMetadata support to extensions (#317)
Also check if Gtk4 is initialized in __init__ and before precompiling
1 parent dd4ceff commit 2e02c5a

File tree

6 files changed

+91
-61
lines changed

6 files changed

+91
-61
lines changed

Project.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b"
1212
GtkObservables = "8710efd8-4ad6-11eb-33ea-2d5ceb25a41c"
1313
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
1414
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
15-
ImageMetadata = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
16-
MultiChannelColors = "d4071afc-4203-49ee-90bc-13ebeb18d604"
1715
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1816
RoundingIntegers = "d5f540fe-1c90-5db3-b776-2e2f362d9394"
1917
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
@@ -24,7 +22,7 @@ Cairo = "0.6, 0.7, 0.8, 1"
2422
Compat = "3, 4"
2523
Graphics = "0.2, 0.3, 0.4, 1"
2624
GtkObservables = "2.1"
27-
Gtk4 = "0.6.2, 0.7"
25+
Gtk4 = "0.7.6"
2826
ImageBase = "0.1"
2927
ImageCore = "0.9, 0.10"
3028
ImageMetadata = "0.9"
@@ -37,9 +35,13 @@ TestImages = "1.9"
3735

3836
[weakdeps]
3937
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
38+
ImageMetadata = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
39+
MultiChannelColors = "d4071afc-4203-49ee-90bc-13ebeb18d604"
4040

4141
[extensions]
4242
ImageViewFileIOExt = "FileIO"
43+
ImageViewMultiChannelColorsExt = "MultiChannelColors"
44+
ImageViewImageMetadataExt = "ImageMetadata"
4345

4446
[extras]
4547
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
@@ -51,4 +53,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5153
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
5254

5355
[targets]
54-
test = ["ImageCore", "ImageIO", "IntervalSets", "OffsetArrays", "Observables", "Test", "TestImages"]
56+
test = ["ImageCore", "ImageIO", "IntervalSets", "MultiChannelColors", "OffsetArrays", "Observables", "Test", "TestImages"]

ext/ImageViewImageMetadataExt.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module ImageViewImageMetadataExt
2+
3+
using ImageMetadata
4+
import ImageView: _mappedarray
5+
6+
_mappedarray(f, img::ImageMeta) = shareproperties(img, _mappedarray(f, data(img)))
7+
8+
end

ext/ImageViewMultiChannelColorsExt.jl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module ImageViewMultiChannelColorsExt
2+
3+
using MultiChannelColors, GtkObservables
4+
using ImageCore, ImageCore.MappedArrays
5+
import ImageView: default_clim, _deflt_clim, channel_clims, mapped_channel_clims, histsignals,
6+
scalechannels, safeminmax, outtype, change_channel, contrast_gui, CLim, GrayLike,
7+
_default_clim, channel_clim, nanz
8+
9+
default_clim(img::AbstractArray{C}) where {C<:AbstractMultiChannelColor} = _default_clim(img, eltype(C))
10+
11+
function _deflt_clim(img::AbstractMatrix{C}) where {C<:AbstractMultiChannelColor}
12+
minval = zero(C)
13+
maxval = oneunit(C)
14+
Observable(CLim(minval, maxval))
15+
end
16+
17+
channel_clims(clim::CLim{C}) where {C<:AbstractMultiChannelColor} = map(f->channel_clim(f, clim), ntuple(i -> (c -> Tuple(c)[i]), length(C)))
18+
19+
function mapped_channel_clims(clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
20+
inits = channel_clims(clim[])
21+
return [map!(x -> channel_clim(c -> Tuple(c)[i], x), Observable(inits[1]), clim) for i = 1:length(C)]
22+
end
23+
24+
function histsignals(enabled::Observable{Bool}, img::Observable, clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
25+
chanarrays = [map(x->mappedarray(c -> Tuple(c)[i], x), img) for i = 1:length(C)]
26+
cls = mapped_channel_clims(clim) #note currently this gets called twice, also in contrast gui creation (a bit inefficient/awkward)
27+
histsigs = [histsignals(enabled, chanarrays[i], cls[i])[1] for i = 1:length(C)]
28+
return histsigs
29+
end
30+
31+
function scalechannels(::Type{Tout}, cmin::AbstractMultiChannelColor{T}, cmax::AbstractMultiChannelColor{T}) where {T,Tout}
32+
return x->Tout(ntuple(i -> nanz(scaleminmax(T, Tuple(cmin)[i], Tuple(cmax)[i])(Tuple(x)[i])), length(cmin)))
33+
end
34+
35+
function safeminmax(cmin::C, cmax::C) where {C<:AbstractMultiChannelColor}
36+
minmaxpairs = ntuple(i -> safeminmax(Tuple(cmin)[i], Tuple(cmax)[i]), length(C))
37+
return C(first.(minmaxpairs)), C(last.(minmaxpairs))
38+
end
39+
40+
outtype(::Type{C}) where C<:AbstractMultiChannelColor = C
41+
42+
function change_channel(col::CLim{C}, chanlim::CLim{G}, i::Int) where {C<:AbstractMultiChannelColor, G<:GrayLike}
43+
cmin, cmax = col.min, col.max
44+
cmin = Base.setindex(cmin, chanlim.min, i)
45+
cmax = Base.setindex(cmax, chanlim.max, i)
46+
return CLim(cmin, cmax)
47+
end
48+
49+
function contrast_gui(enabled::Observable{Bool}, hists::Vector, clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
50+
N = length(C)
51+
@assert length(hists) == N #one signal per color channel
52+
chanlims = channel_clims(clim[])
53+
csigs = Observable.(chanlims)
54+
#make sure that changes to individual channels update the color clim signal and vice versa
55+
for i = 1:N
56+
Observables.ObservablePair(clim, csigs[i]; f=x->channel_clim(c->Tuple(c)[i], x), g=x->change_channel(clim, x, i))
57+
end
58+
names = ["Contrast $i" for i = 1:N]
59+
cguis = []
60+
for i=1:length(hists)
61+
push!(cguis, contrast_gui(enabled, hists[i], csigs[i]; wname = names[i]))
62+
end
63+
return cguis
64+
end
65+
66+
67+
end
68+

src/ImageView.jl

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ end
88

99
using ImageCore, ImageBase, StatsBase
1010
using ImageCore.MappedArrays
11-
using MultiChannelColors
1211
using RoundingIntegers
1312
using Gtk4, GtkObservables, Graphics, Cairo
1413
using Gtk4: Align_START, Align_END, Align_FILL
1514
using GtkObservables.Observables
1615
using AxisArrays: AxisArrays, Axis, AxisArray, axisnames, axisvalues, axisdim
17-
using ImageMetadata
1816
using Compat # for @constprop :none
1917

2018
export AnnotationText, AnnotationPoint, AnnotationPoints,
@@ -559,7 +557,6 @@ end
559557
default_clim(img) = nothing
560558
default_clim(img::AbstractArray{C}) where {C<:GrayLike} = _default_clim(img, eltype(C))
561559
default_clim(img::AbstractArray{C}) where {C<:AbstractRGB} = _default_clim(img, eltype(C))
562-
default_clim(img::AbstractArray{C}) where {C<:AbstractMultiChannelColor} = _default_clim(img, eltype(C))
563560
_default_clim(img, ::Type{Bool}) = nothing
564561
_default_clim(img, ::Type{T}) where {T} = _deflt_clim(img)
565562
function _deflt_clim(img::AbstractMatrix)
@@ -573,12 +570,6 @@ function _deflt_clim(img::AbstractMatrix{T}) where {T<:AbstractRGB}
573570
Observable(CLim(minval, maxval))
574571
end
575572

576-
function _deflt_clim(img::AbstractMatrix{C}) where {C<:AbstractMultiChannelColor}
577-
minval = zero(C)
578-
maxval = oneunit(C)
579-
Observable(CLim(minval, maxval))
580-
end
581-
582573
saferound(x::Integer) = convert(RInteger, x)
583574
saferound(x) = x
584575

@@ -615,7 +606,6 @@ end
615606

616607
channel_clim(f, clim::CLim{C}) where {C<:Colorant} = CLim(f(clim.min), f(clim.max))
617608
channel_clims(clim::CLim{T}) where {T<:AbstractRGB} = map(f->channel_clim(f, clim), (red, green, blue))
618-
channel_clims(clim::CLim{C}) where {C<:AbstractMultiChannelColor} = map(f->channel_clim(f, clim), ntuple(i -> (c -> Tuple(c)[i]), length(C)))
619609

620610
function mapped_channel_clims(clim::Observable{CLim{T}}) where {T<:AbstractRGB}
621611
inits = channel_clims(clim[])
@@ -637,18 +627,6 @@ function histsignals(enabled::Observable{Bool}, img::Observable, clim::Observabl
637627
return histsigs
638628
end
639629

640-
function mapped_channel_clims(clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
641-
inits = channel_clims(clim[])
642-
return [map!(x -> channel_clim(c -> Tuple(c)[i], x), Observable(inits[1]), clim) for i = 1:length(C)]
643-
end
644-
645-
function histsignals(enabled::Observable{Bool}, img::Observable, clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
646-
chanarrays = [map(x->mappedarray(c -> Tuple(c)[i], x), img) for i = 1:length(C)]
647-
cls = mapped_channel_clims(clim) #note currently this gets called twice, also in contrast gui creation (a bit inefficient/awkward)
648-
histsigs = [histsignals(enabled, chanarrays[i], cls[i])[1] for i = 1:length(C)]
649-
return histsigs
650-
end
651-
652630
function scalechannels(::Type{Tout}, cmin::AbstractRGB{T}, cmax::AbstractRGB{T}) where {T,Tout}
653631
r = scaleminmax(T, red(cmin), red(cmax))
654632
g = scaleminmax(T, green(cmin), green(cmax))
@@ -671,15 +649,6 @@ function safeminmax(cmin::T, cmax::T) where {T<:AbstractRGB}
671649
return T(rmin, gmin, bmin), T(rmax, gmax, bmax)
672650
end
673651

674-
function scalechannels(::Type{Tout}, cmin::AbstractMultiChannelColor{T}, cmax::AbstractMultiChannelColor{T}) where {T,Tout}
675-
return x->Tout(ntuple(i -> nanz(scaleminmax(T, Tuple(cmin)[i], Tuple(cmax)[i])(Tuple(x)[i])), length(cmin)))
676-
end
677-
678-
function safeminmax(cmin::C, cmax::C) where {C<:AbstractMultiChannelColor}
679-
minmaxpairs = ntuple(i -> safeminmax(Tuple(cmin)[i], Tuple(cmax)[i]), length(C))
680-
return C(first.(minmaxpairs)), C(last.(minmaxpairs))
681-
end
682-
683652
function prep_contrast(@nospecialize(img::Observable), clim::Observable{CLim{T}}) where {T}
684653
# Set up the signals to calculate the histogram of intensity
685654
enabled = Observable(false) # skip hist calculation if the contrast gui isn't open
@@ -695,7 +664,6 @@ end
695664

696665
outtype(::Type{T}) where T<:GrayLike = Gray{N0f8}
697666
outtype(::Type{C}) where C<:Color = RGB{N0f8}
698-
outtype(::Type{C}) where C<:AbstractMultiChannelColor = C
699667
outtype(::Type{C}) where C<:TransparentColor = RGBA{N0f8}
700668

701669
function prep_contrast(canvas, @nospecialize(img::Observable), clim::Observable{CLim{T}}) where T
@@ -833,7 +801,6 @@ isgray(img) = false
833801

834802
_mappedarray(f, img) = mappedarray(f, img)
835803
_mappedarray(f, img::AxisArray) = AxisArray(mappedarray(f, img.data), AxisArrays.axes(img))
836-
_mappedarray(f, img::ImageMeta) = shareproperties(img, _mappedarray(f, data(img)))
837804

838805
wrap_signal(x) = Observable(x)
839806
wrap_signal(x::Observable) = x
@@ -849,6 +816,10 @@ include("contrast_gui.jl")
849816
include("annotations.jl")
850817

851818
function __init__()
819+
if !Gtk4.initialized[]
820+
error("Gtk4 not initialized")
821+
return
822+
end
852823
# by default, GtkFrame and GtkAspectFrame use rounded corners
853824
# the way to override this is via custom CSS
854825
css="""
@@ -863,6 +834,10 @@ using PrecompileTools
863834
for T in (N0f8, N0f16, Float32)
864835
for C in (Gray, RGB)
865836
img = rand(C{T}, 2, 2)
837+
if !Gtk4.initialized[]
838+
@warn("ImageView precompile skipped: Gtk4 could not be initialized (are you on a headless system?)")
839+
return
840+
end
866841
imshow(img)
867842
clim = ImageView.default_clim(img)
868843
imgsig = Observable(img)

src/contrast_gui.jl

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ change_red(col::Observable, chan::CLim) = change_red(col[], chan)
1717
change_green(col::Observable, chan::CLim) = change_green(col[], chan)
1818
change_blue(col::Observable, chan::CLim) = change_blue(col[], chan)
1919

20-
function change_channel(col::CLim{C}, chanlim::CLim{G}, i::Int) where {C<:AbstractMultiChannelColor, G<:GrayLike}
21-
cmin, cmax = col.min, col.max
22-
cmin = Base.setindex(cmin, chanlim.min, i)
23-
cmax = Base.setindex(cmax, chanlim.max, i)
24-
return CLim(cmin, cmax)
25-
end
2620
change_channel(col::Observable, chanlim::CLim, i::Int) = change_channel(col[], chanlim, i)
2721

2822
function contrast_gui(enabled::Observable{Bool}, hists::Vector, clim::Observable{CLim{T}}) where {T<:AbstractRGB}
@@ -44,23 +38,6 @@ function contrast_gui(enabled::Observable{Bool}, hists::Vector, clim::Observable
4438
return cguis
4539
end
4640

47-
function contrast_gui(enabled::Observable{Bool}, hists::Vector, clim::Observable{CLim{C}}) where {C<:AbstractMultiChannelColor}
48-
N = length(C)
49-
@assert length(hists) == N #one signal per color channel
50-
chanlims = channel_clims(clim[])
51-
csigs = Observable.(chanlims)
52-
#make sure that changes to individual channels update the color clim signal and vice versa
53-
for i = 1:N
54-
Observables.ObservablePair(clim, csigs[i]; f=x->channel_clim(c->Tuple(c)[i], x), g=x->change_channel(clim, x, i))
55-
end
56-
names = ["Contrast $i" for i = 1:N]
57-
cguis = []
58-
for i=1:length(hists)
59-
push!(cguis, contrast_gui(enabled, hists[i], csigs[i]; wname = names[i]))
60-
end
61-
return cguis
62-
end
63-
6441
contrast_gui(enabled, hist::Vector, clim) = contrast_gui(enabled, hist[1], clim)
6542

6643
function contrast_gui(enabled::Observable{Bool}, hist::Observable, clim::Observable; wname="Contrast")

test/contrast.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using ImageView, ImageCore, ImageView.Observables, ImageView.MultiChannelColors
1+
using ImageView, ImageCore, ImageView.Observables, MultiChannelColors
22
using Gtk4: Gtk4
33
using Test
44

0 commit comments

Comments
 (0)