Changeset 11457:a89187176fc8 in orange


Ignore:
Timestamp:
04/16/13 18:47:04 (12 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Added a workaround/fix for different PIL import conventions.

Location:
Orange/utils
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • Orange/utils/__init__.py

    r10998 r11457  
    697697 
    698698import selection 
     699 
     700import render 
  • Orange/utils/render.py

    r10633 r11457  
    77.. index:: 
    88   single: utils; render 
     9 
    910""" 
    1011 
    1112from __future__ import with_statement 
    1213 
     14import os 
    1315import sys 
     16import math 
     17import warnings 
     18import StringIO 
     19import gc 
     20import copy 
     21import binascii 
     22 
     23from functools import wraps 
     24from contextlib import contextmanager 
     25 
    1426import numpy 
    15 import math 
    16 import os.path 
    1727 
    1828from Orange.utils.environ import install_dir 
    1929 
    20 class GeneratorContextManager(object): 
    21    def __init__(self, gen): 
    22        self.gen = gen 
    23    def __enter__(self): 
    24        try: 
    25            return self.gen.next() 
    26        except StopIteration: 
    27            raise RuntimeError("generator didn't yield") 
    28    def __exit__(self, type, value, traceback): 
    29        if type is None: 
    30            try: 
    31                self.gen.next() 
    32            except StopIteration: 
    33                return 
    34            else: 
    35                raise RuntimeError("generator didn't stop") 
    36        else: 
    37            try: 
    38                self.gen.throw(type, value, traceback) 
    39                raise RuntimeError("generator didn't stop after throw()") 
    40            except StopIteration: 
    41                return True 
    42            except: 
    43                # only re-raise if it's *not* the exception that was 
    44                # passed to throw(), because __exit__() must not raise 
    45                # an exception unless __exit__() itself failed.  But 
    46                # throw() has to raise the exception to signal 
    47                # propagation, so this fixes the impedance mismatch  
    48                # between the throw() protocol and the __exit__() 
    49                # protocol. 
    50                # 
    51                if sys.exc_info()[1] is not value: 
    52                    raise 
    53  
    54 def contextmanager(func): 
    55     def helper(*args, **kwds): 
    56         return GeneratorContextManager(func(*args, **kwds)) 
    57     return helper 
    58  
    59 from functools import wraps 
     30 
     31def _pil_safe_import(module): 
     32 
     33    pil, module = module.split(".", 1) 
     34    if pil != "PIL": 
     35        raise ValueError("Expected a module name inside PIL namespace.") 
     36 
     37    if module in sys.modules: 
     38        # 'module' is already imported in the global namespace 
     39        pil_mod = sys.modules.get("PIL." + module) 
     40        if pil_mod is None: 
     41            # The PIL package was not yet imported. 
     42            try: 
     43                # Make sure "PIL" is in sys.modules 
     44                import PIL 
     45            except ImportError: 
     46                # PIL was installed as an egg (it's broken, 
     47                # PIL package can not be imported) 
     48                return None 
     49            # Insert 'module' into the PIL namespace 
     50            sys.modules["PIL." + module] = sys.modules[module] 
     51 
     52        return sys.modules["PIL." + module] 
     53 
     54    try: 
     55        mod = __import__("PIL." + module, fromlist=[module]) 
     56    except ImportError: 
     57        mod = None 
     58 
     59    if mod is not None: 
     60        sys.modules[module] = mod 
     61 
     62    return mod 
     63 
     64# scipy and matplotlib both import modules from PIL but depending 
     65# on the installed version might use different conventions, i.e. 
     66# 
     67# >>> import Image 
     68# >>> from PIL import Image 
     69# 
     70# This can crash the python interpreter with a: 
     71# AccessInit: hash collision: 3 for both 1 and 1 
     72# 
     73# (This is a known common problem with PIL imports and is triggered 
     74# in libImaging/Access.c:add_item) 
     75# 
     76# So we preempt the imports and use a hack where we manually insert 
     77# modules from PIL namespace into global namespace or vice versa 
     78# 
     79# PIL is imported by (as currently imported from Orange): 
     80#    - 'scipy.utils.pilutils' (PIL.Image and PIL.ImageFilter) 
     81#    - 'matplotlib.backend_bases' (PIL.Image) 
     82#    - 'matplotlib.image' (PIL.Image) 
     83# 
     84# Based on grep 'import _imaging PIL/*.py the following modules 
     85# import C extensions: 
     86#    - Image (_imaging) 
     87#    - ImageCms (_imagingcms) 
     88#    - ImageDraw (_imagingagg) 
     89#    - ImageFont (_imagingft) 
     90#    - ImageGL (_imaginggl) 
     91#    - ImageMath (_imagingmath) 
     92#    - ImageTk (_imagingtk) 
     93#    - GifImagePlugin (_imaging_gif) 
     94# 
     95# However only '_imaging' seems to cause the crash. 
     96 
     97 
     98_pil_safe_import("PIL._imaging") 
     99 
     100 
    60101def with_state(func): 
    61102    @wraps(func) 
     
    66107    return wrap 
    67108 
     109 
    68110def with_gc_disabled(func): 
    69     import gc 
    70111    def disabler(): 
    71112        gc.disable() 
     
    74115        finally: 
    75116            gc.enable() 
     117 
    76118    @wraps(func) 
    77119    def wrapper(*args, **kwargs): 
     
    84126    def __init__(self, colors, gamma=None, overflow=(255, 255, 255), underflow=(255, 255, 255), unknown=(0, 0, 0)): 
    85127        self.colors = colors 
    86         self.gamma_func = lambda x, gamma:((math.exp(gamma * math.log(2 * x - 1)) if x > 0.5 else -math.exp(gamma * math.log(-2 * x + 1)) if x != 0.5 else 0.0) + 1) / 2.0 
     128        self.gamma_func = lambda x, gamma: ((math.exp(gamma * math.log(2 * x - 1)) if x > 0.5 else -math.exp(gamma * math.log(-2 * x + 1)) if x != 0.5 else 0.0) + 1) / 2.0 
    87129        self.gamma = gamma 
    88130        self.overflow = overflow 
     
    100142            return self.overflow 
    101143        elif index == len(self.colors) - 1: 
    102             return tuple(self.colors[-1][i] for i in range(3)) # self.colors[-1].green(), self.colors[-1].blue()) 
     144            return tuple(self.colors[-1][i] for i in range(3))  # self.colors[-1].green(), self.colors[-1].blue()) 
    103145        else: 
    104             red1, green1, blue1 = [self.colors[index][i] for i in range(3)] #, self.colors[index].green(), self.colors[index].blue() 
    105             red2, green2, blue2 = [self.colors[index + 1][i] for i in range(3)] #, self.colors[index + 1].green(), self.colors[index + 1].blue() 
     146            red1, green1, blue1 = [self.colors[index][i] for i in range(3)]  # , self.colors[index].green(), self.colors[index].blue() 
     147            red2, green2, blue2 = [self.colors[index + 1][i] for i in range(3)]  # , self.colors[index + 1].green(), self.colors[index + 1].blue() 
    106148            x = val * (len(self.colors) - 1) - index 
    107149            if gamma is not None: 
     
    112154        return self.get_rgb(val, gamma) 
    113155 
     156 
    114157def as_open_file(file, mode="rb"): 
    115158    if isinstance(file, basestring): 
    116159        file = open(file, mode) 
    117     else: # assuming it is file like with proper mode, could check for write, read 
     160    else:  # assuming it is file like with proper mode, could check for write, read 
    118161        pass 
    119162    return file 
     163 
    120164 
    121165class Renderer(object): 
     
    188232 
    189233    def save_render_state(self): 
    190         import copy 
    191234        self.render_state_stack.append(copy.deepcopy(self.render_state)) 
    192235 
     
    237280 
    238281    def string_size_hint(self, text, **kwargs): 
    239         raise NotImpemented 
     282        raise NotImplemented 
    240283 
    241284    @contextmanager 
     
    258301        pass 
    259302 
     303 
    260304class EPSRenderer(Renderer): 
    261     EPS_DRAW_RECT = """/draw_rect  
     305    EPS_DRAW_RECT = """/draw_rect 
    262306{/h exch def /w exch def 
    263307 /y exch def /x exch def 
     
    299343  0 rmoveto 
    300344  show } def 
    301    
     345 
    302346/right_align_show 
    303347{ dup stringwidth pop 
     
    306350  show } def 
    307351""" 
     352 
    308353    def __init__(self, width, height): 
    309354        Renderer.__init__(self, width, height) 
    310         from StringIO import StringIO 
    311         self._eps = StringIO() 
     355        self._eps = StringIO.StringIO() 
    312356        self._eps.write("%%!PS-Adobe-3.0 EPSF-3.0\n%%%%BoundingBox: 0 0 %i %i\n" % (width, height)) 
    313357        self._eps.write(self.EPS_SHOW_FUNCTIONS) 
     
    315359        self.set_font(*self.render_state["font"]) 
    316360        self._inline_func = dict(stroke_color=lambda color: "%f %f %f setrgbcolor" % tuple(255.0 / c for c in color), 
    317                                  fill_color=lambda color:"%f %f %f setrgbcolor" % tuple(255.0 / c for c in color), 
     361                                 fill_color=lambda color: "%f %f %f setrgbcolor" % tuple(255.0 / c for c in color), 
    318362                                 stroke_width=lambda w: "%f setlinewidth" % w) 
    319363 
     
    330374        (x1, y1, x2, y2), samples = gradient 
    331375        binary = "".join([chr(int(c)) for p, s in samples for c in s]) 
    332         import binascii 
    333376        self._eps.write(self.EPS_SET_GRADIENT % (x1, y1, x2, y2, len(samples), binascii.hexlify(binary))) 
    334377 
     
    344387        Renderer.set_render_hints(self, hints) 
    345388        if hints.get("linecap", None): 
    346             map = {"butt":0, "round":1, "rect":2} 
     389            map = {"butt": 0, "round": 1, "rect": 2} 
    347390            self._eps.write("%i setlinecap\n" % (map.get(hints.get("linecap"), 0))) 
    348391 
     
    353396    @with_state 
    354397    def draw_rect(self, x, y, w, h, **kwargs): 
    355         self._eps.write("newpath\n%(x)f %(y)f moveto %(w)f 0 rlineto\n0 %(h)f rlineto %(w)f neg 0 rlineto\nclosepath\n" % dict(x=x, y= -y, w=w, h= -h)) 
     398        self._eps.write("newpath\n%(x)f %(y)f moveto %(w)f 0 rlineto\n0 %(h)f rlineto %(w)f neg 0 rlineto\nclosepath\n" % dict(x=x, y=-y, w=w, h=-h)) 
    356399        self._eps.write("gsave\n") 
    357400        if self.gradient(): 
     
    409452 
    410453    def string_size_hint(self, text, **kwargs): 
    411         import warnings 
    412454        warnings.warn("EpsRenderer class does not suport exact string width estimation", stacklevel=2) 
    413455        return len(text) * self.font()[1] 
    414456 
     457 
    415458def _int_color(color): 
    416459    """ Transform the color tuple (with floats) to tuple with ints 
    417     (needed by PIL)  
     460    (needed by PIL) 
    418461    """ 
    419462    return tuple(map(int, color)) 
     463 
    420464 
    421465class PILRenderer(Renderer): 
    422466    def __init__(self, width, height): 
    423467        Renderer.__init__(self, width, height) 
    424         import Image, ImageDraw, ImageFont 
     468        from PIL import Image, ImageDraw, ImageFont 
    425469        self._pil_image = Image.new("RGB", (int(width), int(height)), (255, 255, 255)) 
    426470        self._draw = ImageDraw.Draw(self._pil_image, "RGB") 
     
    433477    def set_font(self, family, size): 
    434478        Renderer.set_font(self, family, size) 
    435         import ImageFont 
     479        from PIL import ImageFont 
    436480        try: 
    437481            font_file = os.path.join(install_dir, "utils", family + ".ttf") 
     
    441485                self._pil_font = ImageFont.truetype(family + ".ttf", int(size)) 
    442486        except Exception: 
    443             import warnings 
    444487            warnings.warn("Could not load %s.ttf font!" % family, stacklevel=2) 
    445488            try: 
     
    460503        x1, y1 = self._transform(x, y) 
    461504        x2, y2 = self._transform(x + w, y + h) 
    462         self._draw.rectangle((x1, y1, x2 , y2), fill=_int_color(self.fill_color()), 
     505        self._draw.rectangle((x1, y1, x2, y2), fill=_int_color(self.fill_color()), 
    463506                             outline=_int_color(self.stroke_color())) 
    464507 
     
    489532</svg> 
    490533""" 
     534 
    491535    def __init__(self, width, height): 
    492536        Renderer.__init__(self, width, height) 
    493537        self.transform_count_stack = [0] 
    494         import StringIO 
    495538        self._svg = StringIO.StringIO() 
    496539        self._defs = StringIO.StringIO() 
     
    517560 
    518561    def get_stroke(self): 
    519 #        if self.render_state["gradient"]: 
    520 #            return "" 
    521 #        else: 
    522             return 'stroke="rgb(%i, %i, %i)"' % self.stroke_color() + ' stroke-width="%f"' % self.stroke_width() 
     562        return 'stroke="rgb(%i, %i, %i)"' % self.stroke_color() + ' stroke-width="%f"' % self.stroke_width() 
    523563 
    524564    def get_text_alignment(self): 
     
    531571    def draw_line(self, sx, sy, ex, ey): 
    532572        self._svg.write('<line x1="%f" y1="%f" x2="%f" y2="%f" %s %s/>\n' % ((sx, sy, ex, ey) + (self.get_stroke(), self.get_linecap()))) 
    533  
    534 #    @with_state 
    535 #    def draw_lines(self): 
    536573 
    537574    @with_state 
     
    580617        file.write(self.SVG_HEADER % (self.height, self.width, self._defs.getvalue(), self._svg.getvalue())) 
    581618 
     619 
    582620class CairoRenderer(Renderer): 
    583621    def __init__(self, width, height): 
    584622        Renderer.__init__(self, width, height) 
    585  
Note: See TracChangeset for help on using the changeset viewer.