<commit_before># Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.

from urlparse import urlparse
from ZSI import *
from ZSI.client import *
import weakref

from Utility import DOM
import WSDLTools

class SOAPCallInfo:
    """SOAPCallInfo captures the important binding information about a
       SOAP operation, in a structure that is easier to work with than
       raw WSDL structures."""

    def __init__(self, methodName):
        self.methodName = methodName
        self.inheaders = []
        self.outheaders = []
        self.inparams = []
        self.outparams = []
        self.retval = None

    encodingStyle = DOM.NS_SOAP_ENC
    documentation = ''
    soapAction = None
    transport = None
    namespace = None
    location = None
    use = 'encoded'
    style = 'rpc'

    def addInParameter(self, name, type, namespace=None, element_type=0):
        """Add an input parameter description to the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.inparams.append(parameter)
        return parameter

    def addOutParameter(self, name, type, namespace=None, element_type=0):
        """Add an output parameter description to the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.outparams.append(parameter)
        return parameter

    def setReturnParameter(self, name, type, namespace=None, element_type=0):
        """Set the return parameter description for the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.retval = parameter
        return parameter

    def addInHeaderInfo(self, name, type, namespace, element_type=0,
                        mustUnderstand=0):
        """Add an input SOAP header description to the call info."""
        headerinfo = HeaderInfo(name, type, namespace, element_type)
        if mustUnderstand:
            headerinfo.mustUnderstand = 1
        self.inheaders.append(headerinfo)
        return headerinfo

    def addOutHeaderInfo(self, name, type, namespace, element_type=0,
                         mustUnderstand=0):
        """Add an output SOAP header description to the call info."""
        headerinfo = HeaderInfo(name, type, namespace, element_type)
        if mustUnderstand:
            headerinfo.mustUnderstand = 1
        self.outheaders.append(headerinfo)
        return headerinfo

    def getInParameters(self):
        """Return a sequence of the in parameters of the method."""
        return self.inparams

    def getOutParameters(self):
        """Return a sequence of the out parameters of the method."""
        return self.outparams

    def getReturnParameter(self):
        """Return param info about the return value of the method."""
        return self.retval

    def getInHeaders(self):
        """Return a sequence of the in headers of the method."""
        return self.inheaders

    def getOutHeaders(self):
        """Return a sequence of the out headers of the method."""
        return self.outheaders

class ParameterInfo:
    """A ParameterInfo object captures parameter binding information."""
    def __init__(self, name, type, namespace=None, element_type=0):
        if element_type:
            self.element_type = 1
        if namespace is not None:
            self.namespace = namespace
        self.name = name
        self.type = type

    element_type = 0
    namespace = None
    default = None

class HeaderInfo(ParameterInfo):
    """A HeaderInfo object captures SOAP header binding information."""
    def __init__(self, name, type, namespace, element_type=None):
        ParameterInfo.__init__(self, name, type, namespace, element_type)

    mustUnderstand = 0
    actor = None

def callInfoFromWSDL(port, name):
    """Return a SOAPCallInfo given a WSDL port and operation name."""
    wsdl = port.getService().getWSDL()
    binding = port.getBinding()
    portType = binding.getPortType()
    operation = portType.operations[name]
    opbinding = binding.operations[name]
    messages = wsdl.messages
    callinfo = SOAPCallInfo(name)

    addrbinding = port.getAddressBinding()
    if not isinstance(addrbinding, WSDLTools.SoapAddressBinding):
        raise ValueError, 'Unsupported binding type.'
    callinfo.location = addrbinding.location

    soapbinding = binding.findBinding(WSDLTools.SoapBinding)
    if soapbinding is None:
        raise ValueError, 'Missing soap:binding element.'
    callinfo.transport = soapbinding.transport
    callinfo.style = soapbinding.style or 'document'

    soap_op_binding = opbinding.findBinding(WSDLTools.SoapOperationBinding)
    if soap_op_binding is not None:
        callinfo.soapAction = soap_op_binding.soapAction
        callinfo.style = soap_op_binding.style or callinfo.style

    parameterOrder = operation.parameterOrder

    if operation.input is not None:
        message = messages[operation.input.message]
        msgrole = opbinding.input

        mime = msgrole.findBinding(WSDLTools.MimeMultipartRelatedBinding)
        if mime is not None:
            raise ValueError, 'Mime bindings are not supported.'
        else:
            for item in msgrole.findBindings(WSDLTools.SoapHeaderBinding):
                part = messages[item.message].parts[item.part]
                header = callinfo.addInHeaderInfo(
                    part.name,
                    part.element or part.type,
                    item.namespace,
                    element_type = part.element and 1 or 0
                    )
                header.encodingStyle = item.encodingStyle

            body = msgrole.findBinding(WSDLTools.SoapBodyBinding)
            if body is None:
                raise ValueError, 'Missing soap:body binding.'
            callinfo.encodingStyle = body.encodingStyle
            callinfo.namespace = body.namespace
            callinfo.use = body.use

            if body.parts is not None:
                parts = []
                for name in body.parts:
                    parts.append(message.parts[name])
            else:
                parts = message.parts.values()

            for part in parts:
                callinfo.addInParameter(
                    part.name,
                    part.element or part.type,
                    element_type = part.element and 1 or 0
                    )

    if operation.output is not None:
        message = messages[operation.output.message]
        msgrole = opbinding.output

        mime = msgrole.findBinding(WSDLTools.MimeMultipartRelatedBinding)
        if mime is not None:
            raise ValueError, 'Mime bindings are not supported.'
        else:
            for item in msgrole.findBindings(WSDLTools.SoapHeaderBinding):
                part = messages[item.message].parts[item.part]
                header = callinfo.addOutHeaderInfo(
                    part.name,
                    part.element or part.type,
                    item.namespace,
                    element_type = part.element and 1 or 0
                    )
                header.encodingStyle = item.encodingStyle

            body = msgrole.findBinding(WSDLTools.SoapBodyBinding)
            if body is None:
                raise ValueError, 'Missing soap:body binding.'
            callinfo.encodingStyle = body.encodingStyle
            callinfo.namespace = body.namespace
            callinfo.use = body.use

            if body.parts is not None:
                parts = []
                for name in body.parts:
                    parts.append(message.parts[name])
            else:
                parts = message.parts.values()

            if parts:
                callinfo.setReturnParameter(
                    parts[0].name,
                    parts[0].element or parts[0].type,
                    element_type = parts[0].element and 1 or 0
                    )
                for part in parts[1:]:
                    callinfo.addOutParameter(
                        part.name,
                        part.element or part.type,
                        element_type = part.element and 1 or 0
                        )

    return callinfo

class ServiceProxy:
    """A ServiceProxy provides a convenient way to call a remote web
       service that is described with WSDL. The proxy exposes methods
       that reflect the methods of the remote web service."""

    def __init__(self, wsdl, service=None, port=None):
        if not hasattr(wsdl, 'targetNamespace'):
            wsdl = WSDLTools.WSDLReader().loadFromURL(wsdl)
#        for item in wsdl.types.items():
#            self._serializer.loadSchema(item)
        self._service = wsdl.services[service or 0]
        self.__doc__ = self._service.documentation
        self._port = self._service.ports[port or 0]
        self._name = self._service.name
        self._wsdl = wsdl
        binding = self._port.getBinding()
        portType = binding.getPortType()
        for item in portType.operations:
            callinfo = callInfoFromWSDL(self._port, item.name)
            method = MethodProxy(self, callinfo)
            setattr(self, item.name, method)

    def _call(self, name, *args, **kwargs):
        """Call the named remote web service method."""
        if len(args) and len(kwargs):
            raise TypeError(
                'Use positional or keyword argument only.'
                )

        callinfo = getattr(self, name).callinfo
        url = callinfo.location
        (protocol, host, uri, query, fragment, identifier) = urlparse(url)
        port = 80
        if host.find(':'):
            host, port = host.split(':')

        params = callinfo.getInParameters()
        host = str(host)
        port = str(port)
        binding = Binding(host=host, ssl=(protocol == 'https'),
                          port=port, url=uri)
        apply(getattr(binding, callinfo.methodName), '')

        #print binding.ReceiveRaw()

        return binding.Receive()

class MethodProxy:
    """ """
    def __init__(self, parent, callinfo):
        self.__name__ = callinfo.methodName
        self.__doc__ = callinfo.documentation
        self.callinfo = callinfo
        self.parent = weakref.ref(parent)

    def __call__(self, *args, **kwargs):
        return self.parent()._call(self.__name__, *args, **kwargs)
<commit_msg>fixed pretty basic bug where the soap url returned for an operation uses the default port (80) and thus does not include a : mark.<commit_after># Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.

from urlparse import urlparse
from ZSI import *
from ZSI.client import *
import weakref

from Utility import DOM
import WSDLTools

class SOAPCallInfo:
    """SOAPCallInfo captures the important binding information about a
       SOAP operation, in a structure that is easier to work with than
       raw WSDL structures."""

    def __init__(self, methodName):
        self.methodName = methodName
        self.inheaders = []
        self.outheaders = []
        self.inparams = []
        self.outparams = []
        self.retval = None

    encodingStyle = DOM.NS_SOAP_ENC
    documentation = ''
    soapAction = None
    transport = None
    namespace = None
    location = None
    use = 'encoded'
    style = 'rpc'

    def addInParameter(self, name, type, namespace=None, element_type=0):
        """Add an input parameter description to the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.inparams.append(parameter)
        return parameter

    def addOutParameter(self, name, type, namespace=None, element_type=0):
        """Add an output parameter description to the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.outparams.append(parameter)
        return parameter

    def setReturnParameter(self, name, type, namespace=None, element_type=0):
        """Set the return parameter description for the call info."""
        parameter = ParameterInfo(name, type, namespace, element_type)
        self.retval = parameter
        return parameter

    def addInHeaderInfo(self, name, type, namespace, element_type=0,
                        mustUnderstand=0):
        """Add an input SOAP header description to the call info."""
        headerinfo = HeaderInfo(name, type, namespace, element_type)
        if mustUnderstand:
            headerinfo.mustUnderstand = 1
        self.inheaders.append(headerinfo)
        return headerinfo

    def addOutHeaderInfo(self, name, type, namespace, element_type=0,
                         mustUnderstand=0):
        """Add an output SOAP header description to the call info."""
        headerinfo = HeaderInfo(name, type, namespace, element_type)
        if mustUnderstand:
            headerinfo.mustUnderstand = 1
        self.outheaders.append(headerinfo)
        return headerinfo

    def getInParameters(self):
        """Return a sequence of the in parameters of the method."""
        return self.inparams

    def getOutParameters(self):
        """Return a sequence of the out parameters of the method."""
        return self.outparams

    def getReturnParameter(self):
        """Return param info about the return value of the method."""
        return self.retval

    def getInHeaders(self):
        """Return a sequence of the in headers of the method."""
        return self.inheaders

    def getOutHeaders(self):
        """Return a sequence of the out headers of the method."""
        return self.outheaders

class ParameterInfo:
    """A ParameterInfo object captures parameter binding information."""
    def __init__(self, name, type, namespace=None, element_type=0):
        if element_type:
            self.element_type = 1
        if namespace is not None:
            self.namespace = namespace
        self.name = name
        self.type = type

    element_type = 0
    namespace = None
    default = None

class HeaderInfo(ParameterInfo):
    """A HeaderInfo object captures SOAP header binding information."""
    def __init__(self, name, type, namespace, element_type=None):
        ParameterInfo.__init__(self, name, type, namespace, element_type)

    mustUnderstand = 0
    actor = None

def callInfoFromWSDL(port, name):
    """Return a SOAPCallInfo given a WSDL port and operation name."""
    wsdl = port.getService().getWSDL()
    binding = port.getBinding()
    portType = binding.getPortType()
    operation = portType.operations[name]
    opbinding = binding.operations[name]
    messages = wsdl.messages
    callinfo = SOAPCallInfo(name)

    addrbinding = port.getAddressBinding()
    if not isinstance(addrbinding, WSDLTools.SoapAddressBinding):
        raise ValueError, 'Unsupported binding type.'
    callinfo.location = addrbinding.location

    soapbinding = binding.findBinding(WSDLTools.SoapBinding)
    if soapbinding is None:
        raise ValueError, 'Missing soap:binding element.'
    callinfo.transport = soapbinding.transport
    callinfo.style = soapbinding.style or 'document'

    soap_op_binding = opbinding.findBinding(WSDLTools.SoapOperationBinding)
    if soap_op_binding is not None:
        callinfo.soapAction = soap_op_binding.soapAction
        callinfo.style = soap_op_binding.style or callinfo.style

    parameterOrder = operation.parameterOrder

    if operation.input is not None:
        message = messages[operation.input.message]
        msgrole = opbinding.input

        mime = msgrole.findBinding(WSDLTools.MimeMultipartRelatedBinding)
        if mime is not None:
            raise ValueError, 'Mime bindings are not supported.'
        else:
            for item in msgrole.findBindings(WSDLTools.SoapHeaderBinding):
                part = messages[item.message].parts[item.part]
                header = callinfo.addInHeaderInfo(
                    part.name,
                    part.element or part.type,
                    item.namespace,
                    element_type = part.element and 1 or 0
                    )
                header.encodingStyle = item.encodingStyle

            body = msgrole.findBinding(WSDLTools.SoapBodyBinding)
            if body is None:
                raise ValueError, 'Missing soap:body binding.'
            callinfo.encodingStyle = body.encodingStyle
            callinfo.namespace = body.namespace
            callinfo.use = body.use

            if body.parts is not None:
                parts = []
                for name in body.parts:
                    parts.append(message.parts[name])
            else:
                parts = message.parts.values()

            for part in parts:
                callinfo.addInParameter(
                    part.name,
                    part.element or part.type,
                    element_type = part.element and 1 or 0
                    )

    if operation.output is not None:
        message = messages[operation.output.message]
        msgrole = opbinding.output

        mime = msgrole.findBinding(WSDLTools.MimeMultipartRelatedBinding)
        if mime is not None:
            raise ValueError, 'Mime bindings are not supported.'
        else:
            for item in msgrole.findBindings(WSDLTools.SoapHeaderBinding):
                part = messages[item.message].parts[item.part]
                header = callinfo.addOutHeaderInfo(
                    part.name,
                    part.element or part.type,
                    item.namespace,
                    element_type = part.element and 1 or 0
                    )
                header.encodingStyle = item.encodingStyle

            body = msgrole.findBinding(WSDLTools.SoapBodyBinding)
            if body is None:
                raise ValueError, 'Missing soap:body binding.'
            callinfo.encodingStyle = body.encodingStyle
            callinfo.namespace = body.namespace
            callinfo.use = body.use

            if body.parts is not None:
                parts = []
                for name in body.parts:
                    parts.append(message.parts[name])
            else:
                parts = message.parts.values()

            if parts:
                callinfo.setReturnParameter(
                    parts[0].name,
                    parts[0].element or parts[0].type,
                    element_type = parts[0].element and 1 or 0
                    )
                for part in parts[1:]:
                    callinfo.addOutParameter(
                        part.name,
                        part.element or part.type,
                        element_type = part.element and 1 or 0
                        )

    return callinfo

class ServiceProxy:
    """A ServiceProxy provides a convenient way to call a remote web
       service that is described with WSDL. The proxy exposes methods
       that reflect the methods of the remote web service."""

    def __init__(self, wsdl, service=None, port=None):
        if not hasattr(wsdl, 'targetNamespace'):
            wsdl = WSDLTools.WSDLReader().loadFromURL(wsdl)
#        for item in wsdl.types.items():
#            self._serializer.loadSchema(item)
        self._service = wsdl.services[service or 0]
        self.__doc__ = self._service.documentation
        self._port = self._service.ports[port or 0]
        self._name = self._service.name
        self._wsdl = wsdl
        binding = self._port.getBinding()
        portType = binding.getPortType()
        for item in portType.operations:
            callinfo = callInfoFromWSDL(self._port, item.name)
            method = MethodProxy(self, callinfo)
            setattr(self, item.name, method)

    def _call(self, name, *args, **kwargs):
        """Call the named remote web service method."""
        if len(args) and len(kwargs):
            raise TypeError(
                'Use positional or keyword argument only.'
                )

        callinfo = getattr(self, name).callinfo
        url = callinfo.location
        (protocol, host, uri, query, fragment, identifier) = urlparse(url)
        port = 80
        if host.find(':') >= 0:
            host, port = host.split(':')

        params = callinfo.getInParameters()
        host = str(host)
        port = str(port)
        binding = Binding(host=host, ssl=(protocol == 'https'),
                          port=port, url=uri)
        apply(getattr(binding, callinfo.methodName), '')

	#print binding.ReceiveRaw()

        return binding.Receive()

class MethodProxy:
    """ """
    def __init__(self, parent, callinfo):
        self.__name__ = callinfo.methodName
        self.__doc__ = callinfo.documentation
        self.callinfo = callinfo
        self.parent = weakref.ref(parent)

    def __call__(self, *args, **kwargs):
        return self.parent()._call(self.__name__, *args, **kwargs)
<|endoftext|>


<commit_before>#!/usr/bin/env python

from __future__ import print_function

from builtins import map, range, zip
import errno
from itertools import product
from os import getcwd, makedirs, path
import random

import cv2
from keras import backend, layers, models
from keras.applications import vgg16
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler

__all__ = ['class_activation_preprocess', 'class_activation_heatmap',
           'display_filters', 'display_intermediate_activations',
           'maximally_responsive_pattern', 'to_valid_img']

def class_activation_heatmap(file_path, architecture='vgg16',
    class_idx=None, odir=getcwd()):
    """This function implements the idea exposed in
       "Grad-CAM: Visual Explanation from Deep Networks via "
       "Gradient-based Localization', Selvaraju, Cogswell et al.,
       arXiv:1610.02391 [cs.CV]. The gist of it consists in weighting,
       given an input image, a spatial map of different channel activations
       by the gradient of a class with respect to those channels.
       The said channels are assumed to be part of a specific feature map;
       in the present implementation, this feature map is enforced to be the last
       layer of the convolutional base of either of an Inception, ResNet,
       VGG16, VGG19 or Xception architecture (as specified by the eponymous
       parameter).
    """

    # The following is required because we are making use
    # of functions present in Keras for decoding those
    # model's class predictions, as well as functions
    # wrapping some of the image preprocessing steps
    # peculiar to each of those models
    assert architecture in ('inception_v3', 'resnet50','vgg16',
                            'vgg19', 'xception')
    module = getattr(__import__('keras.applications'), 'applications')

    if architecture.startswith('vgg'):
        cls = getattr(getattr(module, architecture), architecture.upper())
    elif architecture == 'inception_v3':
        cls = getattr(getattr(module, architecture), 'InceptionV3')
    elif architecture == 'resnet50':
        cls = getattr(getattr(module, architecture), 'ResNet50')
    else:
        cls = getattr(getattr(module, architecture), architecture.capitalize())

    without_top_model = cls(include_top=False, weights='imagenet')
    # without top model corresponds to the convolutional base
    # of the model. It will facilitate access to the last convolution
    # layer via an index instead of having to specify its name.
    model = cls(weights='imagenet')

    model.summary()

    img = class_activation_preprocess(file_path, model.name)

    predictions = model.predict(img)

    decoded_predictions = getattr(getattr(module, model.name),
        'decode_predictions')(predictions, top=5)[0]
    print("\n\n\nThe top 5 classes predicted for this image, "
          "and their probabilities are as follows:\n", decoded_predictions)

    # If class_idx defaults to None, then the class with largest predicted
    # probability for the input image will be selected to display
    # the corresponding activation heatmap super-imposed on that image
    if class_idx is None:
        class_idx = np.argmax(predictions[0])
    else:
        assert isinstance(class_idx, int) and 0 <= class_idx < 1000

    class_output = model.output[:, class_idx]

    last_convolution_layer = without_top_model.get_layer(index=-2)
    last_convolution_layer = model.get_layer(last_convolution_layer.name)

    class_gradients = backend.gradients(class_output,
        last_convolution_layer.output)[0]
    pooled_class_gradients = backend.mean(class_gradients, axis=(0, 1, 2))

    func = backend.function([model.input], [pooled_class_gradients,
        last_convolution_layer.output[0]])

    pooled_class_gradient_values, last_convolution_layer_output = func([img])

    for channel, value in enumerate(pooled_class_gradient_values):
        last_convolution_layer_output[:, :, channel] *= value

    class_activation_heatmap = np.mean(last_convolution_layer_output, axis=-1)
    class_activation_heatmap = np.maximum(class_activation_heatmap, 0)
    class_activation_heatmap /= np.max(class_activation_heatmap)

    plt.matshow(class_activation_heatmap)
    with open(path.join(odir, 'class_{}_heatmap.png'.format(class_idx)), 'w') as fh:
        plt.savefig(fh)

    img = cv2.imread(file_path)

    class_activation_heatmap = cv2.resize(class_activation_heatmap,
        (img.shape[1], img.shape[0]))
    class_activation_heatmap = np.uint8(255 * class_activation_heatmap)
    class_activation_heatmap = cv2.applyColorMap(
        class_activation_heatmap,
        cv2.COLORMAP_JET)

    img = img + 0.4 * class_activation_heatmap

    cv2.imwrite(path.join(odir, 'class_{}_superimposed_heatmap.png'.format(
        class_idx)), img)

    plt.show()

def class_activation_preprocess(file_path, architecture='vgg16'):
    """The preprocessing steps embodied in the present function
       assume that the model whose class activation heatmap
       we want to display was trained on input images
       of the same format as those fed to the convnet whose
       architecture is specified as one of this function's parameters.
       VGG16 was trained on images of size 224 * 224, with
       some further preprocessing summed up in the function
       'keras.applications.vgg16.preprocess_input'.
    """

    assert path.isfile(file_path)

    from keras.preprocessing import image

    img = image.load_img(file_path, target_size=(224, 224))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0)

    module = getattr(__import__('keras.applications'), 'applications')
    img = getattr(getattr(module, architecture), 'preprocess_input')(img)

    return img

def maximally_responsive_pattern(model, layer_name, filter_index=0,
    img_size=64, num_iterations=50, step_size=1.0):

    assert isinstance(model, models.Model)

    layer_output = model.get_layer(layer_name).output

    # Following is the loss function whose value we are to maximize.
    # Namely, starting from a blank image, we will proceed to doing
    # gradient ascent in input space in order to maximize the response
    # of this filter.
    loss_function = backend.mean(layer_output[:, :, :, filter_index])

    gradients = backend.gradients(loss_function, model.input)[0]
    # Gradient normalization trick:
    gradients /= (backend.sqrt(backend.mean(backend.square(gradients))) + 1e-4)

    func = backend.function([model.input], [loss_function, gradients])
    input_tensor = 128.0 + 20 * np.random.random((1, img_size, img_size, 3))

    for iteration in range(num_iterations):
        _, gradient_values = func([input_tensor])
        input_tensor += step_size * gradient_values

    img = to_valid_img(input_tensor[0])

    return img

def to_valid_img(arr):

    arr -= arr.mean()
    arr /= (arr.std() + 1e-4)
    arr *= 0.1

    arr += 0.5
    arr = np.clip(arr, 0, 1)

    arr *= 255
    arr = np.clip(arr, 0, 255).astype('uint8')

    return arr

def display_filters(model, layer_name, img_size=64,
    margin=5, grid_size=8, odir=getcwd()):

    assert isinstance(model, models.Model)

    grid = np.zeros((grid_size * img_size + (grid_size - 1) * margin,
                     grid_size * img_size + (grid_size - 1) * margin,
                     3))

    for row, column in product(range(grid_size), range(grid_size)):
        picture = maximally_responsive_pattern(
            model, layer_name,
            column + grid_size * row,
            img_size=img_size)

        column_begin = column * (img_size + margin)
        column_end = column_begin + img_size
        row_begin = row * (img_size + margin)
        row_end = row_begin + img_size

        grid[column_begin:column_end, row_begin:row_end, :] = picture

    plt.figure(figsize=(20, 20))
    plt.imshow(grid)
    with open(path.join(odir, layer_name + '.png'), 'w') as fh:
        plt.savefig(fh)
    plt.show()

def display_intermediate_activations(model, file_path, num_layers=8,
    height=150, width=150, odir=getcwd()):

    assert isinstance(model, models.Model)
    assert path.isfile(file_path)

    from keras.preprocessing import image

    img = image.load_img(file_path, target_size=(height, width))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0) / 255.0

    plt.imshow(img[0])
    with open(path.join(odir, 'cat.png'), 'w') as fh:
        plt.savefig(fh)

    layer_names = list()
    layer_outputs = list()
    for layer in model.layers[:num_layers]:
        if isinstance(layer, layers.Conv2D):
            layer_names.append(layer.name)
        layer_outputs.append(layer.output)

    intermediate_activations_model = models.Model(
        input=model.input, output=layer_outputs)
    activations = intermediate_activations_model.predict(img)

    for layer_name, activation in zip(layer_names, activations):
        _, height, _, num_filters = activation.shape
        num_columns = num_filters / 16

        grid = np.zeros((num_columns * height, 16 * height))

        for column, row in product(range(num_columns), range(16)):
            picture = activation[0, :, :, row + 16 * column]

            picture = StandardScaler().fit_transform(picture)
            picture *= 64
            picture += 128
            picture = np.clip(picture, 0, 255).astype('uint8')

            grid[column * height:(column + 1) * height,
                 row * height:(row + 1) * height] = picture

        plt.figure(figsize=(grid.shape[1] / float(height),
            grid.shape[0] / float(height)))
        plt.title(layer_name)
        plt.grid(False)
        plt.imshow(grid, aspect='auto', cmap='viridis')
        with open(path.join(odir, layer_name + '.png'), 'w') as fh:
            plt.savefig(fh)

    plt.show()

def trial_1(odir):

    print("Displaying the activations of every channel in every "
          "intermediate layer activation on a randomly-selected "
          "cat picture from the catsVsDogs test set:\n\n\n")

    model = models.load_model('catsVsDogs_small_convnet_experiment_1.h5')
    # Convnet trained on 2000 images of dogs and cats;
    # no pre-training involved
    model.summary()

    file_path = path.join(path.dirname(getcwd()),
        'data', 'catsVsDogs_small', 'test', 'cats',
        'cat.{}.jpg'.format(random.randint(1500, 1999)))

    display_intermediate_activations(
        model, file_path,
        odir=path.join(odir, 'intermediate_activations')
    )

def trial_2(odir):

    print("\n\n\nDisplaying the response patterns of the first 64 filters "
          "in the first layer of each convolution block of the VGG16 "
          "deep neural network architecture pre-trained on the "
          "ImageNet dataset:\n\n\n")

    model = vgg16.VGG16(include_top=False, weights='imagenet')
    model.summary()

    for i in range(1, 6):
        layer_name = 'block{}_conv1'.format(i)
        display_filters(
            model, layer_name,
            odir=path.join(odir, 'filter_patterns')
        )

def trial_3(odir):

    print("\n\n\nDisplaying a class activation map, i.e. a heatmap of 'class "
          "activation' over an input image that for a particular class "
          "indicates how important each location in that image is to that "
          "classifying that image or an object within as representative "
          "of that class. We are being tricky in submitting an image "
          "of a raven that the neural network finds ambiguous to classifiy "
          "(the top 2 predicted classes are 'vulture' and 'magpie').\n")

    file_path = path.join(path.dirname(getcwd()), 'data', 'raven.jpg')
    class_activation_heatmap(file_path, architecture='vgg16',
        odir=path.join(odir, 'class_activation_maps'))

def main():

    try:
        odir = path.join(path.dirname(getcwd()), 'output',
            'visualizing_convnets')
        makedirs(odir)
        for name in ('class_activation_maps', 'filter_patterns',
                     'intermediate_activations'):
            subdidr = path.join(odir, name)
            makedirs(subdir)
    except OSError as err:
        if err.errno != errno.EEXIST:
            raise

    trial_1(odir)
    trial_2(odir)
    trial_3(odir)

if __name__ == '__main__':

    main()
<commit_msg>Update visualizing_convnets.py<commit_after>#!/usr/bin/env python

from __future__ import print_function

from builtins import map, range, zip
import errno
from itertools import product
from os import getcwd, makedirs, path
import random

import cv2
from keras import backend, layers, models
from keras.applications import vgg16
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler

__all__ = ['class_activation_preprocess', 'class_activation_heatmap',
           'display_filters', 'display_intermediate_activations',
           'maximally_responsive_pattern', 'to_valid_img', 'trial_1',
           'trial_2', 'trial_3']

def class_activation_heatmap(file_path, architecture='vgg16',
    class_idx=None, odir=getcwd()):
    """This function implements the idea exposed in
       "Grad-CAM: Visual Explanation from Deep Networks via "
       "Gradient-based Localization', Selvaraju, Cogswell et al.,
       arXiv:1610.02391 [cs.CV]. The gist of it consists in weighting,
       given an input image, a spatial map of different channel activations
       by the gradient of a class with respect to those channels.
       The said channels are assumed to be part of a specific feature map;
       in the present implementation, this feature map is enforced to be the last
       layer of the convolutional base of either of an Inception, ResNet,
       VGG16, VGG19 or Xception architecture (as specified by the eponymous
       parameter).
    """

    # The following is required because we are making use
    # of functions present in Keras for decoding those
    # model's class predictions, as well as functions
    # wrapping some of the image preprocessing steps
    # peculiar to each of those models
    assert architecture in ('inception_v3', 'resnet50','vgg16',
                            'vgg19', 'xception')
    module = getattr(__import__('keras.applications'), 'applications')

    if architecture.startswith('vgg'):
        cls = getattr(getattr(module, architecture), architecture.upper())
    elif architecture == 'inception_v3':
        cls = getattr(getattr(module, architecture), 'InceptionV3')
    elif architecture == 'resnet50':
        cls = getattr(getattr(module, architecture), 'ResNet50')
    else:
        cls = getattr(getattr(module, architecture), architecture.capitalize())

    without_top_model = cls(include_top=False, weights='imagenet')
    # without top model corresponds to the convolutional base
    # of the model. It will facilitate access to the last convolution
    # layer via an index instead of having to specify its name.
    model = cls(weights='imagenet')

    model.summary()

    img = class_activation_preprocess(file_path, model.name)

    predictions = model.predict(img)

    decoded_predictions = getattr(getattr(module, model.name),
        'decode_predictions')(predictions, top=5)[0]
    print("\n\n\nThe top 5 classes predicted for this image, "
          "and their probabilities are as follows:\n", decoded_predictions)

    # If class_idx defaults to None, then the class with largest predicted
    # probability for the input image will be selected to display
    # the corresponding activation heatmap super-imposed on that image
    if class_idx is None:
        class_idx = np.argmax(predictions[0])
    else:
        assert isinstance(class_idx, int) and 0 <= class_idx < 1000

    class_output = model.output[:, class_idx]

    last_convolution_layer = without_top_model.get_layer(index=-2)
    last_convolution_layer = model.get_layer(last_convolution_layer.name)

    class_gradients = backend.gradients(class_output,
        last_convolution_layer.output)[0]
    pooled_class_gradients = backend.mean(class_gradients, axis=(0, 1, 2))

    func = backend.function([model.input], [pooled_class_gradients,
        last_convolution_layer.output[0]])

    pooled_class_gradient_values, last_convolution_layer_output = func([img])

    for channel, value in enumerate(pooled_class_gradient_values):
        last_convolution_layer_output[:, :, channel] *= value

    class_activation_heatmap = np.mean(last_convolution_layer_output, axis=-1)
    class_activation_heatmap = np.maximum(class_activation_heatmap, 0)
    class_activation_heatmap /= np.max(class_activation_heatmap)

    plt.matshow(class_activation_heatmap)
    with open(path.join(odir, 'class_{}_heatmap.png'.format(class_idx)), 'w') as fh:
        plt.savefig(fh)

    img = cv2.imread(file_path)

    class_activation_heatmap = cv2.resize(class_activation_heatmap,
        (img.shape[1], img.shape[0]))
    class_activation_heatmap = np.uint8(255 * class_activation_heatmap)
    class_activation_heatmap = cv2.applyColorMap(
        class_activation_heatmap,
        cv2.COLORMAP_JET)

    img = img + 0.4 * class_activation_heatmap

    cv2.imwrite(path.join(odir, 'class_{}_superimposed_heatmap.png'.format(
        class_idx)), img)

    plt.show()

def class_activation_preprocess(file_path, architecture='vgg16'):
    """The preprocessing steps embodied in the present function
       assume that the model whose class activation heatmap
       we want to display was trained on input images
       of the same format as those fed to the convnet whose
       architecture is specified as one of this function's parameters.
       VGG16 was trained on images of size 224 * 224, with
       some further preprocessing summed up in the function
       'keras.applications.vgg16.preprocess_input'.
    """

    assert path.isfile(file_path)

    from keras.preprocessing import image

    img = image.load_img(file_path, target_size=(224, 224))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0)

    module = getattr(__import__('keras.applications'), 'applications')
    img = getattr(getattr(module, architecture), 'preprocess_input')(img)

    return img

def maximally_responsive_pattern(model, layer_name, filter_index=0,
    img_size=64, num_iterations=50, step_size=1.0):

    assert isinstance(model, models.Model)

    layer_output = model.get_layer(layer_name).output

    # Following is the loss function whose value we are to maximize.
    # Namely, starting from a blank image, we will proceed to doing
    # gradient ascent in input space in order to maximize the response
    # of this filter.
    loss_function = backend.mean(layer_output[:, :, :, filter_index])

    gradients = backend.gradients(loss_function, model.input)[0]
    # Gradient normalization trick:
    gradients /= (backend.sqrt(backend.mean(backend.square(gradients))) + 1e-4)

    func = backend.function([model.input], [loss_function, gradients])
    input_tensor = 128.0 + 20 * np.random.random((1, img_size, img_size, 3))

    for iteration in range(num_iterations):
        _, gradient_values = func([input_tensor])
        input_tensor += step_size * gradient_values

    img = to_valid_img(input_tensor[0])

    return img

def to_valid_img(arr):

    arr -= arr.mean()
    arr /= (arr.std() + 1e-4)
    arr *= 0.1

    arr += 0.5
    arr = np.clip(arr, 0, 1)

    arr *= 255
    arr = np.clip(arr, 0, 255).astype('uint8')

    return arr

def display_filters(model, layer_name, img_size=64,
    margin=5, grid_size=8, odir=getcwd()):

    assert isinstance(model, models.Model)

    grid = np.zeros((grid_size * img_size + (grid_size - 1) * margin,
                     grid_size * img_size + (grid_size - 1) * margin,
                     3))

    for row, column in product(range(grid_size), range(grid_size)):
        picture = maximally_responsive_pattern(
            model, layer_name,
            column + grid_size * row,
            img_size=img_size)

        column_begin = column * (img_size + margin)
        column_end = column_begin + img_size
        row_begin = row * (img_size + margin)
        row_end = row_begin + img_size

        grid[column_begin:column_end, row_begin:row_end, :] = picture

    plt.figure(figsize=(20, 20))
    plt.imshow(grid)
    with open(path.join(odir, layer_name + '.png'), 'w') as fh:
        plt.savefig(fh)
    plt.show()

def display_intermediate_activations(model, file_path, num_layers=8,
    height=150, width=150, odir=getcwd()):

    assert isinstance(model, models.Model)
    assert path.isfile(file_path)

    from keras.preprocessing import image

    img = image.load_img(file_path, target_size=(height, width))
    img = image.img_to_array(img)
    img = np.expand_dims(img, axis=0) / 255.0

    plt.imshow(img[0])
    with open(path.join(odir, 'cat.png'), 'w') as fh:
        plt.savefig(fh)

    layer_names = list()
    layer_outputs = list()
    for layer in model.layers[:num_layers]:
        if isinstance(layer, layers.Conv2D):
            layer_names.append(layer.name)
        layer_outputs.append(layer.output)

    intermediate_activations_model = models.Model(
        input=model.input, output=layer_outputs)
    activations = intermediate_activations_model.predict(img)

    for layer_name, activation in zip(layer_names, activations):
        _, height, _, num_filters = activation.shape
        num_columns = num_filters / 16

        grid = np.zeros((num_columns * height, 16 * height))

        for column, row in product(range(num_columns), range(16)):
            picture = activation[0, :, :, row + 16 * column]

            picture = StandardScaler().fit_transform(picture)
            picture *= 64
            picture += 128
            picture = np.clip(picture, 0, 255).astype('uint8')

            grid[column * height:(column + 1) * height,
                 row * height:(row + 1) * height] = picture

        plt.figure(figsize=(grid.shape[1] / float(height),
            grid.shape[0] / float(height)))
        plt.title(layer_name)
        plt.grid(False)
        plt.imshow(grid, aspect='auto', cmap='viridis')
        with open(path.join(odir, layer_name + '.png'), 'w') as fh:
            plt.savefig(fh)

    plt.show()

def trial_1(odir):

    print("Displaying the activations of every channel in every "
          "intermediate layer activation on a randomly-selected "
          "cat picture from the catsVsDogs test set:\n\n\n")

    model = models.load_model('catsVsDogs_small_convnet_experiment_1.h5')
    # Convnet trained on 2000 images of dogs and cats;
    # no pre-training involved
    model.summary()

    file_path = path.join(path.dirname(getcwd()),
        'data', 'catsVsDogs_small', 'test', 'cats',
        'cat.{}.jpg'.format(random.randint(1500, 1999)))

    display_intermediate_activations(
        model, file_path,
        odir=path.join(odir, 'intermediate_activations')
    )

def trial_2(odir):

    print("\n\n\nDisplaying the response patterns of the first 64 filters "
          "in the first layer of each convolution block of the VGG16 "
          "deep neural network architecture pre-trained on the "
          "ImageNet dataset:\n\n\n")

    model = vgg16.VGG16(include_top=False, weights='imagenet')
    model.summary()

    for i in range(1, 6):
        layer_name = 'block{}_conv1'.format(i)
        display_filters(
            model, layer_name,
            odir=path.join(odir, 'filter_patterns')
        )

def trial_3(odir):

    print("\n\n\nDisplaying a class activation map, i.e. a heatmap of 'class "
          "activation' over an input image that for a particular class "
          "indicates how important each location in that image is to that "
          "classifying that image or an object within as representative "
          "of that class. We are being tricky in submitting an image "
          "of a raven that the neural network finds ambiguous to classifiy "
          "(the top 2 predicted classes are 'vulture' and 'magpie').\n")

    file_path = path.join(path.dirname(getcwd()), 'data', 'raven.jpg')
    class_activation_heatmap(file_path, architecture='vgg16',
        odir=path.join(odir, 'class_activation_maps'))

def main():

    try:
        odir = path.join(path.dirname(getcwd()), 'output',
            'visualizing_convnets')
        makedirs(odir)
        for name in ('class_activation_maps', 'filter_patterns',
                     'intermediate_activations'):
            subdidr = path.join(odir, name)
            makedirs(subdir)
    except OSError as err:
        if err.errno != errno.EEXIST:
            raise

    trial_1(odir)
    trial_2(odir)
    trial_3(odir)

if __name__ == '__main__':

    main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from collections import defaultdict
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import ValidationError

class AccountAnalyticDistribution(models.Model):
    _name = 'account.analytic.distribution'
    _description = 'Analytic Account Distribution'
    _rec_name = 'account_id'

    account_id = fields.Many2one('account.analytic.account', string='Analytic Account', required=True)
    percentage = fields.Float(string='Percentage', required=True, default=100.0)
    name = fields.Char(string='Name', related='account_id.name', readonly=False)
    tag_id = fields.Many2one('account.analytic.tag', string="Parent tag", required=True)

    _sql_constraints = [
        ('check_percentage', 'CHECK(percentage >= 0 AND percentage <= 100)',
         'The percentage of an analytic distribution should be between 0 and 100.')
    ]

class AccountAnalyticTag(models.Model):
    _name = 'account.analytic.tag'
    _description = 'Analytic Tags'
    name = fields.Char(string='Analytic Tag', index=True, required=True)
    color = fields.Integer('Color Index')
    active = fields.Boolean(default=True, help="Set active to false to hide the Analytic Tag without removing it.")
    active_analytic_distribution = fields.Boolean('Analytic Distribution')
    analytic_distribution_ids = fields.One2many('account.analytic.distribution', 'tag_id', string="Analytic Accounts")
    company_id = fields.Many2one('res.company', string='Company')

class AccountAnalyticGroup(models.Model):
    _name = 'account.analytic.group'
    _description = 'Analytic Categories'
    _parent_store = True
    _rec_name = 'complete_name'

    name = fields.Char(required=True)
    description = fields.Text(string='Description')
    parent_id = fields.Many2one('account.analytic.group', string="Parent", ondelete='cascade', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    parent_path = fields.Char(index=True)
    children_ids = fields.One2many('account.analytic.group', 'parent_id', string="Childrens")
    complete_name = fields.Char('Complete Name', compute='_compute_complete_name', store=True)
    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)

    @api.depends('name', 'parent_id.complete_name')
    def _compute_complete_name(self):
        for group in self:
            if group.parent_id:
                group.complete_name = '%s / %s' % (group.parent_id.complete_name, group.name)
            else:
                group.complete_name = group.name

class AccountAnalyticAccount(models.Model):
    _name = 'account.analytic.account'
    _inherit = ['mail.thread']
    _description = 'Analytic Account'
    _order = 'code, name asc'

    @api.model
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        """
            Override read_group to calculate the sum of the non-stored fields that depend on the user context
        """
        res = super(AccountAnalyticAccount, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
        accounts = self.env['account.analytic.account']
        for line in res:
            if '__domain' in line:
                accounts = self.search(line['__domain'])
            if 'balance' in fields:
                line['balance'] = sum(accounts.mapped('balance'))
            if 'debit' in fields:
                line['debit'] = sum(accounts.mapped('debit'))
            if 'credit' in fields:
                line['credit'] = sum(accounts.mapped('credit'))
        return res

    def _compute_debit_credit_balance(self):
        Curr = self.env['res.currency']
        analytic_line_obj = self.env['account.analytic.line']
        domain = [('account_id', 'in', self.ids)]
        if self._context.get('from_date', False):
            domain.append(('date', '>=', self._context['from_date']))
        if self._context.get('to_date', False):
            domain.append(('date', '<=', self._context['to_date']))
        if self._context.get('tag_ids'):
            tag_domain = expression.OR([[('tag_ids', 'in', [tag])] for tag in self._context['tag_ids']])
            domain = expression.AND([domain, tag_domain])
        if self._context.get('company_ids'):
            domain.append(('company_id', 'in', self._context['company_ids']))

        user_currency = self.env.company.currency_id
        credit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '>=', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_credit = defaultdict(float)
        for l in credit_groups:
            data_credit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
                l['amount'], user_currency, self.env.company, fields.Date.today())

        debit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '<', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_debit = defaultdict(float)
        for l in debit_groups:
            data_debit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
                l['amount'], user_currency, self.env.company, fields.Date.today())

        for account in self:
            account.debit = abs(data_debit.get(account.id, 0.0))
            account.credit = data_credit.get(account.id, 0.0)
            account.balance = account.credit - account.debit

    name = fields.Char(string='Analytic Account', index=True, required=True, tracking=True)
    code = fields.Char(string='Reference', index=True, tracking=True)
    active = fields.Boolean('Active', help="If the active field is set to False, it will allow you to hide the account without removing it.", default=True)

    group_id = fields.Many2one('account.analytic.group', string='Group', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")

    line_ids = fields.One2many('account.analytic.line', 'account_id', string="Analytic Lines")

    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)

    # use auto_join to speed up name_search call
    partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True, tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")

    balance = fields.Monetary(compute='_compute_debit_credit_balance', string='Balance')
    debit = fields.Monetary(compute='_compute_debit_credit_balance', string='Debit')
    credit = fields.Monetary(compute='_compute_debit_credit_balance', string='Credit')

    currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True)

    def name_get(self):
        res = []
        for analytic in self:
            name = analytic.name
            if analytic.code:
                name = '[' + analytic.code + '] ' + name
            if analytic.partner_id:
                name = name + ' - ' + analytic.partner_id.commercial_partner_id.name
            res.append((analytic.id, name))
        return res

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
            return super(AccountAnalyticAccount, self)._name_search(name, args, operator, limit, name_get_uid=name_get_uid)
        args = args or []
        if operator == 'ilike' and not (name or '').strip():
            domain = []
        else:
            # `partner_id` is in auto_join and the searches using ORs with auto_join fields doesn't work
            # we have to cut the search in two searches ... https://github.com/odoo/odoo/issues/25175
            partner_ids = self.env['res.partner']._search([('name', operator, name)], limit=limit, access_rights_uid=name_get_uid)
            domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id', 'in', partner_ids)]
        analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
        return self.browse(analytic_account_ids).name_get()

class AccountAnalyticLine(models.Model):
    _name = 'account.analytic.line'
    _description = 'Analytic Line'
    _order = 'date desc, id desc'

    @api.model
    def _default_user(self):
        return self.env.context.get('user_id', self.env.user.id)

    name = fields.Char('Description', required=True)
    date = fields.Date('Date', required=True, index=True, default=fields.Date.context_today)
    amount = fields.Monetary('Amount', required=True, default=0.0)
    unit_amount = fields.Float('Quantity', default=0.0)
    product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]")
    product_uom_category_id = fields.Many2one(related='product_uom_id.category_id', readonly=True)
    account_id = fields.Many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    partner_id = fields.Many2one('res.partner', string='Partner', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    user_id = fields.Many2one('res.users', string='User', default=_default_user)
    tag_ids = fields.Many2many('account.analytic.tag', 'account_analytic_line_tag_rel', 'line_id', 'tag_id', string='Tags', copy=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company)
    currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True, store=True, compute_sudo=True)
    group_id = fields.Many2one('account.analytic.group', related='account_id.group_id', store=True, readonly=True, compute_sudo=True)

    @api.constrains('company_id', 'account_id')
    def _check_company_id(self):
        for line in self:
            if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
                raise ValidationError(_('The selected account belongs to another company that the one you\'re trying to create an analytic item for'))
<commit_msg>[FIX] analytic: missing depends<commit_after># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from collections import defaultdict
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import ValidationError

class AccountAnalyticDistribution(models.Model):
    _name = 'account.analytic.distribution'
    _description = 'Analytic Account Distribution'
    _rec_name = 'account_id'

    account_id = fields.Many2one('account.analytic.account', string='Analytic Account', required=True)
    percentage = fields.Float(string='Percentage', required=True, default=100.0)
    name = fields.Char(string='Name', related='account_id.name', readonly=False)
    tag_id = fields.Many2one('account.analytic.tag', string="Parent tag", required=True)

    _sql_constraints = [
        ('check_percentage', 'CHECK(percentage >= 0 AND percentage <= 100)',
         'The percentage of an analytic distribution should be between 0 and 100.')
    ]

class AccountAnalyticTag(models.Model):
    _name = 'account.analytic.tag'
    _description = 'Analytic Tags'
    name = fields.Char(string='Analytic Tag', index=True, required=True)
    color = fields.Integer('Color Index')
    active = fields.Boolean(default=True, help="Set active to false to hide the Analytic Tag without removing it.")
    active_analytic_distribution = fields.Boolean('Analytic Distribution')
    analytic_distribution_ids = fields.One2many('account.analytic.distribution', 'tag_id', string="Analytic Accounts")
    company_id = fields.Many2one('res.company', string='Company')

class AccountAnalyticGroup(models.Model):
    _name = 'account.analytic.group'
    _description = 'Analytic Categories'
    _parent_store = True
    _rec_name = 'complete_name'

    name = fields.Char(required=True)
    description = fields.Text(string='Description')
    parent_id = fields.Many2one('account.analytic.group', string="Parent", ondelete='cascade', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    parent_path = fields.Char(index=True)
    children_ids = fields.One2many('account.analytic.group', 'parent_id', string="Childrens")
    complete_name = fields.Char('Complete Name', compute='_compute_complete_name', store=True)
    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)

    @api.depends('name', 'parent_id.complete_name')
    def _compute_complete_name(self):
        for group in self:
            if group.parent_id:
                group.complete_name = '%s / %s' % (group.parent_id.complete_name, group.name)
            else:
                group.complete_name = group.name

class AccountAnalyticAccount(models.Model):
    _name = 'account.analytic.account'
    _inherit = ['mail.thread']
    _description = 'Analytic Account'
    _order = 'code, name asc'

    @api.model
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        """
            Override read_group to calculate the sum of the non-stored fields that depend on the user context
        """
        res = super(AccountAnalyticAccount, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
        accounts = self.env['account.analytic.account']
        for line in res:
            if '__domain' in line:
                accounts = self.search(line['__domain'])
            if 'balance' in fields:
                line['balance'] = sum(accounts.mapped('balance'))
            if 'debit' in fields:
                line['debit'] = sum(accounts.mapped('debit'))
            if 'credit' in fields:
                line['credit'] = sum(accounts.mapped('credit'))
        return res

    @api.depends('line_ids.amount')
    def _compute_debit_credit_balance(self):
        Curr = self.env['res.currency']
        analytic_line_obj = self.env['account.analytic.line']
        domain = [('account_id', 'in', self.ids)]
        if self._context.get('from_date', False):
            domain.append(('date', '>=', self._context['from_date']))
        if self._context.get('to_date', False):
            domain.append(('date', '<=', self._context['to_date']))
        if self._context.get('tag_ids'):
            tag_domain = expression.OR([[('tag_ids', 'in', [tag])] for tag in self._context['tag_ids']])
            domain = expression.AND([domain, tag_domain])
        if self._context.get('company_ids'):
            domain.append(('company_id', 'in', self._context['company_ids']))

        user_currency = self.env.company.currency_id
        credit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '>=', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_credit = defaultdict(float)
        for l in credit_groups:
            data_credit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
                l['amount'], user_currency, self.env.company, fields.Date.today())

        debit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '<', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_debit = defaultdict(float)
        for l in debit_groups:
            data_debit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
                l['amount'], user_currency, self.env.company, fields.Date.today())

        for account in self:
            account.debit = abs(data_debit.get(account.id, 0.0))
            account.credit = data_credit.get(account.id, 0.0)
            account.balance = account.credit - account.debit

    name = fields.Char(string='Analytic Account', index=True, required=True, tracking=True)
    code = fields.Char(string='Reference', index=True, tracking=True)
    active = fields.Boolean('Active', help="If the active field is set to False, it will allow you to hide the account without removing it.", default=True)

    group_id = fields.Many2one('account.analytic.group', string='Group', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")

    line_ids = fields.One2many('account.analytic.line', 'account_id', string="Analytic Lines")

    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)

    # use auto_join to speed up name_search call
    partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True, tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")

    balance = fields.Monetary(compute='_compute_debit_credit_balance', string='Balance')
    debit = fields.Monetary(compute='_compute_debit_credit_balance', string='Debit')
    credit = fields.Monetary(compute='_compute_debit_credit_balance', string='Credit')

    currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True)

    def name_get(self):
        res = []
        for analytic in self:
            name = analytic.name
            if analytic.code:
                name = '[' + analytic.code + '] ' + name
            if analytic.partner_id:
                name = name + ' - ' + analytic.partner_id.commercial_partner_id.name
            res.append((analytic.id, name))
        return res

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
            return super(AccountAnalyticAccount, self)._name_search(name, args, operator, limit, name_get_uid=name_get_uid)
        args = args or []
        if operator == 'ilike' and not (name or '').strip():
            domain = []
        else:
            # `partner_id` is in auto_join and the searches using ORs with auto_join fields doesn't work
            # we have to cut the search in two searches ... https://github.com/odoo/odoo/issues/25175
            partner_ids = self.env['res.partner']._search([('name', operator, name)], limit=limit, access_rights_uid=name_get_uid)
            domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id', 'in', partner_ids)]
        analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
        return self.browse(analytic_account_ids).name_get()

class AccountAnalyticLine(models.Model):
    _name = 'account.analytic.line'
    _description = 'Analytic Line'
    _order = 'date desc, id desc'

    @api.model
    def _default_user(self):
        return self.env.context.get('user_id', self.env.user.id)

    name = fields.Char('Description', required=True)
    date = fields.Date('Date', required=True, index=True, default=fields.Date.context_today)
    amount = fields.Monetary('Amount', required=True, default=0.0)
    unit_amount = fields.Float('Quantity', default=0.0)
    product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]")
    product_uom_category_id = fields.Many2one(related='product_uom_id.category_id', readonly=True)
    account_id = fields.Many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    partner_id = fields.Many2one('res.partner', string='Partner', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    user_id = fields.Many2one('res.users', string='User', default=_default_user)
    tag_ids = fields.Many2many('account.analytic.tag', 'account_analytic_line_tag_rel', 'line_id', 'tag_id', string='Tags', copy=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company)
    currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True, store=True, compute_sudo=True)
    group_id = fields.Many2one('account.analytic.group', related='account_id.group_id', store=True, readonly=True, compute_sudo=True)

    @api.constrains('company_id', 'account_id')
    def _check_company_id(self):
        for line in self:
            if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
                raise ValidationError(_('The selected account belongs to another company that the one you\'re trying to create an analytic item for'))
<|endoftext|>


<commit_before>#Pyjsdl - Copyright (C) 2013 James Garnon <https://gatc.ca/>
#Released under the MIT License <https://opensource.org/licenses/MIT>

from math import pi as _pi
from pyjsdl.rect import Rect
from pyjsdl.color import Color

__docformat__ = 'restructuredtext'

__doc__ = 'Draw shapes'

_return_rect = True

def rect(surface, color, rect, width=0):
    """
    Draw rectangle shape, and returns bounding Rect.
    Argument include surface to draw, color, Rect.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.strokeRect(_rect.x, _rect.y, _rect.width, _rect.height)
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fillRect(_rect.x, _rect.y, _rect.width, _rect.height)
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def circle(surface, color, position, radius, width=0):
    """
    Draw circular shape, and returns bounding Rect.
    Argument include surface to draw, color, position and radius.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    surface.beginPath()
    surface.arc(position[0], position[1], radius, 0, 2*_pi, False)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(position[0]-radius, position[1]-radius, 2*radius, 2*radius))
    else:
        return surface.get_rect().clip(
            Rect(position[0]-radius, position[1]-radius, 2*radius, 2*radius))

def ellipse(surface, color, rect, width=0):
    """
    Draw ellipse shape, and returns bounding Rect.
    Argument include surface to draw, color, and rect.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    surface.saveContext()
    surface.translate(_rect.x + int(_rect.width/2),
                      _rect.y + int(_rect.height/2))
    if _rect.width >= _rect.height:
        surface.scale(_rect.width / (_rect.height*1.0), 1)
        radius = int(_rect.height/2)
    else:
        surface.scale(1, _rect.height / (_rect.width*1.0))
        radius = int(_rect.width/2)
    surface.beginPath()
    surface.arc(0, 0, radius, 0, 2*_pi, False)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    surface.restoreContext()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def arc(surface, color, rect, start_angle, stop_angle, width=1):
    """
    Draw arc shape, and returns bounding Rect.
    Argument include surface to draw, color, rect, start_angle, stop_angle.
    Optional width argument of outline.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    if _rect.width == _rect.height:
        surface.beginPath()
        surface.arc(_rect.x + int(_rect.width/2), _rect.y + int(_rect.height/2),
                    int(_rect.width/2), -start_angle, -stop_angle, True)
        if width:
            surface.setLineWidth(width)
            if surface._stroke_style != color:
                surface._stroke_style = color
                if hasattr(color, 'a'):
                    surface.setStrokeStyle(color)
                else:
                    surface.setStrokeStyle(Color(color))
            surface.stroke()
        else:
            surface.closePath()
            if surface._fill_style != color:
                surface._fill_style = color
                if hasattr(color, 'a'):
                    surface.setFillStyle(color)
                else:
                    surface.setFillStyle(Color(color))
            surface.fill()
    else:
        surface.saveContext()
        surface.translate(_rect.x + int(_rect.width/2),
                          _rect.y + int(_rect.height/2))
        if _rect.width >= _rect.height:
            surface.scale(_rect.width / (_rect.height*1.0), 1)
            radius = int(_rect.height/2)
        else:
            surface.scale(1, _rect.height / (_rect.width*1.0))
            radius = int(_rect.width/2)
        surface.beginPath()
        surface.arc(0, 0, radius, -start_angle, -stop_angle, True)
        if width:
            surface.setLineWidth(width)
            if surface._stroke_style != color:
                surface._stroke_style = color
                if hasattr(color, 'a'):
                    surface.setStrokeStyle(color)
                else:
                    surface.setStrokeStyle(Color(color))
            surface.stroke()
        else:
            surface.closePath()
            if surface._fill_style != color:
                surface._fill_style = color
                if hasattr(color, 'a'):
                    surface.setFillStyle(color)
                else:
                    surface.setFillStyle(Color(color))
            surface.fill()
        surface.restoreContext()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def polygon(surface, color, pointlist, width=0):
    """
    Draw polygon shape, and returns bounding Rect.
    Argument include surface to draw, color, and pointlist.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    surface.beginPath()
    surface.moveTo(*pointlist[0])
    for point in pointlist[1:]:
        surface.lineTo(*point)
    surface.closePath()
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in pointlist]
    ypts = [pt[1] for pt in pointlist]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def line(surface, color, point1, point2, width=1):
    """
    Draw line, and returns bounding Rect.
    Argument include surface to draw, color, point1, point2.
    Optional width argument of line.
    """
    surface.beginPath()
    surface.moveTo(*point1)
    surface.lineTo(*point2)
    surface.setLineWidth(width)
    if surface._stroke_style != color:
        surface._stroke_style = color
        if hasattr(color, 'a'):
            surface.setStrokeStyle(color)
        else:
            surface.setStrokeStyle(Color(color))
    surface.stroke()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in (point1,point2)]
    ypts = [pt[1] for pt in (point1,point2)]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def lines(surface, color, closed, pointlist, width=1):
    """
    Draw interconnected lines, and returns Rect bound.
    Argument include surface to draw, color, closed, and pointlist.
    Optional width argument of line.
    """
    surface.beginPath()
    surface.moveTo(*pointlist[0])
    for point in pointlist[1:]:
        surface.lineTo(*point)
    if closed:
        surface.closePath()
    surface.setLineWidth(width)
    if surface._stroke_style != color:
        surface._stroke_style = color
        if hasattr(color, 'a'):
            surface.setStrokeStyle(color)
        else:
            surface.setStrokeStyle(Color(color))
    surface.stroke()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in pointlist]
    ypts = [pt[1] for pt in pointlist]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def aaline(surface, color, point1, point2, blend=1):
    """
    Calls line(), return bounding Rect.
    """
    rect = line(surface, color, point1, point2, blend)
    return rect

