13

I have some MATLAB image data with the following dimensions (output is from MATLAB):

>> size(im)
ans =
    86    86     3    45

The data imports just fine in Mathematica, except the dimensions are reversed, and there's one extra dimension:

In[179]:= i = Import["Attractims.mat"];
          Dimensions[i]

Out[180]= {1, 45, 3, 86, 86}

It's easy enough to throw out that first dimension. But how can I massage the list to produce one whose dimensions are in the same order as in MATLAB? In other words, I want to reverse the order of the dimensions in a multi-dimensional list. At first this seemed like a trivial problem, but when I sat down to do it I found that I couldn't. Help?

EDIT:

If you want to check out the MATLAB file yourself, you can get it (for the time being) at:

[ edit: resource no longer available. ]

As I mentioned below in comments, I was unable to get Leonid's approach to change the dimensions of the imported data. However, R.M.'s approach almost works: the dimensions of the array are changed appropriately, but the X and Y are reversed. To see what I mean, download and import the data above using something like:

In[340]:= mma = Import["/wherever/Attractims.mat"];
In[341]:= mma2mat = Flatten[mma, Table[{i}, {i, Depth[mma] - 1, 1, -1}]];
In[342]:= Dimensions[mma2mat]
Out[342]= {86, 86, 3, 45, 1}

So far so good. But if you do:

Image[mma2mat[[All, All, All, 45, 1]], "byte"]

you can see that rows and columns have been transposed. I'm having a hard time wrapping my head around this thing, but if this produces the 'correct' image in MATLAB:

image(im(:,:,:,45))

then shouldn't the converted version do the same? In any event, thanks to your collective help I'm able to do the work that needs doing, but it would be nice to understand if I could...

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
shanusmagnus
  • 1,159
  • 10
  • 11
  • 3
    Have a look at Transpose. It can handle multi-dimensional arrays. – b.gates.you.know.what Sep 13 '12 at 19:29
  • Can you give a link to your .MAT file? – Vitaliy Kaurov Sep 13 '12 at 19:30
  • 1
    Use Reverse if it is a single vector – Vitaliy Kaurov Sep 13 '12 at 19:34
  • If you use Flatten you can drop the first dimension and transpose at the same time. – Oleksandr R. Sep 13 '12 at 20:13
  • The problem of transposed columns and rows is because the file was originally saved using MATLAB 5 format, which has a slightly different structure. Try resaving it in MATLAB using the newer HDF5 format using the -v7.3 switch (see my answer). If you do this, then it gives you an upright image. – rm -rf Sep 14 '12 at 01:51
  • Actually, my code was supposed to be exactly such that the part indexing remains the same, in the same order, in Matlab and Mathematica. It worked for me this way in my application. Can not test now alas, to see for myself what's the problem if any. – Leonid Shifrin Sep 14 '12 at 02:35
  • As for the extra dimension: I think this is there just because you can put more than one matrix into a .mat file. They will then be imported into Mathematica in a list of arrays. In matlab, content = load('file.mat') will return a struct with a field for every variable name in that case, a simple load('file.mat')will create/overwrite the corresponding variables in the current workspace... – Albert Retey Dec 15 '15 at 08:32

2 Answers2

14

Some theory

This is not completely trivial, and the reason is in the differences between how Matlab and Mathematica represent tensors (multi-dimensional arrays), of which I will stress three:

  1. Matrices in Matlab are stored in the column-major order (like in Fortran and R), while in Mathematica they are stored in the row-major order (like in C). This is also true for sparse matrices. This has a number of implications for things like data transfer between Matlab and Mathematica (when Matlab engine C API is used), but also for linear indexing. For example,

    mlbmat = [[1 2 3]; [4 5 6]]
    mmamat = {{1,2,3},{4,5,6}}
    
    mlbmat(4) --> 5
    Flatten[mmamat][[4]] --> 4
    
  2. So called trailing (thanks for the correction, @R.M) singleton dimensions - trailing meaning dimension - 1 at the start or end of the dimensions list, but not in the middle - are automatically removed by Matlab, while they are kept in Mathematica. So, for example,

    mlbsingletons = [[[[1],[2],[3]];[[4],[5],[6]]]]
    

    is equivalent to

        [[1 2 3];[4 5 6]]
    

    while in Mathematica this would be

    {{{{1},{2},{3}},{{4},{5},{6}}}}
    

    and these "singleton" dimensions will be kept by the system.

  3. Arrays of higher dimensionality are treated also differently. Higher dimensions are added in Matlab via the pointer mechanism, so they are prepended to the list of dimensions, rather than appended to it.

Translation

I happened to have developed the translation functions in the past, so here I will post and illustrate what I was using. I won't discuss the singleton dimension, just drop it. Here are the conversion functions:

