Source code for RFigure.RFigureCore
# PYTHON 3
""" Unless mentionned otherwise, the code is written by R. Dessalles (Grumpfou).
Published under license GNU General Public License v3.0. Part of the RFigure
project.
"""
import matplotlib
import warnings
import inspect
import textwrap
# with warnings.catch_warnings():
# warnings.simplefilter("ignore")
# matplotlib.use('Qt5Agg')
import matplotlib.pyplot
# matplotlib.pyplot.ion()
matplotlib.pyplot.show._needmain = False
import numpy as np
import os
import tempfile
import re
import traceback
import sys
from . import RFigurePickle
from .RFigureMisc import RPathFormatting, RTextWrap,decoDocFormatting
from .RFigureSearchvar import find_varsdict_from_list
from matplotlib.backends.backend_pdf import PdfPages
__version__ = '3.1'
###################### CONFIG IMPORTATION ##############################
path_to_header = './RFigureConfig/RFigureHeader.py'
file_dir = os.path.realpath(os.path.dirname(__file__))
path_to_header = os.path.join(file_dir,path_to_header)
# For fuiture config
# path_to_config = './RFigureConfig/RFigureConfig.py'
# path_to_config = os.path.join(file_dir,path_to_config)
# config = {}
# if os.path.exists(path_to_config):
# exec(open(path_to_config).read(),{},config)
########################################################################
# matplotlib.pyplot.ion()
[docs]class RFigureCore:
"""
This si the RFigureCore core class.
"""
CURDATE = None # you can reassign CURDATE to the date by default (something like `'20191031'` )
FORMAT = "Figure_%Y%m%d_%s"
ext = '.rfig3'
fig_type_list=['pdf','eps','png','svg']
frame_number = 1 # Usefull in the case where d is a list. tell how many frame we need to take to have the function call
[docs] def __init__(self,
d=None,i=None,c=None,
file_split="# RFIG_start_instructions",
filepath=None):
"""
This function will save the figure into a propper way, in order to open
it again.
Parameters
----------
d : dict
dictionary that contain the variable useful to plot the figure.
i : str or func
instructions, string that contains the python code to create the
figure. If it is a function, takes the source of the function.
c : str
commentaries where the user can describe the figure.
file_split : str
file_split it the string that will separate the instructions. What
will be bollow the first instance of will be considered as the
instructions. If `file_split` string is not encountered, keeps the
whole instructions
filepath : str
the path to the file (useful for the local header and to directly
save the file)
frame : int
Example
-------
>>> import RFigure,numpy
... X = numpy.arange(0,10,0.1)
... Y = numpy.cos(X)
... i = "plot(X,Y)" # the instrucutions
... d = dict(X=X,Y=Y) # the data to display
... c = "This is a test" # the commentaries associate with the figures
... rf = RFigure.RFigureCore(d=d,i=i,c=c)
... rf.show() # execute the instructions to be sure it works
... rf.save(filepath='./Test.rfig3') # only save the rfig3 file
... rf.save(filepath='./Test.rfig3',fig_type='pdf') # save the rfig3 file as well as the pdf associated
"""
if i is None:
self.instructions = ""
elif callable(i):
self.instructions = textwrap.dedent(
'\n'.join(inspect.getsource(i).split('\n')[1:]))
else:
self.instructions = i
self.commentaries = "" if c is None else c
if type(d)==list:
d = find_varsdict_from_list(d,frame_number)
self.dict_variables = {} if d is None else d
self.file_split = file_split
self.clean_instructions() # we clean the instructions
self.filepath = filepath
assert type(self.instructions) is str
assert type(self.commentaries) is str
assert type(self.dict_variables) is dict
assert type(self.file_split) is str
[docs] def execute(self,print_errors=False):
"""
The method executes the instructions (no `show` at the end). Proceed in
four steps:
1. executes the general header file `RFigure/RFigureConfig/RFigureHeader.py`
2. executes the local header file `./.RFigureHeaderLocal.py`
3. updates the locals with the rfig variables `self.dict_variables`
4. executes the rfig instructions `self.instructions`
Parameters
----------
print_errors : bool
if True, will print the errors rather than raise them (to avoid
some troubles with PyQt5)
"""
if self.filepath!=None:
dirpath,_ = os.path.split(self.filepath)
else:
dirpath = '.'
matplotlib.pyplot.close('all')
if os.path.exists(path_to_header):
with open (path_to_header,'r') as fid:
instructions_header_general = fid.read()
else:
print("Could not find the path to header")
instructions_header_general = ""
if not dirpath is None:
path_to_header_local = os.path.join(dirpath,'./.RFigureHeaderLocal.py')
if os.path.exists(path_to_header_local):
with open (path_to_header_local,'r') as fid:
instructions_header_local = fid.read()
else:
instructions_header_local = ""
dict_variables = dict()
def exec_instructions(instructions,filename,globals_):
locals_ = {}
try:
code = compile(instructions,filename,'exec')
exec(code,globals_,**locals_)
globals_.update(locals_)
except Exception as e :
if print_errors:
traceback.print_exc()
return False
else:
raise e
return locals_
# locals_ = {}
globals_ = {}
exec_instructions(instructions_header_general,path_to_header,globals_)
exec_instructions(instructions_header_local,path_to_header_local,globals_)
globals_.update(self.dict_variables.copy())
exec_instructions(self.instructions,'RFigureInstructions',globals_)
return globals_
[docs] def show(self,print_errors=False):
""" Method that execute the code instructions and adds the
matplotlib.pyplot.show() statement at the end.
Parameters
----------
print_errors : bool
if True will print the errors rather than raise them (to avoid
some troubles with PyQt5)
"""
self.execute(print_errors=print_errors)
matplotlib.pyplot.show()
[docs] @decoDocFormatting(fig_type_list)
def save(self,filepath=None,fig_type=None,check_ext=True):
"""
Will save the figure in a rfig file.
Parameters
----------
filepath : str
The filepath where to save the figure. Adds the extension if
necessary. If None, search the attribute self.filepath (if
self.filepath also None, raise an error). Is not None, set
`self.filepath` to the new file path.
fig_type : None, str or list(str)
if not None, will save the figure in the corresponding format (if it
is a list, will save in several formats). Should be in %s.
check_ext: bool
if True, adds if necessary the extension to filepath
Returns
-------
paths : list of str
the paths of the files created/edited (i.e. the rfig file and the
pdf/png/etc. that represents the figure)
"""
if filepath is None:
filepath = self.filepath
if filepath is None:
raise TypeError("The `filepath` needs to be specified")
objects = [self.dict_variables,self.instructions]
RFigurePickle.save(objects,filepath,self.commentaries,
version = __version__,ext=self.ext)
paths = [filepath]
if fig_type:
paths1 = self.savefig(filepath,fig_type=fig_type)
paths += paths1
self.filepath = filepath
return paths
[docs] @decoDocFormatting(fig_type_list)
def savefig(self,fig_path,fig_type='png'):
"""Method that will save the figure with the corresponding extention.
Parameters
----------
fig_path : str
The path of where to save the figure the figure.
fig_type : None, str or list(str)
if not None, will save the figure in the corresponding format (if it
is a list, will save in several formats). Should be in %s.
Returns
-------
paths : list of str
the paths of the files created/edited (i.e. the rfig file and the
pdf/png/etc. that represents the figure)
"""
if type(fig_type)==list:
paths = []
for ext in fig_type:
paths += self.savefig(fig_path=fig_path, fig_type=ext)
return paths
assert fig_type in self.fig_type_list
dirpath,_=os.path.split(fig_path)
if fig_type not in self.fig_type_list and not (fig_type is None):
raise ValueError('fig_type should be in '+str(self.fig_type_list))
matplotlib.pyplot.ion()
globals_ = self.execute()
# `RFIG_savefig_kargs` is a dict that contains the possible kargs to
# add when using `matplotlib.figure.Figure.savefig(...,**kargs)`
if 'RFIG_savefig_kargs' in globals_:
RFIG_savefig_kargs = globals_['RFIG_savefig_kargs']
else:
RFIG_savefig_kargs = dict()
# we make the list of all the figures
figures=[manager.canvas.figure for manager in \
matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
paths = []
if fig_type in {'png','eps','svg'}:
fig_path,_ = os.path.splitext(fig_path)
# if there is more than one figure, we will save under the names :
# Sometitle_00.png , Sometitle_01.png, Sometitle_02.png
if len(figures)>1:
to_zfill=np.log10(len(figures))+1
for i,fig in enumerate(figures):
nb='_'+str(i).zfill(int(to_zfill))
f = fig_path+nb+'.'+fig_type
fig.savefig(f,bbox_inches='tight',**RFIG_savefig_kargs)
paths.append(f)
elif len(figures)>0:
f = fig_path+'.'+fig_type
figures[0].savefig(fig_path+'.'+fig_type,bbox_inches='tight',
**RFIG_savefig_kargs)
paths.append(f)
elif fig_type=='pdf':
fig_path,_ = os.path.splitext(fig_path)
fig_path += '.'+fig_type
pp = PdfPages(fig_path)
for fig in figures:
# pp.savefig(fig,bbox_inches='tight',transparent=True)
pp.savefig(fig,bbox_inches='tight',**RFIG_savefig_kargs)
pp.close()
paths.append(fig_path)
matplotlib.pyplot.close('all')
matplotlib.pyplot.ioff()
print ("========== END SAVE =========")
return paths
[docs] def clean_instructions(self):
"""Ensure that the instruction are idented at the the first level.
"""
current_instructions = self.instructions[:]
list_lines = current_instructions.split('\n')
# list_lines = [line.rstrip() for line in list_lines]
min_tab = np.infty
for line in list_lines:
if len(line)>0: #if it is not an emptyline
i=0
while i<len(line) and line[i]=='\t' :
i+=1
min_tab=np.min([min_tab,i])
if min_tab==0:
break
if min_tab>0 and min_tab!=np.infty:
for i,line in enumerate(list_lines):
if len(line)>min_tab:
list_lines[i]=line[int(min_tab):]
self.instructions='\n'.join(list_lines)
self.instructions = self.instructions.split(self.file_split)[-1]
[docs] def open(self,filepath):
"""Open the rfig file from filepath
Parameters
----------
filepath : str
the path of the rfigure. Set the attribute self.filepath to this
value
"""
if not os.path.exists(filepath):
filepath += self.ext
assert os.path.exists(filepath), filepath+" does not exist."
o,c,v = RFigurePickle.load(filepath)
assert v>= "2"
self.commentaries = c
self.instructions = o[1]
self.dict_variables = o[0]
self.filepath = filepath
[docs] @classmethod
def load(cls,filepath):
"""Return a RFigureCore instance that had openned the rfigure.
Parameters
----------
filepath : str
the path of the rfigure to load
Returns
-------
rfig : RFigureCore
The RFigureCore instance created.
"""
rfig=cls()
rfig.open(filepath)
return rfig
[docs] @staticmethod
def update(fig_path,d=None,i=None,c=None,mode='append',fig_type=None):
"""
Update the dict_variables of an already existing file:
Parameters
----------
fig_path: str
The rfig2 file to update.
d: dict
The dict to update with.
i: str
The instricution to update with.
c: str
The commentary to update with.
mode: str in ['append','replace']
if mode=='append':
will update the dict and add instructions and
commentaries to the allready instructions and
commentaries
if mode=='replace':
will replace the dict, instructions and
commentaries
Returns
-------
rfig : RFigureCore instance
the RFigureCore instance with updated dict_variables
"""
rfig = RFigureCore.load(fig_path)
assert mode in ['append','replace']
if d!=None:
if mode == 'append':
rfig.dict_variables.update(d)
else:
rfig.dict_variables = d
if i!=None:
if mode == 'append':
rfig.instructions += i
else:
rfig.instructions = i
if c!=None:
if mode == 'append':
rfig.commentaries += c
else:
rfig.commentaries = c
rfig.save(fig_type=fig_type)
return rfig
[docs] def formatName(self,filepath=None,replace_current=True):
"""
Format the filename under the format: "path/Figure_YYYYMMDD_foo.rfig3"
where foo is the current filename. If the filpath is already under this
format, do not change it. It updates the attribute `self.filepath`.
Parameters
----------
filepath : str
the filepath to format, by default takes `self.filename`
replace_current : bool
if True, then replace the current `self.filepath` by the one
obtained
Returns
-------
filepath : str
the new filepath with the updated file name
Example
-------
>>> rf = RFigureCore(filpath='./foo/faa.rfig3')
>>> rf.formatName()
"./foo/Figure_20181201_faa.rfig3"
>>> rf.formatName(filepath='./foo/fii.rfig3')
"./foo/Figure_20181201_fii.rfig3"
>>> rf.formatName(filepath='./foo/Figure_20181201_fuu.rfig3')
"./foo/Figure_20181201_fuu.rfig3"
(with each time, 20181201 corresponding to the curent date)
"""
if filepath is None:
filepath = self.filepath
# format = config.get('formatName','Figure_%Y%m%d_%s')
# Furture config:
# path_to_config_local = os.path.join(dirpath,'./.RFigureConfigLocal.py')
# if os.path.exists(path_to_config_local):
# con
if self.CURDATE is None:
date = None
else:
date = datetime.datetime.strptime(self.CURDATE,'%Y%m%d')
filepath = RPathFormatting(self.FORMAT,date=date).formatFilepath(filepath=filepath)
if replace_current: self.filepath = filepath
return filepath
[docs] def formatExt(self,filepath=None,ext=None,replace_current=True):
"""
Changes/adds if necessary the extension to the filepath. Update the
attribute `self.filepath` accordingly
Parameters
----------
filepath : str
the filepath to format.
ext : str
the extension (the dot needs to be included). By default, takes
`self.ext`.
replace_current : bool
if True, then replace the current `self.filepath` by the one
obtained
Returns
-------
filepath : str
the new filepath with the updated file extension
"""
if ext==None: ext=self.ext
dirpath,filename = os.path.split(filepath)
if not filename.endswith(ext):
filename,_=os.path.splitext(filename)
filename += ext
filepath = os.path.join(dirpath,filename)
if replace_current: self.filepath = filepath
return filepath
if __name__ == '__main__':
from . import RFigureGui
RFigureGui.main(sys.argv)