15

Does anyone know of any methods of extracting the data from a MATLAB fig file using Python? I know these are binary files but the methods in the Python Cookbook for .mat files http://www.scipy.org/Cookbook/Reading_mat_files don't seem to work for .fig files...

Thanks in advance for any help, Dan

Dan
  • 1,209
  • 3
  • 13
  • 29

6 Answers6

12

.fig files are .mat files (containing a struct), see http://undocumentedmatlab.com/blog/fig-files-format/

As the reference you give states, structs are only supported up to v7.1: http://www.scipy.org/Cookbook/Reading_mat_files

So, in MATLAB I save using -v7:

plot([1 2],[3 4])
hgsave(gcf,'c','-v7');

Then in Python 2.6.4 I use:

>>> from scipy.io import loadmat
>>> x = loadmat('c.fig')
>>> x
{'hgS_070000': array([[<scipy.io.matlab.mio5.mat_struct object at 0x1500e70>]], dtype=object), '__version__': '1.0', '__header__': 'MATLAB 5.0 MAT-file, Platform: MACI64, Created on: Fri Nov 18 12:02:31 2011', '__globals__': []}
>>> x['hgS_070000'][0,0].__dict__
{'handle': array([[1]], dtype=uint8), 'children': array([[<scipy.io.matlab.mio5.mat_struct object at 0x1516030>]], dtype=object), '_fieldnames': ['type', 'handle', 'properties', 'children', 'special'], 'type': array([u'figure'], dtype='<U6'), 'properties': array([[<scipy.io.matlab.mio5.mat_struct object at 0x1500fb0>]], dtype=object), 'special': array([], shape=(1, 0), dtype=float64)}

Where I used .__dict__ to see how to traverse the structure. E.g. to get XData and YData I can use:

>>> x['hgS_070000'][0,0].children[0,0].children[0,0].properties[0,0].XData
array([[1, 2]], dtype=uint8)
>>> x['hgS_070000'][0,0].children[0,0].children[0,0].properties[0,0].YData
array([[3, 4]], dtype=uint8)

Showing that I'd used plot([1 2],[3 4]) in MATLAB (the child is the axis and the grandchild is the lineseries).

Ramashalanka
  • 8,564
  • 1
  • 35
  • 46
11

Here is my update from Sascha's post. Now it can:

  • display rotated, tex labels
  • display xticks and yticks
  • better handling of markers
  • grid on/off
  • better axes and legend enumeration handling
  • maintain figure size

The code is below:

from scipy.io import loadmat
import numpy as np
import matplotlib.pyplot as plt