ClearAll[newDims, fromMmaToMtlb, fromMtlbToMma];
newDims[tensor_] := 
   Join[Take[#, -2], Drop[#, -2]] &@Dimensions[tensor];

fromMmaToMtlb[tensor_] := Map[Transpose, tensor, {-3}];

fromMtlbToMma[tensor_] :=
  With[{values = Flatten[tensor], dims = Reverse[Dimensions[tensor]]},
     Map[Transpose, First@Fold[Partition, values, dims], {-3}]];

I used these to communicate with Matlab via its C engine API. If you start e.g. with the following array:

tst  = {{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, 
       {{13,14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}};

which has dimensions

Dimensions@tst

(*  {4,3,2}   *)

then here is what is the equivalent Matlab array:

mlbtst = fromMmaToMtlb[tst]

(*  
    {{{1,3,5},{2,4,6}},{{7,9,11},{8,10,12}},
    {{13,15,17},{14,16,18}},{{19,21,23},{20,22,24}}}
*)

with (Matlab) dimensions

newDims[tst]

(*  {3,2,4}  *)

which are the reverse of Dimensions[mlbtst], due to the column-major order vs row-major order difference. Now, the reverse would be:

fromMtlbToMma[mlbtst]

(*
      {{{1,2},{3,4},{5,6}},{{7,8},{9,10},{11,12}},
      {{13,14},{15,16},{17,18}},{{19,20},{21,22},{23,24}}}
*) 

so we get back our original array.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Nice explanation, but I wouldn't say that all singleton dimensions are automatically removed — only trailing singleton dimensions are automatically removed, which is why writing something as [[[1],[2],[3]]] gives a false sense of depth (i.e., you can never equate it to Mathematica's lists). It is indeed possible to have non-trailing singleton dimensions. For example: x = reshape(magic(4),[1,4,4]); size(x) – rm -rf Sep 13 '12 at 21:43
  • @R.M. Yes, you are right, thanks. I actually meant the trailing ones. Those in the middle are not removed. Will edit the post. As a matter of fact, this Matlab's behavior, among other things, makes it difficult to write, say, a translator from Matlab to Mathematica (for example :-)). – Leonid Shifrin Sep 13 '12 at 21:49
  • Re: the edit, it's only dimensions at the end of the dimensions list... those at the start are also retained, like in my example above – rm -rf Sep 13 '12 at 21:55
  • @R.M I can't launch Matlab now for some reason, so can not test, but I used to think that those dimensions are removed from both ends. You are probably right, but why then in [[[[1],[2],[3]];[[4],[5],[6]]]] both outer vector brackets and inner vector brackets are removed? – Leonid Shifrin Sep 13 '12 at 22:07
  • Well, my understanding is that the brackets don't really guide the dimensions at all. It just serves as a convenient container, but it's the commas/spaces and semicolons that indicate the rows/columns. You probably might have gotten used to writing it that way, but typically it is just written as [1,2,3;4,5,6]. In essence, the brackets are stripped away until only one is required to hold the contents and don't have special meaning like List does in mma. AFAIK, there is no way to enter a multiple dimension (>2) array by hand. One would need to reshape or cat it to build it. – rm -rf Sep 13 '12 at 22:20
  • This is also why MATLAB has no convenient way of displaying a multidimensional array, like Mathematica does using nested lists. If you try to display a multi dimensional array, it'll output it in several parts of 2D arrays. For instance, displaying x from my comment above will give you several such outputs: `x(:,:,1) =
    [16     5     9     4],..., x(:,:,4) =
    
    [13     8    12     1]`. An equivalent to _Mathematica_'s list structure would be the cell arrays in MATLAB, which are also entered using `{}`. These can be nested, ragged, hold different objects, etc.
    
    – rm -rf Sep 13 '12 at 22:22
  • @R.M You are probably right, although have a hunch that this disappearance of inner brackets is not on the grammar level. In any case, I was never fond of how higher dimensions are implemented in Matlab - not too consistent to my mind, and I strongly suspect they were added as an afterthought. – Leonid Shifrin Sep 13 '12 at 22:25
  • Thanks for another thorough and thoughtful answer, Leonid. However, I must be misunderstanding something because I can't get it to work! If i is the data imported from Matlab, with dimensions {1, 45, 3, 86, 86} then Dimensions[fromMtlbToMma[i]] is still reporting {1, 45, 3, 86, 86}. What am I missing? – shanusmagnus Sep 14 '12 at 01:02
  • @shanusmagnus This is correct of you measure dimensions for one array in Matlab (size function) and another array (processed by fromMtlbToMma) by Dimensions. But if both lists are already in Mathematica and both measured with Dimensions, then I am at a loss. It may be that you have to remove the singleton trailing dimension (1), since my code assumed those are absent. If not, some small explicit self-contained example array would be nice to see, with your comments on what exactly you'd expect to get. – Leonid Shifrin Sep 14 '12 at 02:33
  • I just wanted to import the Matlab matrix into Mma, and then perform some conversion on it in Mma s.t. the dimension (as reported with Dimension[]) of the converted array would look the same in Mma as the original did in Matlab. This is the result I get using R.M.'s method (I just have to ignore that leftover singleton afterward) with details provided above. I'm not sure what's going on wrt your conversion method, but sadly I lack the savvy in either Matlab or Mma to say anything more useful for troubleshooting it. :( – shanusmagnus Sep 14 '12 at 02:45
  • @shanusmagnus I will look at it later when I get access to Matlab, to see what the problem with my approach is. I posted it since I was solving this exact problem in a general way for my Mathematica - Matlab application in the past and tested it extensively on a number of examples. Alas, I can not tell more without testing, but since you have another answer which mostly works this should not be a serious problem for you. – Leonid Shifrin Sep 14 '12 at 03:04
14

Leonid has given you the theory behind why the dimensions get flipped — it's because of how arrays are indexed. However, I offer a much simpler way of doing the transformation using the powerful second argument of Flatten. First, let's create an example in MATLAB:

mat = reshape(magic(32),[1,2,4,8,16]);
size(mat)
% ans = 1 2 4 8 16

save('~/test.mat','mat','-v7.3')

Now we import this in Mathematica

mma = Import["~/test.mat", {"HDF5", "Datasets", "mat"}];
Dimensions@mma
(* {16, 8, 4, 2, 1} *)

Ok, so to convert this to MATLAB, the transformation is as simple as the following:

mma2mat = Flatten[mma, Table[{i}, {i, Depth[mma] - 1, 1, -1}]];

The above is a generalized transpose of the list and see Leonid's excellent answer for an understanding of the second argument of Flatten.


Verification:

You can check that the results are the same by comparing slices of the array in both MATLAB and Mathematica:

In MATLAB:

squeeze(mat(1,1,:,1,1))'
% ans = 1024 65 896 193

In Mathematica:

mma2mat[[1, 1, ;; , 1, 1]]
(* {1024., 65., 896., 193.} *)
rm -rf
  • 88,781
  • 21
  • 293
  • 472
  • +1. I was expecting that there are simpler ways of doing that. However, the focus of the question was on the inverse - to get stuff from Matlab to mma, so I just gave the direct conversion for completeness (not that I say that there isn't a simpler way for the reverse transform either). – Leonid Shifrin Sep 13 '12 at 22:09
  • @LeonidShifrin The inverse of what I've written? My answer also shows how to convert from MATLAB to Mathematica... The reverse would be as simple as reversing the order in Flatten – rm -rf Sep 13 '12 at 22:13
  • Actually, I had in mind equivalence in the sense of linear indexing. So, Flatten[list] in mma produces the same flat list as when you send flat data for fromMmaToMtlb[list] and restore the correct Matlab dimensions, and then flatten your result in Matlab via linear indexing. Your code produces a different list from my fromMmaToMtlb, so you probably had something else in mind. A pity I can't check in Matlab now. – Leonid Shifrin Sep 13 '12 at 22:21
  • @LeonidShifrin My answer above is related to your fromMtlbToMma, but yes, there are differences — I don't think yours gives the correct transformation. Specifically, if you use your function on my example above, it returns a list of dimension {16, 8, 4, 1, 2} (the last two are flipped). Similarly, if you try fromMmaToMtlb, you get a list of {1, 2, 4, 16, 8} whereas I started with an array of [1, 2, 4, 8, 16] (again, last two are flipped). I think this is a small issue with note keeping, but I haven't looked through the logic of yours in detail. – rm -rf Sep 13 '12 at 22:33
  • Mine arose from the necessity of 2-way exchange between Mathematica and Matlab via Matlab C API. That API asks you to provide a flat list of data and a list of dimensions according to Matlab conventions (size), and constructs an array from that data. Complementary API functions do the reverse, allowing you to extract those components. When I was doing this for multidimensional arrays, I found the recipe I posted (otherwise data did not match the indexing). I don't now remember the details alas, and right now can not check, but we can return to this later if you wish. – Leonid Shifrin Sep 13 '12 at 22:37
  • +1 -- slightly shorter than Table: List /@ Range[Depth@mma - 1, 1, -1] – Mr.Wizard Sep 14 '12 at 14:57
  • @Mr.Wizard {Range[...]}\[Transpose] is shorter still in the FE :) – rm -rf Sep 14 '12 at 15:01
  • Yes, but I have been unsuccessful in inserting that T in code here. :^) – Mr.Wizard Sep 14 '12 at 15:04
  • Is there any newer version for the data format applicable here? You use v7.3. – Léo Léopold Hertz 준영 Sep 05 '16 at 13:11
  • I think your data verification method is not sufficient in Mathematica. Assume you have 123456x123456 matrix to be visualised. – Léo Léopold Hertz 준영 Sep 23 '16 at 10:22