背景,了解一下
目前在做一个有点科研性质的软件项目,应用程序难免要涉及到一些科学计算上相关的东西,而matlab是科学计算领域最常用的工具,这不,刚接到产品经理描述的需求,用户会将matlab产生的结果文件(.mat)上传到系统,系统解析这个结果,然后在网页上展示。虽说最近哈工大被禁用matlab风波一时,大家隐忧不解,但是没有好的替代品前,还是得硬着头皮继续用着(客户不是哈工大,还可以用)。 在接到这样的需求时,首先去确认了下能否实现,随手baidu,出来不少神仙们写的java读取matlab数据的博客,内心放心了一大半,能解决就好。产品经理一听能够解决,心里美滋滋,以最快的速度进行了需求确认->产品设计->评审、并向客户要了份测试数据交了过来,于是那时那刻手里攥着一份.mat数据,对它还一所知。
问题解决思路
接下来就得以科学严谨的态度来解决问题了,大概分这样几步:
- 找研究算法同事(他们的电脑一般都装有matlab)帮忙打开这个.mat文件,看看它长什么样子(何方妖怪),结果,清秀的像个excel表格。
先了解下matlab,所谓磨刀不误砍柴工。有些道友可能会直接施展CV大法,轻松搞定。但是如果根基薄弱的话,还是不建议这样做,提前了解多一些,还是多少可以减少点后期的问题,而且也能更加从容的应对后续的变化。
动手、尝试与封装
matlab扫盲学习,简单总结
Matlab可以说是一个计算平台,也可以说是一门高级语言,它有着自定义的数据类型,和其他语言相似,也有数值、字符、map等。下图是在后续实现中看到的封装matlab数据类型类的部分截图:
比较疑惑的可能是sparse 和 cell 。sparse是稀疏矩阵,cell 是索引数据容器的数据类型,它是一个数组,只是数组里存放的是其他类型数据的索引。数组和矩阵是matlab总信息和数据的基本表示形式。
通过官网学习是才是扫盲的第一步, 从matlab语言基础知识可以了解到更多数据类型的介绍,比如table类型,还记的.mat打开后清秀的样子吗,大胆猜测产品经理拿来的数据可能就和table有关。
扫盲方式: 先从官网学习,一知半解,然后再实践中对照印证。
动手,尝试过程记录
百度一下清一色 都是使用ujmp-jmatio 来实.mat文件内容的读写,
1. 尝试使用jmatio, 结果失败了。
- 首先在项目中引入
<dependency>
<groupId>net.sourceforge.jmatio</groupId>
<artifactId>jmatio</artifactId>
<version>1.0</version>
</dependency>
- 实在读取mat文件,结果报错了
MatFileReader reader = new MatFileReader("QA_table.mat");
Exception in thread "main" java.lang.ClassCastException: com.jmatio.types.MLUInt32 cannot be cast to com.jmatio.types.MLUInt8
ujmp-jmatio 实际上也是引用了jmatio, 再进行了一层封装,尝试使用ujmp-jmatio结果抛了同样的错误,怀疑是不是版本的问题,看了mvnrepository, 只有1.0版本,matlab不同版本数据定义有差异,估计我拿到的文件版本确实不兼容了。 这条路没有再深究下去。
2. 转换为mfl,结果成功了
在github 搜索了一番,找到项目 https://github.com/HebiRobotics/MFL, 引入项目尝试
<dependency>
<groupId>us.hebi.matlab.mat</groupId>
<artifactId>mfl-core</artifactId>
<version>0.5.6</version>
</dependency>
Mat5File file = Mat5.readFromFile("QA_table.mat");
System.out.println(file);
很庆幸,没有再报错了,看看打印出来的结果:
Mat5File{description='MATLAB 5.0 MAT-file, Platform: PCWIN64, Created on: Wed Apr 01 14:52:21 2020', subsysOffset=213, byteOrder=LITTLE_ENDIAN, version=1}
QA_table = 1x1 table
ndims: 2.0
nrows: 3.0
rownames: {}
nvars: 16.0
varnames: 1x16 cell
data: 1x16 cell
props: 1x1 struct
Description: ''
VariableDescriptions: {}
VariableUnits: {}
DimensionNames: 1x2 cell
UserData: []
"" = 1x6600 uint8
从结果可以看出文件有两个主体,一个是name为QA_table的 table, 另一个是name为空串的unit8, 第一个应该就是要读取的主体内容(1x6600 unit8 一时没弄明白是什么含义),MFL库中没有table结构相关的封装类,但是可以直接使用getStruct来获取。
// 使用name 或者索引都可以
Struct struct = file.getStruct("QA_table");
// Struct struct = file.getStruct(0);
假如要读取图1中filename列中的三行分别的值,先简单分析下file 打印的信息(以下没有任何官方说明,仅为个人推测和实践):
- ndims: 表示维度,当前为2维数据
- nrow3: 数据行数,3行,和图1是一致的。
- nvars: 变量个数,等同于列数
- varnames: 存放列名
- data: 存放数据
- props:表的属性
以下是具体读取信息的代码:
// 首先获取数据, data: 1x16 cell
Cell data = struct.getCell("data");
// 读取cell中第二列 ,就是filename列
Cell filenameValues = data.get(0, 1);
// 打印所有filename 值
for (int i=0; i<3; i++) {
System.out.println(filenameValues.get(i, 0) .toString());
}
之所以像上面这般读取,是因为给的数据结构是 116 cell数组,数组内元素 是 31 的cell数组;
接来下就是封装工具,将表格转为json数据,提供给业务方调用,具体代码就不贴了。整个过程就是如此,感谢抽空阅读。