def plotFig(filename,fignr=1):
   d = loadmat(filename,squeeze_me=True, struct_as_record=False)
   matfig = d['hgS_070000']
   childs = matfig.children
   ax1 = [c for c in childs if c.type == 'axes']
   if(len(ax1) > 0):
       ax1 = ax1[0]
   legs = [c for c in childs if c.type == 'scribe.legend']
   if(len(legs) > 0):
       legs = legs[0]
   else:
       legs=0
   pos = matfig.properties.Position
   size = np.array([pos[2]-pos[0],pos[3]-pos[1]])/96
   plt.figure(fignr,figsize=size)
   plt.clf()
   plt.hold(True)
   counter = 0    
   for line in ax1.children:
       if line.type == 'graph2d.lineseries':
           if hasattr(line.properties,'Marker'):
               mark = "%s" % line.properties.Marker
               if(mark != "none"):
                   mark = mark[0]
           else:
               mark = '.'
           if hasattr(line.properties,'LineStyle'):
               linestyle = "%s" % line.properties.LineStyle
           else:
               linestyle = '-'
           if hasattr(line.properties,'Color'):
               r,g,b =  line.properties.Color
           else:
               r = 0
               g = 0
               b = 1
           if hasattr(line.properties,'MarkerSize'):
               marker_size = line.properties.MarkerSize
           else:
               marker_size = -1                
           x = line.properties.XData
           y = line.properties.YData
           if(mark == "none"):
               plt.plot(x,y,linestyle=linestyle,color=[r,g,b])
           elif(marker_size==-1):
               plt.plot(x,y,marker=mark,linestyle=linestyle,color=[r,g,b])
           else:
               plt.plot(x,y,marker=mark,linestyle=linestyle,color=[r,g,b],ms=marker_size)
       elif line.type == 'text':
           if counter == 0:
               plt.xlabel("$%s$" % line.properties.String,fontsize =16)
           elif counter == 1:
               plt.ylabel("$%s$" % line.properties.String,fontsize = 16)
           elif counter == 3:
               plt.title("$%s$" % line.properties.String,fontsize = 16)
           counter += 1        
   plt.grid(ax1.properties.XGrid)
   
   if(hasattr(ax1.properties,'XTick')):
       if(hasattr(ax1.properties,'XTickLabelRotation')):
           plt.xticks(ax1.properties.XTick,ax1.properties.XTickLabel,rotation=ax1.properties.XTickLabelRotation)
       else:
           plt.xticks(ax1.properties.XTick,ax1.properties.XTickLabel)
   if(hasattr(ax1.properties,'YTick')):
       if(hasattr(ax1.properties,'YTickLabelRotation')):
           plt.yticks(ax1.properties.YTick,ax1.properties.YTickLabel,rotation=ax1.properties.YTickLabelRotation)
       else:
           plt.yticks(ax1.properties.YTick,ax1.properties.YTickLabel)
   plt.xlim(ax1.properties.XLim)
   plt.ylim(ax1.properties.YLim)
   if legs:        
       leg_entries = tuple(['$' + l + '$' for l in legs.properties.String])
       py_locs = ['upper center','lower center','right','left','upper right','upper left','lower right','lower left','best','best']
       MAT_locs=['North','South','East','West','NorthEast', 'NorthWest', 'SouthEast', 'SouthWest','Best','none']
       Mat2py = dict(zip(MAT_locs,py_locs))
       location = legs.properties.Location
       plt.legend(leg_entries,loc=Mat2py[location])
   plt.hold(False)
   plt.show()

updated for python >3.x, (see comments from @robert-pollak):

from scipy.io import loadmat
import numpy as np
import matplotlib.pyplot as plt

def plotFig(filename,fignr=1, subfig=1):
   d = loadmat(filename,squeeze_me=True, struct_as_record=False)
   matfig = d['hgS_070000']
   childs = matfig.children

   sfig = max(0, subfig - 1)
   
   ax1 = [c for c in childs if c.type == 'axes']
   if(len(ax1) > 0):
       sfig = min(sfig, len(ax1) - 1)
       ax1 = ax1[sfig]
   
   legs = [c for c in childs if c.type == 'scribe.legend']
   if(len(legs) > 0):
       legs = legs[sfig]
   else:
       legs=0
   pos = matfig.properties.Position
   size = np.array([pos[2]-pos[0],pos[3]-pos[1]])/96
   plt.figure(fignr,figsize=size)
   plt.clf()
   #plt.hold(True)
   counter = 0    
   for line in ax1.children:
       if line.type == 'graph2d.lineseries':
           if hasattr(line.properties,'Marker'):
               mark = "%s" % line.properties.Marker
               if(mark != "none"):
                   mark = mark[0]
           else:
               mark = '.'
           if hasattr(line.properties,'LineStyle'):
               linestyle = "%s" % line.properties.LineStyle
           else:
               linestyle = '-'
           if hasattr(line.properties,'Color'):
               r,g,b =  line.properties.Color
           else:
               r = 0
               g = 0
               b = 1
           if hasattr(line.properties,'MarkerSize'):
               marker_size = line.properties.MarkerSize
           else:
               marker_size = -1                
           x = line.properties.XData
           y = line.properties.YData
           if(mark == "none"):
               plt.plot(x,y,linestyle=linestyle,color=[r,g,b])
           elif(marker_size==-1):
               plt.plot(x,y,marker=mark,linestyle=linestyle,color=[r,g,b])
           else:
               plt.plot(x,y,marker=mark,linestyle=linestyle,color=[r,g,b],ms=marker_size)
       elif line.type == 'text':
           if counter == 0:
               plt.xlabel("$%s$" % line.properties.String,fontsize =16)
           elif counter == 1:
               plt.ylabel("$%s$" % line.properties.String,fontsize = 16)
           elif counter == 3:
               plt.title("$%s$" % line.properties.String,fontsize = 16)
           counter += 1        
   plt.grid(ax1.properties.XGrid)

   if(hasattr(ax1.properties,'XTick')):
       if(hasattr(ax1.properties,'XTickLabelRotation')):
           plt.xticks(ax1.properties.XTick,ax1.properties.XTickLabel,rotation=ax1.properties.XTickLabelRotation)
       else:
           plt.xticks(ax1.properties.XTick,ax1.properties.XTickLabel)
   if(hasattr(ax1.properties,'YTick')):
       if(hasattr(ax1.properties,'YTickLabelRotation')):
           plt.yticks(ax1.properties.YTick,ax1.properties.YTickLabel,rotation=ax1.properties.YTickLabelRotation)
       else:
           plt.yticks(ax1.properties.YTick,ax1.properties.YTickLabel)
   plt.xlim(ax1.properties.XLim)
   plt.ylim(ax1.properties.YLim)
   if legs:        
       leg_entries = tuple(['$' + l + '$' for l in legs.properties.String])
       py_locs = ['upper center','lower center','right','left','upper right','upper left','lower right','lower left','best','best']
       MAT_locs=['north','south','east','west','northeast', 'northwest', 'southeast', 'southwest','best','none']
       Mat2py = dict(zip(MAT_locs,py_locs))
       location = legs.properties.Location.lower()
       plt.legend(leg_entries,loc=Mat2py[ location ])
   #plt.hold(False)
   plt.show()
