import os import dill as pickle import numpy as np import imp try: from . import svg_to_axes except: import svg_to_axes from optparse import OptionParser import types import traceback import importlib.util import sys def __check_module__(module_name): """ Checks if module can be imported without actually importing it """ module_spec = importlib.util.find_spec(module_name) if module_spec is None: print("Module: {} NOT found".format(module_name)) return None else: # print('Module: {} can be imported!'.format(module_name)) return module_spec def __import_module_from_spec__(module_spec): """ Import the module via the passed in module specification Returns the newly imported module """ try: module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) return module except: return module_spec.loader.load_module(module_spec.name) def __import_module_from_name__(module_name): if "." in module_name: raise ValueError( "Use the basename for the module, and include all submodules in the function name. e.g. package_name: figurefirst, function_name: mpl_function.adjust_spines" ) try: if sys.version_info > (3, 0): module_spec = __check_module__(module_name) package = __import_module_from_spec__(module_spec) return package else: f, filename, description = imp.find_module(package_name) package = imp.load_module(package_name, f, filename, description) return package except: raise ValueError( "Could not find package: " + package_name + ", maybe you need to install it?" ) def __is_mpl_call_saveable__(function_name): not_saveable = ["add_artist"] if function_name in not_saveable: return False else: return True def __save_fifidata__( data_filename, layout_key, package, function, title, args_description, *args, **kwargs ): if title == "figurefirst.regenerate.replot": # this is a code word for not saving the data return # load if os.path.exists(data_filename): fifidata_file = open(data_filename, "rb") fifidata = pickle.load(fifidata_file) fifidata_file.close() else: fifidata = {} if layout_key not in fifidata.keys(): fifidata[layout_key] = [] # new figure action (e.g. mpl call) new_figure_action = { "package": package, "function": function, "title": title, "args_description": args_description, "args": args, "kwargs": kwargs, "traceback": traceback.extract_stack(), } # delete duplicate entries if 0: idx = [] for i, action in enumerate(fifidata[layout_key]): if action["title"] == title: idx.append(i) if len(idx) > 0: for i in sorted(idx, reverse=True): del fifidata[layout_key][i] if len(idx) > 0: fifidata[layout_key].insert(np.min(idx), new_figure_action) else: fifidata[layout_key].append(new_figure_action) # fifidata[layout_key].append(new_figure_action) # save fifidata_file = open(data_filename, "wb") pickle.dump(fifidata, fifidata_file) fifidata_file.close() def __load_custom_function__(package_name, function_name): if package_name == "figurefirst": function_name = "mpl_functions." + function_name elif package_name == "custom": if type(function_name) == types.FunctionType: return function_name else: package_name = function_name.split(".")[0] function_name = function_name[len(package_name) + 1 :] package = __import_module_from_name__(package_name) nest = function_name.split(".") function = package for attr in nest: function = getattr(function, attr) return function def replot(layout_filename, output_filename="template", data_filename=""): """ Regenerate a figure from saved data layout_filename - path and name for layout svg file output_filename - path and name for output svg file (default replaces the layout_filename with the output) data_filename - path and name for the data.pickle file that was generated by figurefirst with the raw graphical source data and commands. Default of None will search for the data.pickle file in the same directory using the default name. """ if data_filename == "": data_filename = layout_filename.split(".svg")[0] + "_data.dillpickle" if output_filename == "template": output_filename = layout_filename layout = svg_to_axes.FigureLayout(layout_filename) layout.make_mplfigures(hide=True) fifidata_file = open(data_filename, "rb") fifidata = pickle.load(fifidata_file) fifidata_file.close() for layout_key in fifidata.keys(): if layout_key == "Supplemental Data": continue for action in fifidata[layout_key]: ax = layout.axes[layout_key] info = [ "figurefirst.regenerate.replot" ] # trick function into skipping save package = action["package"] function = action["function"] if package == "none": continue args = action["args"] kwargs = action["kwargs"] print("=" * 100) print("Package: " + package + " | Function: " + str(function)) print(action["traceback"]) if package == "matplotlib" or package == "figurefirst": ax.__getattr__("_" + function)(info, *args, **kwargs) else: ax.__getattr__("_custom")(info, function, *args, **kwargs) for figure in layout.figures.keys(): try: layout.append_figure_to_layer( layout.figures[figure], figure, cleartarget=True ) except: print("Skipping figure: " + figure) layout.write_svg(output_filename) def load_data_file(filename): if filename[-4:] == ".svg": data_filename = filename.split(".svg")[0] + "_data.dillpickle" print("Automatically finding data file: " + data_filename) else: data_filename = filename if os.path.exists(data_filename): f = open(data_filename, "rb") data = pickle.load(f) f.close() else: print("No data file: " + data_filename) data = None return data def save_data_file(data, filename): if filename[-4:] == ".svg": data_filename = filename.split(".svg")[0] + "_data.dillpickle" print("Automatically finding data file: " + data_filename) else: data_filename = filename f = open(data_filename, "wb") data = pickle.dump(data, f) f.close() def clear_fifidata(data_filename, layout_key="all"): """ layout_key - either a single layout key, or 'all' to delete everything """ data = load_data_file(data_filename) if data is not None: if layout_key == "all": layout_keys = [k for k in data.keys()] else: layout_keys = [layout_key] for layout_key in layout_keys: if layout_key in data.keys(): print("Clearing: " + str(layout_key)) data[layout_key] = [] save_data_file(data, data_filename) def compress(filename, max_length=500): """ Attempt to downsize (via interpolation) large arguments. Very experimental. Searches for arguments that are: 1. A np.ndarray of length > max_length 2. A list of np.ndarrays where each element of the list is identical in length, and that length > max_length """ fifidata = load_data_file(filename) for layout_key in fifidata.keys(): for action in fifidata[layout_key]: args = action["args"] new_args = [] for i, arg in enumerate(args): new_arg = arg if type(arg) == np.ndarray: if len(arg.shape) > 1: pass elif len(arg) > max_length: xp = np.linspace(0, 1, len(arg)) x = np.linspace(0, 1, max_length) new_arg = np.interp(x, xp, arg) elif type(arg) == list and len(arg) > 0: if type(arg[0]) == np.ndarray: lengths = [len(a) for a in arg] if len(np.unique(lengths)) == 1: # all same length if lengths[0] > max_length: # too long new_arg = [] for a in arg: xp = np.linspace(0, 1, len(a)) x = np.linspace(0, 1, max_length) n = np.interp(x, xp, a) new_arg.append(n) new_args.append(new_arg) action["args"] = new_args compressed_fifidatafile = ( filename.split(".dillpickle")[0] + "_compressed.dillpickle" ) fifidata_file = open(compressed_fifidatafile, "wb") pickle.dump(fifidata, fifidata_file) fifidata_file.close() return compressed_fifidatafile def __write_lines__(file, string, string_replacements={}): if len(string_replacements) > 0: for key, val in string_replacements.items(): string = string.replace(key, val) file.writelines(string + "\n") def __write_label__(file, label, heading, with_breaks=False, string_replacements={}): s = "#" * heading + " " + label __write_lines__(file, s, string_replacements=string_replacements) if with_breaks: __write_break__(file) def __write_break__(file, length=125): s = "#" * length file.writelines(s + "\n") def __write_data__(file, data, decimals=None, string_replacements={}): """ Supports: - single value - list - 1d array - 2d array - list of 1d arrays (or lists) """ # single value if not hasattr(data, "__iter__"): __write_lines__(file, str(data), string_replacements=string_replacements) return # list of arrays or lists if type(data) is list: if hasattr(data[0], "__iter__"): for i, data_i in enumerate(data): __write_label__(file, "Number: " + str(i + 1), 6) __write_data__(file, data_i, decimals) return # 2d array if type(data) is np.ndarray: if len(data.shape) == 2: __write_label__(file, "2-dimensional array, lines = rows", 6) for i in range(data.shape[0]): __write_data__(file, data[i], decimals) return # list or 1d array if hasattr(data, "__iter__"): if not hasattr(data[0], "__iter__"): if type(data) == np.ndarray: data = data.tolist() # also converts to 32 bit, saves space # check if all ints data_types = [int == type(d) for d in data] all_ints = all(elem for elem in data_types) if not all_ints: if decimals is not None: f = "%." + str(decimals) + "f" data = [f % i for i in data] s = ", ".join(map(str, data)) __write_lines__(file, s, string_replacements=string_replacements) def __write_action__(file, action, decimals, string_replacements={}): if len(action["args_description"]) == 0: return else: layout_key = action["layout_key"] layout_key = [__clean_layout_key__(l) for l in layout_key] layout_key = ", ".join(map(str, layout_key)) if action["layout_key"] != "Supplemental Data": __write_label__( file, "Panel element name: " + layout_key, 3, string_replacements=string_replacements, ) __write_label__( file, "Plot feature name: " + action["title"], 3, string_replacements=string_replacements, ) else: __write_label__( file, action["title"], 3, string_replacements=string_replacements ) __write_break__(file, int(len("Plot feature name: " + action["title"]) * 1.25)) for i, arg_title in enumerate(action["args_description"]): __write_label__(file, arg_title, 4, string_replacements=string_replacements) __write_data__( file, action["args"][i], decimals, string_replacements=string_replacements, ) __write_break__(file) def __clean_layout_key__(layout_key): s = str(layout_key) s = s.replace("u'", "") s = s.replace(" ", "_") s = s.replace(",", "") s = s.replace("'", "") s = s.strip("()[]{}") return s def write_to_csv( data_filename, figure, panel_id_to_layout_keys=None, header="", decimals=None, string_replacements={}, ): """ figure - name, or number, for figure panel_id_to_layout_keys - dict of panel ids (e.g. 'a') that point to a list of associated layout_keys header - a string (you can include \n and # if you want multiple lines and markdown syntax) """ csv_filename = data_filename.split(".dillpickle")[0] + "_summary.md" file = open(csv_filename, "w+") data = load_data_file(data_filename) if panel_id_to_layout_keys is None: panel_id_to_layout_keys = {} for layout_key in data.keys(): panel_id = __clean_layout_key__(layout_key) panel_id_to_layout_keys[panel_id] = [layout_key] if "Supplemental Data" in data.keys(): panel_id_to_layout_keys["Supplemental Data"] = ["Supplemental Data"] # write header __write_break__(file) figurefirst_header = "##### This file was automatically generated from source data using FigureFirst: http://flyranch.github.io/figurefirst/" h = header + "\n" + figurefirst_header + "\n" __write_lines__(file, h, string_replacements=string_replacements) __write_break__(file) __write_label__( file, "Figure: " + str(figure), 1, with_breaks=True, string_replacements=string_replacements, ) panel_id_names = list(panel_id_to_layout_keys.keys()) panel_id_names.sort() if "Supplemental Data" in data.keys(): # put the supplement at the end idx = panel_id_names.index("Supplemental Data") del panel_id_names[idx] panel_id_names.append("Supplemental Data") # write data for panel_id in panel_id_names: if panel_id != "Supplemental Data": __write_label__(file, "Panel: " + panel_id, 2, with_breaks=True) else: __write_label__(file, panel_id, 2, with_breaks=True) layout_keys = panel_id_to_layout_keys[panel_id] for layout_key in layout_keys: for action in data[layout_key]: print("######################################") print(layout_key) print(action["package"]) print(action["function"]) print(action["title"]) print(action["args_description"]) action["layout_key"] = layout_key __write_action__( file, action, decimals, string_replacements=string_replacements ) file.close() def list_layout_keys(data_filename): data = load_data_file(data_filename) for key in data.keys(): print(key) if __name__ == "__main__": parser = OptionParser() parser.add_option( "--layout", type="str", dest="layout", default="", help="path to layout svg" ) parser.add_option( "--output", type="str", dest="output", default="template", help="path to output svg", ) parser.add_option( "--data", type="str", dest="data", default="", help="path to fifi data file (a pickle file)", ) (options, args) = parser.parse_args() replot(options.layout, options.output, options.data)