diff --git a/src/AbstractImageReconstruction.jl b/src/AbstractImageReconstruction.jl index 1d40c06..e85a1ed 100644 --- a/src/AbstractImageReconstruction.jl +++ b/src/AbstractImageReconstruction.jl @@ -9,7 +9,7 @@ import Base: put!, take!, fieldtypes, fieldtype, ismissing, propertynames, paren include("AlgorithmInterface.jl") include("StructTransforms.jl") -include("AlgorithmPlan.jl") +include("RecoPlans/RecoPlans.jl") include("MiscAlgorithms/MiscAlgorithms.jl") end # module diff --git a/src/AlgorithmPlan.jl b/src/AlgorithmPlan.jl deleted file mode 100644 index 055bb5d..0000000 --- a/src/AlgorithmPlan.jl +++ /dev/null @@ -1,415 +0,0 @@ -export RecoPlan - -export AbstractPlanListener, TransientListener, SerializableListener -abstract type AbstractPlanListener end -abstract type TransientListener <: AbstractPlanListener end -abstract type SerializableListener <: AbstractPlanListener end - -const LISTENER_TAG = "_listener" - -mutable struct RecoPlan{T<:Union{AbstractImageReconstructionParameters, AbstractImageReconstructionAlgorithm}} - parent::Union{Nothing, RecoPlan} - values::Dict{Symbol, Any} - listeners::Dict{Symbol, Vector{AbstractPlanListener}} - setProperties::Dict{Symbol, Bool} - function RecoPlan(::Type{T}; kwargs...) where {T<:AbstractImageReconstructionParameters} - dict = Dict{Symbol, Any}() - listeners = Dict{Symbol, Vector{AbstractPlanListener}}() - setProperties = Dict{Symbol, Bool}() - for field in filter(f -> !startswith(string(f), "_"), fieldnames(T)) - dict[field] = missing - listeners[field] = AbstractPlanListener[] - setProperties[field] = false - end - plan = new{getfield(parentmodule(T), nameof(T))}(nothing, dict, listeners, setProperties) - setvalues!(plan, kwargs...) - return plan - end - function RecoPlan(::Type{T}) where {T<:AbstractImageReconstructionAlgorithm} - dict = Dict{Symbol, Any}() - listeners = Dict{Symbol, Vector{AbstractPlanListener}}() - setProperties = Dict{Symbol, Bool}() - dict[:parameter] = missing - listeners[:parameter] = AbstractPlanListener[] - setProperties[:parameter] = false - return new{getfield(parentmodule(T), nameof(T))}(nothing, dict, listeners, setProperties) - end -end - -function setvalues!(plan::RecoPlan{T}; kwargs...) where {T<:AbstractImageReconstructionParameters} - kwargs = values(kwargs) - for field in propertynames(plan) - if haskey(kwargs, field) - setvalue!(plan, field, kwargs[field]) - end - end -end - -export parent, parent! -parent(plan::RecoPlan) = getfield(plan, :parent) -parent!(plan::RecoPlan, parent::RecoPlan) = setfield!(plan, :parent, parent) -function parentfields(plan::RecoPlan) - trace = Symbol[] - return parentfields!(trace, plan) -end -function parentfields!(trace::Vector{Symbol}, plan::RecoPlan) - p = parent(plan) - if !isnothing(p) - for property in propertynames(p) - if getproperty(p, property) === plan - pushfirst!(trace, property) - return parentfields!(trace, p) - end - end - end - return trace -end - -function RecoPlan(parent::RecoPlan, t::Type; kwargs...) - plan = RecoPlan(t; kwargs...) - parent!(plan, parent) - return plan -end - -Base.propertynames(plan::RecoPlan{T}) where {T} = keys(getfield(plan, :values)) - -Base.getproperty(plan::RecoPlan{T}, name::Symbol) where {T} = getfield(plan, :values)[name] -Base.getindex(plan::RecoPlan{T}, name::Symbol) where {T} = Base.getproperty(plan, name) - -export propertyupdate!, ispropertyset, setvalue!, valueupdate -function propertyupdate!(listener::AbstractPlanListener, origin, field, old, new) - # NOP -end -function Base.setproperty!(plan::RecoPlan{T}, name::Symbol, x::X) where {T, X} - old = getproperty(plan, name) - setvalue!(plan, name, x) - getfield(plan, :setProperties)[name] = true - for listener in getlisteners(plan, name) - try - propertyupdate!(listener, plan, name, old, x) - catch e - @error "Exception in listener $listener " e - end - end -end -ispropertyset(plan::RecoPlan, name::Symbol) = getfield(plan, :setProperties)[name] -Base.setindex!(plan::RecoPlan, x, name::Symbol) = Base.setproperty!(plan, name, x) -function valueupdate(listener::AbstractPlanListener, origin, field, old, new) - # NOP -end -function setvalue!(plan::RecoPlan{T}, name::Symbol, x::X) where {T, X} - old = Base.getproperty(plan, name) - t = type(plan, name) - if !haskey(getfield(plan, :values), name) - error("type $T has no field $name") - elseif X <: t || X <: RecoPlan{<:t} || ismissing(x) - getfield(plan, :values)[name] = x - else - getfield(plan, :values)[name] = convert(t, x) - end - new = Base.getproperty(plan, name) - for listener in getlisteners(plan, name) - try - valueupdate(listener, plan, name, old, new) - catch e - @error "Exception in listener $listener " e - end - end - return new -end - -Base.ismissing(plan::RecoPlan, name::Symbol) = ismissing(getfield(plan, :values)[name]) - -export setAll! -function setAll!(plan::RecoPlan{T}, name::Symbol, x) where {T<:AbstractImageReconstructionParameters} - fields = getfield(plan, :values) - nestedPlans = filter(entry -> isa(last(entry), RecoPlan), fields) - for (key, nested) in nestedPlans - key != name && setAll!(nested, name, x) - end - if haskey(fields, name) - try - plan[name] = x - catch ex - @warn "Could not set $name of $T with value of type $(typeof(x))" - end - end -end -setAll!(plan::RecoPlan{<:AbstractImageReconstructionAlgorithm}, name::Symbol, x) = setAll!(plan.parameter, name, x) -function setAll!(plan; kwargs...) - for key in keys(kwargs) - setAll!(plan, key, kwargs[key]) - end -end -setAll!(plan::RecoPlan, dict::Dict{Symbol, Any}) = setAll!(plan; dict...) -setAll!(plan::RecoPlan, dict::Dict{String, Any}) = setAll!(plan, Dict{Symbol, Any}(Symbol(k) => v for (k,v) in dict)) - - -export types, type -types(::RecoPlan{T}) where {T<:AbstractImageReconstructionParameters} = fieldtypes(T) -type(::RecoPlan{T}, name::Symbol) where {T<:AbstractImageReconstructionParameters} = fieldtype(T, name) - -function type(plan::RecoPlan{T}, name::Symbol) where {T<:AbstractImageReconstructionAlgorithm} - if name == :parameter - return RecoPlan - else - error("type $(typeof(plan)) has no field $name") - end -end -types(::RecoPlan{T}) where {T<:AbstractImageReconstructionAlgorithm} = [type(plan, name) for name in propertynames(plan)] - - -export clear! -function clear!(plan::RecoPlan{T}, preserve::Bool = true) where {T<:AbstractImageReconstructionParameters} - dict = getfield(plan, :values) - set = getfield(plan, :setProperties) - for key in keys(dict) - value = dict[key] - if typeof(value) <: RecoPlan && preserve - clear!(value, preserve) - else - dict[key] = missing - set[key] = false - end - end - return plan -end -clear!(plan::RecoPlan{T}, preserve::Bool = true) where {T<:AbstractImageReconstructionAlgorithm} = clear!(plan.parameter, preserve) - -export getlisteners, addListener!, removeListener! -getlisteners(plan::RecoPlan, field::Symbol) = getfield(plan, :listeners)[field] -function addListener!(plan::RecoPlan, field::Symbol, listener::AbstractPlanListener) - listeners = getlisteners(plan, field) - push!(listeners, listener) -end -function removeListener!(plan::RecoPlan, field::Symbol, listener::AbstractPlanListener) - listeners = getlisteners(plan, field) - idx = findall(x->isequal(x, listener), listeners) - isnothing(idx) && deleteat!(listeners, idx) -end - -export build -function build(plan::RecoPlan{T}) where {T<:AbstractImageReconstructionParameters} - fields = copy(getfield(plan, :values)) - nestedPlans = filter(entry -> isa(last(entry), RecoPlan), fields) - for (name, nested) in nestedPlans - fields[name] = build(nested) - end - fields = filter(entry -> !ismissing(last(entry)), fields) - return T(;fields...) -end -function build(plan::RecoPlan{T}) where {T<:AbstractImageReconstructionAlgorithm} - parameter = build(plan[:parameter]) - return T(parameter) -end - -export toPlan -function toPlan(param::AbstractImageReconstructionParameters) - args = Dict{Symbol, Any}() - plan = RecoPlan(typeof(param)) - for field in fieldnames(typeof(param)) - value = getproperty(param, field) - if typeof(value) <: AbstractImageReconstructionParameters || typeof(value) <: AbstractImageReconstructionAlgorithm - args[field] = toPlan(plan, value) - else - args[field] = value - end - end - setvalues!(plan; args...) - return plan -end -function toPlan(parent::RecoPlan, x) - plan = toPlan(x) - parent!(plan, parent) - return plan -end -toPlan(algo::AbstractImageReconstructionAlgorithm) = toPlan(algo, parameter(algo)) -toPlan(algo::AbstractImageReconstructionAlgorithm, params::AbstractImageReconstructionParameters) = toPlan(typeof(algo), params) -function toPlan(::Type{T}, params::AbstractImageReconstructionParameters) where {T<:AbstractImageReconstructionAlgorithm} - plan = RecoPlan(T) - plan[:parameter] = toPlan(plan, params) - return plan -end - -toDictModule(plan::RecoPlan{T}) where {T} = parentmodule(T) -toDictType(plan::RecoPlan{T}) where {T} = RecoPlan{getfield(parentmodule(T), nameof(T))} -function addDictValue!(dict, value::RecoPlan) - for field in propertynames(value) - x = getproperty(value, field) - if !ismissing(x) - dict[string(field)] = toDictValue(type(value, field), x) - end - end - listeners = filter(x-> !isempty(last(x)), getfield(value, :listeners)) - if !isempty(listeners) - listenerDict = Dict{String, Any}() - for (field, l) in listeners - serializable = filter(x-> x isa SerializableListener, l) - if !isempty(serializable) - listenerDict[string(field)] = toDictValue(typeof(l), l) - end - end - if !isempty(listenerDict) - dict[LISTENER_TAG] = listenerDict - end - end - return dict -end - -export plandir, planpath -function plandir(m::Module) - if m != AbstractImageReconstruction && hasproperty(m, :plandir) - return getproperty(m, :plandir)() - else - return @get_scratch!(string(m)) - end -end -planpath(m::Module, name::AbstractString) = joinpath(plandir(m), string(name, ".toml")) - -export loadPlan -loadPlan(m::Module, name::AbstractString, modules::Vector{Module}) = loadPlan(planpath(m, name), modules) -function loadPlan(filename::AbstractString, modules::Vector{Module}) - dict = TOML.parsefile(filename) - modDict = createModuleDataTypeDict(modules) - plan = loadPlan!(dict, modDict) - loadListeners!(plan, dict, modDict) - return plan -end -function createModuleDataTypeDict(modules::Vector{Module}) - modDict = Dict{String, Dict{String, Union{DataType, UnionAll, Function}}}() - for mod in modules - typeDict = Dict{String, Union{DataType, UnionAll, Function}}() - for field in names(mod) - try - t = getfield(mod, field) - if t isa DataType || t isa UnionAll || t isa Function - typeDict[string(t)] = t - end - catch - end - end - modDict[string(mod)] = typeDict - end - return modDict -end -function loadPlan!(dict::Dict{String, Any}, modDict) - re = r"RecoPlan\{(.*)\}" - m = match(re, dict[TYPE_TAG]) - if !isnothing(m) - type = m.captures[1] - mod = dict[MODULE_TAG] - plan = RecoPlan(modDict[mod][type]) - loadPlan!(plan, dict, modDict) - return plan - else - # Has to be parameter or algo or broken toml - # TODO implement - error("Not implemented yet") - end -end -function loadPlan!(plan::RecoPlan{T}, dict::Dict{String, Any}, modDict) where {T<:AbstractImageReconstructionAlgorithm} - temp = loadPlan!(dict["parameter"], modDict) - parent!(temp, plan) - setvalue!(plan, :parameter, temp) - return plan -end -function loadPlan!(plan::RecoPlan{T}, dict::Dict{String, Any}, modDict) where {T<:AbstractImageReconstructionParameters} - for name in propertynames(plan) - t = type(plan, name) - param = missing - key = string(name) - if haskey(dict, key) - if t <: AbstractImageReconstructionAlgorithm || t <: AbstractImageReconstructionParameters - param = loadPlan!(dict[key], modDict) - parent!(param, plan) - else - param = loadPlanValue(T, name, t, dict[key], modDict) - end - end - setvalue!(plan, name, param) - end - return plan -end -loadPlanValue(parent::Type{T}, field::Symbol, type, value, modDict) where T <: AbstractImageReconstructionParameters = loadPlanValue(type, value, modDict) -# Type{<:T} where {T} -function loadPlanValue(t::UnionAll, value::Dict, modDict) - if value[TYPE_TAG] == string(Type) - return modDict[value[MODULE_TAG]][value[VALUE_TAG]] - else - return fromTOML(specializeType(t, value, modDict), value) - end -end -function loadPlanValue(::Type{Vector{<:T}}, value::Vector, modDict) where {T} - result = Any[] - for val in value - type = modDict[val[MODULE_TAG]][val[TYPE_TAG]] - push!(result, fromTOML(type, val)) - end - # Narrow vector - return identity.(result) -end -uniontypes(t::Union) = Base.uniontypes(t) -#uniontypes(t::Union) = [t.a, uniontypes(t.b)...] -#uniontypes(t::DataType) = [t] -function loadPlanValue(t::Union, value::Dict, modDict) - types = uniontypes(t) - idx = findfirst(x-> string(x) == value[UNION_TYPE_TAG], types) - if isnothing(idx) - toml = tomlType(value, modDict, prefix = "union") - idx = !isnothing(toml) ? findfirst(x-> toml <: x, types) : idx # Potentially check if more than one fits and chose most fitting - end - type = isnothing(idx) ? t : types[idx] - return loadPlanValue(type, value[VALUE_TAG], modDict) -end -loadPlanValue(t::DataType, value::Dict, modDict) = fromTOML(specializeType(t, value, modDict), value) -loadPlanValue(t, value, modDict) = fromTOML(t, value) - -function tomlType(dict::Dict, modDict; prefix::String = "") - if haskey(dict, "_$(prefix)module") && haskey(dict, "_$(prefix)type") - mod = dict["_$(prefix)module"] - type = dict["_$(prefix)type"] - if haskey(modDict, mod) && haskey(modDict[mod], type) - return modDict[mod][type] - end - end - return nothing -end -function specializeType(t::Union{DataType, UnionAll}, value::Dict, modDict) - if isconcretetype(t) - return t - end - type = tomlType(value, modDict) - return !isnothing(type) && type <: t ? type : t -end - -loadListeners!(plan, dict, modDict) = loadListeners!(plan, plan, dict, modDict) -function loadListeners!(root::RecoPlan, plan::RecoPlan{T}, dict, modDict) where {T<:AbstractImageReconstructionAlgorithm} - loadListeners!(root, plan.parameter, dict["parameter"], modDict) -end -function loadListeners!(root::RecoPlan, plan::RecoPlan{T}, dict, modDict) where {T<:AbstractImageReconstructionParameters} - if haskey(dict, ".listener") - for (property, listenerDicts) in dict[".listener"] - for listenerDict in listenerDicts - listener = loadListener(root, listenerDict, modDict) - addListener!(plan, Symbol(property), listener) - end - end - end - for property in propertynames(plan) - value = plan[property] - if value isa RecoPlan - loadListeners!(root, value, dict[string(property)], modDict) - end - end -end -export loadListener -function loadListener(root, dict, modDict) - type = tomlType(dict, modDict) - return loadListener(type, root, dict, modDict) -end - -export savePlan -savePlan(filename::AbstractString, plan::RecoPlan) = toTOML(filename, plan) -savePlan(m::Module, planname::AbstractString, plan::RecoPlan) = savePlan(planpath(m, planname), plan) - -include("LinkedFieldListener.jl") \ No newline at end of file diff --git a/src/RecoPlans/Listeners.jl b/src/RecoPlans/Listeners.jl new file mode 100644 index 0000000..085b73e --- /dev/null +++ b/src/RecoPlans/Listeners.jl @@ -0,0 +1,25 @@ +export TransientListener, SerializableListener +abstract type TransientListener <: AbstractPlanListener end +abstract type SerializableListener <: AbstractPlanListener end + +const LISTENER_TAG = "_listener" + +export propertyupdate!, valueupdate +function propertyupdate!(listener::AbstractPlanListener, origin, field, old, new) + # NOP +end +function valueupdate(listener::AbstractPlanListener, origin, field, old, new) + # NOP +end + +export getlisteners, addListener!, removeListener! +getlisteners(plan::RecoPlan, field::Symbol) = getfield(plan, :listeners)[field] +function addListener!(plan::RecoPlan, field::Symbol, listener::AbstractPlanListener) + listeners = getlisteners(plan, field) + push!(listeners, listener) +end +function removeListener!(plan::RecoPlan, field::Symbol, listener::AbstractPlanListener) + listeners = getlisteners(plan, field) + idx = findall(x->isequal(x, listener), listeners) + isnothing(idx) && deleteat!(listeners, idx) +end \ No newline at end of file diff --git a/src/RecoPlans/RecoPlans.jl b/src/RecoPlans/RecoPlans.jl new file mode 100644 index 0000000..6997140 --- /dev/null +++ b/src/RecoPlans/RecoPlans.jl @@ -0,0 +1,211 @@ +export AbstractPlanListener +abstract type AbstractPlanListener end + +export RecoPlan +mutable struct RecoPlan{T<:Union{AbstractImageReconstructionParameters, AbstractImageReconstructionAlgorithm}} + parent::Union{Nothing, RecoPlan} + values::Dict{Symbol, Any} + listeners::Dict{Symbol, Vector{AbstractPlanListener}} + setProperties::Dict{Symbol, Bool} + function RecoPlan(::Type{T}; kwargs...) where {T<:AbstractImageReconstructionParameters} + dict = Dict{Symbol, Any}() + listeners = Dict{Symbol, Vector{AbstractPlanListener}}() + setProperties = Dict{Symbol, Bool}() + for field in filter(f -> !startswith(string(f), "_"), fieldnames(T)) + dict[field] = missing + listeners[field] = AbstractPlanListener[] + setProperties[field] = false + end + plan = new{getfield(parentmodule(T), nameof(T))}(nothing, dict, listeners, setProperties) + setvalues!(plan, kwargs...) + return plan + end + function RecoPlan(::Type{T}) where {T<:AbstractImageReconstructionAlgorithm} + dict = Dict{Symbol, Any}() + listeners = Dict{Symbol, Vector{AbstractPlanListener}}() + setProperties = Dict{Symbol, Bool}() + dict[:parameter] = missing + listeners[:parameter] = AbstractPlanListener[] + setProperties[:parameter] = false + return new{getfield(parentmodule(T), nameof(T))}(nothing, dict, listeners, setProperties) + end +end + + +Base.propertynames(plan::RecoPlan{T}) where {T} = keys(getfield(plan, :values)) +Base.getproperty(plan::RecoPlan{T}, name::Symbol) where {T} = getfield(plan, :values)[name] +Base.getindex(plan::RecoPlan{T}, name::Symbol) where {T} = Base.getproperty(plan, name) + +export types, type +types(::RecoPlan{T}) where {T<:AbstractImageReconstructionParameters} = fieldtypes(T) +type(::RecoPlan{T}, name::Symbol) where {T<:AbstractImageReconstructionParameters} = fieldtype(T, name) + +function type(plan::RecoPlan{T}, name::Symbol) where {T<:AbstractImageReconstructionAlgorithm} + if name == :parameter + return RecoPlan + else + error("type $(typeof(plan)) has no field $name") + end +end +types(::RecoPlan{T}) where {T<:AbstractImageReconstructionAlgorithm} = [type(plan, name) for name in propertynames(plan)] + + +export ispropertyset, setvalue! +function Base.setproperty!(plan::RecoPlan{T}, name::Symbol, x::X) where {T, X} + old = getproperty(plan, name) + setvalue!(plan, name, x) + getfield(plan, :setProperties)[name] = true + for listener in getlisteners(plan, name) + try + propertyupdate!(listener, plan, name, old, x) + catch e + @error "Exception in listener $listener " e + end + end +end +ispropertyset(plan::RecoPlan, name::Symbol) = getfield(plan, :setProperties)[name] +Base.setindex!(plan::RecoPlan, x, name::Symbol) = Base.setproperty!(plan, name, x) +function setvalue!(plan::RecoPlan{T}, name::Symbol, x::X) where {T, X} + old = Base.getproperty(plan, name) + t = type(plan, name) + if !haskey(getfield(plan, :values), name) + error("type $T has no field $name") + elseif X <: t || X <: RecoPlan{<:t} || ismissing(x) + getfield(plan, :values)[name] = x + else + getfield(plan, :values)[name] = convert(t, x) + end + new = Base.getproperty(plan, name) + for listener in getlisteners(plan, name) + try + valueupdate(listener, plan, name, old, new) + catch e + @error "Exception in listener $listener " e + end + end + return new +end +function setvalues!(plan::RecoPlan{T}; kwargs...) where {T<:AbstractImageReconstructionParameters} + kwargs = values(kwargs) + for field in propertynames(plan) + if haskey(kwargs, field) + setvalue!(plan, field, kwargs[field]) + end + end +end + +export setAll! +function setAll!(plan::RecoPlan{T}, name::Symbol, x) where {T<:AbstractImageReconstructionParameters} + fields = getfield(plan, :values) + nestedPlans = filter(entry -> isa(last(entry), RecoPlan), fields) + for (key, nested) in nestedPlans + key != name && setAll!(nested, name, x) + end + if haskey(fields, name) + try + plan[name] = x + catch ex + @warn "Could not set $name of $T with value of type $(typeof(x))" + end + end +end +setAll!(plan::RecoPlan{<:AbstractImageReconstructionAlgorithm}, name::Symbol, x) = setAll!(plan.parameter, name, x) +function setAll!(plan; kwargs...) + for key in keys(kwargs) + setAll!(plan, key, kwargs[key]) + end +end +setAll!(plan::RecoPlan, dict::Dict{Symbol, Any}) = setAll!(plan; dict...) +setAll!(plan::RecoPlan, dict::Dict{String, Any}) = setAll!(plan, Dict{Symbol, Any}(Symbol(k) => v for (k,v) in dict)) + +export clear! +function clear!(plan::RecoPlan{T}, preserve::Bool = true) where {T<:AbstractImageReconstructionParameters} + dict = getfield(plan, :values) + set = getfield(plan, :setProperties) + for key in keys(dict) + value = dict[key] + if typeof(value) <: RecoPlan && preserve + clear!(value, preserve) + else + dict[key] = missing + set[key] = false + end + end + return plan +end +clear!(plan::RecoPlan{T}, preserve::Bool = true) where {T<:AbstractImageReconstructionAlgorithm} = clear!(plan.parameter, preserve) + + +export parent, parent! +parent(plan::RecoPlan) = getfield(plan, :parent) +parent!(plan::RecoPlan, parent::RecoPlan) = setfield!(plan, :parent, parent) +function parentfields(plan::RecoPlan) + trace = Symbol[] + return parentfields!(trace, plan) +end +function parentfields!(trace::Vector{Symbol}, plan::RecoPlan) + p = parent(plan) + if !isnothing(p) + for property in propertynames(p) + if getproperty(p, property) === plan + pushfirst!(trace, property) + return parentfields!(trace, p) + end + end + end + return trace +end + +function RecoPlan(parent::RecoPlan, t::Type; kwargs...) + plan = RecoPlan(t; kwargs...) + parent!(plan, parent) + return plan +end + +Base.ismissing(plan::RecoPlan, name::Symbol) = ismissing(getfield(plan, :values)[name]) + +export build +function build(plan::RecoPlan{T}) where {T<:AbstractImageReconstructionParameters} + fields = copy(getfield(plan, :values)) + nestedPlans = filter(entry -> isa(last(entry), RecoPlan), fields) + for (name, nested) in nestedPlans + fields[name] = build(nested) + end + fields = filter(entry -> !ismissing(last(entry)), fields) + return T(;fields...) +end +function build(plan::RecoPlan{T}) where {T<:AbstractImageReconstructionAlgorithm} + parameter = build(plan[:parameter]) + return T(parameter) +end + +export toPlan +function toPlan(param::AbstractImageReconstructionParameters) + args = Dict{Symbol, Any}() + plan = RecoPlan(typeof(param)) + for field in fieldnames(typeof(param)) + value = getproperty(param, field) + if typeof(value) <: AbstractImageReconstructionParameters || typeof(value) <: AbstractImageReconstructionAlgorithm + args[field] = toPlan(plan, value) + else + args[field] = value + end + end + setvalues!(plan; args...) + return plan +end +function toPlan(parent::RecoPlan, x) + plan = toPlan(x) + parent!(plan, parent) + return plan +end +toPlan(algo::AbstractImageReconstructionAlgorithm) = toPlan(algo, parameter(algo)) +toPlan(algo::AbstractImageReconstructionAlgorithm, params::AbstractImageReconstructionParameters) = toPlan(typeof(algo), params) +function toPlan(::Type{T}, params::AbstractImageReconstructionParameters) where {T<:AbstractImageReconstructionAlgorithm} + plan = RecoPlan(T) + plan[:parameter] = toPlan(plan, params) + return plan +end + +include("Listeners.jl") +include("Serialization.jl") \ No newline at end of file diff --git a/src/RecoPlans/Serialization.jl b/src/RecoPlans/Serialization.jl new file mode 100644 index 0000000..a7aa252 --- /dev/null +++ b/src/RecoPlans/Serialization.jl @@ -0,0 +1,244 @@ +export plandir, planpath +function plandir(m::Module) + if m != AbstractImageReconstruction && hasproperty(m, :plandir) + return getproperty(m, :plandir)() + else + return @get_scratch!(string(m)) + end +end +planpath(m::Module, name::AbstractString) = joinpath(plandir(m), string(name, ".toml")) + +export savePlan +savePlan(filename::AbstractString, plan::RecoPlan) = toTOML(filename, plan) +savePlan(m::Module, planname::AbstractString, plan::RecoPlan) = savePlan(planpath(m, planname), plan) + +export toTOML, toDict, toDict!, toDictValue +# TODO adapt tomlType +const MODULE_TAG = "_module" +const TYPE_TAG = "_type" +const VALUE_TAG = "_value" +const UNION_TYPE_TAG = "_uniontype" +const UNION_MODULE_TAG = "_unionmodule" + + +function toTOML(fileName::AbstractString, value) + open(fileName, "w") do io + toTOML(io, value) + end +end + +function toTOML(io::IO, value) + dict = toDict(value) + TOML.print(io, dict) do x + toTOML(x) + end +end + +toTOML(x::Module) = string(x) +toTOML(x::Symbol) = string(x) +toTOML(x::T) where {T<:Enum} = string(x) +toTOML(x::Array) = toTOML.(x) +toTOML(x::Type{T}) where T = string(x) +toTOML(x::Nothing) = Dict() + +function toDict(value) + dict = Dict{String, Any}() + return toDict!(dict, value) +end + +function toDict!(dict, value) + dict[MODULE_TAG] = toDictModule(value) + dict[TYPE_TAG] = toDictType(value) + addDictValue!(dict, value) + return dict +end +toDictModule(value) = parentmodule(typeof(value)) +toDictType(value) = nameof(typeof(value)) +function addDictValue!(dict, value) + for field in fieldnames(typeof(value)) + dict[string(field)] = toDictValue(fieldtype(typeof(value), field), getfield(value, field)) + end +end + +toDictType(value::Function) = nameof(value) +function addDictValue!(dict, value::Function) + # NOP +end + +toDictValue(type, value) = toDictValue(value) +function toDictValue(x) + if fieldcount(typeof(x)) > 0 + return toDict(x) + else + return x + end +end +toDictValue(x::Array) = toDictValue.(x) +toDictValue(x::Type{T}) where T = toDict(x) +function toDict!(dict, ::Type{T}) where T + dict[MODULE_TAG] = parentmodule(T) + dict[TYPE_TAG] = Type + dict[VALUE_TAG] = T + return dict +end + +function toDictValue(type::Union, value) + dict = Dict{String, Any}() + dict[MODULE_TAG] = toDictModule(type) + dict[TYPE_TAG] = toDictType(type) + dict[VALUE_TAG] = toDictValue(value) + dict[UNION_TYPE_TAG] = typeof(value) # directly type to not remove parametric fields + dict[UNION_MODULE_TAG] = toDictModule(typeof(value)) + return dict +end + +export loadPlan +loadPlan(m::Module, name::AbstractString, modules::Vector{Module}) = loadPlan(planpath(m, name), modules) +function loadPlan(filename::AbstractString, modules::Vector{Module}) + dict = TOML.parsefile(filename) + modDict = createModuleDataTypeDict(modules) + plan = loadPlan!(dict, modDict) + loadListeners!(plan, dict, modDict) + return plan +end +function createModuleDataTypeDict(modules::Vector{Module}) + modDict = Dict{String, Dict{String, Union{DataType, UnionAll, Function}}}() + for mod in modules + typeDict = Dict{String, Union{DataType, UnionAll, Function}}() + for field in names(mod) + try + t = getfield(mod, field) + if t isa DataType || t isa UnionAll || t isa Function + typeDict[string(t)] = t + end + catch + end + end + modDict[string(mod)] = typeDict + end + return modDict +end +function loadPlan!(dict::Dict{String, Any}, modDict) + re = r"RecoPlan\{(.*)\}" + m = match(re, dict[TYPE_TAG]) + if !isnothing(m) + type = m.captures[1] + mod = dict[MODULE_TAG] + plan = RecoPlan(modDict[mod][type]) + loadPlan!(plan, dict, modDict) + return plan + else + # Has to be parameter or algo or broken toml + # TODO implement + error("Not implemented yet") + end +end +function loadPlan!(plan::RecoPlan{T}, dict::Dict{String, Any}, modDict) where {T<:AbstractImageReconstructionAlgorithm} + temp = loadPlan!(dict["parameter"], modDict) + parent!(temp, plan) + setvalue!(plan, :parameter, temp) + return plan +end +function loadPlan!(plan::RecoPlan{T}, dict::Dict{String, Any}, modDict) where {T<:AbstractImageReconstructionParameters} + for name in propertynames(plan) + t = type(plan, name) + param = missing + key = string(name) + if haskey(dict, key) + if t <: AbstractImageReconstructionAlgorithm || t <: AbstractImageReconstructionParameters + param = loadPlan!(dict[key], modDict) + parent!(param, plan) + else + param = loadPlanValue(T, name, t, dict[key], modDict) + end + end + setvalue!(plan, name, param) + end + return plan +end +loadPlanValue(parent::Type{T}, field::Symbol, type, value, modDict) where T <: AbstractImageReconstructionParameters = loadPlanValue(type, value, modDict) +# Type{<:T} where {T} +function loadPlanValue(t::UnionAll, value::Dict, modDict) + if value[TYPE_TAG] == string(Type) + return modDict[value[MODULE_TAG]][value[VALUE_TAG]] + else + return fromTOML(specializeType(t, value, modDict), value) + end +end +function loadPlanValue(::Type{Vector{<:T}}, value::Vector, modDict) where {T} + result = Any[] + for val in value + type = modDict[val[MODULE_TAG]][val[TYPE_TAG]] + push!(result, fromTOML(type, val)) + end + # Narrow vector + return identity.(result) +end +uniontypes(t::Union) = Base.uniontypes(t) +#uniontypes(t::Union) = [t.a, uniontypes(t.b)...] +#uniontypes(t::DataType) = [t] +function loadPlanValue(t::Union, value::Dict, modDict) + types = uniontypes(t) + idx = findfirst(x-> string(x) == value[UNION_TYPE_TAG], types) + if isnothing(idx) + toml = tomlType(value, modDict, prefix = "union") + idx = !isnothing(toml) ? findfirst(x-> toml <: x, types) : idx # Potentially check if more than one fits and chose most fitting + end + type = isnothing(idx) ? t : types[idx] + return loadPlanValue(type, value[VALUE_TAG], modDict) +end +loadPlanValue(t::DataType, value::Dict, modDict) = fromTOML(specializeType(t, value, modDict), value) +loadPlanValue(t, value, modDict) = fromTOML(t, value) + +function tomlType(dict::Dict, modDict; prefix::String = "") + if haskey(dict, "_$(prefix)module") && haskey(dict, "_$(prefix)type") + mod = dict["_$(prefix)module"] + type = dict["_$(prefix)type"] + if haskey(modDict, mod) && haskey(modDict[mod], type) + return modDict[mod][type] + end + end + return nothing +end +function specializeType(t::Union{DataType, UnionAll}, value::Dict, modDict) + if isconcretetype(t) + return t + end + type = tomlType(value, modDict) + return !isnothing(type) && type <: t ? type : t +end + +loadListeners!(plan, dict, modDict) = loadListeners!(plan, plan, dict, modDict) +function loadListeners!(root::RecoPlan, plan::RecoPlan{T}, dict, modDict) where {T<:AbstractImageReconstructionAlgorithm} + loadListeners!(root, plan.parameter, dict["parameter"], modDict) +end +function loadListeners!(root::RecoPlan, plan::RecoPlan{T}, dict, modDict) where {T<:AbstractImageReconstructionParameters} + if haskey(dict, ".listener") + for (property, listenerDicts) in dict[".listener"] + for listenerDict in listenerDicts + listener = loadListener(root, listenerDict, modDict) + addListener!(plan, Symbol(property), listener) + end + end + end + for property in propertynames(plan) + value = plan[property] + if value isa RecoPlan + loadListeners!(root, value, dict[string(property)], modDict) + end + end +end +export loadListener +function loadListener(root, dict, modDict) + type = tomlType(dict, modDict) + return loadListener(type, root, dict, modDict) +end + +fromTOML(t, x) = x +function fromTOML(::Type{Nothing}, x::Dict) #where {T} + if isempty(x) + return nothing + end + error("Unexpected value $x for Nothing, expected empty Dict") +end +fromTOML(::Type{V}, x::Vector) where {T, V<:Vector{<:T}} = fromTOML.(T, x) diff --git a/src/StructTransforms.jl b/src/StructTransforms.jl index d16e55f..1d305fe 100644 --- a/src/StructTransforms.jl +++ b/src/StructTransforms.jl @@ -1,91 +1,4 @@ -export toTOML, toDict, toDict!, toDictValue, toKwargs, toKwargs!, fromKwargs - -# TODO adapt tomlType -const MODULE_TAG = "_module" -const TYPE_TAG = "_type" -const VALUE_TAG = "_value" -const UNION_TYPE_TAG = "_uniontype" -const UNION_MODULE_TAG = "_unionmodule" - -function toTOML(fileName::AbstractString, value) - open(fileName, "w") do io - toTOML(io, value) - end -end - -function toTOML(io::IO, value) - dict = toDict(value) - TOML.print(io, dict) do x - toTOML(x) - end -end - -toTOML(x::Module) = string(x) -toTOML(x::Symbol) = string(x) -toTOML(x::T) where {T<:Enum} = string(x) -toTOML(x::Array) = toTOML.(x) -toTOML(x::Type{T}) where T = string(x) -toTOML(x::Nothing) = Dict() - -fromTOML(t, x) = x -function fromTOML(::Type{Nothing}, x::Dict) #where {T} - if isempty(x) - return nothing - end - error("Unexpected value $x for Nothing, expected empty Dict") -end -fromTOML(::Type{V}, x::Vector) where {T, V<:Vector{<:T}} = fromTOML.(T, x) - -function toDict(value) - dict = Dict{String, Any}() - return toDict!(dict, value) -end - -function toDict!(dict, value) - dict[MODULE_TAG] = toDictModule(value) - dict[TYPE_TAG] = toDictType(value) - addDictValue!(dict, value) - return dict -end -toDictModule(value) = parentmodule(typeof(value)) -toDictType(value) = nameof(typeof(value)) -function addDictValue!(dict, value) - for field in fieldnames(typeof(value)) - dict[string(field)] = toDictValue(fieldtype(typeof(value), field), getfield(value, field)) - end -end - -toDictType(value::Function) = nameof(value) -function addDictValue!(dict, value::Function) - # NOP -end - -toDictValue(type, value) = toDictValue(value) -function toDictValue(x) - if fieldcount(typeof(x)) > 0 - return toDict(x) - else - return x - end -end -toDictValue(x::Array) = toDictValue.(x) -toDictValue(x::Type{T}) where T = toDict(x) -function toDict!(dict, ::Type{T}) where T - dict[MODULE_TAG] = parentmodule(T) - dict[TYPE_TAG] = Type - dict[VALUE_TAG] = T - return dict -end - -function toDictValue(type::Union, value) - dict = Dict{String, Any}() - dict[MODULE_TAG] = toDictModule(type) - dict[TYPE_TAG] = toDictType(type) - dict[VALUE_TAG] = toDictValue(value) - dict[UNION_TYPE_TAG] = typeof(value) # directly type to not remove parametric fields - dict[UNION_MODULE_TAG] = toDictModule(typeof(value)) - return dict -end +export toKwargs, toKwargs!, fromKwargs function toKwargs(value; kwargs...) dict = Dict{Symbol, Any}()