Skip to content

Commit d5feb5b

Browse files
authored
Struct array writing (JuliaIO#207)
Allow struct array writing in mat files via the `MatlabStructArray` type. Includes: * a new `MatlabStructArray` type with all related functionality and docs * a new `MatlabClassObject` type and compatibility with `MatlabStructArray` * a fix for single `Char` writing
1 parent 2df47dd commit d5feb5b

23 files changed

+864
-164
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MAT"
22
uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
3-
version = "0.10.7"
3+
version = "0.11.0"
44

55
[deps]
66
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"

docs/Project.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
33
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
4-
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
4+
MAT = "23992714-dd62-5051-b70f-ba57cb901cac"
55

66
[compat]
77
Documenter = "1"
8-
Literate = "2"

docs/make.jl

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,13 @@
11
execute = isempty(ARGS) || ARGS[1] == "run"
22

3-
org, reps = :JuliaIO, :MAT
4-
eval(:(using $reps))
3+
org, repo = :JuliaIO, :MAT
4+
eval(:(using $repo))
55
using Documenter
6-
using Literate
76

87
# https://juliadocs.github.io/Documenter.jl/stable/man/syntax/#@example-block
98
ENV["GKSwstype"] = "100"
109
ENV["GKS_ENCODING"] = "utf-8"
1110

12-
# generate examples using Literate
13-
lit = joinpath(@__DIR__, "lit")
14-
src = joinpath(@__DIR__, "src")
15-
gen = joinpath(@__DIR__, "src/generated")
16-
17-
base = "$org/$reps.jl"
18-
repo_root_url =
19-
"https://github.com/$base/blob/main/docs/lit/examples"
20-
nbviewer_root_url =
21-
"https://nbviewer.org/github/$base/tree/gh-pages/dev/generated/examples"
22-
binder_root_url =
23-
"https://mybinder.org/v2/gh/$base/gh-pages?filepath=dev/generated/examples"
24-
25-
26-
repo = eval(:($reps))
27-
DocMeta.setdocmeta!(repo, :DocTestSetup, :(using $reps); recursive=true)
28-
29-
for (root, _, files) in walkdir(lit), file in files
30-
splitext(file)[2] == ".jl" || continue # process .jl files only
31-
ipath = joinpath(root, file)
32-
opath = splitdir(replace(ipath, lit => gen))[1]
33-
Literate.markdown(ipath, opath; documenter = execute, # run examples
34-
repo_root_url, nbviewer_root_url, binder_root_url)
35-
Literate.notebook(ipath, opath; execute = false, # no-run notebooks
36-
repo_root_url, nbviewer_root_url, binder_root_url)
37-
end
38-
39-
40-
# Documentation structure
41-
ismd(f) = splitext(f)[2] == ".md"
42-
pages(folder) =
43-
[joinpath("generated/", folder, f) for f in readdir(joinpath(gen, folder)) if ismd(f)]
44-
4511
isci = get(ENV, "CI", nothing) == "true"
4612

4713
format = Documenter.HTML(;
@@ -52,14 +18,14 @@ format = Documenter.HTML(;
5218
)
5319

5420
makedocs(;
55-
modules = [repo],
21+
modules = [MAT],
5622
authors = "Contributors",
5723
sitename = "$repo.jl",
5824
format,
5925
pages = [
6026
"Home" => "index.md",
27+
"Object Arrays" => "object_arrays.md",
6128
"Methods" => "methods.md",
62-
# "Examples" => pages("examples")
6329
],
6430
warnonly = [:missing_docs,],
6531
)

docs/src/index.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,65 @@ This Julia package
1010
[(MAT.jl)](https://github.com/JuliaIO/MAT.jl)
1111
provides tools
1212
for reading and writing MATLAB format data files in Julia.
13+
14+
## Basic Usage
15+
16+
To read a single variable from a MAT file (compressed files are detected and handled automatically):
17+
18+
```julia
19+
using MAT
20+
file = matopen("matfile.mat")
21+
read(file, "varname") # note that this does NOT introduce a variable ``varname`` into scope
22+
close(file)
23+
```
24+
25+
To write a variable to a MAT file:
26+
27+
```julia
28+
file = matopen("matfile.mat", "w")
29+
write(file, "varname", variable)
30+
close(file)
31+
```
32+
33+
To read all variables from a MAT file as a Dict:
34+
35+
```julia
36+
vars = matread("matfile.mat")
37+
```
38+
39+
To write a Dict to a MAT file, using its keys as variable names.
40+
The `compress` argument is optional, and compression is off by default:
41+
42+
```julia
43+
matwrite("matfile.mat", Dict(
44+
"myvar1" => 0,
45+
"myvar2" => 1
46+
); compress = true)
47+
```
48+
49+
To write in MATLAB v4 format:
50+
51+
```julia
52+
matwrite("matfile.mat", Dict(
53+
"myvar1" => 0,
54+
"myvar2" => 1
55+
);version="v4")
56+
```
57+
58+
To get a list of variable names in a MAT file:
59+
60+
```julia
61+
file = matopen("matfile.mat")
62+
varnames = keys(file)
63+
close(file)
64+
```
65+
66+
To check for the presence of a variable name in a MAT file:
67+
68+
```julia
69+
file = matopen("matfile.mat")
70+
if haskey(file, "variable")
71+
# something
72+
end
73+
close(file)
74+
```

docs/src/methods.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
## Methods usage
77

88
```@autodocs
9-
Modules = [MAT]
9+
Modules = [MAT, MAT.MAT_types]
1010
```

docs/src/object_arrays.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Objects and struct arrays
2+
3+
To better handle special cases we have these types since MAT 0.11:
4+
* [`MatlabStructArray`](@ref MatlabStructArray)
5+
* [`MatlabClassObject`](@ref MatlabClassObject)
6+
7+
## Struct arrays vs Cell arrays
8+
9+
Cell arrays are written for `Array{Any}` or any other unsupported element type:
10+
11+
```julia
12+
sarr = Any[
13+
Dict("x"=>1.0, "y"=>2.0),
14+
Dict("x"=>3.0, "y"=>4.0)
15+
]
16+
matwrite("matfile.mat", Dict("cell" => sarr))
17+
18+
```
19+
20+
Inside MATLAB you will find:
21+
22+
```matlab
23+
>> load('matfile.mat')
24+
>> cell
25+
26+
cell =
27+
28+
2×1 cell array
29+
30+
{1×1 struct}
31+
{1×1 struct}
32+
```
33+
34+
Read and write behavior for struct arrays is different. For struct arrays we use the `MatlabStructArray` type. You can also write with MAT.jl using Dict arrays `AbstractArray{<:AbstractDict}` if all the Dicts have equal keys, which will automatically convert internally to `MatlabStructArray`.
35+
36+
```julia
37+
sarr = Dict{String, Any}[
38+
Dict("x"=>1.0, "y"=>2.0),
39+
Dict("x"=>3.0, "y"=>4.0)
40+
]
41+
matwrite("matfile.mat", Dict("s" => sarr))
42+
# which is the same as:
43+
matwrite("matfile.mat", Dict("s" => MatlabStructArray(sarr)))
44+
# which is the same as:
45+
matwrite("matfile.mat", Dict("s" => MatlabStructArray(["x", "y"], [[1.0, 3.0], [2.0, 4.0]])))
46+
```
47+
48+
Now you'll find the following inside MATLAB:
49+
50+
```matlab
51+
>> load('matfile.mat')
52+
>> s
53+
54+
s =
55+
56+
[2x1 struct, 576 bytes]
57+
x: 1
58+
y: 2
59+
```
60+
61+
Note that when you read the file again, you'll find the `MatlabStructArray`, which you can convert back to the Dict array with `Array`:
62+
63+
```julia
64+
julia> sarr = matread("matfile.mat")["struct_array"]
65+
MatlabStructArray{1} with 2 columns:
66+
"x": Any[1.0, 3.0]
67+
"y": Any[2.0, 4.0]
68+
69+
julia> sarr["x"]
70+
2-element Vector{Any}:
71+
1.0
72+
3.0
73+
74+
julia> Array(sarr)
75+
2-element Vector{Dict{String, Any}}:
76+
Dict("x" => 1.0, "y" => 2.0)
77+
Dict("x" => 3.0, "y" => 4.0)
78+
79+
```
80+
81+
Note that before v0.11 MAT.jl will read struct arrays as a Dict with concatenated arrays in the fields/keys, which is equal to `Dict(sarr)`.
82+
83+
## Object Arrays
84+
85+
You can write an old class object with the `MatlabClassObject` and arrays of objects with `MatlabStructArray` by providing the class name. These are also the types you obtain when you read files.
86+
87+
Write a single class object:
88+
```julia
89+
d = Dict("foo" => 5.0)
90+
obj = MatlabClassObject(d, "TestClassOld")
91+
matwrite("matfile.mat", Dict("tc_old" => obj))
92+
```
93+
94+
A class object array
95+
```julia
96+
class_array = MatlabStructArray(["foo"], [[5.0, "bar"]], "TestClassOld")
97+
matwrite("matfile.mat", Dict("class_array" => class_array))
98+
```
99+
100+
Also a class object array, but will be converted to `MatlabStructArray` internally:
101+
```julia
102+
class_array = MatlabClassObject[
103+
MatlabClassObject(Dict("foo" => 5.0), "TestClassOld"),
104+
MatlabClassObject(Dict("foo" => "bar"), "TestClassOld")
105+
]
106+
matwrite("matfile.mat", Dict("class_array" => class_array))
107+
```
108+
109+
A cell array:
110+
```julia
111+
cell_array = Any[
112+
MatlabClassObject(Dict("foo" => 5.0), "TestClassOld"),
113+
MatlabClassObject(Dict("a" => "bar"), "AnotherClass")
114+
]
115+
matwrite("matfile.mat", Dict("cell_array" => cell_array))
116+
```
117+

src/MAT.jl

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ module MAT
2626

2727
using HDF5, SparseArrays
2828

29+
include("MAT_types.jl")
30+
using .MAT_types
31+
2932
include("MAT_HDF5.jl")
3033
include("MAT_v5.jl")
3134
include("MAT_v4.jl")
3235

3336
using .MAT_HDF5, .MAT_v5, .MAT_v4
3437

3538
export matopen, matread, matwrite, @read, @write
39+
export MatlabStructArray, MatlabClassObject
3640

3741
# Open a MATLAB file
3842
const HDF5_HEADER = UInt8[0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a]
@@ -140,47 +144,37 @@ end
140144

141145
# Write a dict to a MATLAB file
142146
"""
143-
matwrite(filename, d::Dict; compress::Bool = false, version::String)
147+
matwrite(filename, d::Dict; compress::Bool = false, version::String = "v7.3")
144148
145149
Write a dictionary containing variable names as keys and values as values
146150
to a Matlab file, opening and closing it automatically.
147151
"""
148-
function matwrite(filename::AbstractString, dict::AbstractDict{S, T}; compress::Bool = false, version::String ="") where {S, T}
149-
152+
function matwrite(filename::AbstractString, dict::AbstractDict{S, T}; compress::Bool = false, version::String ="v7.3") where {S, T}
150153
if version == "v4"
151154
file = open(filename, "w")
152155
m = MAT_v4.Matlabv4File(file, false)
153-
try
154-
for (k, v) in dict
155-
local kstring
156-
try
157-
kstring = ascii(convert(String, k))
158-
catch x
159-
error("matwrite requires a Dict with ASCII keys")
160-
end
161-
write(m, kstring, v)
162-
end
163-
finally
164-
close(file)
165-
end
166-
167-
else
168-
156+
_write_dict(m, dict)
157+
elseif version == "v7.3"
169158
file = matopen(filename, "w"; compress = compress)
170-
try
171-
for (k, v) in dict
172-
local kstring
173-
try
174-
kstring = ascii(convert(String, k))
175-
catch x
176-
error("matwrite requires a Dict with ASCII keys")
177-
end
178-
write(file, kstring, v)
159+
_write_dict(file, dict)
160+
else
161+
error("writing for \"$(version)\" is not supported")
162+
end
163+
end
164+
165+
function _write_dict(fileio, dict::AbstractDict)
166+
try
167+
for (k, v) in dict
168+
local kstring
169+
try
170+
kstring = ascii(convert(String, k))
171+
catch x
172+
error("matwrite requires a Dict with ASCII keys")
179173
end
180-
finally
181-
close(file)
174+
write(fileio, kstring, v)
182175
end
183-
176+
finally
177+
close(fileio)
184178
end
185179
end
186180

0 commit comments

Comments
 (0)