def aalines(surface, color, closed, pointlist, blend=1):
    """
    Calls lines(), return bounding Rect.
    """
    rect = lines(surface, color, closed, pointlist, blend)
    return rect

def bounding_rect_return(setting):
    """
    Set whether draw functions return bounding Rect.
    Setting (bool) defaults to True on module initialization.
    """
    global _return_rect
    _return_rect = setting

#depreciated
def _set_return(*args):
    msg = 'set_return depreciated, use bounding_rect_return'
    print(msg)
    raise Exception(msg)
set_return = _set_return

<commit_msg>minor revision of draw aaline<commit_after>#Pyjsdl - Copyright (C) 2013 James Garnon <https://gatc.ca/>
#Released under the MIT License <https://opensource.org/licenses/MIT>

from math import pi as _pi
from pyjsdl.rect import Rect
from pyjsdl.color import Color

__docformat__ = 'restructuredtext'

__doc__ = 'Draw shapes'

_return_rect = True

def rect(surface, color, rect, width=0):
    """
    Draw rectangle shape, and returns bounding Rect.
    Arguments include surface to draw, color, Rect.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.strokeRect(_rect.x, _rect.y, _rect.width, _rect.height)
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fillRect(_rect.x, _rect.y, _rect.width, _rect.height)
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def circle(surface, color, position, radius, width=0):
    """
    Draw circular shape, and returns bounding Rect.
    Arguments include surface to draw, color, position and radius.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    surface.beginPath()
    surface.arc(position[0], position[1], radius, 0, 2*_pi, False)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(position[0]-radius, position[1]-radius, 2*radius, 2*radius))
    else:
        return surface.get_rect().clip(
            Rect(position[0]-radius, position[1]-radius, 2*radius, 2*radius))

