<commit_before># -*- coding: utf-8 -*-

import numpy as np
from scipy import interpolate,optimize
from . import Langmuir
from langmuir import DimensionlessLangmuirPoissonSoln

class NEAC(Langmuir):
  """
  Considers space charge and collector NEA, assumes emitter PEA, ignores back emission.

  This class explicitly ignres the possibility that the emitter has PEA but considers the possibility the collector features NEA. The model is based on :cite:`paper-2013.01.16_nea_collector`.

  Attributes
  ----------
  :class:`NEAC` objects have the same attributes as :class:`tec.TECBase`; "motive_data" is structured specifically for this model and contains the following data. For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.

  * saturation_pt: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the saturation point.
  * virt_critical_pt: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the critical point.
  * dps: Langmuir's dimensionless Poisson's equation solution object.
  * spclmbs_max_dist: Space charge limited mode boundary surface (spclmbs) maximum distance [m]. The distance below which the TEC experiences no space charge limited mode.

  Examples and interface testing
  ------------------------------
  >>> from tec_neac import TEC_NEAC
  >>> em_dict = {"temp":1000,
  ...            "barrier":1,
  ...            "voltage":0,
  ...            "position":0,
  ...            "richardson":10,
  ...            "emissivity":0.5}
  >>> co_dict = {"temp":300,
  ...            "barrier":0.8,
  ...            "voltage":0,
  ...            "position":10,
  ...            "richardson":10,
  ...            "emissivity":0.5,
  ...            "nea":0.2,}
  >>> input_dict = {"Emitter":em_dict, "Collector":co_dict}
  >>> example_tec = TEC_NEAC(input_dict)
  >>> isinstance(example_tec["motive_data"]["saturation_pt"]["output_voltage"],float)
  True
  >>> isinstance(example_tec["motive_data"]["saturation_pt"]["output_current_density"],float)
  True
  >>> isinstance(example_tec["motive_data"]["virt_critical_pt"]["output_voltage"],float)
  True
  >>> isinstance(example_tec["motive_data"]["virt_critical_pt"]["output_current_density"],float)
  True
  >>> isinstance(example_tec["motive_data"]["spclmbs_max_dist"],float)
  True
  >>> type(example_tec["motive_data"]["dps"])
  <class 'tec.dimensionlesslangmuirpoissonsoln.DimensionlessLangmuirPoissonSoln'>
  """

  def calc_motive(self):
    """
    Calculates the motive (meta)data and populates the 'motive_data' attribute.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.

    self["motive_data"] = {}
    self["motive_data"]["dps"] = DimensionlessLangmuirPoissonSoln()

    self["motive_data"]["spclmbs_max_dist"] = self.calc_spclmbs_max_dist()
    self["motive_data"]["saturation_pt"] = self.calc_saturation_pt()
    self["motive_data"]["virt_critical_pt"] = self.calc_virt_critical_pt()

    if self.calc_output_voltage() < self["motive_data"]["saturation_pt"]["output_voltage"]:
      # Accelerating mode.
      self["motive_data"]["max_motive_ht"] = self["Emitter"].calc_barrier_ht()
    elif self.calc_output_voltage() > self["motive_data"]["virt_critical_pt"]["output_voltage"]:
      # Retarding mode.
      self["motive_data"]["max_motive_ht"] = self["Collector"].calc_barrier_ht()
    else:
      # Space charge limited mode.
      output_current_density = optimize.brentq(self.output_voltage_target_function,\
        self["motive_data"]["saturation_pt"]["output_current_density"],\
        self["motive_data"]["virt_critical_pt"]["output_current_density"])

      barrier = physical_constants["boltzmann"] * self["Emitter"]["temp"] * \
        np.log(self["Emitter"].calc_saturation_current()/output_current_density)
      self["motive_data"]["max_motive_ht"] = barrier + self["Emitter"].calc_barrier_ht()

  def calc_spclmbs_max_dist(self):
    """
    Space charge limited mode boundary surface (spclmbs) maximum interelectrode distance.

    :returns: The distance below which the no space charge limited mode is experienced [m].
    :rtype: float
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # I am using the suffix "vr" to denote "Virtual cRitical point."
    co_motive_vr = self["Collector"]["nea"]/ \
      (physical_constants["boltzmann"] * self["Emitter"]["temp"])
    co_position_vr = self["motive_data"]["dps"].get_position(co_motive_vr,branch="rhs")

    spclbs_max_dist = (co_position_vr * self["Emitter"]["temp"]**(3./4)) / \
      (self["Emitter"].calc_saturation_current()**(1./2)) * \
      ((physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3)/ \
      (2*np.pi*physical_constants["electron_mass"]*physical_constants["electron_charge"]**2))**(1./4)

    return spclbs_max_dist

  def calc_saturation_pt(self):
    """
    Determine saturation point condition.

    :rtype: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the saturation point.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # If the device is operating within the space charge limited mode boundary surface, we can immediately set the values and exit.
    if self.calc_interelectrode_spacing() <= self["motive_data"]["spclmbs_max_dist"]:
      return {"output_voltage":self.calc_contact_potential(),
              "output_current_density":self["Emitter"].calc_saturation_current()}

    output_current_density = self["Emitter"].calc_saturation_current()

    position = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    motive = self["motive_data"]["dps"].get_motive(position)

    output_voltage = (self["Emitter"]["barrier"] + self["Collector"]["nea"] - \
      self["Collector"]["barrier"] - \
      motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]) / \
      physical_constants["electron_charge"]

    return {"output_voltage":output_voltage,
            "output_current_density":output_current_density}

  def calc_virt_critical_pt(self):
    """
    Determine virtual critical point condition.

    :rtype: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the virtual critical point.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # If the device is operating within the space charge limited mode boundary surface, we can immediately set the values and exit.
    if self.calc_interelectrode_spacing() <= self["motive_data"]["spclmbs_max_dist"]:
      return {"output_voltage":self.calc_contact_potential(),
              "output_current_density":self["Emitter"].calc_saturation_current()}

    output_current_density = optimize.brentq(self.virt_critical_point_target_function,\
      self["Emitter"].calc_saturation_current(),0)

    motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)
    output_voltage = (self["Emitter"]["barrier"] - self["Collector"]["barrier"] + \
      physical_constants["boltzmann"] * self["Emitter"]["temp"] * motive) / \
      physical_constants["electron_charge"]

    return {"output_voltage":output_voltage,
            "output_current_density":output_current_density}

  def virt_critical_point_target_function(self,output_current_density):
    """
    Target function for virtual critical point rootfinder.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names. Since this method is only about the virtual critical point, I don't use any suffix.
    co_motive = self["Collector"]["nea"]/ \
      (physical_constants["boltzmann"] * self["Emitter"]["temp"])
    co_position = self["motive_data"]["dps"].get_position(co_motive,branch="rhs")

    if output_current_density == 0:
      em_motive = np.inf
    else:
      em_motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)

    em_position = self["motive_data"]["dps"].get_position(em_motive)

    # offset is the dimensionless distance term which, along with the emitter and collector dimensionless distance, sums to zero.
    offset = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    return co_position - (em_position + offset)

  def output_voltage_target_function(self,output_current_density):
    """
    Target function for the output voltage rootfinder.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    em_motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)
    em_position = self["motive_data"]["dps"].get_position(em_motive)

    offset = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    co_position = em_position + offset
    co_motive = self["motive_data"]["dps"].get_motive(co_position)

    return self.calc_output_voltage() - ((self["Emitter"]["barrier"] + \
      em_motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]) - \
      (self["Collector"]["barrier"] - self["Collector"]["nea"] + \
      co_motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]))/ \
      physical_constants["electron_charge"]
<commit_msg>Properly imported physical_constants.<commit_after># -*- coding: utf-8 -*-

import numpy as np
from scipy import interpolate,optimize
from tec import physical_constants
from . import Langmuir
from langmuir import DimensionlessLangmuirPoissonSoln

