| 1 | """ |
|---|
| 2 | NeuroTools.visual_logging |
|---|
| 3 | ========================= |
|---|
| 4 | |
|---|
| 5 | Log graphs, rather than text. This is useful when dealing with large data |
|---|
| 6 | structures, such as arrays. x-y data is plotted as a PNG file, which is stored |
|---|
| 7 | inside a zip archive. |
|---|
| 8 | |
|---|
| 9 | You can specify a logging level such that only graphs with an importance above |
|---|
| 10 | that level will be created. e.g., if the logging level is set to WARNING, |
|---|
| 11 | log graphs with a level of DEBUG or INFO will not be created. |
|---|
| 12 | |
|---|
| 13 | The interface is a restricted version of that available in the standard |
|---|
| 14 | library's logging module. |
|---|
| 15 | |
|---|
| 16 | Functions |
|---|
| 17 | --------- |
|---|
| 18 | |
|---|
| 19 | basicConfig - specify the zipfile that will be used to store the graphs, and |
|---|
| 20 | the logging level (DEBUG, INFO, WARN, etc) |
|---|
| 21 | debug - plots data with level DEBUG |
|---|
| 22 | info - plots data with level INFO |
|---|
| 23 | warning - plots data with level WARNING |
|---|
| 24 | error - plots data with level ERROR |
|---|
| 25 | critical - plots data with level CRITICAL |
|---|
| 26 | exception - plots data with level ERROR |
|---|
| 27 | log - plots data with a user-specified level |
|---|
| 28 | |
|---|
| 29 | """ |
|---|
| 30 | |
|---|
| 31 | import zipfile, atexit, os |
|---|
| 32 | from NeuroTools import check_dependency |
|---|
| 33 | from datetime import datetime |
|---|
| 34 | from logging import CRITICAL, DEBUG, ERROR, FATAL, INFO, WARN, WARNING, NOTSET |
|---|
| 35 | from time import sleep |
|---|
| 36 | |
|---|
| 37 | if check_dependency('pylab'): |
|---|
| 38 | import pylab |
|---|
| 39 | |
|---|
| 40 | _filename = 'visual_log.zip' |
|---|
| 41 | _zipfile = None |
|---|
| 42 | _level = INFO |
|---|
| 43 | _last_timestamp = '' |
|---|
| 44 | |
|---|
| 45 | def _remove_if_empty(): |
|---|
| 46 | if len(_zipfile.namelist()) == 0 and os.path.exists(_filename): |
|---|
| 47 | os.remove(_filename) |
|---|
| 48 | |
|---|
| 49 | def basicConfig(filename, level=INFO): |
|---|
| 50 | global _zipfile, _filename, _level |
|---|
| 51 | _filename = filename |
|---|
| 52 | _level = level |
|---|
| 53 | #_zipfile.close() |
|---|
| 54 | if os.path.exists(filename) and zipfile.is_zipfile(filename): |
|---|
| 55 | mode = 'a' |
|---|
| 56 | else: |
|---|
| 57 | mode = 'w' |
|---|
| 58 | _zipfile = zipfile.ZipFile(filename, mode=mode, compression=zipfile.ZIP_DEFLATED) |
|---|
| 59 | atexit.register(_zipfile.close) |
|---|
| 60 | atexit.register(_remove_if_empty) |
|---|
| 61 | |
|---|
| 62 | def _reopen(): |
|---|
| 63 | global _zipfile |
|---|
| 64 | if (_zipfile.fp is None) or _zipfile.fp.closed: |
|---|
| 65 | _zipfile = zipfile.ZipFile(_filename, mode='a', compression=zipfile.ZIP_DEFLATED) |
|---|
| 66 | |
|---|
| 67 | def flush(): |
|---|
| 68 | """Until the zipfile is closed (normally on exit), the zipfile cannot |
|---|
| 69 | be accessed by other tools. Calling flush() closes the zipfile, which |
|---|
| 70 | will be reopened the next time a log function is called. |
|---|
| 71 | """ |
|---|
| 72 | _zipfile.close() |
|---|
| 73 | |
|---|
| 74 | def _get_timestamp(): |
|---|
| 75 | """At the moment, it is not possible to create visual |
|---|
| 76 | logs at a rate of more than one/second.""" |
|---|
| 77 | global _last_timestamp |
|---|
| 78 | timestamp = datetime.now().strftime('%Y%m%d-%H%M%S') |
|---|
| 79 | while timestamp == _last_timestamp: |
|---|
| 80 | sleep(0.1) |
|---|
| 81 | timestamp = datetime.now().strftime('%Y%m%d-%H%M%S') |
|---|
| 82 | _last_timestamp = timestamp |
|---|
| 83 | return timestamp |
|---|
| 84 | |
|---|
| 85 | def _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs): |
|---|
| 86 | _reopen() |
|---|
| 87 | timestamp = _get_timestamp() |
|---|
| 88 | # create figure |
|---|
| 89 | pylab.clf() |
|---|
| 90 | if xdata is not None: |
|---|
| 91 | pylab.plot(xdata, ydata, **kwargs) |
|---|
| 92 | else: |
|---|
| 93 | if hasattr(ydata, 'shape') and len(ydata.shape) > 1: |
|---|
| 94 | pylab.matshow(ydata, **kwargs) |
|---|
| 95 | pylab.colorbar() |
|---|
| 96 | else: |
|---|
| 97 | pylab.plot(ydata) |
|---|
| 98 | pylab.xlabel(xlabel) |
|---|
| 99 | pylab.ylabel(ylabel) |
|---|
| 100 | pylab.title(title) |
|---|
| 101 | # add it to the zipfile |
|---|
| 102 | fig_name = timestamp + '.png' |
|---|
| 103 | pylab.savefig(fig_name) |
|---|
| 104 | _zipfile.write(fig_name, |
|---|
| 105 | os.path.join(os.path.basename(os.path.splitext(_filename)[0]), fig_name)) |
|---|
| 106 | os.remove(timestamp+'.png') |
|---|
| 107 | |
|---|
| 108 | def debug(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 109 | if _level <= DEBUG: |
|---|
| 110 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 111 | |
|---|
| 112 | def info(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 113 | if _level <= INFO: |
|---|
| 114 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 115 | |
|---|
| 116 | def warning(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 117 | if _level <= WARNING: |
|---|
| 118 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 119 | |
|---|
| 120 | def error(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 121 | if _level <= ERROR: |
|---|
| 122 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 123 | |
|---|
| 124 | def critical(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 125 | if _level <= CRITICAL: |
|---|
| 126 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 127 | |
|---|
| 128 | def exception(ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 129 | if _level <= ERROR: |
|---|
| 130 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 131 | |
|---|
| 132 | def log(level, ydata, xdata=None, xlabel='', ylabel='', title='', **kwargs): |
|---|
| 133 | if _level <= level: |
|---|
| 134 | _plot_fig(ydata, xdata, xlabel, ylabel, title, **kwargs) |
|---|
| 135 | |
|---|
| 136 | def test(): |
|---|
| 137 | test_file = 'visual_logging_test.zip' |
|---|
| 138 | if os.path.exists(test_file): |
|---|
| 139 | os.remove(test_file) |
|---|
| 140 | basicConfig(test_file, level=DEBUG) |
|---|
| 141 | xdata = pylab.arange(0, 2*pylab.pi, 0.02*pylab.pi) |
|---|
| 142 | debug(pylab.sin(xdata), xdata, 'x', 'sin(x)', 'visual_logging test 1') |
|---|
| 143 | flush() |
|---|
| 144 | debug(0.5*pylab.sin(2*xdata-0.3), xdata, 'x', 'sin(2x-0.3)/2') |
|---|
| 145 | debug(pylab.sqrt(xdata), xdata, 'x', 'sqrt(x)') |
|---|
| 146 | flush() |
|---|
| 147 | zf = zipfile.ZipFile(test_file, 'r') |
|---|
| 148 | print zf.namelist() |
|---|
| 149 | assert len(zf.namelist()) == 3, zf.namelist() |
|---|
| 150 | zf.close() |
|---|
| 151 | |
|---|
| 152 | # ============================================================================== |
|---|
| 153 | if __name__ == '__main__': |
|---|
| 154 | test() |
|---|