Coverage for cc_modules/cc_plot.py: 92%

24 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_plot.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

13 CamCOPS is free software: you can redistribute it and/or modify 

14 it under the terms of the GNU General Public License as published by 

15 the Free Software Foundation, either version 3 of the License, or 

16 (at your option) any later version. 

17 

18 CamCOPS is distributed in the hope that it will be useful, 

19 but WITHOUT ANY WARRANTY; without even the implied warranty of 

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

23 You should have received a copy of the GNU General Public License 

24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

25 

26=============================================================================== 

27 

28**Plotting functions.** 

29 

30PROPER WAY TO USE MATPLOTLIB: 

31 

32- https://jbarillari.blogspot.co.uk/2009/09/threadsafety-and-matplotlibpylab.html?m=1 

33- https://sjohannes.wordpress.com/2010/06/11/using-matplotlib-in-a-web-application/amp/ 

34- https://matplotlib.org/faq/howto_faq.html#howto-webapp 

35- https://matplotlib.org/examples/api/agg_oo.html#api-agg-oo 

36 

37In summary: matplotlib is easy to use in a way that has global state, but that 

38will break in a threading application. Using the Figure() API is safe. Thus: 

39 

40.. code-block:: python 

41 

42 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas 

43 from matplotlib.figure import Figure 

44 

45 fig = Figure() 

46 canvas = FigureCanvas(fig) 

47 ax = fig.add_subplot(111) 

48 ax.plot([1, 2, 3]) 

49 ax.set_title('hi mom') 

50 ax.grid(True) 

51 ax.set_xlabel('time') 

52 ax.set_ylabel('volts') 

53 canvas.print_figure('test') 

54 

55""" # noqa 

56 

57# ============================================================================= 

58# Basic imports 

59# ============================================================================= 

60 

61import atexit 

62import logging 

63import os 

64import shutil 

65import tempfile 

66 

67from cardinal_pythonlib.logs import BraceStyleAdapter 

68 

69log = BraceStyleAdapter(logging.getLogger(__name__)) 

70 

71 

72# ============================================================================= 

73# Constants 

74# ============================================================================= 

75 

76ENVVAR_HOME = "HOME" 

77ENVVAR_MPLCONFIGDIR = "MPLCONFIGDIR" 

78 

79# ============================================================================= 

80# Import matplotlib 

81# ============================================================================= 

82 

83# We need to use os.environ, since per-request stuff won't be initialized yet. 

84# That goes for anything that affects imports (to avoid the complexity of 

85# delayed imports). 

86if ENVVAR_MPLCONFIGDIR in os.environ: 

87 # 1+2. Use a writable static directory (speeds pyplot loads hugely). 

88 _mpl_config_dir = os.environ[ENVVAR_MPLCONFIGDIR] 

89else: 

90 # 1. Make a temporary directory (must be a directory per process, I'm sure) 

91 _mpl_config_dir = tempfile.mkdtemp() 

92 # 2. Ensure temporary directory is removed when this process exits. 

93 atexit.register(lambda: shutil.rmtree(_mpl_config_dir, ignore_errors=True)) 

94 

95# 3. Tell matplotlib about this directory prior to importing it 

96# http://matplotlib.org/faq/environment_variables_faq.html 

97os.environ[ENVVAR_MPLCONFIGDIR] = _mpl_config_dir 

98 

99# 4. Another nasty matplotlib hack 

100# matplotlib.font_manager reads os.environ.get('HOME') directly, and 

101# searches ~/.fonts for fonts. That's fine unless a user is calling with 

102# sudo -u USER, leaving $HOME as it was but removing the permissions - then 

103# matplotlib crashes out with e.g. 

104# PermissionError: [Errno 13] Permission denied: '/home/rudolf/.fonts/SABOI___.TTF' # noqa 

105# Note that an empty string won't help either, since the check is 

106# "is not None". 

107# You can't assign None to an os.environ member; see 

108# http://stackoverflow.com/questions/3575165; do this: 

109if ENVVAR_HOME in os.environ: 

110 _old_home = os.environ[ENVVAR_HOME] 

111 del os.environ[ENVVAR_HOME] 

112else: 

113 _old_home = None 

114 

115# 5. Import matplotlib 

116log.debug( 

117 "Importing matplotlib (can be slow) (MPLCONFIGDIR={})...", _mpl_config_dir 

118) 

119# noinspection PyUnresolvedReferences 

120import matplotlib # noqa: E402,F401 

121 

122# 6. Restore $HOME 

123if _old_home is not None: 

124 os.environ[ENVVAR_HOME] = _old_home 

125 

126# 7. Set the backend 

127# REPLACED BY OO METHOD # matplotlib.use("Agg") # also the default backend 

128# ... http://matplotlib.org/faq/usage_faq.html#what-is-a-backend 

129# ... http://matplotlib.org/faq/howto_faq.html 

130# matplotlib.use("cairo") # cairo backend corrupts some SVG figures 

131 

132# Load this once so we can tell the user we're importing it and it's slow 

133# REPLACED BY OO METHOD # import matplotlib.pyplot # noqa 

134 

135log.debug("... finished importing matplotlib") 

136 

137# REPLACED BY OO METHOD # # THEN DO e.g. # import matplotlib.pyplot as plt