Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bea378a
Add support for loading mxOPAQUE_CLASS types
foreverallama Aug 20, 2025
33c6455
MAT_subsys.load_subsys!: Fix indexing for parsing metadata from regio…
foreverallama Aug 21, 2025
2bf9c3a
Merge branch 'master' into pr/205
matthijscox Nov 17, 2025
02444a6
first step in subsystem refactoring
matthijscox Nov 17, 2025
c2b55cd
stateless MAT_subsys module
matthijscox Nov 17, 2025
2e6b848
test read(matopen(filepath), ::String) for opaque subsystems
matthijscox Nov 17, 2025
3090b7f
MatlabOpaque to_string conversion
matthijscox Nov 17, 2025
e0c9055
MatlabOpaque to_datetime conversion
matthijscox Nov 17, 2025
bac5677
fix to_string test
matthijscox Nov 17, 2025
fbde008
MatlabOpaque to_duration conversion
matthijscox Nov 17, 2025
15e3d62
automatic MatlabOpaque conversion
matthijscox Nov 17, 2025
1139120
attempt to fix ci: change Dates compat and more explicit type writing
matthijscox Nov 18, 2025
4b95172
MatlabOpaque categorical support
matthijscox Nov 18, 2025
edefb99
MatlabOpaque table support
matthijscox Nov 18, 2025
386f67f
Support for single row tables and ND columns
matthijscox Nov 19, 2025
482d10a
implement PR comments
matthijscox Nov 19, 2025
4469b40
document type conversions
matthijscox Nov 19, 2025
c94581f
small refactor
matthijscox Nov 19, 2025
513be33
reduce SnoopCompile invalidations
matthijscox Nov 19, 2025
871662e
Tests for nested objects, handle class objects
foreverallama Nov 19, 2025
8455021
Add support for loading dynamic properties
foreverallama Nov 19, 2025
44b0dc4
mcos refactor: avoid opaque array conversion and extracted load_mcos_…
matthijscox Nov 20, 2025
ae064f0
bluestyle formatting
matthijscox Nov 20, 2025
e1f2f96
avoid printing massive objects
matthijscox Nov 20, 2025
7f3e153
MAT_subsys.jl: Display warning instead of throwing error for unknown …
foreverallama Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
style = "blue"
8 changes: 8 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ version = "0.11.0"
[deps]
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[compat]
BufferedStreams = "0.4.1, 1"
CodecZlib = "0.5, 0.6, 0.7"
Dates = "1"
HDF5 = "0.16, 0.17"
PooledArrays = "1.4.3"
StringEncodings = "0.3.7"
Tables = "1.12.1"
julia = "1.6"

[extras]
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ makedocs(;
pages = [
"Home" => "index.md",
"Object Arrays" => "object_arrays.md",
"Types" => "types.md",
"Methods" => "methods.md",
],
warnonly = [:missing_docs,],
Expand Down
25 changes: 25 additions & 0 deletions docs/src/types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Types and conversions

MAT.jl uses the following type conversions from MATLAB types to Julia types:

| MATLAB | Julia |
| -------- | ------- |
| numerical array | `Array{T}` |
| cell array | `Array{Any}` |
| char array | `String` |
| `struct` | `Dict{String,Any}` |
| `struct` array | `MAT.MatlabStructArray` |
| old class object | `MAT.MatlabClassObject` |
| new (opaque) class | `MAT.MatlabOpaque` |

A few of the `MatlabOpaque` classes are automatically converted upon reading:

| MATLAB | Julia |
| -------- | ------- |
| `string` | `String` |
| `datetime` | `Dates.DateTime` |
| `duration` | `Dates.Millisecond` |
| `category` | `PooledArrays.PooledArray` |
| `table` | `MAT.MatlabTable` (or any other table) |

Note that single element arrays are typically converted to scalars in Julia, because MATLAB cannot distinguish between scalars and `1x1` sized arrays.
98 changes: 69 additions & 29 deletions src/MAT.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,23 @@ using HDF5, SparseArrays
include("MAT_types.jl")
using .MAT_types

include("MAT_subsys.jl")
include("MAT_HDF5.jl")
include("MAT_v5.jl")
include("MAT_v4.jl")

using .MAT_HDF5, .MAT_v5, .MAT_v4
using .MAT_HDF5, .MAT_v5, .MAT_v4, .MAT_subsys

export matopen, matread, matwrite, @read, @write
export MatlabStructArray, MatlabClassObject
export MatlabStructArray, MatlabClassObject, MatlabOpaque, MatlabTable

# Open a MATLAB file
const HDF5_HEADER = UInt8[0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a]
function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Bool, ff::Bool, compress::Bool)
function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Bool, ff::Bool, compress::Bool; table::Type=MatlabTable)
# When creating new files, create as HDF5 by default
fs = filesize(filename)
if cr && (tr || fs == 0)
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress)
return MAT_HDF5.matopen(filename, rd, wr, cr, tr, ff, compress, Base.ENDIAN_BOM == 0x04030201; table=table)
elseif fs == 0
error("File \"$filename\" does not exist and create was not specified")
end
Expand Down Expand Up @@ -72,26 +73,26 @@ function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Boo
if wr || cr || tr || ff
error("creating or appending to MATLAB v5 files is not supported")
end
return MAT_v5.matopen(rawfid, endian_indicator)
return MAT_v5.matopen(rawfid, endian_indicator; table=table)
end

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

