From 192b9085f4c7d7821dd628cc4b0385c679908788 Mon Sep 17 00:00:00 2001 From: vsaase Date: Wed, 11 Dec 2019 11:23:45 +0100 Subject: [PATCH 1/9] initial read support --- src/MAT.jl | 28 +- src/MAT_v4.jl | 392 ++++++++++++++++++++++++ test/v4/testcomplex_4.2c_SOL2.mat | Bin 0 -> 176 bytes test/v4/testdouble_4.2c_SOL2.mat | Bin 0 -> 103 bytes test/v4/testmatrix_4.2c_SOL2.mat | Bin 0 -> 151 bytes test/v4/testminus_4.2c_SOL2.mat | Bin 0 -> 38 bytes test/v4/testmulti_4.2c_SOL2.mat | Bin 0 -> 240 bytes test/v4/testonechar_4.2c_SOL2.mat | Bin 0 -> 40 bytes test/v4/testsparse_4.2c_SOL2.mat | Bin 0 -> 223 bytes test/v4/testsparsecomplex_4.2c_SOL2.mat | Bin 0 -> 294 bytes test/v4/teststring_4.2c_SOL2.mat | Bin 0 -> 375 bytes test/v4/teststringarray_4.2c_SOL2.mat | Bin 0 -> 156 bytes 12 files changed, 411 insertions(+), 9 deletions(-) create mode 100644 src/MAT_v4.jl create mode 100644 test/v4/testcomplex_4.2c_SOL2.mat create mode 100644 test/v4/testdouble_4.2c_SOL2.mat create mode 100644 test/v4/testmatrix_4.2c_SOL2.mat create mode 100644 test/v4/testminus_4.2c_SOL2.mat create mode 100644 test/v4/testmulti_4.2c_SOL2.mat create mode 100644 test/v4/testonechar_4.2c_SOL2.mat create mode 100644 test/v4/testsparse_4.2c_SOL2.mat create mode 100644 test/v4/testsparsecomplex_4.2c_SOL2.mat create mode 100644 test/v4/teststring_4.2c_SOL2.mat create mode 100644 test/v4/teststringarray_4.2c_SOL2.mat diff --git a/src/MAT.jl b/src/MAT.jl index d2e944e..b791f8e 100644 --- a/src/MAT.jl +++ b/src/MAT.jl @@ -28,8 +28,9 @@ using HDF5, SparseArrays include("MAT_HDF5.jl") include("MAT_v5.jl") +include("MAT_v4.jl") -using .MAT_HDF5, .MAT_v5 +using .MAT_HDF5, .MAT_v5, .MAT_v4 export matopen, matread, matwrite, names, exists, @read, @write @@ -44,21 +45,30 @@ function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Boo error("File \"$filename\" does not exist and create was not specified") end - # Test whether this is a MAT file - if fs < 128 - error("File \"$filename\" is too small to be a supported MAT file") - end rawfid = open(filename, "r") # Check for MAT v4 file - magic = read!(rawfid, Vector{UInt8}(undef, 4)) - for i = 1:length(magic) - if magic[i] == 0 + M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, false) + if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 + close(rawfid) + swap_bytes = false + return MAT_v4.matopen(rawfid, swap_bytes) + else + seek(rawfid, 0) + M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, true) + if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 close(rawfid) - error("\"$filename\" is not a MAT file, or is an unsupported (v4) MAT file") + swap_bytes = true + return MAT_v4.matopen(rawfid, swap_bytes) end end + # Test whether this is a MAT file + if fs < 128 + close(rawfid) + error("File \"$filename\" is too small to be a supported MAT file") + end + # Check for MAT v5 file seek(rawfid, 124) version = read(rawfid, UInt16) diff --git a/src/MAT_v4.jl b/src/MAT_v4.jl new file mode 100644 index 0000000..4fcaf68 --- /dev/null +++ b/src/MAT_v4.jl @@ -0,0 +1,392 @@ +# MAT_v4.jl +# Tools for reading MATLAB v4 files in Julia +# +# Copyright (C) 2012 Simon Kornblith +# Copyright (C) 2019 Victor Saase +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# MATLAB's file format documentation can be found at +# http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf + +module MAT_v4 +using CodecZlib, BufferedStreams, HDF5, SparseArrays +import Base: read, write, close +import HDF5: names, exists + +round_uint8(data) = round.(UInt8, data) +complex_array(a, b) = complex.(a, b) + +mutable struct Matlabv4File <: HDF5.DataFile + ios::IOStream + swap_bytes::Bool + varnames::Dict{String, Int64} + + Matlabv4File(ios, swap_bytes) = new(ios, swap_bytes) +end + +const mLITTLE_ENDIAN = 0 +const mBIG_ENDIAN = 1 +const mVAX_DFLOAT = 2 +const mVAX_GFLOAT = 3 +const mGRAY = 4 + +const pTYPE = Dict( + 0 => Float64, + 1 => Float32, + 2 => Int32, + 3 => Int16, + 4 => UInt16, + 5 => UInt8 +) + +const tNUMERIC = 0 +const tTEXT = 1 +const tSPARSE = 2 + +const imagfREAL = 0 +const imagfCOMPLEX = 1 + +read_bswap(f::IO, swap_bytes::Bool, ::Type{T}) where T = + swap_bytes ? bswap(read(f, T)) : read(f, T) +function read_bswap(f::IO, swap_bytes::Bool, ::Type{T}, dim::Union{Int, Tuple{Vararg{Int}}}) where T + d = read!(f, Array{T}(undef, dim)) + if swap_bytes + for i = 1:length(d) + @inbounds d[i] = bswap(d[i]) + end + end + d +end + +function read_bswap(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T + readbytes!(f, reinterpret(UInt8, d)) + if swap_bytes + for i = 1:length(d) + @inbounds d[i] = bswap(d[i]) + end + end + d +end + +skip_padding(f::IO, nbytes::Int, hbytes::Int) = if nbytes % hbytes != 0 + skip(f, hbytes-(nbytes % hbytes)) +end + +# Read data type and number of bytes at the start of a data element +function read_header(f::IO, swap_bytes::Bool) + dtype = read_bswap(f, swap_bytes, Int32) + + M = div(rem(dtype, 10000), 1000) + O = div(rem(dtype, 1000), 100) + P = div(rem(dtype, 100), 10) + T = div(rem(dtype, 10), 1) + + mrows = read_bswap(f, swap_bytes, Int32) + ncols = read_bswap(f, swap_bytes, Int32) + imagf = read_bswap(f, swap_bytes, Int32) + namlen = read_bswap(f, swap_bytes, Int32) + + M, O, P, T, mrows, ncols, imagf, namlen +end + +# Read data element as a vector of a given type +function read_element(f::IO, swap_bytes::Bool, ::Type{T}) where T + (dtype, nbytes, hbytes) = read_header(f, swap_bytes) + data = read_bswap(f, swap_bytes, T, Int(div(nbytes, sizeof(T)))) + skip_padding(f, nbytes, hbytes) + data +end + +# Read data element as encoded type +function read_data(f::IO, swap_bytes::Bool) + (dtype, nbytes, hbytes) = read_header(f, swap_bytes) + read_type = READ_TYPES[dtype] + data = read_bswap(f, swap_bytes, read_type, Int(div(nbytes, sizeof(read_type)))) + skip_padding(f, nbytes, hbytes) + data +end + +# Read data element as encoded type with given dimensions, converting +# to another type if necessary and collapsing one-element matrices to +# scalars +function read_data(f::IO, swap_bytes::Bool, ::Type{T}, dimensions::Vector{Int32}) where T + (dtype, nbytes, hbytes) = read_header(f, swap_bytes) + read_type = READ_TYPES[dtype] + if (read_type === UInt8) && (T === Bool) + read_type = Bool + end + + read_array = any(dimensions .!= 1) + if sizeof(read_type)*prod(dimensions) != nbytes + error("Invalid element length") + end + if read_array + data = read_bswap(f, swap_bytes, read_type, tuple(convert(Vector{Int}, dimensions)...)) + else + data = read_bswap(f, swap_bytes, read_type) + end + skip_padding(f, nbytes, hbytes) + + read_array ? convert(Array{T}, data) : convert(T, data) +end + +function read_cell(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) + data = Array{Any}(undef, convert(Vector{Int}, dimensions)...) + for i = 1:length(data) + (ignored_name, data[i]) = read_matrix(f, swap_bytes) + end + data +end + +function read_struct(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, is_object::Bool) + if is_object + class = String(read_element(f, swap_bytes, UInt8)) + end + field_length = read_element(f, swap_bytes, Int32)[1] + field_names = read_element(f, swap_bytes, UInt8) + n_fields = div(length(field_names), field_length) + + # Get field names as strings + field_name_strings = Vector{String}(undef, n_fields) + n_el = prod(dimensions) + for i = 1:n_fields + sname = field_names[(i-1)*field_length+1:i*field_length] + index = something(findfirst(iszero, sname), 0) + field_name_strings[i] = String(index == 0 ? sname : sname[1:index-1]) + end + + data = Dict{String, Any}() + sizehint!(data, n_fields+1) + if is_object + data["class"] = class + end + + if n_el == 1 + # Read a single struct into a dict + for field_name in field_name_strings + data[field_name] = read_matrix(f, swap_bytes)[2] + end + else + # Read multiple structs into a dict of arrays + for field_name in field_name_strings + data[field_name] = Array{Any}(undef, dimensions...) + end + for i = 1:n_el + for field_name in field_name_strings + data[field_name][i] = read_matrix(f, swap_bytes)[2] + end + end + end + + data +end + +function plusone!(A) + for i = 1:length(A) + @inbounds A[i] += 1 + end + A +end + +function read_sparse(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, flags::Vector{UInt32}) + local m::Int, n::Int + if length(dimensions) == 2 + (m, n) = dimensions + elseif length(dimensions) == 1 + m = dimensions[1] + n = 1 + elseif length(dimensions) == 0 + m = 0 + n = 0 + else + error("invalid dimensions encountered for sparse array") + end + + m = isempty(dimensions) ? 0 : dimensions[1] + n = length(dimensions) <= 1 ? 0 : dimensions[2] + ir = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32))) + jc = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32))) + if (flags[1] & (1 << 9)) != 0 # logical + # WTF. For some reason logical sparse matrices are tagged as doubles. + pr = read_element(f, swap_bytes, Bool) + else + pr = read_data(f, swap_bytes) + if (flags[1] & (1 << 11)) != 0 # complex + pr = complex_array(pr, read_data(f, swap_bytes)) + end + end + + SparseMatrixCSC(m, n, jc, ir, pr) +end + +truncate_to_uint8(x) = x % UInt8 + +function read_string(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) + (dtype, nbytes, hbytes) = read_header(f, swap_bytes) + if dtype <= 2 || dtype == 16 + # If dtype <= 2, this may give an error on non-ASCII characters, since the string + # would be ISO-8859-1 and not UTF-8. However, MATLAB 2012b always saves strings with + # a 2-byte encoding in v6 format, and saves UTF-8 in v7 format. Thus, this may never + # happen in the wild. + chars = read!(f, Vector{UInt8}(undef, nbytes)) + if dimensions[1] <= 1 + data = String(chars) + else + data = Vector{String}(undef, dimensions[1]) + for i = 1:dimensions[1] + data[i] = rstrip(String(chars[i:dimensions[1]:end])) + end + end + elseif dtype <= 4 || dtype == 17 + # Technically, if dtype == 3 or dtype == 4, this is ISO-8859-1 and not Unicode. + # However, the first 256 Unicode code points are derived from ISO-8859-1, so UCS-2 + # is a superset of 2-byte ISO-8859-1. + chars = read_bswap(f, swap_bytes, UInt16, convert(Int, div(nbytes, 2))) + bufs = [IOBuffer() for i = 1:dimensions[1]] + i = 1 + while i <= length(chars) + for j = 1:dimensions[1] + char = convert(Char, chars[i]) + if 255 < convert(UInt32, char) + # Newer versions of MATLAB seem to write some mongrel UTF-8... + char = String([truncate_to_uint8(chars[i] >> 8), truncate_to_uint8(chars[i])])[1] + end + write(bufs[j], char) + i += 1 + end + end + + if dimensions[1] == 0 + data = "" + elseif dimensions[1] == 1 + data = String(take!(bufs[1])) + else + data = String[rstrip(String(take!(buf))) for buf in bufs] + end + else + error("Unsupported string type") + end + skip_padding(f, nbytes, hbytes) + data +end + +# Read matrix data +function read_matrix(f::IO, swap_bytes::Bool) + M, O, P, T, mrows, ncols, imagf, namlen = read_header(f, swap_bytes) + if ncols == 0 || mrows == 0 + # If one creates a cell array using + # y = cell(m, n) + # then MATLAB will save the empty cells as zero-byte matrices. If one creates a + # empty cells using + # a = {[], [], []} + # then MATLAB does not save the empty cells as zero-byte matrices. To avoid + # surprises, we produce an empty array in both cases. + return ("", Matrix{Union{}}(undef, 0, 0)) + end + name = String(read_bswap(f, M==mBIG_ENDIAN, Vector{UInt8}(undef, namlen))[1:end-1]) + if T == tNUMERIC || T == tSPARSE + real_data = read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols*mrows)) + if imagf == imagfCOMPLEX + imag_data = read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols*mrows)) + data = complex.(real_data, imag_data) + else + data = real_data + end + datamat = reshape(data, Int(mrows), Int(ncols)) + if T == tNUMERIC + return (name, datamat) + elseif T == tSPARSE + if size(datamat,2) == 3 + return (name, sparse(datamat[:,1], datamat[:,2], datamat[:,3])) + else + return (name, sparse(datamat[:,1], datamat[:,2], complex.(datamat[:,3], datamat[:,4]))) + end + end + elseif T == tTEXT + if mrows > 1 + charvec = UInt8.(read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols*mrows))) + charmat = reshape(charvec, Int(mrows), Int(ncols)) + data = [String(charmat[i,:]) for i in 1:mrows] + else + data = String(UInt8.(read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols)))) + end + end +end + +# Open MAT file for reading +matopen(ios::IOStream, endian_indicator::Bool) = + Matlabv4File(ios, endian_indicator) + +# Read whole MAT file +function read(matfile::Matlabv4File) + seek(matfile.ios, 0) + vars = Dict{String, Any}() + while !eof(matfile.ios) + (name, data) = read_matrix(matfile.ios, matfile.swap_bytes) + vars[name] = data + end + vars +end + +# Read only variable names from an HDF5 file +function getvarnames(matfile::Matlabv4File) + if !isdefined(matfile, :varnames) + seek(matfile.ios, 128) + matfile.varnames = varnames = Dict{String, Int64}() + while !eof(matfile.ios) + offset = position(matfile.ios) + (dtype, nbytes, hbytes) = read_header(matfile.ios, matfile.swap_bytes) + f = matfile.ios + + read_element(f, matfile.swap_bytes, UInt32) + read_element(f, matfile.swap_bytes, Int32) + varnames[String(read_element(f, matfile.swap_bytes, UInt8))] = offset + + seek(matfile.ios, offset+nbytes+hbytes) + end + end + matfile.varnames +end + +exists(matfile::Matlabv4File, varname::String) = + haskey(getvarnames(matfile), varname) +names(matfile::Matlabv4File) = + keys(getvarnames(matfile)) + +# Read a variable from a MAT file +function read(matfile::Matlabv4File, varname::String) + varnames = getvarnames(matfile) + if !haskey(varnames, varname) + error("no variable $varname in file") + end + seek(matfile.ios, varnames[varname]) + (name, data) = read_matrix(matfile.ios, matfile.swap_bytes) + data +end + +# Complain about writing to a MAT file +function write(parent::Matlabv4File, name::String, s) + error("Writing to a MATLAB v4 file is not currently supported. Create a new file instead.") +end + +# Close MAT file +close(matfile::Matlabv4File) = close(matfile.ios) +end diff --git a/test/v4/testcomplex_4.2c_SOL2.mat b/test/v4/testcomplex_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..36621b25c08f18e4545100c6eaec015123c3bf9f GIT binary patch literal 176 zcmZQzV1B{Cz`zK^oKTvlB(=CCIX|}`C$)mX{sT}H2<)FNn3q;>eb#2;GBsn7820@T p{+azyc_{zfo>i5WKJ&V`pz2S<^g~R6n{x&x4mWop0dqG(`$IPYCV6bhD=3Sn-kr dCzQ{hRiznf5$7NT6&L-x`{nNCr4Eu1c>u#Y9lihn literal 0 HcmV?d00001 diff --git a/test/v4/testmatrix_4.2c_SOL2.mat b/test/v4/testmatrix_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..3698c8853b46d4a42194002523b57fddfb225908 GIT binary patch literal 151 zcmZQzV1B{Cz`zW|tUwF`+$E{SCAo0_8%Z(4iJjLfdiEf6^2tVdAI64fzvu`z literal 0 HcmV?d00001 diff --git a/test/v4/testminus_4.2c_SOL2.mat b/test/v4/testminus_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..cc207ed9f32095f39b7690e2dc1e2dc0d55ee8e0 GIT binary patch literal 38 kcmZQzV1B{Cz`zK_K#GB@B(=CCH#4uam|_11kN^V%0A!m6lK=n! literal 0 HcmV?d00001 diff --git a/test/v4/testmulti_4.2c_SOL2.mat b/test/v4/testmulti_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..9c6ba793cf41bf36447ab7a1890447fe5e939614 GIT binary patch literal 240 zcmZQzV1B{Cz`zW|tUwF`Oo>TXrytD# literal 0 HcmV?d00001 diff --git a/test/v4/testonechar_4.2c_SOL2.mat b/test/v4/testonechar_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..cdb4191c7d2eb0ac66d4f6add250e1f6a604d892 GIT binary patch literal 40 mcmZQzV1CKKz`zK_K#GBoBe96VA*KN&#sC0t%m%jr literal 0 HcmV?d00001 diff --git a/test/v4/testsparse_4.2c_SOL2.mat b/test/v4/testsparse_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..55cbd3c1b3d65630beae47832ffbcc7a6fd43354 GIT binary patch literal 223 zcmZQzV1C8Gz`y~-%s>nR+$E{SCB+4aMa8KM_8%Z(4iJjL0i+NJVB)xFLh2mArZB+G Wa}aKwqPFu=`o5P%3ch@jFi^D)#D&<~Y{yA#GIl?DK-2^cH@ literal 0 HcmV?d00001 diff --git a/test/v4/teststring_4.2c_SOL2.mat b/test/v4/teststring_4.2c_SOL2.mat new file mode 100644 index 0000000000000000000000000000000000000000..137561e1f636d7b08959e43e969a6984eb7a3b37 GIT binary patch literal 375 zcmZ{gO%6an423I}j^YxvVJTQKEJbWAm;*SP>$ruVzPB0Cq-nqQwbP79e2PePdwTn0 zi61w=`E_0<(adUEA-dyDRLQ$>X9acO7HmP(fmx@H{cwJe*OdBxH||jjyl4?yTB eFvZ{y=>Xxw^u;tl_z+ Date: Thu, 12 Dec 2019 12:12:32 +0100 Subject: [PATCH 2/9] v4 write support --- src/MAT.jl | 22 ++- src/MAT_v4.jl | 312 +++++++++++++---------------------- test/v4/out.mat | Bin 0 -> 151 bytes test/v4/testvec_4_GLNX86.mat | Bin 0 -> 93 bytes 4 files changed, 132 insertions(+), 202 deletions(-) create mode 100644 test/v4/out.mat create mode 100644 test/v4/testvec_4_GLNX86.mat diff --git a/src/MAT.jl b/src/MAT.jl index b791f8e..6daebe8 100644 --- a/src/MAT.jl +++ b/src/MAT.jl @@ -32,7 +32,7 @@ include("MAT_v4.jl") using .MAT_HDF5, .MAT_v5, .MAT_v4 -export matopen, matread, matwrite, names, exists, @read, @write +export matopen, matread, matwrite, matwrite4, names, exists, @read, @write # Open a MATLAB file const HDF5_HEADER = UInt8[0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a] @@ -50,14 +50,12 @@ function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Boo # Check for MAT v4 file M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, false) if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 - close(rawfid) swap_bytes = false return MAT_v4.matopen(rawfid, swap_bytes) else seek(rawfid, 0) M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, true) if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 - close(rawfid) swap_bytes = true return MAT_v4.matopen(rawfid, swap_bytes) end @@ -170,5 +168,23 @@ function matwrite(filename::AbstractString, dict::AbstractDict{S, T}; compress:: close(file) end end + +function matwrite4(filename::AbstractString, dict::AbstractDict{S, T}) where {S, T} + file = open(filename, "w") + m = MAT_v4.Matlabv4File(file, false) + try + for (k, v) in dict + local kstring + try + kstring = ascii(convert(String, k)) + catch x + error("matwrite requires a Dict with ASCII keys") + end + write(m, kstring, v) + end + finally + close(file) + end end +end \ No newline at end of file diff --git a/src/MAT_v4.jl b/src/MAT_v4.jl index 4fcaf68..933df7e 100644 --- a/src/MAT_v4.jl +++ b/src/MAT_v4.jl @@ -2,7 +2,7 @@ # Tools for reading MATLAB v4 files in Julia # # Copyright (C) 2012 Simon Kornblith -# Copyright (C) 2019 Victor Saase +# Copyright (C) 2019 Victor Saase (modified from MAT_v5.jl) # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -27,7 +27,7 @@ # http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf module MAT_v4 -using CodecZlib, BufferedStreams, HDF5, SparseArrays +using BufferedStreams, HDF5, SparseArrays import Base: read, write, close import HDF5: names, exists @@ -86,10 +86,6 @@ function read_bswap(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T d end -skip_padding(f::IO, nbytes::Int, hbytes::Int) = if nbytes % hbytes != 0 - skip(f, hbytes-(nbytes % hbytes)) -end - # Read data type and number of bytes at the start of a data element function read_header(f::IO, swap_bytes::Bool) dtype = read_bswap(f, swap_bytes, Int32) @@ -107,187 +103,6 @@ function read_header(f::IO, swap_bytes::Bool) M, O, P, T, mrows, ncols, imagf, namlen end -# Read data element as a vector of a given type -function read_element(f::IO, swap_bytes::Bool, ::Type{T}) where T - (dtype, nbytes, hbytes) = read_header(f, swap_bytes) - data = read_bswap(f, swap_bytes, T, Int(div(nbytes, sizeof(T)))) - skip_padding(f, nbytes, hbytes) - data -end - -# Read data element as encoded type -function read_data(f::IO, swap_bytes::Bool) - (dtype, nbytes, hbytes) = read_header(f, swap_bytes) - read_type = READ_TYPES[dtype] - data = read_bswap(f, swap_bytes, read_type, Int(div(nbytes, sizeof(read_type)))) - skip_padding(f, nbytes, hbytes) - data -end - -# Read data element as encoded type with given dimensions, converting -# to another type if necessary and collapsing one-element matrices to -# scalars -function read_data(f::IO, swap_bytes::Bool, ::Type{T}, dimensions::Vector{Int32}) where T - (dtype, nbytes, hbytes) = read_header(f, swap_bytes) - read_type = READ_TYPES[dtype] - if (read_type === UInt8) && (T === Bool) - read_type = Bool - end - - read_array = any(dimensions .!= 1) - if sizeof(read_type)*prod(dimensions) != nbytes - error("Invalid element length") - end - if read_array - data = read_bswap(f, swap_bytes, read_type, tuple(convert(Vector{Int}, dimensions)...)) - else - data = read_bswap(f, swap_bytes, read_type) - end - skip_padding(f, nbytes, hbytes) - - read_array ? convert(Array{T}, data) : convert(T, data) -end - -function read_cell(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) - data = Array{Any}(undef, convert(Vector{Int}, dimensions)...) - for i = 1:length(data) - (ignored_name, data[i]) = read_matrix(f, swap_bytes) - end - data -end - -function read_struct(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, is_object::Bool) - if is_object - class = String(read_element(f, swap_bytes, UInt8)) - end - field_length = read_element(f, swap_bytes, Int32)[1] - field_names = read_element(f, swap_bytes, UInt8) - n_fields = div(length(field_names), field_length) - - # Get field names as strings - field_name_strings = Vector{String}(undef, n_fields) - n_el = prod(dimensions) - for i = 1:n_fields - sname = field_names[(i-1)*field_length+1:i*field_length] - index = something(findfirst(iszero, sname), 0) - field_name_strings[i] = String(index == 0 ? sname : sname[1:index-1]) - end - - data = Dict{String, Any}() - sizehint!(data, n_fields+1) - if is_object - data["class"] = class - end - - if n_el == 1 - # Read a single struct into a dict - for field_name in field_name_strings - data[field_name] = read_matrix(f, swap_bytes)[2] - end - else - # Read multiple structs into a dict of arrays - for field_name in field_name_strings - data[field_name] = Array{Any}(undef, dimensions...) - end - for i = 1:n_el - for field_name in field_name_strings - data[field_name][i] = read_matrix(f, swap_bytes)[2] - end - end - end - - data -end - -function plusone!(A) - for i = 1:length(A) - @inbounds A[i] += 1 - end - A -end - -function read_sparse(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, flags::Vector{UInt32}) - local m::Int, n::Int - if length(dimensions) == 2 - (m, n) = dimensions - elseif length(dimensions) == 1 - m = dimensions[1] - n = 1 - elseif length(dimensions) == 0 - m = 0 - n = 0 - else - error("invalid dimensions encountered for sparse array") - end - - m = isempty(dimensions) ? 0 : dimensions[1] - n = length(dimensions) <= 1 ? 0 : dimensions[2] - ir = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32))) - jc = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32))) - if (flags[1] & (1 << 9)) != 0 # logical - # WTF. For some reason logical sparse matrices are tagged as doubles. - pr = read_element(f, swap_bytes, Bool) - else - pr = read_data(f, swap_bytes) - if (flags[1] & (1 << 11)) != 0 # complex - pr = complex_array(pr, read_data(f, swap_bytes)) - end - end - - SparseMatrixCSC(m, n, jc, ir, pr) -end - -truncate_to_uint8(x) = x % UInt8 - -function read_string(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) - (dtype, nbytes, hbytes) = read_header(f, swap_bytes) - if dtype <= 2 || dtype == 16 - # If dtype <= 2, this may give an error on non-ASCII characters, since the string - # would be ISO-8859-1 and not UTF-8. However, MATLAB 2012b always saves strings with - # a 2-byte encoding in v6 format, and saves UTF-8 in v7 format. Thus, this may never - # happen in the wild. - chars = read!(f, Vector{UInt8}(undef, nbytes)) - if dimensions[1] <= 1 - data = String(chars) - else - data = Vector{String}(undef, dimensions[1]) - for i = 1:dimensions[1] - data[i] = rstrip(String(chars[i:dimensions[1]:end])) - end - end - elseif dtype <= 4 || dtype == 17 - # Technically, if dtype == 3 or dtype == 4, this is ISO-8859-1 and not Unicode. - # However, the first 256 Unicode code points are derived from ISO-8859-1, so UCS-2 - # is a superset of 2-byte ISO-8859-1. - chars = read_bswap(f, swap_bytes, UInt16, convert(Int, div(nbytes, 2))) - bufs = [IOBuffer() for i = 1:dimensions[1]] - i = 1 - while i <= length(chars) - for j = 1:dimensions[1] - char = convert(Char, chars[i]) - if 255 < convert(UInt32, char) - # Newer versions of MATLAB seem to write some mongrel UTF-8... - char = String([truncate_to_uint8(chars[i] >> 8), truncate_to_uint8(chars[i])])[1] - end - write(bufs[j], char) - i += 1 - end - end - - if dimensions[1] == 0 - data = "" - elseif dimensions[1] == 1 - data = String(take!(bufs[1])) - else - data = String[rstrip(String(take!(buf))) for buf in bufs] - end - else - error("Unsupported string type") - end - skip_padding(f, nbytes, hbytes) - data -end - # Read matrix data function read_matrix(f::IO, swap_bytes::Bool) M, O, P, T, mrows, ncols, imagf, namlen = read_header(f, swap_bytes) @@ -304,7 +119,7 @@ function read_matrix(f::IO, swap_bytes::Bool) name = String(read_bswap(f, M==mBIG_ENDIAN, Vector{UInt8}(undef, namlen))[1:end-1]) if T == tNUMERIC || T == tSPARSE real_data = read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols*mrows)) - if imagf == imagfCOMPLEX + if T == tNUMERIC && imagf == imagfCOMPLEX imag_data = read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols*mrows)) data = complex.(real_data, imag_data) else @@ -328,6 +143,7 @@ function read_matrix(f::IO, swap_bytes::Bool) else data = String(UInt8.(read_bswap(f, M==mBIG_ENDIAN, Vector{pTYPE[P]}(undef, ncols)))) end + return (name, data) end end @@ -346,21 +162,23 @@ function read(matfile::Matlabv4File) vars end -# Read only variable names from an HDF5 file +# Read only variable names function getvarnames(matfile::Matlabv4File) if !isdefined(matfile, :varnames) - seek(matfile.ios, 128) + seek(matfile.ios, 0) matfile.varnames = varnames = Dict{String, Int64}() while !eof(matfile.ios) offset = position(matfile.ios) - (dtype, nbytes, hbytes) = read_header(matfile.ios, matfile.swap_bytes) + M, O, P, T, mrows, ncols, imagf, namlen = read_header(matfile.ios, matfile.swap_bytes) f = matfile.ios - - read_element(f, matfile.swap_bytes, UInt32) - read_element(f, matfile.swap_bytes, Int32) - varnames[String(read_element(f, matfile.swap_bytes, UInt8))] = offset - - seek(matfile.ios, offset+nbytes+hbytes) + + name = String(read_bswap(f, M==mBIG_ENDIAN, Vector{UInt8}(undef, namlen))[1:end-1]) + varnames[name] = offset + imag_offset = 0 + if imagf == imagfCOMPLEX + imag_offset = mrows*ncols + end + seek(matfile.ios, offset+20+namlen+mrows*mcols+imag_offset) end end matfile.varnames @@ -382,11 +200,107 @@ function read(matfile::Matlabv4File, varname::String) data end -# Complain about writing to a MAT file +function colvals(A::AbstractSparseMatrix) + rows = rowvals(A) + cols = similar(rows) + m,n = size(A) + for i=1:n + for j in nzrange(A,i) + cols[j] = i + end + end + cols +end + function write(parent::Matlabv4File, name::String, s) - error("Writing to a MATLAB v4 file is not currently supported. Create a new file instead.") + M = Int(parent.swap_bytes) + O = 0 + P = 0 + for p=keys(pTYPE) + if eltype(s) == pTYPE[p] || eltype(s) == Complex{pTYPE[p]} + P = p + end + end + if pTYPE[P] != eltype(s) && Complex{pTYPE[P]} != eltype(s) && + !(s isa AbstractString && pTYPE[P] == Float64) && + !(s isa Vector{String} && pTYPE[P] == Float64) + error("invalid value type when writing v4 file") + end + if s isa AbstractSparseMatrix + T = tSPARSE + elseif s isa AbstractString || s isa Vector{String} + T = tTEXT + else + T = tNUMERIC + end + write(parent.ios, Int32(1000*M + 100*O + 10*P + T)) + + mrows = 1 + ncols = 1 + if s isa AbstractVector + ncols = length(s) + elseif s isa AbstractMatrix + if s isa AbstractSparseMatrix + mrows = nnz(s) + ncols = 3 + if eltype(s) <: Complex + ncols = 4 + end + else + mrows, ncols = size(s) + end + elseif s isa Vector{String} + ncols = length(s[1]) + mrows = length(s) + end + write(parent.ios, Int32(mrows)) + write(parent.ios, Int32(ncols)) + + imagf = 0 + if eltype(s) <: Complex && T == tNUMERIC + imagf = 1 + end + write(parent.ios, Int32(imagf)) + + namlen = length(name) + 1 + write(parent.ios, Int32(namlen)) + + write(parent.ios, Vector{UInt8}(name)) + write(parent.ios, UInt8(0)) + + if s isa AbstractArray && !(s isa Vector{String}) + write(parent.ios, reshape(real(s), length(s))) + if imagf == 1 + write(parent.ios, reshape(imag(s), length(s))) + end + elseif s isa Number + write(parent.ios, real(s)) + if imagf == 1 + write(parent.ios, imag(s)) + end + elseif s isa AbstractString + floatarray = Float64.(Vector{UInt8}(name)) + write(parent.ios, floatarray) + elseif s isa Vector{String} + floatarray = Matrix{Float64}(undef, mrows, ncols) + for (i,strel) = enumerate(s) + floatarray[i,:] = Float64.(Vector{UInt8}(strel)) + end + write(parent.ios, floatarray) + elseif T == tSPARSE + rows = rowvals(s) + cols = colvals(s) + vals = nonzeros(s) + write(parent.ios, pTYPE[P].(rows)) + write(parent.ios, pTYPE[P].(cols)) + write(parent.ios, pTYPE[P].(real(vals))) + if eltype(s) <: Complex + write(parent.ios, pTYPE[P].(imag(vals))) + end + end end # Close MAT file close(matfile::Matlabv4File) = close(matfile.ios) + end diff --git a/test/v4/out.mat b/test/v4/out.mat new file mode 100644 index 0000000000000000000000000000000000000000..c36237349336e5bd3479c4fcfbabb5db24bfb53f GIT binary patch literal 151 zcmZQ#U|`??VrC!)0d62JNi8lZE=VjYP6Z1wFnq9wG8`Z@2b5-j(GX!c373b9Ko|lJ LXaZ2K2!=QSCK(4x literal 0 HcmV?d00001 diff --git a/test/v4/testvec_4_GLNX86.mat b/test/v4/testvec_4_GLNX86.mat new file mode 100644 index 0000000000000000000000000000000000000000..76c51d01388a1770b348bc603ebfdd51bc011f0c GIT binary patch literal 93 zcmZQzU|?VZVn!ea0d62p%Pfg6NGwXsEoNw}RcCt2dfCzKZN^*eBX#y5CCJLTfc%P- Z{F3;z%$yRC Date: Thu, 12 Dec 2019 23:40:19 +0100 Subject: [PATCH 3/9] add tests for v4 --- src/MAT_v4.jl | 14 ++++++------ test/readwrite4.jl | 52 +++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + test/v4/out.mat | Bin 151 -> 0 bytes 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 test/readwrite4.jl delete mode 100644 test/v4/out.mat diff --git a/src/MAT_v4.jl b/src/MAT_v4.jl index 933df7e..7722666 100644 --- a/src/MAT_v4.jl +++ b/src/MAT_v4.jl @@ -175,10 +175,10 @@ function getvarnames(matfile::Matlabv4File) name = String(read_bswap(f, M==mBIG_ENDIAN, Vector{UInt8}(undef, namlen))[1:end-1]) varnames[name] = offset imag_offset = 0 + skip(f, mrows*ncols*sizeof(pTYPE[P])) if imagf == imagfCOMPLEX - imag_offset = mrows*ncols + skip(f, mrows*ncols*sizeof(pTYPE[P])) end - seek(matfile.ios, offset+20+namlen+mrows*mcols+imag_offset) end end matfile.varnames @@ -237,7 +237,7 @@ function write(parent::Matlabv4File, name::String, s) mrows = 1 ncols = 1 - if s isa AbstractVector + if s isa AbstractVector && !(s isa Vector{String}) ncols = length(s) elseif s isa AbstractMatrix if s isa AbstractSparseMatrix @@ -252,10 +252,12 @@ function write(parent::Matlabv4File, name::String, s) elseif s isa Vector{String} ncols = length(s[1]) mrows = length(s) + elseif s isa AbstractString + ncols = length(s) end write(parent.ios, Int32(mrows)) write(parent.ios, Int32(ncols)) - + imagf = 0 if eltype(s) <: Complex && T == tNUMERIC imagf = 1 @@ -268,7 +270,7 @@ function write(parent::Matlabv4File, name::String, s) write(parent.ios, Vector{UInt8}(name)) write(parent.ios, UInt8(0)) - if s isa AbstractArray && !(s isa Vector{String}) + if s isa AbstractArray && T == tNUMERIC write(parent.ios, reshape(real(s), length(s))) if imagf == 1 write(parent.ios, reshape(imag(s), length(s))) @@ -279,7 +281,7 @@ function write(parent::Matlabv4File, name::String, s) write(parent.ios, imag(s)) end elseif s isa AbstractString - floatarray = Float64.(Vector{UInt8}(name)) + floatarray = Float64.(Vector{UInt8}(s)) write(parent.ios, floatarray) elseif s isa Vector{String} floatarray = Matrix{Float64}(undef, mrows, ncols) diff --git a/test/readwrite4.jl b/test/readwrite4.jl new file mode 100644 index 0000000..c751125 --- /dev/null +++ b/test/readwrite4.jl @@ -0,0 +1,52 @@ +using MAT, Test + +function check(filename, result) + matfile = matopen(filename) + for (k, v) in result + @test exists(matfile, k) + got = read(matfile, k) + if !isequal(got, v) || (typeof(got) != typeof(v) && (!isa(got, String) || !(isa(v, String)))) + close(matfile) + error(""" + Data mismatch reading $k from $filename ($format) + + Got $(typeof(got)): + + $(repr(got)) + + Expected $(typeof(v)): + + $(repr(v)) + """) + end + end + @test union!(Set(), names(matfile)) == union!(Set(), keys(result)) + close(matfile) + + mat = matread(filename) + if !isequal(mat, result) + error(""" + Data mismatch reading $filename ($format) + + Got: + + $(repr(mat)) + + Expected: + + $(repr(result)) + """) + close(matfile) + return false + end + + return true +end +cd(dirname(@__FILE__)) +for filename in readdir("v4") + #println("testing $filename") + d = matread("v4/$filename") + matwrite4("v4/tmp.mat", d) + check("v4/tmp.mat", d) + rm("v4/tmp.mat") +end diff --git a/test/runtests.jl b/test/runtests.jl index 5b9bec1..159a125 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,5 @@ using SparseArrays, LinearAlgebra include("read.jl") +include("readwrite4.jl") include("write.jl") diff --git a/test/v4/out.mat b/test/v4/out.mat deleted file mode 100644 index c36237349336e5bd3479c4fcfbabb5db24bfb53f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmZQ#U|`??VrC!)0d62JNi8lZE=VjYP6Z1wFnq9wG8`Z@2b5-j(GX!c373b9Ko|lJ LXaZ2K2!=QSCK(4x From eeb51727a9e520571e489cc9d71fd36d3c51b0f6 Mon Sep 17 00:00:00 2001 From: vsaase Date: Thu, 12 Dec 2019 23:49:03 +0100 Subject: [PATCH 4/9] move check routine --- src/MAT.jl | 12 ++---------- src/MAT_v4.jl | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/MAT.jl b/src/MAT.jl index 6daebe8..77cdfe4 100644 --- a/src/MAT.jl +++ b/src/MAT.jl @@ -48,17 +48,9 @@ function matopen(filename::AbstractString, rd::Bool, wr::Bool, cr::Bool, tr::Boo rawfid = open(filename, "r") # Check for MAT v4 file - M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, false) - if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 - swap_bytes = false + (isv4, swap_bytes) = MAT_v4.checkv4(rawfid) + if isv4 return MAT_v4.matopen(rawfid, swap_bytes) - else - seek(rawfid, 0) - M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(rawfid, true) - if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 - swap_bytes = true - return MAT_v4.matopen(rawfid, swap_bytes) - end end # Test whether this is a MAT file diff --git a/src/MAT_v4.jl b/src/MAT_v4.jl index 7722666..7224930 100644 --- a/src/MAT_v4.jl +++ b/src/MAT_v4.jl @@ -86,6 +86,22 @@ function read_bswap(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T d end +function checkv4(f::IO) + M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(f, false) + if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 + swap_bytes = false + return (true, swap_bytes) + else + seek(f, 0) + M, O, P, T, mrows, ncols, imagf, namlen = MAT_v4.read_header(f, true) + if 0<=M<=4 && O == 0 && 0<=P<=5 && 0<=T<=2 && mrows>=0 && ncols>=0 && 0<=imagf<=1 && namlen>0 + swap_bytes = true + return (true, swap_bytes) + end + end + return (false, false) +end + # Read data type and number of bytes at the start of a data element function read_header(f::IO, swap_bytes::Bool) dtype = read_bswap(f, swap_bytes, Int32) From a97969f8b9d37126dd6f23adfe7c0b3995b80742 Mon Sep 17 00:00:00 2001 From: vsaase Date: Thu, 12 Dec 2019 23:54:43 +0100 Subject: [PATCH 5/9] edit docs --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d0bb48..cdb37df 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,15 @@ matwrite("matfile.mat", Dict( ); compress = true) ``` +To write in MATLAB v4 format: + +```julia +matwrite4("matfile.mat", Dict( + "myvar1" => 0, + "myvar2" => 1 +)) +``` + To get a list of variable names in a MAT file: ```julia @@ -75,9 +84,9 @@ close(file) ## Caveats -* All files are written in MATLAB v7.3 format. -* MATLAB v4 files are not currently supported. +* All files are written in MATLAB v7.3 format by default. +* Writing in MATLAB v4 format is provided by the matwrite4 function. ## Credits -The MAT_HDF5 module, which provides read/write support for MATLAB v7.3 files, was written primarily by [Tim Holy](https://github.com/timholy/). The MAT_v5 module, which provides read support for MATLAB v5/v6/v7 files, was written primarily by [Simon Kornblith](https://github.com/simonster/). +The MAT_HDF5 module, which provides read/write support for MATLAB v7.3 files, was written primarily by [Tim Holy](https://github.com/timholy/). The MAT_v5 module, which provides read support for MATLAB v5/v6/v7 files, was written primarily by [Simon Kornblith](https://github.com/simonster/). The MAT_v4 module, which provides read and write support for MATLAB v4 files, was written primarily by [Victor Saase](https://github.com/vsaase/). From 54d2de263d0fe29916a42732146dbd7474afabea Mon Sep 17 00:00:00 2001 From: Brian Quadras Date: Fri, 19 May 2023 15:14:51 +0530 Subject: [PATCH 6/9] added functionality to write into v4 mat files using keyword argument --- Project.toml | 2 +- README.md | 4 +-- src/MAT.jl | 69 ++++++++++++++++++++++++---------------------- test/readwrite4.jl | 2 +- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Project.toml b/Project.toml index 74c05da..3d4c198 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MAT" uuid = "23992714-dd62-5051-b70f-ba57cb901cac" -version = "0.10.4" +version = "0.10.5" [deps] BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d" diff --git a/README.md b/README.md index 72e1d2a..476ff97 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ To write in MATLAB v4 format: matwrite4("matfile.mat", Dict( "myvar1" => 0, "myvar2" => 1 -)) +);version="v4") ``` To get a list of variable names in a MAT file: @@ -86,7 +86,7 @@ close(file) ## Caveats * All files are written in MATLAB v7.3 format by default. -* Writing in MATLAB v4 format is provided by the matwrite4 function. +* Writing in MATLAB v4 format is provided by the matwrite function with keyword argument. ## Credits diff --git a/src/MAT.jl b/src/MAT.jl index 365bebc..6a25eb5 100644 --- a/src/MAT.jl +++ b/src/MAT.jl @@ -32,7 +32,7 @@ include("MAT_v4.jl") using .MAT_HDF5, .MAT_v5, .MAT_v4 -export matopen, matread, matwrite, matwrite4, names, exists, @read, @write +export matopen, matread, matwrite, @read, @write # Open a MATLAB file const HDF5_HEADER = UInt8[0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a] @@ -139,25 +139,47 @@ end # Write a dict to a MATLAB file """ - matwrite(filename, d::Dict; compress::Bool = false) + matwrite(filename, d::Dict; compress::Bool = false, version::String) Write a dictionary containing variable names as keys and values as values to a Matlab file, opening and closing it automatically. """ -function matwrite(filename::AbstractString, dict::AbstractDict{S, T}; compress::Bool = false) where {S, T} - file = matopen(filename, "w"; compress = compress) - try - for (k, v) in dict - local kstring - try - kstring = ascii(convert(String, k)) - catch x - error("matwrite requires a Dict with ASCII keys") +function matwrite(filename::AbstractString, dict::AbstractDict{S, T}; compress::Bool = false, version::String ="") where {S, T} + + if version == "v4" + file = open(filename, "w") + m = MAT_v4.Matlabv4File(file, false) + try + for (k, v) in dict + local kstring + try + kstring = ascii(convert(String, k)) + catch x + error("matwrite requires a Dict with ASCII keys") + end + write(m, kstring, v) end - write(file, kstring, v) + finally + close(file) end - finally - close(file) + + else + + file = matopen(filename, "w"; compress = compress) + try + for (k, v) in dict + local kstring + try + kstring = ascii(convert(String, k)) + catch x + error("matwrite requires a Dict with ASCII keys") + end + write(file, kstring, v) + end + finally + close(file) + end + end end @@ -175,23 +197,4 @@ end return keys(matfile) end - -function matwrite4(filename::AbstractString, dict::AbstractDict{S, T}) where {S, T} - file = open(filename, "w") - m = MAT_v4.Matlabv4File(file, false) - try - for (k, v) in dict - local kstring - try - kstring = ascii(convert(String, k)) - catch x - error("matwrite requires a Dict with ASCII keys") - end - write(m, kstring, v) - end - finally - close(file) - end -end - end diff --git a/test/readwrite4.jl b/test/readwrite4.jl index fb6fbfa..16b8f47 100644 --- a/test/readwrite4.jl +++ b/test/readwrite4.jl @@ -46,7 +46,7 @@ cd(dirname(@__FILE__)) for filename in readdir("v4") #println("testing $filename") d = matread("v4/$filename") - matwrite4("v4/tmp.mat", d) + matwrite("v4/tmp.mat", d; version="v4") check("v4/tmp.mat", d) rm("v4/tmp.mat") end From 8d0e77adad18ce74b514df39bcd0edc0ec9c6b7f Mon Sep 17 00:00:00 2001 From: Brian Quadras Date: Fri, 19 May 2023 23:14:23 +0530 Subject: [PATCH 7/9] Reset version number --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3d4c198..74c05da 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MAT" uuid = "23992714-dd62-5051-b70f-ba57cb901cac" -version = "0.10.5" +version = "0.10.4" [deps] BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d" From 2c90d2bb962a0376a42bf1733e8be308a1c5ff6b Mon Sep 17 00:00:00 2001 From: Brian Quadras Date: Sat, 20 May 2023 14:10:03 +0530 Subject: [PATCH 8/9] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 476ff97..90d2b4f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ### Read and write MATLAB files in Julia -This library can read MATLAB `.mat` files, both in the older v5/v6/v7 format, as well as the newer v7.3 format. +This library can read MATLAB `.mat` files, both in the older v4/v5/v6/v7 format, as well as the newer v7.3 format. ## Installation From 54bf18b4bc6e5a8ca61888783b93485b5b8f90a0 Mon Sep 17 00:00:00 2001 From: Brian Quadras Date: Wed, 7 Jun 2023 08:14:03 +0530 Subject: [PATCH 9/9] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90d2b4f..7c46531 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ matwrite("matfile.mat", Dict( To write in MATLAB v4 format: ```julia -matwrite4("matfile.mat", Dict( +matwrite("matfile.mat", Dict( "myvar1" => 0, "myvar2" => 1 );version="v4")