Henrik
  • 2,180
  • 16
  • 29
johnml1135
  • 4,519
  • 3
  • 23
  • 18
  • How would you run this script to conver or open .fig files – August Feb 16 '17 at 10:24
  • I preffer using WinPython and Spyder for my development: http://winpython.github.io/ – johnml1135 Feb 17 '17 at 13:29
  • 3
    This script works great - the only thing I had to change was to check whether the XGrid attribute was present: if hasattr(ax1.properties, 'XGrid'): plt.grid(ax1.properties.XGrid) – Matt Williams Apr 11 '19 at 00:39
  • 2
    @MattWilliams Yes, and with matplotlib 3 the `plt.hold` calls have to be removed. Also, in my case I have to change the `MAT_locs` entries to lower case. – Robert Pollak Jul 30 '19 at 14:13
  • @RobertPollak could this be extended further to account for shapes too? I can get it parse out the scatter or line plots but any shapes are not captured and skipped. – Dante Smith Sep 23 '20 at 15:08
  • @DanteSmith I have no idea, I don't need this. – Robert Pollak Sep 24 '20 at 11:29
  • @DanteSmith - My best guess is to iterate out ax1.children and see if you can determine the types of plotted data. Work it out piece by piece, and then feel free to post your updated code. – johnml1135 Sep 25 '20 at 13:59
  • has posted updated code for python 3.1x, and added subfigure support – Henrik May 15 '23 at 08:53
8

I found Alex's answer very appealing, but I extended his code a bit. First of all, I included the preamble to show where the figure, ylabel, etc. comes from. Second of all, I included the legend! I'm rather new to Python, so any suggestions for improvements are highly welcomed.

