Skip to content

Commit 45f8231

Browse files
Loading MatlabOpaque types
Merge PR "Update handling of mxOPAQUE_CLASS objects and Subsystem in MAT_v5.jl (#205)" * Add support for loading mxOPAQUE_CLASS types * MAT_subsys.jl: New file MAT_subsys with methods to set, parse and retrieve subsystem data * MAT_v5.jl: New method "read_opaque" to handle mxOPAQUE_CLASS * MAT_v5.jl: New method "read_subsystem" to handle subsystem data * MAT.jl (matread): Update to clear subsystem and object cache after load Support for loading mxOPAQUE_CLASS objects in v7.3 HDF5 format * MAT_HDF5.jl (matopen): New argument Endian indicator, Reads and parses subsystem on load * MAT_HDF5.jl (close): Update to write endian header based on system endianness * MAT_HDF5.jl (m_read::HDF5.Dataset): Update to handle MATLAB_object_decode (mxOPAQUE_CLASS) types * MAT_HDF5.jl (m_read::HDF5.Group): Update to read subsystem data and function_handles * MAT.jl (matopen): Update function calls Updated test for struct_table_datetime.mat to ensure accurate deserialization (including nested properties) in both v7 and v7.3 formats * test/read.jl: Update tests for "function_handles.mat" and "struct_table_datetime.mat" * MAT_subsys.load_subsys!: Fix indexing for parsing metadata from region offests * first step in subsystem refactoring * stateless MAT_subsys module * test read(matopen(filepath), ::String) for opaque subsystems * MatlabOpaque to_string conversion * MatlabOpaque to_datetime conversion * fix to_string test * MatlabOpaque to_duration conversion * automatic MatlabOpaque conversion * attempt to fix ci: change Dates compat and more explicit type writing * MatlabOpaque categorical support * MatlabOpaque table support * Support for single row tables and ND columns * implement PR comments * document type conversions * small refactor * reduce SnoopCompile invalidations * Tests for nested objects, handle class objects * Add support for loading dynamic properties * mcos refactor: avoid opaque array conversion and extracted load_mcos_names! * bluestyle formatting * avoid printing massive objects * MAT_subsys.jl: Display warning instead of throwing error for unknown property types --------- Co-authored-by: Matthijs Cox <[email protected]>
1 parent d5feb5b commit 45f8231

17 files changed

+1405
-307
lines changed

.JuliaFormatter.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
style = "blue"

Project.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@ version = "0.11.0"
55
[deps]
66
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
77
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
8+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
89
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
10+
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
911
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
12+
StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68"
13+
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1014

1115
[compat]
1216
BufferedStreams = "0.4.1, 1"
1317
CodecZlib = "0.5, 0.6, 0.7"
18+
Dates = "1"
1419
HDF5 = "0.16, 0.17"
20+
PooledArrays = "1.4.3"
21+
StringEncodings = "0.3.7"
22+
Tables = "1.12.1"
1523
julia = "1.6"
1624