class NEAC(Langmuir):
  """
  Considers space charge and collector NEA, assumes emitter PEA, ignores back emission.

  This class explicitly ignres the possibility that the emitter has PEA but considers the possibility the collector features NEA. The model is based on :cite:`paper-2013.01.16_nea_collector`.

  Attributes
  ----------
  :class:`NEAC` objects have the same attributes as :class:`tec.TECBase`; "motive_data" is structured specifically for this model and contains the following data. For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.

  * saturation_pt: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the saturation point.
  * virt_critical_pt: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the critical point.
  * dps: Langmuir's dimensionless Poisson's equation solution object.
  * spclmbs_max_dist: Space charge limited mode boundary surface (spclmbs) maximum distance [m]. The distance below which the TEC experiences no space charge limited mode.

  Examples and interface testing
  ------------------------------
  >>> from tec_neac import TEC_NEAC
  >>> em_dict = {"temp":1000,
  ...            "barrier":1,
  ...            "voltage":0,
  ...            "position":0,
  ...            "richardson":10,
  ...            "emissivity":0.5}
  >>> co_dict = {"temp":300,
  ...            "barrier":0.8,
  ...            "voltage":0,
  ...            "position":10,
  ...            "richardson":10,
  ...            "emissivity":0.5,
  ...            "nea":0.2,}
  >>> input_dict = {"Emitter":em_dict, "Collector":co_dict}
  >>> example_tec = TEC_NEAC(input_dict)
  >>> isinstance(example_tec["motive_data"]["saturation_pt"]["output_voltage"],float)
  True
  >>> isinstance(example_tec["motive_data"]["saturation_pt"]["output_current_density"],float)
  True
  >>> isinstance(example_tec["motive_data"]["virt_critical_pt"]["output_voltage"],float)
  True
  >>> isinstance(example_tec["motive_data"]["virt_critical_pt"]["output_current_density"],float)
  True
  >>> isinstance(example_tec["motive_data"]["spclmbs_max_dist"],float)
  True
  >>> type(example_tec["motive_data"]["dps"])
  <class 'tec.dimensionlesslangmuirpoissonsoln.DimensionlessLangmuirPoissonSoln'>
  """

  def calc_motive(self):
    """
    Calculates the motive (meta)data and populates the 'motive_data' attribute.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.

    self["motive_data"] = {}
    self["motive_data"]["dps"] = DimensionlessLangmuirPoissonSoln()

    self["motive_data"]["spclmbs_max_dist"] = self.calc_spclmbs_max_dist()
    self["motive_data"]["saturation_pt"] = self.calc_saturation_pt()
    self["motive_data"]["virt_critical_pt"] = self.calc_virt_critical_pt()

    if self.calc_output_voltage() < self["motive_data"]["saturation_pt"]["output_voltage"]:
      # Accelerating mode.
      self["motive_data"]["max_motive_ht"] = self["Emitter"].calc_barrier_ht()
    elif self.calc_output_voltage() > self["motive_data"]["virt_critical_pt"]["output_voltage"]:
      # Retarding mode.
      self["motive_data"]["max_motive_ht"] = self["Collector"].calc_barrier_ht()
    else:
      # Space charge limited mode.
      output_current_density = optimize.brentq(self.output_voltage_target_function,\
        self["motive_data"]["saturation_pt"]["output_current_density"],\
        self["motive_data"]["virt_critical_pt"]["output_current_density"])

      barrier = physical_constants["boltzmann"] * self["Emitter"]["temp"] * \
        np.log(self["Emitter"].calc_saturation_current()/output_current_density)
      self["motive_data"]["max_motive_ht"] = barrier + self["Emitter"].calc_barrier_ht()

  def calc_spclmbs_max_dist(self):
    """
    Space charge limited mode boundary surface (spclmbs) maximum interelectrode distance.

    :returns: The distance below which the no space charge limited mode is experienced [m].
    :rtype: float
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # I am using the suffix "vr" to denote "Virtual cRitical point."
    co_motive_vr = self["Collector"]["nea"]/ \
      (physical_constants["boltzmann"] * self["Emitter"]["temp"])
    co_position_vr = self["motive_data"]["dps"].get_position(co_motive_vr,branch="rhs")

    spclbs_max_dist = (co_position_vr * self["Emitter"]["temp"]**(3./4)) / \
      (self["Emitter"].calc_saturation_current()**(1./2)) * \
      ((physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3)/ \
      (2*np.pi*physical_constants["electron_mass"]*physical_constants["electron_charge"]**2))**(1./4)

    return spclbs_max_dist

  def calc_saturation_pt(self):
    """
    Determine saturation point condition.

    :rtype: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the saturation point.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # If the device is operating within the space charge limited mode boundary surface, we can immediately set the values and exit.
    if self.calc_interelectrode_spacing() <= self["motive_data"]["spclmbs_max_dist"]:
      return {"output_voltage":self.calc_contact_potential(),
              "output_current_density":self["Emitter"].calc_saturation_current()}

    output_current_density = self["Emitter"].calc_saturation_current()

    position = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    motive = self["motive_data"]["dps"].get_motive(position)

    output_voltage = (self["Emitter"]["barrier"] + self["Collector"]["nea"] - \
      self["Collector"]["barrier"] - \
      motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]) / \
      physical_constants["electron_charge"]

    return {"output_voltage":output_voltage,
            "output_current_density":output_current_density}

  def calc_virt_critical_pt(self):
    """
    Determine virtual critical point condition.

    :rtype: Dictionary with keys "output_voltage" [V] and "output_current_density" [A m^-2] at the virtual critical point.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    # If the device is operating within the space charge limited mode boundary surface, we can immediately set the values and exit.
    if self.calc_interelectrode_spacing() <= self["motive_data"]["spclmbs_max_dist"]:
      return {"output_voltage":self.calc_contact_potential(),
              "output_current_density":self["Emitter"].calc_saturation_current()}

    output_current_density = optimize.brentq(self.virt_critical_point_target_function,\
      self["Emitter"].calc_saturation_current(),0)

    motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)
    output_voltage = (self["Emitter"]["barrier"] - self["Collector"]["barrier"] + \
      physical_constants["boltzmann"] * self["Emitter"]["temp"] * motive) / \
      physical_constants["electron_charge"]

    return {"output_voltage":output_voltage,
            "output_current_density":output_current_density}

  def virt_critical_point_target_function(self,output_current_density):
    """
    Target function for virtual critical point rootfinder.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names. Since this method is only about the virtual critical point, I don't use any suffix.
    co_motive = self["Collector"]["nea"]/ \
      (physical_constants["boltzmann"] * self["Emitter"]["temp"])
    co_position = self["motive_data"]["dps"].get_position(co_motive,branch="rhs")

    if output_current_density == 0:
      em_motive = np.inf
    else:
      em_motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)

    em_position = self["motive_data"]["dps"].get_position(em_motive)

    # offset is the dimensionless distance term which, along with the emitter and collector dimensionless distance, sums to zero.
    offset = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    return co_position - (em_position + offset)

  def output_voltage_target_function(self,output_current_density):
    """
    Target function for the output voltage rootfinder.
    """
    # For brevity, "dimensionless" prefix omitted from "position" and "motive" variable names.
    em_motive = np.log(self["Emitter"].calc_saturation_current()/output_current_density)
    em_position = self["motive_data"]["dps"].get_position(em_motive)

    offset = self.calc_interelectrode_spacing() * \
      ((2 * np.pi * physical_constants["electron_mass"] * physical_constants["electron_charge"]**2) / \
      (physical_constants["permittivity0"]**2 * physical_constants["boltzmann"]**3))**(1.0/4) * \
      (output_current_density**(1.0/2))/(self["Emitter"]["temp"]**(3.0/4))

    co_position = em_position + offset
    co_motive = self["motive_data"]["dps"].get_motive(co_position)

    return self.calc_output_voltage() - ((self["Emitter"]["barrier"] + \
      em_motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]) - \
      (self["Collector"]["barrier"] - self["Collector"]["nea"] + \
      co_motive * physical_constants["boltzmann"] * self["Emitter"]["temp"]))/ \
      physical_constants["electron_charge"]
<|endoftext|>


<commit_before>#!/usr/bin/env python
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import StringIO

import in_generator
import template_expander

from in_file import InFile

def _char_dict(tags, index):
    new_dict = {}
    for tag in tags:
        if index >= len(tag):
            continue
        char = tag[index].lower()
        if char not in new_dict:
            new_dict[char] = [tag]
        else:
            new_dict[char].append(tag)
    return new_dict

def _generate_if(name, index):
    conditions = []
    while index < len(name):
        conditions.append("data[%d] == '%c'" % (index, name[index].lower()))
        index += 1
    return "if (%s)\n" % " && ".join(conditions)

def _print_switch(buf, tag_list, index):
    indent = '    ' * (index + 2)
    data = _char_dict(tag_list, index)
    # FIXME: No need to switch if there's only a single item in the data dict
    buf.write(indent + 'switch (data[%d]) {\n' % index)
    for char, tags in data.iteritems():
        buf.write(indent + "case '%c':\n" % char)
        if len(tags) > 1:
            _print_switch(buf, tags, index + 1)
        else:
            tag = tags[0]
            retval = indent + "    return %sTag.localName().impl();\n" % tag
            if len(tag) == index + 1:
                buf.write(retval)
            else:
                buf.write(indent + '    ' + _generate_if(tag, index + 1))
                buf.write('    ' + retval)
                buf.write(indent + "    return 0;\n")

    buf.write(indent + '}\n')
    buf.write(indent + "return 0;\n")

class ElementLookupTrieWriter(in_generator.Writer):
    # FIXME: Inherit all these from somewhere.
    defaults = {
        'JSInterfaceName': None,
        'constructorNeedsCreatedByParser': None,
        'constructorNeedsFormElement': None,
        'contextConditional': None,
        'interfaceName': None,
        'noConstructor': None,
        'runtimeEnabled': None,
    }
    default_parameters = {
        'attrsNullNamespace': None,
        'namespace': '',
        'namespacePrefix': '',
        'namespaceURI': '',
        'fallbackInterfaceName': '',
        'fallbackJSInterfaceName': '',
    }

    def __init__(self, in_file_paths):
        super(ElementLookupTrieWriter, self).__init__(in_file_paths)
        self._tags = [entry['name'] for entry in self.in_file.name_dictionaries]
        self._namespace = self.in_file.parameters['namespace'].strip('"')
        self._outputs = {
            (self._namespace + "ElementLookupTrie.h"): self.generate_header,
            (self._namespace + "ElementLookupTrie.cpp"): self.generate_implementation,
        }

    @template_expander.use_jinja('ElementLookupTrie.h.tmpl')
    def generate_header(self):
        return {
            'namespace': self._namespace,
        }

    @template_expander.use_jinja('ElementLookupTrie.cpp.tmpl')
    def generate_implementation(self):
        size_dict = {}
        for tag in self._tags:
            length = len(tag)
            if length not in size_dict:
                size_dict[length] = [tag]
            else:
                size_dict[length].append(tag)

        buf = StringIO.StringIO()
        for length, tags in size_dict.iteritems():
            buf.write("    case %d:\n" % length)
            _print_switch(buf, tags, 0)
        return {
            'namespace': self._namespace,
            'body': buf.getvalue(),
        }

if __name__ == "__main__":
    in_generator.Maker(ElementLookupTrieWriter).main(sys.argv)
<commit_msg>Python style cleanups after https://src.chromium.org/viewvc/blink?view=rev&revision=162322<commit_after>#!/usr/bin/env python
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import StringIO
from collections import defaultdict

import in_generator
import template_expander

def _char_dict(tags, index):
    new_dict = defaultdict(list)
    for tag in tags:
        if index >= len(tag):
            continue
        new_dict[tag[index].lower()].append(tag)
    return new_dict

def _generate_if(name, index):
    conditions = []
    for i in range(index, len(name)):
        conditions.append("data[%d] == '%c'" % (i, name[i].lower()))
    return "if (%s)\n" % " && ".join(conditions)

def _print_switch(buf, tag_list, index):
    indent = '    ' * (index + 2)
    data = _char_dict(tag_list, index)
    # FIXME: No need to switch if there's only a single item in the data dict
    buf.write(indent + 'switch (data[%d]) {\n' % index)
    for char, tags in data.iteritems():
        buf.write(indent + "case '%c':\n" % char)
        if len(tags) > 1:
            _print_switch(buf, tags, index + 1)
        else:
            tag = tags[0]
            retval = indent + "    return %sTag.localName().impl();\n" % tag
            if len(tag) == index + 1:
                buf.write(retval)
            else:
                buf.write(indent + '    ' + _generate_if(tag, index + 1))
                buf.write('    ' + retval)
                buf.write(indent + "    return 0;\n")

    buf.write(indent + '}\n')
    buf.write(indent + "return 0;\n")

class ElementLookupTrieWriter(in_generator.Writer):
    # FIXME: Inherit all these from somewhere.
    defaults = {
        'JSInterfaceName': None,
        'constructorNeedsCreatedByParser': None,
        'constructorNeedsFormElement': None,
        'contextConditional': None,
        'interfaceName': None,
        'noConstructor': None,
        'runtimeEnabled': None,
    }
    default_parameters = {
        'attrsNullNamespace': None,
        'namespace': '',
        'namespacePrefix': '',
        'namespaceURI': '',
        'fallbackInterfaceName': '',
        'fallbackJSInterfaceName': '',
    }

    def __init__(self, in_file_paths):
        super(ElementLookupTrieWriter, self).__init__(in_file_paths)
        self._tags = [entry['name'] for entry in self.in_file.name_dictionaries]
        self._namespace = self.in_file.parameters['namespace'].strip('"')
        self._outputs = {
            (self._namespace + "ElementLookupTrie.h"): self.generate_header,
            (self._namespace + "ElementLookupTrie.cpp"): self.generate_implementation,
        }

    @template_expander.use_jinja('ElementLookupTrie.h.tmpl')
    def generate_header(self):
        return {
            'namespace': self._namespace,
        }

    @template_expander.use_jinja('ElementLookupTrie.cpp.tmpl')
    def generate_implementation(self):
        size_dict = defaultdict(list)
        for tag in self._tags:
            size_dict[len(tag)].append(tag)

        buf = StringIO.StringIO()
        for length, tags in size_dict.iteritems():
            buf.write("    case %d:\n" % length)
            _print_switch(buf, tags, 0)
        return {
            'namespace': self._namespace,
            'body': buf.getvalue(),
        }

if __name__ == "__main__":
    in_generator.Maker(ElementLookupTrieWriter).main(sys.argv)
<|endoftext|>


<commit_before>"""Data structures to hold collections of images, with optional caching."""

from __future__ import with_statement

import os
from glob import glob
import re
from copy import copy

import numpy as np
import six
from PIL import Image

from skimage.external.tifffile import TiffFile

__all__ = ['MultiImage', 'ImageCollection', 'concatenate_images',
           'imread_collection_wrapper']

def concatenate_images(ic):
    """Concatenate all images in the image collection into an array.

    Parameters
    ----------
    ic: an iterable of images (including ImageCollection and MultiImage)
        The images to be concatenated.

    Returns
    -------
    ar : np.ndarray
        An array having one more dimension than the images in `ic`.

    See Also
    --------
    ImageCollection.concatenate, MultiImage.concatenate

    Raises
    ------
    ValueError
        If images in `ic` don't have identical shapes.
    """
    all_images = [img[np.newaxis, ...] for img in ic]
    try:
        ar = np.concatenate(all_images)
    except ValueError:
        raise ValueError('Image dimensions must agree.')
    return ar

def alphanumeric_key(s):
    """Convert string to list of strings and ints that gives intuitive sorting.

    Parameters
    ----------
    s: string

    Returns
    -------
    k: a list of strings and ints

    Examples
    --------
    >>> alphanumeric_key('z23a')
    ['z', 23, 'a']
    >>> filenames = ['f9.10.png', 'e10.png', 'f9.9.png', 'f10.10.png',
    ...              'f10.9.png']
    >>> sorted(filenames)
    ['e10.png', 'f10.10.png', 'f10.9.png', 'f9.10.png', 'f9.9.png']
    >>> sorted(filenames, key=alphanumeric_key)
    ['e10.png', 'f9.9.png', 'f9.10.png', 'f10.9.png', 'f10.10.png']
    """
    k = [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', s)]
    return k

class MultiImage(object):
    """A class containing a single multi-frame image.

    Parameters
    ----------
    filename : str
        The complete path to the image file.
    conserve_memory : bool, optional
        Whether to conserve memory by only caching a single frame. Default is
        True.

    Notes
    -----
    If ``conserve_memory=True`` the memory footprint can be reduced, however
    the performance can be affected because frames have to be read from file
    more often.

    The last accessed frame is cached, all other frames will have to be read
    from file.

    The current implementation makes use of PIL.

    Examples
    --------
    >>> from skimage import data_dir

    >>> img = MultiImage(data_dir + '/multipage.tif') # doctest: +SKIP
    >>> len(img) # doctest: +SKIP
    2
    >>> for frame in img: # doctest: +SKIP
    ...     print(frame.shape) # doctest: +SKIP
    (15, 10)
    (15, 10)

    """
    def __init__(self, filename, conserve_memory=True, dtype=None):
        """Load a multi-img."""
        self._filename = filename
        self._conserve_memory = conserve_memory
        self._dtype = dtype
        self._cached = None

        if filename.lower().endswith(('.tiff', '.tif')):
            self.tif_img = img = TiffFile(self._filename)

        else:
            img = Image.open(self._filename)
            self.tif_img = None

        if self._conserve_memory:
            self._numframes = self._find_numframes(img)

        else:
            self._frames = self._getallframes(img)
            self._numframes = len(self._frames)

        img.close()

    @property
    def filename(self):
        return self._filename

    @property
    def conserve_memory(self):
        return self._conserve_memory

    def _find_numframes(self, img):
        """Find the number of frames in the multi-img."""
        if self.tif_img:
            return len(img.pages)

        else:
            i = 0
            while True:
                i += 1
                try:
                    img.seek(i)
                except EOFError:
                    break
            return i

    def _getframe(self, framenum):
        """Open the image and extract the frame."""
        if self.tif_img:
            return self.tif_img[framenum].asarray()

        else:
            img = Image.open(self.filename)
            for i in range(framenum + 1):
                img.seek(i)
            ret = np.asarray(img, dtype=self._dtype)
            img.close()
            return ret

    def _getallframes(self, img):
        """Extract all frames from the multi-img."""
        if self.tif_img:
            return [p.asarray() for p in self.tif_img.pages]

        else:
            frames = []
            try:
                i = 0
                while True:
                    frames.append(np.asarray(img, dtype=self._dtype))
                    i += 1
                    img.seek(i)
            except EOFError:
                return frames

    def __getitem__(self, n):
        """Return the n-th frame as an array.

        Parameters
        ----------
        n : int
            Number of the required frame.

        Returns
        -------
        frame : ndarray
           The n-th frame.
        """
        numframes = self._numframes
        if -numframes <= n < numframes:
            n = n % numframes
        else:
            raise IndexError("There are only %s frames in the image"
                             % numframes)

        if self.conserve_memory:
            if not self._cached == n:
                frame = self._getframe(n)
                self._cached = n
                self._cachedframe = frame
            return self._cachedframe
        else:
            return self._frames[n]

    def __iter__(self):
        """Iterate over the frames."""
        for i in range(len(self)):
            yield self[i]

    def __len__(self):
        """Number of images in collection."""
        return self._numframes

    def __str__(self):
        return str(self.filename) + ' [%s frames]' % self._numframes

    def concatenate(self):
        """Concatenate all images in the multi-image into an array.

        Returns
        -------
        ar : np.ndarray
            An array having one more dimension than the images in `self`.

        See Also
        --------
        concatenate_images

        Raises
        ------
        ValueError
            If images in the `MultiImage` don't have identical shapes.
        """
        return concatenate_images(self)

class ImageCollection(object):
    """Load and manage a collection of image files.

    Note that files are always stored in alphabetical order. Also note that
    slicing returns a new ImageCollection, *not* a view into the data.

    Parameters
    ----------
    load_pattern : str or list
        Pattern glob or filenames to load. The path can be absolute or
        relative.  Multiple patterns should be separated by os.pathsep,
        e.g. '/tmp/work/*.png:/tmp/other/*.jpg'.  Also see
        implementation notes below.
    conserve_memory : bool, optional
        If True, never keep more than one in memory at a specific
        time.  Otherwise, images will be cached once they are loaded.

    Other parameters
    ----------------
    load_func : callable
        ``imread`` by default.  See notes below.

    Attributes
    ----------
    files : list of str
        If a glob string is given for `load_pattern`, this attribute
        stores the expanded file list.  Otherwise, this is simply
        equal to `load_pattern`.

    Notes
    -----
    ImageCollection can be modified to load images from an arbitrary
    source by specifying a combination of `load_pattern` and
    `load_func`.  For an ImageCollection ``ic``, ``ic[5]`` uses
    ``load_func(file_pattern[5])`` to load the image.

    Imagine, for example, an ImageCollection that loads every tenth
    frame from a video file::

      class AVILoader:
          video_file = 'myvideo.avi'

          def __call__(self, frame):
              return video_read(self.video_file, frame)

      avi_load = AVILoader()

      frames = range(0, 1000, 10) # 0, 10, 20, ...
      ic = ImageCollection(frames, load_func=avi_load)

      x = ic[5] # calls avi_load(frames[5]) or equivalently avi_load(50)

    Another use of ``load_func`` would be to convert all images to ``uint8``::

      def imread_convert(f):
          return imread(f).astype(np.uint8)

      ic = ImageCollection('/tmp/*.png', load_func=imread_convert)

    Examples
    --------
    >>> import skimage.io as io
    >>> from skimage import data_dir

    >>> coll = io.ImageCollection(data_dir + '/lena*.png')
    >>> len(coll)
    2
    >>> coll[0].shape
    (512, 512, 3)

    >>> ic = io.ImageCollection('/tmp/work/*.png:/tmp/other/*.jpg')

    """
    def __init__(self, load_pattern, conserve_memory=True, load_func=None):
        """Load and manage a collection of images."""
        if isinstance(load_pattern, six.string_types):
            load_pattern = load_pattern.split(os.pathsep)
            self._files = []
            for pattern in load_pattern:
                self._files.extend(glob(pattern))
            self._files = sorted(self._files, key=alphanumeric_key)
        else:
            self._files = load_pattern

        if conserve_memory:
            memory_slots = 1
        else:
            memory_slots = len(self._files)

        self._conserve_memory = conserve_memory
        self._cached = None

        if load_func is None:
            from ._io import imread
            self.load_func = imread
        else:
            self.load_func = load_func

        self.data = np.empty(memory_slots, dtype=object)

    @property
    def files(self):
        return self._files

    @property
    def conserve_memory(self):
        return self._conserve_memory

    def __getitem__(self, n):
        """Return selected image(s) in the collection.

        Loading is done on demand.

        Parameters
        ----------
        n : int or slice
            The image number to be returned, or a slice selecting the images
            and ordering to be returned in a new ImageCollection.

        Returns
        -------
        img : ndarray or ImageCollection.
            The `n`-th image in the collection, or a new ImageCollection with
            the selected images.

        """
        if hasattr(n, '__index__'):
            n = n.__index__()

        if type(n) not in [int, slice]:
            raise TypeError('slicing must be with an int or slice object')

        if type(n) is int:
            n = self._check_imgnum(n)
            idx = n % len(self.data)

            if (self.conserve_memory and n != self._cached) or \
                (self.data[idx] is None):
                self.data[idx] = self.load_func(self.files[n])
                self._cached = n

            return self.data[idx]
        else:
            # A slice object was provided, so create a new ImageCollection
            # object. Any loaded image data in the original ImageCollection
            # will be copied by reference to the new object.  Image data
            # loaded after this creation is not linked.
            fidx = range(len(self.files))[n]
            new_ic = copy(self)
            new_ic._files = [self.files[i] for i in fidx]
            if self.conserve_memory:
                if self._cached in fidx:
                    new_ic._cached = fidx.index(self._cached)
                    new_ic.data = np.copy(self.data)
                else:
                    new_ic.data = np.empty(1, dtype=object)
            else:
                new_ic.data = self.data[fidx]
            return new_ic

    def _check_imgnum(self, n):
        """Check that the given image number is valid."""
        num = len(self.files)
        if -num <= n < num:
            n = n % num
        else:
            raise IndexError("There are only %s images in the collection"
                             % num)
        return n

    def __iter__(self):
        """Iterate over the images."""
        for i in range(len(self)):
            yield self[i]

    def __len__(self):
        """Number of images in collection."""
        return len(self.files)

    def __str__(self):
        return str(self.files)

    def reload(self, n=None):
        """Clear the image cache.

        Parameters
        ----------
        n : None or int
            Clear the cache for this image only. By default, the
            entire cache is erased.

        """
        self.data = np.empty_like(self.data)

    def concatenate(self):
        """Concatenate all images in the collection into an array.

        Returns
        -------
        ar : np.ndarray
            An array having one more dimension than the images in `self`.

        See Also
        --------
        concatenate_images

        Raises
        ------
        ValueError
            If images in the `ImageCollection` don't have identical shapes.
        """
        return concatenate_images(self)

def imread_collection_wrapper(imread):
    def imread_collection(load_pattern, conserve_memory=True):
        """Return an `ImageCollection` from files matching the given pattern.

        Note that files are always stored in alphabetical order. Also note that
        slicing returns a new ImageCollection, *not* a view into the data.

        See `skimage.io.ImageCollection` for details.

        Parameters
        ----------
        load_pattern : str or list
            Pattern glob or filenames to load. The path can be absolute or
            relative.  Multiple patterns should be separated by a colon,
            e.g. '/tmp/work/*.png:/tmp/other/*.jpg'.  Also see
            implementation notes below.
        conserve_memory : bool, optional
            If True, never keep more than one in memory at a specific
            time.  Otherwise, images will be cached once they are loaded.

        """
        return ImageCollection(load_pattern, conserve_memory=conserve_memory,
                               load_func=imread)
    return imread_collection
<commit_msg>Add a comment about reading in order<commit_after>"""Data structures to hold collections of images, with optional caching."""

from __future__ import with_statement

import os
from glob import glob
import re
from copy import copy

import numpy as np
import six
from PIL import Image

from skimage.external.tifffile import TiffFile

__all__ = ['MultiImage', 'ImageCollection', 'concatenate_images',
           'imread_collection_wrapper']

def concatenate_images(ic):
    """Concatenate all images in the image collection into an array.

    Parameters
    ----------
    ic: an iterable of images (including ImageCollection and MultiImage)
        The images to be concatenated.

    Returns
    -------
    ar : np.ndarray
        An array having one more dimension than the images in `ic`.

    See Also
    --------
    ImageCollection.concatenate, MultiImage.concatenate

    Raises
    ------
    ValueError
        If images in `ic` don't have identical shapes.
    """
    all_images = [img[np.newaxis, ...] for img in ic]
    try:
        ar = np.concatenate(all_images)
    except ValueError:
        raise ValueError('Image dimensions must agree.')
    return ar

def alphanumeric_key(s):
    """Convert string to list of strings and ints that gives intuitive sorting.

    Parameters
    ----------
    s: string

    Returns
    -------
    k: a list of strings and ints

    Examples
    --------
    >>> alphanumeric_key('z23a')
    ['z', 23, 'a']
    >>> filenames = ['f9.10.png', 'e10.png', 'f9.9.png', 'f10.10.png',
    ...              'f10.9.png']
    >>> sorted(filenames)
    ['e10.png', 'f10.10.png', 'f10.9.png', 'f9.10.png', 'f9.9.png']
    >>> sorted(filenames, key=alphanumeric_key)
    ['e10.png', 'f9.9.png', 'f9.10.png', 'f10.9.png', 'f10.10.png']
    """
    k = [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', s)]
    return k

class MultiImage(object):
    """A class containing a single multi-frame image.

    Parameters
    ----------
    filename : str
        The complete path to the image file.
    conserve_memory : bool, optional
        Whether to conserve memory by only caching a single frame. Default is
        True.

    Notes
    -----
    If ``conserve_memory=True`` the memory footprint can be reduced, however
    the performance can be affected because frames have to be read from file
    more often.

    The last accessed frame is cached, all other frames will have to be read
    from file.

    The current implementation makes use of PIL.

    Examples
    --------
    >>> from skimage import data_dir

    >>> img = MultiImage(data_dir + '/multipage.tif') # doctest: +SKIP
    >>> len(img) # doctest: +SKIP
    2
    >>> for frame in img: # doctest: +SKIP
    ...     print(frame.shape) # doctest: +SKIP
    (15, 10)
    (15, 10)

    """
    def __init__(self, filename, conserve_memory=True, dtype=None):
        """Load a multi-img."""
        self._filename = filename
        self._conserve_memory = conserve_memory
        self._dtype = dtype
        self._cached = None

        if filename.lower().endswith(('.tiff', '.tif')):
            self.tif_img = img = TiffFile(self._filename)

        else:
            img = Image.open(self._filename)
            self.tif_img = None

        if self._conserve_memory:
            self._numframes = self._find_numframes(img)

        else:
            self._frames = self._getallframes(img)
            self._numframes = len(self._frames)

        img.close()

    @property
    def filename(self):
        return self._filename

    @property
    def conserve_memory(self):
        return self._conserve_memory

    def _find_numframes(self, img):
        """Find the number of frames in the multi-img."""
        if self.tif_img:
            return len(img.pages)

        else:
            i = 0
            while True:
                i += 1
                try:
                    img.seek(i)
                except EOFError:
                    break
            return i

    def _getframe(self, framenum):
        """Open the image and extract the frame."""
        if self.tif_img:
            return self.tif_img[framenum].asarray()

        else:
            img = Image.open(self.filename)
            # GIFs must be read *in order*
            for i in range(framenum + 1):
                img.seek(i)
            ret = np.asarray(img, dtype=self._dtype)
            img.close()
            return ret

    def _getallframes(self, img):
        """Extract all frames from the multi-img."""
        if self.tif_img:
            return [p.asarray() for p in self.tif_img.pages]

        else:
            frames = []
            try:
                i = 0
                while True:
                    frames.append(np.asarray(img, dtype=self._dtype))
                    i += 1
                    img.seek(i)
            except EOFError:
                return frames

    def __getitem__(self, n):
        """Return the n-th frame as an array.

        Parameters
        ----------
        n : int
            Number of the required frame.

        Returns
        -------
        frame : ndarray
           The n-th frame.
        """
        numframes = self._numframes
        if -numframes <= n < numframes:
            n = n % numframes
        else:
            raise IndexError("There are only %s frames in the image"
                             % numframes)

        if self.conserve_memory:
            if not self._cached == n:
                frame = self._getframe(n)
                self._cached = n
                self._cachedframe = frame
            return self._cachedframe
        else:
            return self._frames[n]

    def __iter__(self):
        """Iterate over the frames."""
        for i in range(len(self)):
            yield self[i]

    def __len__(self):
        """Number of images in collection."""
        return self._numframes

    def __str__(self):
        return str(self.filename) + ' [%s frames]' % self._numframes

    def concatenate(self):
        """Concatenate all images in the multi-image into an array.

        Returns
        -------
        ar : np.ndarray
            An array having one more dimension than the images in `self`.

        See Also
        --------
        concatenate_images

        Raises
        ------
        ValueError
            If images in the `MultiImage` don't have identical shapes.
        """
        return concatenate_images(self)

class ImageCollection(object):
    """Load and manage a collection of image files.

    Note that files are always stored in alphabetical order. Also note that
    slicing returns a new ImageCollection, *not* a view into the data.

    Parameters
    ----------
    load_pattern : str or list
        Pattern glob or filenames to load. The path can be absolute or
        relative.  Multiple patterns should be separated by os.pathsep,
        e.g. '/tmp/work/*.png:/tmp/other/*.jpg'.  Also see
        implementation notes below.
    conserve_memory : bool, optional
        If True, never keep more than one in memory at a specific
        time.  Otherwise, images will be cached once they are loaded.

    Other parameters
    ----------------
    load_func : callable
        ``imread`` by default.  See notes below.

    Attributes
    ----------
    files : list of str
        If a glob string is given for `load_pattern`, this attribute
        stores the expanded file list.  Otherwise, this is simply
        equal to `load_pattern`.

    Notes
    -----
    ImageCollection can be modified to load images from an arbitrary
    source by specifying a combination of `load_pattern` and
    `load_func`.  For an ImageCollection ``ic``, ``ic[5]`` uses
    ``load_func(file_pattern[5])`` to load the image.

    Imagine, for example, an ImageCollection that loads every tenth
    frame from a video file::

      class AVILoader:
          video_file = 'myvideo.avi'

          def __call__(self, frame):
              return video_read(self.video_file, frame)

      avi_load = AVILoader()

      frames = range(0, 1000, 10) # 0, 10, 20, ...
      ic = ImageCollection(frames, load_func=avi_load)

      x = ic[5] # calls avi_load(frames[5]) or equivalently avi_load(50)

    Another use of ``load_func`` would be to convert all images to ``uint8``::

      def imread_convert(f):
          return imread(f).astype(np.uint8)

      ic = ImageCollection('/tmp/*.png', load_func=imread_convert)

    Examples
    --------
    >>> import skimage.io as io
    >>> from skimage import data_dir

    >>> coll = io.ImageCollection(data_dir + '/lena*.png')
    >>> len(coll)
    2
    >>> coll[0].shape
    (512, 512, 3)

    >>> ic = io.ImageCollection('/tmp/work/*.png:/tmp/other/*.jpg')

    """
    def __init__(self, load_pattern, conserve_memory=True, load_func=None):
        """Load and manage a collection of images."""
        if isinstance(load_pattern, six.string_types):
            load_pattern = load_pattern.split(os.pathsep)
            self._files = []
            for pattern in load_pattern:
                self._files.extend(glob(pattern))
            self._files = sorted(self._files, key=alphanumeric_key)
        else:
            self._files = load_pattern

        if conserve_memory:
            memory_slots = 1
        else:
            memory_slots = len(self._files)

        self._conserve_memory = conserve_memory
        self._cached = None

        if load_func is None:
            from ._io import imread
            self.load_func = imread
        else:
            self.load_func = load_func

        self.data = np.empty(memory_slots, dtype=object)

    @property
    def files(self):
        return self._files

    @property
    def conserve_memory(self):
        return self._conserve_memory

    def __getitem__(self, n):
        """Return selected image(s) in the collection.

        Loading is done on demand.

        Parameters
        ----------
        n : int or slice
            The image number to be returned, or a slice selecting the images
            and ordering to be returned in a new ImageCollection.

        Returns
        -------
        img : ndarray or ImageCollection.
            The `n`-th image in the collection, or a new ImageCollection with
            the selected images.

        """
        if hasattr(n, '__index__'):
            n = n.__index__()

        if type(n) not in [int, slice]:
            raise TypeError('slicing must be with an int or slice object')

        if type(n) is int:
            n = self._check_imgnum(n)
            idx = n % len(self.data)

            if (self.conserve_memory and n != self._cached) or \
                (self.data[idx] is None):
                self.data[idx] = self.load_func(self.files[n])
                self._cached = n

            return self.data[idx]
        else:
            # A slice object was provided, so create a new ImageCollection
            # object. Any loaded image data in the original ImageCollection
            # will be copied by reference to the new object.  Image data
            # loaded after this creation is not linked.
            fidx = range(len(self.files))[n]
            new_ic = copy(self)
            new_ic._files = [self.files[i] for i in fidx]
            if self.conserve_memory:
                if self._cached in fidx:
                    new_ic._cached = fidx.index(self._cached)
                    new_ic.data = np.copy(self.data)
                else:
                    new_ic.data = np.empty(1, dtype=object)
            else:
                new_ic.data = self.data[fidx]
            return new_ic

    def _check_imgnum(self, n):
        """Check that the given image number is valid."""
        num = len(self.files)
        if -num <= n < num:
            n = n % num
        else:
            raise IndexError("There are only %s images in the collection"
                             % num)
        return n

    def __iter__(self):
        """Iterate over the images."""
        for i in range(len(self)):
            yield self[i]

    def __len__(self):
        """Number of images in collection."""
        return len(self.files)

    def __str__(self):
        return str(self.files)

    def reload(self, n=None):
        """Clear the image cache.

        Parameters
        ----------
        n : None or int
            Clear the cache for this image only. By default, the
            entire cache is erased.

        """
        self.data = np.empty_like(self.data)

    def concatenate(self):
        """Concatenate all images in the collection into an array.

        Returns
        -------
        ar : np.ndarray
            An array having one more dimension than the images in `self`.

        See Also
        --------
        concatenate_images

        Raises
        ------
        ValueError
            If images in the `ImageCollection` don't have identical shapes.
        """
        return concatenate_images(self)

def imread_collection_wrapper(imread):
    def imread_collection(load_pattern, conserve_memory=True):
        """Return an `ImageCollection` from files matching the given pattern.

        Note that files are always stored in alphabetical order. Also note that
        slicing returns a new ImageCollection, *not* a view into the data.

        See `skimage.io.ImageCollection` for details.

        Parameters
        ----------
        load_pattern : str or list
            Pattern glob or filenames to load. The path can be absolute or
            relative.  Multiple patterns should be separated by a colon,
            e.g. '/tmp/work/*.png:/tmp/other/*.jpg'.  Also see
            implementation notes below.
        conserve_memory : bool, optional
            If True, never keep more than one in memory at a specific
            time.  Otherwise, images will be cached once they are loaded.

        """
        return ImageCollection(load_pattern, conserve_memory=conserve_memory,
                               load_func=imread)
    return imread_collection
<|endoftext|>


<commit_before>import os
import shlex
import subprocess
import sys
import tempfile
from builtins import filter
from builtins import map
from builtins import range
from builtins import str

import scipy.sparse as sparse

from ..base import MLClassifierBase
from ..dataset import save_to_arff

try:
    from shlex import quote as cmd_quote
except ImportError:
    from pipes import quote as cmd_quote

class Meka(MLClassifierBase):
    """Wrapper for the MEKA classifier

    For more information on how to use this class see the tutorial: :doc:`../meka`
    """

    def __init__(self, meka_classifier=None, weka_classifier=None,
                 java_command=None, meka_classpath=None):
        """Initializes the MEKA Wrapper

        Attributes
        ----------
        meka_classifier : str
            The MEKA classifier string and parameters from the MEKA API,
            such as :code:`meka.classifiers.multilabel.MULAN -S RAkEL2`
        weka_classifier : str
            The WEKA classifier string and parameters from the WEKA API,
            such as :code:`weka.classifiers.trees.J48`
        java_command : str
            Path to test the java command
        meka_classpath: str
            Path to the MEKA class path folder, usually the folder lib
            in the directory MEKA was extracted into
        """
        super(Meka, self).__init__()

        self.java_command = java_command
        if self.java_command is None:
            # TODO: this will not be needed once we're python 3 ready - we will
            # use it only in python 2.7 cases
            from whichcraft import which
            self.java_command = which("java")

            if self.java_command is None:
                raise ValueError("Java not found")

        self.meka_classpath = meka_classpath
        if self.meka_classpath is None:
            self.meka_classpath = os.environ.get('MEKA_CLASSPATH')

            if self.meka_classpath is None:
                raise ValueError("No meka classpath defined")

        self.meka_classifier = meka_classifier
        self.verbosity = 5
        self.weka_classifier = weka_classifier
        self.output = None
        self.warnings = None
        self.require_dense = [False, False]
        self.copyable_attrs = [
            'meka_classifier',
            'weka_classifier',
            'java_command',
            'meka_classpath'
        ]
        self.clean()

    def clean(self):
        """Sets various attributes to :code:`None`"""
        self.results = None
        self.statistics = None
        self.output = None
        self.error = None
        self.label_count = None
        self.instance_count = None

    def remove_temporary_files(self, temporary_files):
        """Internal function for cleaning temporary files"""
        for file_object in temporary_files:
            file_name = file_object.name
            file_object.close()
            if os.path.exists(file_name):
                os.remove(file_name)

            arff_file_name = file_name + '.arff'
            if os.path.exists(arff_file_name):
                os.remove(arff_file_name)

    def run_meka_command(self, args):
        """Runs the MEKA command

        Parameters
        ----------
        args : str
            the Java command to run
        """
        command_args = [
            self.java_command,
            '-cp', "{}*".format(self.meka_classpath),
            self.meka_classifier,
        ]

        if self.weka_classifier is not None:
            command_args += ['-W', self.weka_classifier]

        command_args += args

        meka_command = cmd_quote(" ".join(command_args))

        pipes = subprocess.Popen(shlex.split(
            meka_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        self.output, self.error = pipes.communicate()
        if type(self.output) == bytes:
            self.output = self.output.decode(sys.stdout.encoding)
        if type(self.error) == bytes:
            self.error = self.error.decode(sys.stdout.encoding)

        if pipes.returncode != 0:
            raise Exception(self.output + self.error)

    def fit(self, X, y):
        """Fit classifier with training data

        Internally this method dumps X and y to temporary arff files and
        runs MEKA with relevant arguments using :func:`run`. It uses a
        sparse DOK representation (:class:`scipy.sparse.dok_matrix`)
        of the X matrix.

        Parameters
        ----------
        X : numpy.ndarray or scipy.sparse
            input features of shape :code:`(n_samples, n_features)`
        y : numpy.ndarray or scipy.sparse
            binary indicator matrix with label assigments of shape
            :code:`(n_samples, n_features)`

        Returns
        -------
        skmultilearn.ext.meka.Meka
            fitted instance of self
        """
        self.clean()
        X = self.ensure_input_format(
            X, sparse_format='dok', enforce_sparse=True)
        y = self.ensure_output_format(
            y, sparse_format='dok', enforce_sparse=True)
        self.label_count = y.shape[1]

        # we need this in case threshold needs to be recalibrated in meka
        self.train_data_ = save_to_arff(X, y)
        train_arff = tempfile.NamedTemporaryFile(delete=False)
        classifier_dump_file = tempfile.NamedTemporaryFile(delete=False)

        try:
            with open(train_arff.name + '.arff', 'w') as fp:
                fp.write(self.train_data_)

            input_args = [
                '-verbosity', "0",
                '-split-percentage', "100",
                '-t', train_arff.name + '.arff',
                '-d', classifier_dump_file.name,
            ]

            self.run_meka_command(input_args)
            self.classifier_dump = None
            with open(classifier_dump_file.name, 'rb') as fp:
                self.classifier_dump = fp.read()
        finally:
            self.remove_temporary_files([train_arff, classifier_dump_file])

        return self

    def predict(self, X):
        """Predict label assignments for X

        Internally this method dumps X to temporary arff files and
        runs MEKA with relevant arguments using :func:`run`. It uses a
        sparse DOK representation (:class:`scipy.sparse.dok_matrix`)
        of the X matrix.

        Parameters
        ----------
        X : numpy.ndarray or scipy.sparse
            input features of shape :code:`(n_samples, n_features)`

        Returns
        -------
        scipy.sparse of int
            sparse matrix of integers with shape :code:`(n_samples, n_features)`
        """
        X = self.ensure_input_format(
            X, sparse_format='dok', enforce_sparse=True)
        self.instance_count = X.shape[0]

        if self.classifier_dump is None:
            raise Exception('Not classified')

        sparse_y = sparse.coo_matrix((X.shape[0], self.label_count), dtype=int)

        try:
            train_arff = tempfile.NamedTemporaryFile(delete=False)
            test_arff = tempfile.NamedTemporaryFile(delete=False)
            classifier_dump_file = tempfile.NamedTemporaryFile(delete=False)

            with open(train_arff.name + '.arff', 'w') as fp:
                fp.write(self.train_data_)

            with open(classifier_dump_file.name, 'wb') as fp:
                fp.write(self.classifier_dump)

            with open(test_arff.name + '.arff', 'w') as fp:
                fp.write(save_to_arff(X, sparse_y))

            args = [
                '-l', classifier_dump_file.name
            ]

            self.run(train_arff.name + '.arff', test_arff.name + '.arff', args)
            self.parse_output()

        finally:
            self.remove_temporary_files(
                [train_arff, test_arff, classifier_dump_file])

        return self.results

    def run(self, train_file, test_file, additional_arguments=[]):
        """Runs the meka classifiers

        Parameters
        ----------
        train_file : str
            path to train :code:`.arff` file in meka format
            (big endian, labels first in attributes list).
        test_file : str
            path to test :code:`.arff` file in meka format
            (big endian, labels first in attributes list).

        Returns
        -------
        predictions: sparse binary indicator matrix [n_test_samples, n_labels]
            array of binary label vectors including label predictions of
            shape :code:`(n_test_samples, n_labels)`
        """
        self.output = None
        self.warnings = None

        # meka_command_string = 'java -cp "/home/niedakh/pwr/old/meka-1.5/lib/*" meka.classifiers.multilabel.MULAN -S RAkEL2
        # -threshold 0 -t {train} -T {test} -verbosity {verbosity} -W weka.classifiers.bayes.NaiveBayes'
        # meka.classifiers.multilabel.LC, weka.classifiers.bayes.NaiveBayes

        args = [
                   '-t', train_file,
                   '-T', test_file,
                   '-verbosity', str(5),
               ] + additional_arguments

        self.run_meka_command(args)
        return self

    def parse_output(self):
        """Internal function for parsing MEKA output."""
        if self.output is None:
            self.results = None
            self.statistics = None
            return None

        predictions_split_head = '==== PREDICTIONS'
        predictions_split_foot = '|==========='

        if self.label_count is None:
            self.label_count = map(lambda y: int(y.split(')')[1].strip()), [
                x for x in self.output.split('\n') if 'Number of labels' in x])[0]

        if self.instance_count is None:
            self.instance_count = int(float(filter(lambda x: '==== PREDICTIONS (N=' in x, self.output.split(
                '\n'))[0].split('(')[1].split('=')[1].split(')')[0]))
        self.predictions = self.output.split(predictions_split_head)[1].split(
            predictions_split_foot)[0].split('\n')[1:-1]

        self.predictions = [y.split(']')[0]
                            for y in [x.split('] [')[1] for x in self.predictions]]
        self.predictions = [[a for a in [f.strip() for f in z.split(',')] if len(a) > 0]
                            for z in self.predictions]
        self.predictions = [[int(a) for a in z] for z in self.predictions]

        assert self.verbosity == 5

        self.results = sparse.lil_matrix(
            (self.instance_count, self.label_count), dtype='int')
        for row in range(self.instance_count):
            for label in self.predictions[row]:
                self.results[row, label] = 1

        statistics = [x for x in self.output.split(
            '== Evaluation Info')[1].split('\n') if len(x) > 0 and '==' not in x]
        statistics = [y for y in [z.strip() for z in statistics] if '  ' in y]
        array_data = [z for z in statistics if '[' in z]
        non_array_data = [z for z in statistics if '[' not in z]

        self.statistics = {}
        for row in non_array_data:
            r = row.strip().split('  ')
            r = [z for z in r if len(z) > 0]
            r = [z.strip() for z in r]
            if len(r) < 2:
                continue
            try:
                test_value = float(r[1])
            except ValueError:
                test_value = r[1]

            r[1] = test_value
            self.statistics[r[0]] = r[1]

        for row in array_data:
            r = row.strip().split('[')
            r = [z.strip() for z in r]
            r[1] = r[1].replace(', ', ' ').replace(
                ',', '.').replace(']', '').split(' ')
            r[1] = [x for x in r[1] if len(x) > 0]
            self.statistics[r[0]] = r[1]

        return self.results, self.statistics
<commit_msg>don't split command on windows<commit_after>import os
import shlex
import subprocess
import sys
import tempfile
from builtins import filter
from builtins import map
from builtins import range
from builtins import str

import scipy.sparse as sparse

from ..base import MLClassifierBase
from ..dataset import save_to_arff

try:
    from shlex import quote as cmd_quote
except ImportError:
    from pipes import quote as cmd_quote

class Meka(MLClassifierBase):
    """Wrapper for the MEKA classifier

    For more information on how to use this class see the tutorial: :doc:`../meka`
    """

    def __init__(self, meka_classifier=None, weka_classifier=None,
                 java_command=None, meka_classpath=None):
        """Initializes the MEKA Wrapper

        Attributes
        ----------
        meka_classifier : str
            The MEKA classifier string and parameters from the MEKA API,
            such as :code:`meka.classifiers.multilabel.MULAN -S RAkEL2`
        weka_classifier : str
            The WEKA classifier string and parameters from the WEKA API,
            such as :code:`weka.classifiers.trees.J48`
        java_command : str
            Path to test the java command
        meka_classpath: str
            Path to the MEKA class path folder, usually the folder lib
            in the directory MEKA was extracted into
        """
        super(Meka, self).__init__()

        self.java_command = java_command
        if self.java_command is None:
            # TODO: this will not be needed once we're python 3 ready - we will
            # use it only in python 2.7 cases
            from whichcraft import which
            self.java_command = which("java")

            if self.java_command is None:
                raise ValueError("Java not found")

        self.meka_classpath = meka_classpath
        if self.meka_classpath is None:
            self.meka_classpath = os.environ.get('MEKA_CLASSPATH')

            if self.meka_classpath is None:
                raise ValueError("No meka classpath defined")

        self.meka_classifier = meka_classifier
        self.verbosity = 5
        self.weka_classifier = weka_classifier
        self.output = None
        self.warnings = None
        self.require_dense = [False, False]
        self.copyable_attrs = [
            'meka_classifier',
            'weka_classifier',
            'java_command',
            'meka_classpath'
        ]
        self.clean()

    def clean(self):
        """Sets various attributes to :code:`None`"""
        self.results = None
        self.statistics = None
        self.output = None
        self.error = None
        self.label_count = None
        self.instance_count = None

    def remove_temporary_files(self, temporary_files):
        """Internal function for cleaning temporary files"""
        for file_object in temporary_files:
            file_name = file_object.name
            file_object.close()
            if os.path.exists(file_name):
                os.remove(file_name)

            arff_file_name = file_name + '.arff'
            if os.path.exists(arff_file_name):
                os.remove(arff_file_name)

    def run_meka_command(self, args):
        """Runs the MEKA command

        Parameters
        ----------
        args : str
            the Java command to run
        """
        command_args = [
            self.java_command,
            '-cp', "{}*".format(self.meka_classpath),
            self.meka_classifier,
        ]

        if self.weka_classifier is not None:
            command_args += ['-W', self.weka_classifier]

        command_args += args

        meka_command = cmd_quote(" ".join(command_args))

        if sys.platform != 'win32':
            meka_command = shlex.split(meka_command)

        pipes = subprocess.Popen(meka_command,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 universal_newlines=True)
        self.output, self.error = pipes.communicate()
        if type(self.output) == bytes:
            self.output = self.output.decode(sys.stdout.encoding)
        if type(self.error) == bytes:
            self.error = self.error.decode(sys.stdout.encoding)

        if pipes.returncode != 0:
            raise Exception(self.output + self.error)

    def fit(self, X, y):
        """Fit classifier with training data

        Internally this method dumps X and y to temporary arff files and
        runs MEKA with relevant arguments using :func:`run`. It uses a
        sparse DOK representation (:class:`scipy.sparse.dok_matrix`)
        of the X matrix.

        Parameters
        ----------
        X : numpy.ndarray or scipy.sparse
            input features of shape :code:`(n_samples, n_features)`
        y : numpy.ndarray or scipy.sparse
            binary indicator matrix with label assigments of shape
            :code:`(n_samples, n_features)`

        Returns
        -------
        skmultilearn.ext.meka.Meka
            fitted instance of self
        """
        self.clean()
        X = self.ensure_input_format(
            X, sparse_format='dok', enforce_sparse=True)
        y = self.ensure_output_format(
            y, sparse_format='dok', enforce_sparse=True)
        self.label_count = y.shape[1]

        # we need this in case threshold needs to be recalibrated in meka
        self.train_data_ = save_to_arff(X, y)
        train_arff = tempfile.NamedTemporaryFile(delete=False)
        classifier_dump_file = tempfile.NamedTemporaryFile(delete=False)

        try:
            with open(train_arff.name + '.arff', 'w') as fp:
                fp.write(self.train_data_)

            input_args = [
                '-verbosity', "0",
                '-split-percentage', "100",
                '-t', train_arff.name + '.arff',
                '-d', classifier_dump_file.name,
            ]

            self.run_meka_command(input_args)
            self.classifier_dump = None
            with open(classifier_dump_file.name, 'rb') as fp:
                self.classifier_dump = fp.read()
        finally:
            self.remove_temporary_files([train_arff, classifier_dump_file])

        return self

    def predict(self, X):
        """Predict label assignments for X

        Internally this method dumps X to temporary arff files and
        runs MEKA with relevant arguments using :func:`run`. It uses a
        sparse DOK representation (:class:`scipy.sparse.dok_matrix`)
        of the X matrix.

        Parameters
        ----------
        X : numpy.ndarray or scipy.sparse
            input features of shape :code:`(n_samples, n_features)`

        Returns
        -------
        scipy.sparse of int
            sparse matrix of integers with shape :code:`(n_samples, n_features)`
        """
        X = self.ensure_input_format(
            X, sparse_format='dok', enforce_sparse=True)
        self.instance_count = X.shape[0]

        if self.classifier_dump is None:
            raise Exception('Not classified')

        sparse_y = sparse.coo_matrix((X.shape[0], self.label_count), dtype=int)

        try:
            train_arff = tempfile.NamedTemporaryFile(delete=False)
            test_arff = tempfile.NamedTemporaryFile(delete=False)
            classifier_dump_file = tempfile.NamedTemporaryFile(delete=False)

            with open(train_arff.name + '.arff', 'w') as fp:
                fp.write(self.train_data_)

            with open(classifier_dump_file.name, 'wb') as fp:
                fp.write(self.classifier_dump)

            with open(test_arff.name + '.arff', 'w') as fp:
                fp.write(save_to_arff(X, sparse_y))

            args = [
                '-l', classifier_dump_file.name
            ]

            self.run(train_arff.name + '.arff', test_arff.name + '.arff', args)
            self.parse_output()

        finally:
            self.remove_temporary_files(
                [train_arff, test_arff, classifier_dump_file])

        return self.results

    def run(self, train_file, test_file, additional_arguments=[]):
        """Runs the meka classifiers

        Parameters
        ----------
        train_file : str
            path to train :code:`.arff` file in meka format
            (big endian, labels first in attributes list).
        test_file : str
            path to test :code:`.arff` file in meka format
            (big endian, labels first in attributes list).

        Returns
        -------
        predictions: sparse binary indicator matrix [n_test_samples, n_labels]
            array of binary label vectors including label predictions of
            shape :code:`(n_test_samples, n_labels)`
        """
        self.output = None
        self.warnings = None

        # meka_command_string = 'java -cp "/home/niedakh/pwr/old/meka-1.5/lib/*" meka.classifiers.multilabel.MULAN -S RAkEL2
        # -threshold 0 -t {train} -T {test} -verbosity {verbosity} -W weka.classifiers.bayes.NaiveBayes'
        # meka.classifiers.multilabel.LC, weka.classifiers.bayes.NaiveBayes

        args = [
                   '-t', train_file,
                   '-T', test_file,
                   '-verbosity', str(5),
               ] + additional_arguments

        self.run_meka_command(args)
        return self

    def parse_output(self):
        """Internal function for parsing MEKA output."""
        if self.output is None:
            self.results = None
            self.statistics = None
            return None

        predictions_split_head = '==== PREDICTIONS'
        predictions_split_foot = '|==========='

        if self.label_count is None:
            self.label_count = map(lambda y: int(y.split(')')[1].strip()), [
                x for x in self.output.split('\n') if 'Number of labels' in x])[0]

        if self.instance_count is None:
            self.instance_count = int(float(filter(lambda x: '==== PREDICTIONS (N=' in x, self.output.split(
                '\n'))[0].split('(')[1].split('=')[1].split(')')[0]))
        self.predictions = self.output.split(predictions_split_head)[1].split(
            predictions_split_foot)[0].split('\n')[1:-1]

        self.predictions = [y.split(']')[0]
                            for y in [x.split('] [')[1] for x in self.predictions]]
        self.predictions = [[a for a in [f.strip() for f in z.split(',')] if len(a) > 0]
                            for z in self.predictions]
        self.predictions = [[int(a) for a in z] for z in self.predictions]

        assert self.verbosity == 5

        self.results = sparse.lil_matrix(
            (self.instance_count, self.label_count), dtype='int')
        for row in range(self.instance_count):
            for label in self.predictions[row]:
                self.results[row, label] = 1

        statistics = [x for x in self.output.split(
            '== Evaluation Info')[1].split('\n') if len(x) > 0 and '==' not in x]
        statistics = [y for y in [z.strip() for z in statistics] if '  ' in y]
        array_data = [z for z in statistics if '[' in z]
        non_array_data = [z for z in statistics if '[' not in z]

        self.statistics = {}
        for row in non_array_data:
            r = row.strip().split('  ')
            r = [z for z in r if len(z) > 0]
            r = [z.strip() for z in r]
            if len(r) < 2:
                continue
            try:
                test_value = float(r[1])
            except ValueError:
                test_value = r[1]

            r[1] = test_value
            self.statistics[r[0]] = r[1]

        for row in array_data:
            r = row.strip().split('[')
            r = [z.strip() for z in r]
            r[1] = r[1].replace(', ', ' ').replace(
                ',', '.').replace(']', '').split(' ')
            r[1] = [x for x in r[1] if len(x) > 0]
            self.statistics[r[0]] = r[1]

        return self.results, self.statistics
<|endoftext|>