def plotFig(filename,fignr=1):
    from scipy.io import loadmat
    from numpy import size
    from matplotlib.pyplot import plot,figure,hold,xlabel,ylabel,show,clf,xlim,legend
    d = loadmat(filename,squeeze_me=True, struct_as_record=False)
    ax1 = d['hgS_070000'].children
    if size(ax1) > 1:
        legs= ax1[1]
        ax1 = ax1[0]
    else:
        legs=0
    figure(fignr)
    clf()
    hold(True)
    counter = 0    
    for line in ax1.children:
        if line.type == 'graph2d.lineseries':
            if hasattr(line.properties,'Marker'):
                mark = "%s" % line.properties.Marker
                mark = mark[0]
            else:
                mark = '.'
            if hasattr(line.properties,'LineStyle'):
                linestyle = "%s" % line.properties.LineStyle
            else:
                linestyle = '-'
            if hasattr(line.properties,'Color'):
                r,g,b =  line.properties.Color
            else:
                r = 0
                g = 0
                b = 1
            if hasattr(line.properties,'MarkerSize'):
                marker_size = line.properties.MarkerSize
            else:
                marker_size = 1                
            x = line.properties.XData
            y = line.properties.YData
            plot(x,y,marker=mark,linestyle=linestyle,color=color(r,g,b),markersize=marker_size)
        elif line.type == 'text':
            if counter < 1:
                xlabel("%s" % line.properties.String,fontsize =16)
                counter += 1
            elif counter < 2:
                ylabel("%s" % line.properties.String,fontsize = 16)
                counter += 1        
    xlim(ax1.properties.XLim)
    if legs:        
        leg_entries = tuple(legs.properties.String)
        py_locs = ['upper center','lower center','right','left','upper right','upper left','lower right','lower left','best']
        MAT_locs=['North','South','East','West','NorthEast', 'NorthWest', 'SouthEast', 'SouthWest','Best']
        Mat2py = dict(zip(MAT_locs,py_locs))
        location = legs.properties.Location
        legend(leg_entries,loc=Mat2py[location])
    hold(False)
    show()
Alex44
  • 3,597
  • 7
  • 39
  • 56
Sascha
  • 456
  • 4
  • 6
2

This is much easier way available. It's based on the newer Scipy and loadmat:

http://answerpot.com/showthread.php?3707193-loadmat+and+figure

And my small extension to it for the simple 2D lines is:

from scipy.io import loadmat



d = loadmat('../impulse_all.fig',squeeze_me=True, struct_as_record=False)
# d = loadmat('R11_resuspension.fig',squeeze_me=True, struct_as_record=False)
ax1 = d['hgS_070000'].children
if size(ax1) > 1:
    ax1 = ax1[0]

figure
hold(True)
counter = 0
for line in ax1.children:
    if line.type == 'graph2d.lineseries':
        marker = "%s" % line.properties.Marker
        linestyle = "%s" % line.properties.LineStyle
        r,g,b =  line.properties.Color
        marker_size = line.properties.MarkerSize
        x = line.properties.XData
        y = line.properties.YData
        plot(x,y,marker,linestyle=linestyle,color = (r,g,b),markersize=marker_size)
    elif line.type == 'text':
        if counter < 1:
            xlabel("%s" % line.properties.String,fontsize =16)
            counter += 1
        elif counter < 2:
            ylabel("%s" % line.properties.String,fontsize = 16)
            counter += 1



hold(False)
Alex
  • 118
  • 5
2

When you save a MATLAB figure, it dumps the Handle Graphics hierarchy into a structure, saves it to a .mat file, and changes the extension to .fig. So .fig files are just .mat files, and if the data you're looking for was stored somewhere in the original figure it will be in there. If you manually change the extension back to .mat you can load it into MATLAB and take a look.

I'm afraid I don't know much about reading .mat files from Python, but if you have a way of doing that in general, you should also be able to read in a .fig file.

Sam Roberts
  • 23,951
  • 1
  • 40
  • 64
1

Using the posts of Sascha, I just wanted to extract the x-data and y-data stored in a .fig file.

Below is my python function, which is a simplification of the Sascha's function aiming at only extracting the data. Output is a dictionary. Its keys are the corresponding labels of the data in the figure.

I put it there. Glad if this could save a few minutes to someone else !

import numpy
from scipy.io import loadmat

def read_fig(filename):
    output = {}
    d = loadmat(filename, squeeze_me=True, struct_as_record=False)
    matfig = d['hgS_070000']
    childs = matfig.children
    ax1 = [c for c in childs if c.type == 'axes'][0]
    for line in ax1.children:
        try:
            if line.type == 'graph2d.lineseries':
                x = line.properties.XData
                y = line.properties.YData
                leg = line.properties.DisplayName
                print leg
                output[leg] = numpy.column_stack((x, y))
        except:
            print 'One children is ignored...'
    return output
AuréLien
  • 31
  • 3