1725
[extras]

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ makedocs(;
2525
pages = [
2626
"Home" => "index.md",
2727
"Object Arrays" => "object_arrays.md",
28+
"Types" => "types.md",
2829
"Methods" => "methods.md",
2930
],
3031
warnonly = [:missing_docs,],

docs/src/types.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Types and conversions
2+
3+
MAT.jl uses the following type conversions from MATLAB types to Julia types:
4+
5+
| MATLAB | Julia |
6+
| -------- | ------- |
7+
| numerical array | `Array{T}` |
8+
| cell array | `Array{Any}` |
9+
| char array | `String` |
10+
| `struct` | `Dict{String,Any}` |
11+
| `struct` array | `MAT.MatlabStructArray` |
12+
| old class object | `MAT.MatlabClassObject` |
13+
| new (opaque) class | `MAT.MatlabOpaque` |
14+
15+
A few of the `MatlabOpaque` classes are automatically converted upon reading:
16+
17+
| MATLAB | Julia |
18+
| -------- | ------- |
19+
| `string` | `String` |
20+
| `datetime` | `Dates.DateTime` |
21+
| `duration` | `Dates.Millisecond` |
22+
| `category` | `PooledArrays.PooledArray` |
23+
| `table` | `MAT.MatlabTable` (or any other table) |
24+
25+
Note that single element arrays are typically converted to scalars in Julia, because MATLAB cannot distinguish between scalars and `1x1` sized arrays.

src/MAT.jl

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,23 @@ using HDF5, SparseArrays
2929
include("MAT_types.jl")
3030
using .MAT_types
3131

32+
include("MAT_subsys.jl")
3233
include("MAT_HDF5.jl")
3334
include("MAT_v5.jl")
3435
include("MAT_v4.jl")
3536

36-
using .MAT_HDF5, .MAT_v5, .MAT_v4
37+
using .MAT_HDF5, .MAT_v5, .MAT_v4, .MAT_subsys
3738

3839
export matopen, matread, matwrite, @read, @write
39-
export MatlabStructArray, MatlabClassObject
40+
export MatlabStructArray, MatlabClassObject, MatlabOpaque, MatlabTable
4041

4142
# Open a MATLAB file
4243
const HDF5_HEADER = UInt8[0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a]
43-
function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Bool, ff::Bool, compress::Bool)
44+
function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Bool, ff::Bool, compress::Bool; table::Type=MatlabTable)
4445
# When creating new files, create as HDF5 by default
4546
fs = filesize(filename)
4647
if cr && (tr || fs == 0)
47-
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress)
48+
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress, Base.ENDIAN_BOM == 0x04030201; table=table)
4849
elseif fs == 0
4950
error("File \"$filename\" does not exist and create was not specified")
5051
end
@@ -72,26 +73,26 @@ function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Boo
7273
if wr || cr || tr || ff
7374
error("creating or appending to MATLAB v5 files is not supported")
7475
end
75-
return MAT_v5.matopen(rawfid, endian_indicator)
76+
return MAT_v5.matopen(rawfid, endian_indicator; table=table)
7677
end
7778

7879
# Check for HDF5 file
7980
for offset = 512:512:fs-8
8081
seek(rawfid, offset)
8182
if read!(rawfid, Vector{UInt8}(undef, 8)) == HDF5_HEADER
8283
close(rawfid)
83-
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress)
84+
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress, endian_indicator == 0x494D; table=table)
8485
end
8586
end
8687

8788
close(rawfid)
8889
error("\"$filename\" is not a MAT file")
8990
end
9091

91-
function matopen(fname::AbstractString, mode::AbstractString; compress::Bool = false)
92-
mode == "r" ? matopen(fname, true , false, false, false, false, false) :
93-
mode == "r+" ? matopen(fname, true , true , false, false, false, compress) :
94-
mode == "w" ? matopen(fname, false, true , true , true , false, compress) :
92+
function matopen(fname::AbstractString, mode::AbstractString; compress::Bool = false, table::Type = MatlabTable)
93+
mode == "r" ? matopen(fname, true , false, false, false, false, false; table=table) :
94+
mode == "r+" ? matopen(fname, true , true , false, false, false, compress; table=table) :
95+
mode == "w" ? matopen(fname, false, true , true , true , false, compress; table=table) :
9596
# mode == "w+" ? matopen(fname, true , true , true , true , false, compress) :
9697
# mode == "a" ? matopen(fname, false, true , true , false, true, compress) :
9798
# mode == "a+" ? matopen(fname, true , true , true , false, true, compress) :
@@ -110,8 +111,8 @@ function matopen(f::Function, args...; kwargs...)
110111
end
111112