def ellipse(surface, color, rect, width=0):
    """
    Draw ellipse shape, and returns bounding Rect.
    Arguments include surface to draw, color, and rect.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    surface.saveContext()
    surface.translate(_rect.x + int(_rect.width/2),
                      _rect.y + int(_rect.height/2))
    if _rect.width >= _rect.height:
        surface.scale(_rect.width / (_rect.height*1.0), 1)
        radius = int(_rect.height/2)
    else:
        surface.scale(1, _rect.height / (_rect.width*1.0))
        radius = int(_rect.width/2)
    surface.beginPath()
    surface.arc(0, 0, radius, 0, 2*_pi, False)
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    surface.restoreContext()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def arc(surface, color, rect, start_angle, stop_angle, width=1):
    """
    Draw arc shape, and returns bounding Rect.
    Arguments include surface to draw, color, rect, start_angle, stop_angle.
    Optional width argument of outline.
    """
    if hasattr(rect, 'width'):
        _rect = rect
    else:
        _rect = Rect(rect)
    if _rect.width == _rect.height:
        surface.beginPath()
        surface.arc(_rect.x + int(_rect.width/2), _rect.y + int(_rect.height/2),
                    int(_rect.width/2), -start_angle, -stop_angle, True)
        if width:
            surface.setLineWidth(width)
            if surface._stroke_style != color:
                surface._stroke_style = color
                if hasattr(color, 'a'):
                    surface.setStrokeStyle(color)
                else:
                    surface.setStrokeStyle(Color(color))
            surface.stroke()
        else:
            surface.closePath()
            if surface._fill_style != color:
                surface._fill_style = color
                if hasattr(color, 'a'):
                    surface.setFillStyle(color)
                else:
                    surface.setFillStyle(Color(color))
            surface.fill()
    else:
        surface.saveContext()
        surface.translate(_rect.x + int(_rect.width/2),
                          _rect.y + int(_rect.height/2))
        if _rect.width >= _rect.height:
            surface.scale(_rect.width / (_rect.height*1.0), 1)
            radius = int(_rect.height/2)
        else:
            surface.scale(1, _rect.height / (_rect.width*1.0))
            radius = int(_rect.width/2)
        surface.beginPath()
        surface.arc(0, 0, radius, -start_angle, -stop_angle, True)
        if width:
            surface.setLineWidth(width)
            if surface._stroke_style != color:
                surface._stroke_style = color
                if hasattr(color, 'a'):
                    surface.setStrokeStyle(color)
                else:
                    surface.setStrokeStyle(Color(color))
            surface.stroke()
        else:
            surface.closePath()
            if surface._fill_style != color:
                surface._fill_style = color
                if hasattr(color, 'a'):
                    surface.setFillStyle(color)
                else:
                    surface.setFillStyle(Color(color))
            surface.fill()
        surface.restoreContext()
    if not _return_rect:
        return None
    if surface._display:
        return surface._display._surface_rect.clip(_rect)
    else:
        return surface.get_rect().clip(_rect)

def polygon(surface, color, pointlist, width=0):
    """
    Draw polygon shape, and returns bounding Rect.
    Arguments include surface to draw, color, and pointlist.
    Optional width argument of outline, which defaults to 0 for filled shape.
    """
    surface.beginPath()
    surface.moveTo(*pointlist[0])
    for point in pointlist[1:]:
        surface.lineTo(*point)
    surface.closePath()
    if width:
        surface.setLineWidth(width)
        if surface._stroke_style != color:
            surface._stroke_style = color
            if hasattr(color, 'a'):
                surface.setStrokeStyle(color)
            else:
                surface.setStrokeStyle(Color(color))
        surface.stroke()
    else:
        if surface._fill_style != color:
            surface._fill_style = color
            if hasattr(color, 'a'):
                surface.setFillStyle(color)
            else:
                surface.setFillStyle(Color(color))
        surface.fill()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in pointlist]
    ypts = [pt[1] for pt in pointlist]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def line(surface, color, point1, point2, width=1):
    """
    Draw line, and returns bounding Rect.
    Arguments include surface to draw, color, point1, point2.
    Optional width argument of line.
    """
    surface.beginPath()
    surface.moveTo(*point1)
    surface.lineTo(*point2)
    surface.setLineWidth(width)
    if surface._stroke_style != color:
        surface._stroke_style = color
        if hasattr(color, 'a'):
            surface.setStrokeStyle(color)
        else:
            surface.setStrokeStyle(Color(color))
    surface.stroke()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in (point1,point2)]
    ypts = [pt[1] for pt in (point1,point2)]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def lines(surface, color, closed, pointlist, width=1):
    """
    Draw interconnected lines, and returns Rect bound.
    Arguments include surface to draw, color, closed, and pointlist.
    Optional width argument of line.
    """
    surface.beginPath()
    surface.moveTo(*pointlist[0])
    for point in pointlist[1:]:
        surface.lineTo(*point)
    if closed:
        surface.closePath()
    surface.setLineWidth(width)
    if surface._stroke_style != color:
        surface._stroke_style = color
        if hasattr(color, 'a'):
            surface.setStrokeStyle(color)
        else:
            surface.setStrokeStyle(Color(color))
    surface.stroke()
    if not _return_rect:
        return None
    xpts = [pt[0] for pt in pointlist]
    ypts = [pt[1] for pt in pointlist]
    xmin, xmax = min(xpts), max(xpts)
    ymin, ymax = min(ypts), max(ypts)
    if surface._display:
        return surface._display._surface_rect.clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))
    else:
        return surface.get_rect().clip(
            Rect(xmin, ymin, xmax-xmin+1, ymax-ymin+1))

def aaline(surface, color, point1, point2, blend=1):
    """
    Draw line, and returns bounding Rect.
    Arguments include surface to draw, color, point1, point2.
    """
    rect = line(surface, color, point1, point2)
    return rect

def aalines(surface, color, closed, pointlist, blend=1):
    """
    Draw interconnected lines, and returns Rect bound.
    Arguments include surface to draw, color, closed, and pointlist.
    """
    rect = lines(surface, color, closed, pointlist)
    return rect

def bounding_rect_return(setting):
    """
    Set whether draw functions return bounding Rect.
    Setting (bool) defaults to True on module initialization.
    """
    global _return_rect
    _return_rect = setting

#depreciated
def _set_return(*args):
    msg = 'set_return depreciated, use bounding_rect_return'
    print(msg)
    raise Exception(msg)
set_return = _set_return

<|endoftext|>