close(rawfid)
error("\"$filename\" is not a MAT file")
end

function matopen(fname::AbstractString, mode::AbstractString; compress::Bool = false)
mode == "r" ? matopen(fname, true , false, false, false, false, false) :
mode == "r+" ? matopen(fname, true , true , false, false, false, compress) :
mode == "w" ? matopen(fname, false, true , true , true , false, compress) :
function matopen(fname::AbstractString, mode::AbstractString; compress::Bool = false, table::Type = MatlabTable)
mode == "r" ? matopen(fname, true , false, false, false, false, false; table=table) :
mode == "r+" ? matopen(fname, true , true , false, false, false, compress; table=table) :
mode == "w" ? matopen(fname, false, true , true , true , false, compress; table=table) :
# mode == "w+" ? matopen(fname, true , true , true , true , false, compress) :
# mode == "a" ? matopen(fname, false, true , true , false, true, compress) :
# mode == "a+" ? matopen(fname, true , true , true , false, true, compress) :
Expand All @@ -110,8 +111,8 @@ function matopen(f::Function, args...; kwargs...)
end

"""
matopen(filename [, mode]; compress = false) -> handle
matopen(f::Function, filename [, mode]; compress = false) -> f(handle)
matopen(filename [, mode]; compress = false, table = MatlabTable) -> handle
matopen(f::Function, filename [, mode]; compress = false, table = MatlabTable) -> f(handle)

Mode defaults to `"r"` for read.
It can also be `"w"` for write,
Expand All @@ -121,18 +122,71 @@ Compression on reading is detected/handled automatically; the `compress`
keyword argument only affects write operations.

Use with `read`, `write`, `close`, `keys`, and `haskey`.

Optional keyword argument is the `table` type, for automatic conversion of Matlab tables.
Note that Matlab tables may contain non-vector colums which cannot always be converted to a Julia table, like `DataFrame`.

# Example

```julia
using MAT, DataFrames
filepath = abspath(pkgdir(MAT), "./test/v7.3/struct_table_datetime.mat")
fid = matopen(filepath; table = DataFrame)
keys(fid)

# outputs

1-element Vector{String}:
"s"

```

Now you can read any of the keys
```
s = read(fid, "s")
close(fid)
s

# outputs

Dict{String, Any} with 2 entries:
"testDatetime" => DateTime("2019-12-02T16:42:49.634")
"testTable" => 3×5 DataFrame…

```
"""
matopen

# Read all variables from a MATLAB file
"""
matread(filename) -> Dict
matread(filename; table = MatlabTable) -> Dict

Return a dictionary of all the variables and values in a Matlab file,
opening and closing it automatically.

Optionally provide the `table` type to convert Matlab tables into. Default uses a simple `MatlabTable` type.

# Example

```julia
using MAT, DataFrames
filepath = abspath(pkgdir(MAT), "./test/v7.3/struct_table_datetime.mat")
vars = matread(filepath; table = DataFrame)
vars["s"]["testTable"]

# outputs

3×5 DataFrame
Row │ FlightNum Customer Date Rating Comment
│ Float64 String DateTime String String
─────┼─────────────────────────────────────────────────────────────────────────────────────
1 │ 1261.0 Jones 2016-12-20T00:00:00 Good Flight left on time, not crowded
2 │ 547.0 Brown 2016-12-21T00:00:00 Poor Late departure, ran out of dinne…
3 │ 3489.0 Smith 2016-12-22T00:00:00 Fair Late, but only by half an hour. …
```
"""
function matread(filename::AbstractString)
file = matopen(filename)
function matread(filename::AbstractString; table::Type=MatlabTable)
file = matopen(filename; table=table)
local vars
try
vars = read(file)
Expand Down Expand Up @@ -178,18 +232,4 @@ function _write_dict(fileio, dict::AbstractDict)
end
end

###
### v0.10.0 deprecations
###

export exists
@noinline function exists(matfile::Union{MAT_v4.Matlabv4File,MAT_v5.Matlabv5File,MAT_HDF5.MatlabHDF5File}, varname::String)
Base.depwarn("`exists(matfile, varname)` is deprecated, use `haskey(matfile, varname)` instead.", :exists)
return haskey(matfile, varname)
end
@noinline function Base.names(matfile::Union{MAT_v4.Matlabv4File,MAT_v5.Matlabv5File,MAT_HDF5.MatlabHDF5File})
Base.depwarn("`names(matfile)` is deprecated, use `keys(matfile)` instead.", :names)
return keys(matfile)
end

end
Loading
Loading