112113
"""
113-
matopen(filename [, mode]; compress = false) -> handle
114-
matopen(f::Function, filename [, mode]; compress = false) -> f(handle)
114+
matopen(filename [, mode]; compress = false, table = MatlabTable) -> handle
115+
matopen(f::Function, filename [, mode]; compress = false, table = MatlabTable) -> f(handle)
115116
116117
Mode defaults to `"r"` for read.
117118
It can also be `"w"` for write,
@@ -121,18 +122,71 @@ Compression on reading is detected/handled automatically; the `compress`
121122
keyword argument only affects write operations.
122123
123124
Use with `read`, `write`, `close`, `keys`, and `haskey`.
125+
126+
Optional keyword argument is the `table` type, for automatic conversion of Matlab tables.
127+
Note that Matlab tables may contain non-vector colums which cannot always be converted to a Julia table, like `DataFrame`.
128+
129+
# Example
130+
131+
```julia
132+
using MAT, DataFrames
133+
filepath = abspath(pkgdir(MAT), "./test/v7.3/struct_table_datetime.mat")
134+
fid = matopen(filepath; table = DataFrame)
135+
keys(fid)
136+
137+
# outputs
138+
139+
1-element Vector{String}:
140+
"s"
141+
142+
```
143+
144+
Now you can read any of the keys
145+
```
146+
s = read(fid, "s")
147+
close(fid)
148+
s
149+
150+
# outputs
151+
152+
Dict{String, Any} with 2 entries:
153+
"testDatetime" => DateTime("2019-12-02T16:42:49.634")
154+
"testTable" => 3×5 DataFrame…
155+
156+
```
124157
"""
125158
matopen
126159

127160
# Read all variables from a MATLAB file
128161
"""
129-
matread(filename) -> Dict
162+
matread(filename; table = MatlabTable) -> Dict
130163
131164
Return a dictionary of all the variables and values in a Matlab file,
132165
opening and closing it automatically.
166+
167+
Optionally provide the `table` type to convert Matlab tables into. Default uses a simple `MatlabTable` type.
168+
169+
# Example
170+
171+
```julia
172+
using MAT, DataFrames
173+
filepath = abspath(pkgdir(MAT), "./test/v7.3/struct_table_datetime.mat")
174+
vars = matread(filepath; table = DataFrame)
175+
vars["s"]["testTable"]
176+
177+
# outputs
178+
179+
3×5 DataFrame
180+
Row │ FlightNum Customer Date Rating Comment
181+
│ Float64 String DateTime String String
182+
─────┼─────────────────────────────────────────────────────────────────────────────────────
183+
1 │ 1261.0 Jones 2016-12-20T00:00:00 Good Flight left on time, not crowded
184+
2 │ 547.0 Brown 2016-12-21T00:00:00 Poor Late departure, ran out of dinne…
185+
3 │ 3489.0 Smith 2016-12-22T00:00:00 Fair Late, but only by half an hour. …
186+
```
133187
"""
134-
function matread(filename::AbstractString)
135-
file = matopen(filename)
188+
function matread(filename::AbstractString; table::Type=MatlabTable)
189+
file = matopen(filename; table=table)
136190
local vars
137191
try
138192
vars = read(file)
@@ -178,18 +232,4 @@ function _write_dict(fileio, dict::AbstractDict)
178232
end
179233
end
180234

181-
###
182-
### v0.10.0 deprecations
183-
###
184-
185-
export exists
186-
@noinline function exists(matfile::Union{MAT_v4.Matlabv4File,MAT_v5.Matlabv5File,MAT_HDF5.MatlabHDF5File}, varname::String)
187-
Base.depwarn("`exists(matfile, varname)` is deprecated, use `haskey(matfile, varname)` instead.", :exists)
188-
return haskey(matfile, varname)
189-
end
190-
@noinline function Base.names(matfile::Union{MAT_v4.Matlabv4File,MAT_v5.Matlabv5File,MAT_HDF5.MatlabHDF5File})
191-
Base.depwarn("`names(matfile)` is deprecated, use `keys(matfile)` instead.", :names)
192-
return keys(matfile)
193-
end
194-
195235
end

0 commit comments

Comments
 (0)