<commit_before># -*- coding: utf-8 -*-
import os
from tempfile import mkdtemp
from zipfile import ZipFile
from shutil import rmtree

from bs4 import BeautifulSoup

from .lines import DwCACoreLine
from .utils import _EmbeddedCSV

class DwCAReader(object):
    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def __init__(self, path):
        """Opens the file, reads all metadata and store it in self.meta
        (BeautifulSoup obj.) Also already open the core file so we've
        a file descriptor for further access."""
        self.archive_path = path

        self._unzipped_folder_path = self._unzip()
        self._metaxml = self._parse_metaxml_file()

        # Load the (scientific) metadata file and store its representation
        # in metadata attribute for future use.
        self.metadata = self._parse_metadata_file()
        self.core_rowtype = self._get_core_type()
        self.extensions_rowtype = self._get_extensions_types()

        self._corefile = _EmbeddedCSV(self._metaxml.core,
                                      self._unzipped_folder_path)

    @property
    #TODO: decide, test and document what we guarantee about ordering
    def lines(self):
        """Get a list containing all (core) lines of the archive"""
        return list(self.each_line())

    def get_line(self, line_id):
        """Get the line whose id is line_id."""
        for line in self.each_line():
            if line.id == str(line_id):
                return line
        else:
            return None

    def absolute_temporary_path(self, relative_path):
        """Returns the absolute path of the file located at relative_path within the archive.

        Notes:
            - The file at this path is temporary and will be removed when closing the instance.
            - File existence is not tested.
        """

        return os.path.abspath(os.path.join(self._unzipped_folder_path, relative_path))

    def _read_additional_file(self, relative_path):
        """Read an additional file in the archive and return its content."""
        p = self.absolute_temporary_path(relative_path)
        return open(p).read()

    def _create_temporary_folder(self):
        return mkdtemp()[1]

    def _parse_metadata_file(self):
        """Loads the archive (scientific) Metadata file, parse it with
        BeautifulSoup and return its content."""

        # This method should be called only after ._metaxml attribute is set
        # because the name/path to metadat file is stored in the "metadata"
        # attribute of the "archive" tag
        metadata_file = self._get_metadata_filename()
        return self._parse_xml_included_file(metadata_file)

    def _parse_metaxml_file(self):
        """Loads the meta.xml, parse it with BeautifulSoup and return its
        content."""
        return self._parse_xml_included_file('meta.xml')

    def _parse_xml_included_file(self, relative_path):
        """Loads, parse with BeautifulSoup and returns XML file located
        at relative_path."""
        return BeautifulSoup(self._read_additional_file(relative_path), "xml")

    def _unzip(self):
        """Unzip the current archive in a temporary directory and returns its path."""
        unzipped_folder = self._create_temporary_folder()
        #TODO: check content of file!!!! It may, for example contains
        #absolute path (see zipfile doc)
        ZipFile(self.archive_path, 'r').extractall(unzipped_folder)
        return unzipped_folder

    def close(self):
        self._cleanup_temporary_folder()

    def _cleanup_temporary_folder(self):
        rmtree(self._unzipped_folder_path, False)

    def _get_core_type(self):
        return self._metaxml.core['rowType']

    def _get_extensions_types(self):
        return [e['rowType'] for e in self._metaxml.findAll('extension')]

    def core_contains_term(self, term_url):
        return term_url in self.core_terms

    @property
    def core_terms(self):
        """Returns a set of all the terms (URL) contained in Core file."""
        term_names = [f['term'] for f in self._metaxml.core.findAll('field')]
        return set(term_names)

    def _get_metadata_filename(self):
        return self._metaxml.archive['metadata']

    #TODO: decide, test and document what we guarantee about ordering
    # We'll have to edit test_lines_property() if we don't guarantee the
    # same order
    def each_line(self):
        self._corefile.reset_line_iterator()

        # Some Archives (Currently GBIF Results) have line-level (source data)
        # In that case, we'll pass all of them to the line.
        try:
            sm = self.source_metadata
        except AttributeError:
            sm = None

        for line in self._corefile.lines():
            yield DwCACoreLine(line, self._metaxml, self._unzipped_folder_path, sm)

class GBIFResultsReader(DwCAReader):
    def __init__(self, path):
        super(GBIFResultsReader, self).__init__(path)

        self.source_metadata = self._dataset_metadata_to_dict('dataset')

    # Compared to a standard DwC-A, GBIF results export contains
    # two additional files to give details about IP rights and citations
    # We make them accessible trough two simples properties
    def _dataset_metadata_to_dict(self, folder):
        dataset_dir = os.path.join(self._unzipped_folder_path, folder)

        r = {}
        for f in os.listdir(dataset_dir):
            if os.path.isfile(os.path.join(dataset_dir, f)):
                key = os.path.splitext(f)[0]
                r[key] = self._parse_xml_included_file(os.path.join(folder, f))

        return r

    @property
    def citations(self):
        return self._read_additional_file('citations.txt')

    @property
    def rights(self):
        return self._read_additional_file('rights.txt')
<commit_msg>Fix misplaced comment.<commit_after># -*- coding: utf-8 -*-
import os
from tempfile import mkdtemp
from zipfile import ZipFile
from shutil import rmtree

from bs4 import BeautifulSoup

from .lines import DwCACoreLine
from .utils import _EmbeddedCSV

class DwCAReader(object):
    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def __init__(self, path):
        """Opens the file, reads all metadata and store it in self.meta
        (BeautifulSoup obj.) Also already open the core file so we've
        a file descriptor for further access."""
        self.archive_path = path

        self._unzipped_folder_path = self._unzip()
        self._metaxml = self._parse_metaxml_file()

        # Load the (scientific) metadata file and store its representation
        # in metadata attribute for future use.
        self.metadata = self._parse_metadata_file()
        self.core_rowtype = self._get_core_type()
        self.extensions_rowtype = self._get_extensions_types()

        self._corefile = _EmbeddedCSV(self._metaxml.core,
                                      self._unzipped_folder_path)

    @property
    #TODO: decide, test and document what we guarantee about ordering
    def lines(self):
        """Get a list containing all (core) lines of the archive"""
        return list(self.each_line())

    def get_line(self, line_id):
        """Get the line whose id is line_id."""
        for line in self.each_line():
            if line.id == str(line_id):
                return line
        else:
            return None

    def absolute_temporary_path(self, relative_path):
        """Returns the absolute path of the file located at relative_path within the archive.

        Notes:
            - The file at this path is temporary and will be removed when closing the instance.
            - File existence is not tested.
        """

        return os.path.abspath(os.path.join(self._unzipped_folder_path, relative_path))

    def _read_additional_file(self, relative_path):
        """Read an additional file in the archive and return its content."""
        p = self.absolute_temporary_path(relative_path)
        return open(p).read()

    def _create_temporary_folder(self):
        return mkdtemp()[1]

    def _parse_metadata_file(self):
        """Loads the archive (scientific) Metadata file, parse it with
        BeautifulSoup and return its content."""

        # This method should be called only after ._metaxml attribute is set
        # because the name/path to metadat file is stored in the "metadata"
        # attribute of the "archive" tag
        metadata_file = self._get_metadata_filename()
        return self._parse_xml_included_file(metadata_file)

    def _parse_metaxml_file(self):
        """Loads the meta.xml, parse it with BeautifulSoup and return its
        content."""
        return self._parse_xml_included_file('meta.xml')

    def _parse_xml_included_file(self, relative_path):
        """Loads, parse with BeautifulSoup and returns XML file located
        at relative_path."""
        return BeautifulSoup(self._read_additional_file(relative_path), "xml")

    def _unzip(self):
        """Unzip the current archive in a temporary directory and returns its path."""
        unzipped_folder = self._create_temporary_folder()
        #TODO: check content of file!!!! It may, for example contains
        #absolute path (see zipfile doc)
        ZipFile(self.archive_path, 'r').extractall(unzipped_folder)
        return unzipped_folder

    def close(self):
        self._cleanup_temporary_folder()

    def _cleanup_temporary_folder(self):
        rmtree(self._unzipped_folder_path, False)

    def _get_core_type(self):
        return self._metaxml.core['rowType']

    def _get_extensions_types(self):
        return [e['rowType'] for e in self._metaxml.findAll('extension')]

    def core_contains_term(self, term_url):
        return term_url in self.core_terms

    @property
    def core_terms(self):
        """Returns a set of all the terms (URL) contained in Core file."""
        term_names = [f['term'] for f in self._metaxml.core.findAll('field')]
        return set(term_names)

    def _get_metadata_filename(self):
        return self._metaxml.archive['metadata']

    #TODO: decide, test and document what we guarantee about ordering
    # We'll have to edit test_lines_property() if we don't guarantee the
    # same order
    def each_line(self):
        self._corefile.reset_line_iterator()

        # Some Archives (Currently GBIF Results) have line-level (source data)
        # In that case, we'll pass all of them to the line.
        try:
            sm = self.source_metadata
        except AttributeError:
            sm = None

        for line in self._corefile.lines():
            yield DwCACoreLine(line, self._metaxml, self._unzipped_folder_path, sm)

class GBIFResultsReader(DwCAReader):
    def __init__(self, path):
        super(GBIFResultsReader, self).__init__(path)

        self.source_metadata = self._dataset_metadata_to_dict('dataset')

    def _dataset_metadata_to_dict(self, folder):
        dataset_dir = os.path.join(self._unzipped_folder_path, folder)

        r = {}
        for f in os.listdir(dataset_dir):
            if os.path.isfile(os.path.join(dataset_dir, f)):
                key = os.path.splitext(f)[0]
                r[key] = self._parse_xml_included_file(os.path.join(folder, f))

        return r

    # Compared to a standard DwC-A, GBIF results export contains
    # two additional files to give details about IP rights and citations
    # We make them accessible trough two simples properties

    @property
    def citations(self):
        return self._read_additional_file('citations.txt')

    @property
    def rights(self):
        return self._read_additional_file('rights.txt')
<|endoftext|>


<commit_before>"""
Usage:

    ./manage.py import_operator_contacts < nocrecords.xml
"""
import warnings
from io import open
from bs4 import BeautifulSoup
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
from ...models import Operator
from ...views import FIRST_OPERATORS

class Command(BaseCommand):
    input = 0

    @classmethod
    def format_address(cls, address):
        address_parts = address.split(', ')
        address_last_line_parts = address_parts[-1].split(' ')
        if len(address_last_line_parts) > 2:
            pre_postcode = ' '.join(address_last_line_parts[:-2])
            postcode = ' '.join(address_last_line_parts[-2:])
            address_parts[-1] = pre_postcode + '\n' + postcode
        return '\n'.join(address_parts)

    def handle(self, *args, **options):
        with open(self.input, encoding='cp1252') as input:
            soup = BeautifulSoup(input, 'html.parser')

        noc_codes = {
            record.pubnmid.string: record.noccode.string
            for record in soup.noctable.find_all('noctablerecord')
        }

        for public_name in soup.publicname.find_all('publicnamerecord'):
            noc_code = noc_codes.get(public_name.pubnmid.string)

            if not noc_code or len(noc_code) < 4:
                continue

            try:
                operator = Operator.objects.get(pk=noc_code.replace('=', ''))
            except Operator.DoesNotExist as e:
                warnings.warn('{} {}'.format(e, noc_code))
                continue

            if noc_code in FIRST_OPERATORS:
                operator.url = 'https://www.firstgroup.com/%s' % FIRST_OPERATORS[noc_code]
                operator.email = ''
                operator.phone = ''
                operator.save()
                continue

            website = public_name.website.string
            address = public_name.complenq.string
            email = public_name.ttrteenq.string
            phone = public_name.fareenq.string

            if website or address or email or phone:
                if website:
                    website = website.split('#')[-2]
                    if '.' in website and 'mailto:' not in website and ' ' not in website:
                        if website.startswith('http'):
                            operator.url = website
                        else:
                            operator.url = 'http://' + website
                if address and len(address) <= 128 and ', ' in address:
                    operator.address = self.format_address(address)
                if email and '@' in email and ' ' not in email.strip():
                    operator.email = email.strip()
                if phone and len(phone) <= 128:
                    operator.phone = phone

                try:
                    operator.save()
                except ValidationError as errors:
                    errors = dict(errors)
                    if 'email' in errors:
                        operator.email = ''
                    if 'url' in errors:
                        operator.url = ''
                    operator.save()
<commit_msg>#51 update operator contacts import<commit_after>"""
Usage:

    ./manage.py import_operator_contacts < nocrecords.xml
"""
import warnings
from io import open
from bs4 import BeautifulSoup
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
from ...models import Operator
from ...views import FIRST_OPERATORS

class Command(BaseCommand):
    input = 0

    @classmethod
    def format_address(cls, address):
        address_parts = address.split(', ')
        address_last_line_parts = address_parts[-1].split(' ')
        if len(address_last_line_parts) > 2:
            pre_postcode = ' '.join(address_last_line_parts[:-2])
            postcode = ' '.join(address_last_line_parts[-2:])
            address_parts[-1] = pre_postcode + '\n' + postcode
        return '\n'.join(address_parts)

    def handle(self, *args, **options):
        with open(self.input, encoding='cp1252') as input:
            soup = BeautifulSoup(input, 'html.parser')

        noc_codes = {
            record.pubnmid.string: record.noccode.string
            for record in soup.noctable.find_all('noctablerecord')
        }

        for public_name in soup.publicname.find_all('publicnamerecord'):
            noc_code = noc_codes.get(public_name.pubnmid.string)

            if not noc_code or len(noc_code) != 4:
                continue

            try:
                operator = Operator.objects.get(operatorcode__code=noc_code.replace('=', ''),
                                                operatorcode__source__name='National Operator Codes')
            except Operator.DoesNotExist as e:
                warnings.warn('{} {}'.format(e, noc_code))
                continue

            if noc_code in FIRST_OPERATORS:
                operator.url = 'https://www.firstgroup.com/%s' % FIRST_OPERATORS[noc_code]
                operator.email = ''
                operator.phone = ''
                operator.save()
                continue

            website = public_name.website.string
            address = public_name.complenq.string
            email = public_name.ttrteenq.string
            phone = public_name.fareenq.string

            if website or address or email or phone:
                if website:
                    website = website.split('#')[-2]
                    if '.' in website and 'mailto:' not in website and ' ' not in website:
                        if website.startswith('http'):
                            operator.url = website
                        else:
                            operator.url = 'http://' + website
                if address and len(address) <= 128 and ', ' in address:
                    operator.address = self.format_address(address)
                if email and '@' in email and ' ' not in email.strip():
                    operator.email = email.strip()
                if phone and len(phone) <= 128:
                    operator.phone = phone

                try:
                    operator.save()
                except ValidationError as errors:
                    errors = dict(errors)
                    if 'email' in errors:
                        operator.email = ''
                    if 'url' in errors:
                        operator.url = ''
                    operator.save()
<|endoftext|>


<commit_before>"""Miscellaneous types."""

from collections import namedtuple
from functools import total_ordering

# With older Python version, pathlib2 might be preferred.
try:
    from pathlib2 import Path, PurePath
except ImportError:
    from pathlib import Path, PurePath

from . import util
from .patient import GleasonScore, Lesion, Patient

@total_ordering
class ImageMode(object):
    """Image mode identifier."""
    def __init__(self, value, sep='-'):
        """Initialize with a string or a sequence."""
        if isinstance(value, str):
            value = value.split(sep)
        self.value = tuple(value)
        self.sep = sep

    def __iter__(self):
        return iter(self.value)

    def __getitem__(self, key):
        return self.__class__(self.value[key])

    def __len__(self):
        return len(self.value)

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, ', '.join(self))

    def __str__(self):
        return self.sep.join(iter(self))

    def __lt__(self, other):
        return tuple(self) < tuple(ImageMode(other))

    def __eq__(self, other):
        return tuple(self) == tuple(ImageMode(other))

    def __hash__(self):
        return hash(tuple(self))

    # def __add__(self, other):
    #     """Append a component."""
    #     return self.__class__(self.value + (other,))

    # def __sub__(self, other):
    #     """Remove a tailing component."""
    #     v = self.value
    #     if v[-1] == other:
    #         v = v[:-1]
    #     return self.__class__(v)

def _fmt_seq(seq):
    return '-'.join(str(x) for x in seq if x is not None)

def namedtuple_fmt(*args, **kwargs):
    t = namedtuple(*args, **kwargs)
    t.__str__ = _fmt_seq
    return t

ImageMode_NT = namedtuple_fmt('ImageMode_NT', ['modality', 'model', 'param'])
ImageTarget = namedtuple_fmt('ImageTarget', ['case', 'scan', 'lesion'])
TextureSpec = namedtuple_fmt('TextureSpec', ['method', 'winsize', 'feature'])
ROISpec = namedtuple_fmt('ROISpec', ['type', 'id'])
AlgParams = namedtuple_fmt('AlgParams', ['depthmin', 'depthmax',
                                         'sidemin', 'sidemax', 'nrois'])

__all__ = list(n for n in globals() if n[:1] != '_')
<commit_msg>imports<commit_after>"""Miscellaneous types."""

from collections import namedtuple
from functools import total_ordering

# With older Python version, pathlib2 might be preferred.
try:
    from pathlib2 import Path, PurePath
except ImportError:
    from pathlib import Path, PurePath

from .patient import GleasonScore, Lesion, Patient

@total_ordering
class ImageMode(object):
    """Image mode identifier."""
    def __init__(self, value, sep='-'):
        """Initialize with a string or a sequence."""
        if isinstance(value, str):
            value = value.split(sep)
        self.value = tuple(value)
        self.sep = sep

    def __iter__(self):
        return iter(self.value)

    def __getitem__(self, key):
        return self.__class__(self.value[key])

    def __len__(self):
        return len(self.value)

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, ', '.join(self))

    def __str__(self):
        return self.sep.join(iter(self))

    def __lt__(self, other):
        return tuple(self) < tuple(ImageMode(other))

    def __eq__(self, other):
        return tuple(self) == tuple(ImageMode(other))

    def __hash__(self):
        return hash(tuple(self))

    # def __add__(self, other):
    #     """Append a component."""
    #     return self.__class__(self.value + (other,))

    # def __sub__(self, other):
    #     """Remove a tailing component."""
    #     v = self.value
    #     if v[-1] == other:
    #         v = v[:-1]
    #     return self.__class__(v)

def _fmt_seq(seq):
    return '-'.join(str(x) for x in seq if x is not None)

def namedtuple_fmt(*args, **kwargs):
    t = namedtuple(*args, **kwargs)
    t.__str__ = _fmt_seq
    return t

ImageMode_NT = namedtuple_fmt('ImageMode_NT', ['modality', 'model', 'param'])
ImageTarget = namedtuple_fmt('ImageTarget', ['case', 'scan', 'lesion'])
TextureSpec = namedtuple_fmt('TextureSpec', ['method', 'winsize', 'feature'])
ROISpec = namedtuple_fmt('ROISpec', ['type', 'id'])
AlgParams = namedtuple_fmt('AlgParams', ['depthmin', 'depthmax',
                                         'sidemin', 'sidemax', 'nrois'])

__all__ = list(n for n in globals() if n[:1] != '_')
<|endoftext|>


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

STAGING = 'staging'
PRODUCTION = 'production'

COMMIT_AUTHOR_NAME = 'Mimiron'
COMMIT_AUTHOR_EMAIL = ''
<commit_msg>rm(constants): remove STAGING and PRODUCTION consts<commit_after># -*- coding: utf-8 -*-

COMMIT_AUTHOR_NAME = 'Mimiron'
COMMIT_AUTHOR_EMAIL = ''
<|endoftext|>


<commit_before>from __future__ import absolute_import, division, print_function

import math

import cctbx.miller
import wx
from cctbx.crystal import symmetry
from wx.lib.agw.floatspin import EVT_FLOATSPIN, FloatSpin
from scitbx.matrix import col

class UCSettingsFrame(wx.MiniFrame):
    def __init__(self, *args, **kwds):
        super(UCSettingsFrame, self).__init__(*args, **kwds)
        szr = wx.BoxSizer(wx.VERTICAL)
        self.phil_params = args[0].params
        panel = UCSettingsPanel(self)
        self.SetSizer(szr)
        szr.Add(panel, 1, wx.EXPAND)
        szr.Fit(panel)
        self.panel = panel
        self.sizer = szr
        self.Fit()
        self.Bind(wx.EVT_CLOSE, lambda evt: self.Destroy(), self)

class UCSettingsPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        super(UCSettingsPanel, self).__init__(*args, **kwds)

        self.phil_params = args[0].phil_params

        # Needed to draw and delete the rings.  XXX Applies to
        # calibration_frame as well?
        self._pyslip = self.GetParent().GetParent().pyslip

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(sizer)

        # Number of decimal digits for distances.
        self.digits = 2

        # Wavelength control.
        beam = self._pyslip.tiles.raw_image.get_beam()
        self._wavelength = beam.get_wavelength()

        # Unit cell controls.
        if self.phil_params.calibrate_unitcell.unitcell is not None:
            self._cell = list(self.phil_params.calibrate_unitcell.unitcell.parameters())
        else:
            self._cell = [4.18, 4.72, 58.38, 89.44, 89.63, 75.85]

        if self.phil_params.calibrate_unitcell.spacegroup is not None:
            self._spacegroup = self.phil_params.calibrate_unitcell.spacegroup
        else:
            self._spacegroup = "P1"

        self._cell_control_names = [
            "uc_a_ctrl",
            "uc_b_ctrl",
            "uc_c_ctrl",
            "uc_alpha_ctrl",
            "uc_beta_ctrl",
            "uc_gamma_ctrl",
        ]

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_a = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[0],
            value=self._cell[0],
        )
        box.Add(
            self.uc_a, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="a"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_a)

        self.uc_alpha = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[3],
            value=self._cell[3],
        )
        box.Add(
            self.uc_alpha,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="alpha"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_alpha)

        sizer.Add(box)

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_b = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[1],
            value=self._cell[1],
        )
        box.Add(
            self.uc_b, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="b"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_b)

        self.uc_beta = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[4],
            value=self._cell[4],
        )
        box.Add(
            self.uc_beta, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(
            wx.StaticText(self, label="beta"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_beta)

        sizer.Add(box)

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_c = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[2],
            value=self._cell[2],
        )
        box.Add(
            self.uc_c, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="c"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_c)

        self.uc_gamma = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[5],
            value=self._cell[5],
        )
        box.Add(
            self.uc_gamma,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="gamma"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_gamma)

        sizer.Add(box)

        # Space group control
        box = wx.BoxSizer(wx.HORIZONTAL)

        self.space_group_ctrl = wx.TextCtrl(
            self, name="space group", value=self._spacegroup
        )
        box.Add(
            self.space_group_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Space group"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        self.Bind(wx.EVT_TEXT, self.OnSpaceGroup, self.space_group_ctrl)

        sizer.Add(box)

        # Distance control
        img = self.GetParent().GetParent()._img
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.distance_ctrl = FloatSpin(
            self,
            digits=self.digits,
            name="Detector Distance",
            value=img.get_detector_distance(),
        )
        self.distance_ctrl.SetIncrement(0.5)
        box.Add(
            self.distance_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtd = wx.StaticText(self, label="Detector Distance")
        box.Add(txtd, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.distance_ctrl)
        sizer.Add(box)

        # Wavelength control
        img = self.GetParent().GetParent()._img
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.wavelength_ctrl = FloatSpin(
            self, digits=4, name="Wavelength", value=img.get_wavelength()
        )
        self.wavelength_ctrl.SetIncrement(0.05)
        box.Add(
            self.wavelength_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtw = wx.StaticText(self, label="Wavelength")
        box.Add(txtw, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.wavelength_ctrl)
        sizer.Add(box)

        # d_min control
        if self.phil_params.calibrate_unitcell.d_min is not None:
            self.d_min = self.phil_params.calibrate_unitcell.d_min
        else:
            self.d_min = 3.5
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.d_min_ctrl = FloatSpin(
            self, digits=self.digits, name="d_min", value=self.d_min
        )
        box.Add(
            self.d_min_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtd = wx.StaticText(self, label="Highest resolution for ring display")
        box.Add(txtd, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.d_min_ctrl)
        sizer.Add(box)

        # Centering controls.
        self._center = [0, 0]
        box = wx.BoxSizer(wx.HORIZONTAL)

        self.spinner_fast = FloatSpin(
            self, digits=self.digits, name="fast_ctrl", value=self._center[0]
        )
        box.Add(
            self.spinner_fast,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Center fast"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCenter, self.spinner_fast)

        self.spinner_slow = FloatSpin(
            self, digits=self.digits, name="slow_ctrl", value=self._center[1]
        )
        box.Add(
            self.spinner_slow,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Center slow"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        origin_box = wx.BoxSizer(wx.HORIZONTAL)
        self.origin = wx.StaticText(self, label="")
        origin_box.Add(self.origin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCenter, self.spinner_slow)

        sizer.Add(box)
        sizer.Add(origin_box)

        self.DrawRings()

    def __del__(self):
        if hasattr(self, "_ring_layer") and self._ring_layer is not None:
            self._pyslip.DeleteLayer(self._ring_layer)

    def OnSpinCenter(self, event):
        obj = event.EventObject
        name = obj.GetName()

        if name == "fast_ctrl":
            self._center[0] = obj.GetValue()
        elif name == "slow_ctrl":
            self._center[1] = obj.GetValue()

        self.DrawRings()

    def OnSpinCell(self, event):
        obj = event.EventObject
        name = obj.GetName()

        self._cell[self._cell_control_names.index(name)] = obj.GetValue()

        self.DrawRings()

    def OnSpin(self, event):
        self.DrawRings()

    def OnSpaceGroup(self, event):
        obj = event.EventObject
        self._spacegroup = obj.GetValue()

        self.DrawRings()

    def _draw_rings_layer(self, dc, data, map_rel):
        """Draw a points layer.

        dc       the device context to draw on
        data     an iterable of point tuples:
                 (x, y, place, radius, colour, x_off, y_off, pdata)
        map_rel  points relative to map if True, MUST BE TRUE for lightweight
        Assumes all points are the same colour, saving 100's of ms.
        """

        assert map_rel is True
        if len(data) == 0:
            return
        (lon, lat, place, radius, colour, x_off, y_off, pdata) = data[0]

        scale = 2 ** self._pyslip.tiles.zoom_level

        # Draw points on map/view, using transparency if implemented.
        try:
            dc = wx.GCDC(dc)
        except NotImplementedError:
            pass
        dc.SetPen(wx.Pen(colour))
        dc.SetBrush(wx.Brush(colour, wx.TRANSPARENT))
        for (lon, lat, place, radius, colour, x_off, y_off, pdata) in data:
            (x, y) = self._pyslip.ConvertGeo2View((lon, lat))
            dc.DrawCircle(x, y, radius * scale)

    def DrawRings(self):
        frame = self.GetParent().GetParent()

        try:
            uc = symmetry(
                unit_cell=self._cell, space_group_symbol=str(self._spacegroup)
            )
            hkl_list = cctbx.miller.build_set(
                uc, False, d_min=self.d_min_ctrl.GetValue()
            )
        except Exception as e:
            frame.update_statusbar(str(e))
            return

        frame.update_statusbar(
            "%d %d %d %d %d %d, " % tuple(self._cell)
            + "number of indices: %d" % len(hkl_list.indices())
        )

        spacings = list(hkl_list.d_spacings())
        print("Printing spacings, len: %s" % len(spacings))

        def cmp(a, b):
            if a[1] > b[1]:
                return 1
            elif a[1] < b[1]:
                return -1
            return 0

        from functools import cmp_to_key  # https://github.com/dials/dials/issues/1190

        spacings = sorted(spacings, key=cmp_to_key(cmp), reverse=True)

        for d in spacings:
            print(d)

        detector = self._pyslip.tiles.raw_image.get_detector()

        wavelength = float(self.wavelength_ctrl.GetValue())
        distance = float(self.distance_ctrl.GetValue())
        pixel_size = detector[0].get_pixel_size()[
            0
        ]  # FIXME assumes square pixels, and that all panels use same pixel size

        twotheta = hkl_list.two_theta(wavelength=wavelength)
        L_mm = []
        L_pixels = []
        for tt in twotheta:
            L_mm.append(distance * math.tan(tt[1]))
        for lmm in L_mm:
            L_pixels.append(lmm / pixel_size)

        xrayframe = self.GetParent().GetParent()
        panel_id, beam_pixel_fast, beam_pixel_slow = xrayframe.get_beam_center_px()

        if len(detector) > 1:
            (
                beam_pixel_slow,
                beam_pixel_fast,
            ) = xrayframe.pyslip.tiles.flex_image.tile_readout_to_picture(
                panel_id, beam_pixel_slow - 0.5, beam_pixel_fast - 0.5
            )

        center = self._pyslip.tiles.picture_fast_slow_to_map_relative(
            beam_pixel_fast + self._center[0], beam_pixel_slow + self._center[1]
        )

        # XXX Transparency?
        ring_data = [
            (center[0], center[1], {"colour": "red", "radius": pxl}) for pxl in L_pixels
        ]

        # Remove the old ring layer, and draw a new one.
        if hasattr(self, "_ring_layer") and self._ring_layer is not None:
            self._pyslip.DeleteLayer(self._ring_layer)
            self._ring_layer = None
        self._ring_layer = self._pyslip.AddPointLayer(
            ring_data,
            map_rel=True,
            visible=True,
            show_levels=[-3, -2, -1, 0, 1, 2, 3, 4, 5],
            renderer=self._draw_rings_layer,
            name="<ring_layer>",
        )
        panel = detector[0]
        fast = col(panel.get_fast_axis())
        slow = col(panel.get_slow_axis())
        norm = col(panel.get_normal())
        x = -panel.pixel_to_millimeter(self._center)[0]
        y = -panel.pixel_to_millimeter(self._center)[1]
        z = -(panel.get_distance() - distance)
        origin = (fast * x + slow * y + norm * z) + col(panel.get_origin())
        self.origin.SetLabel("Panel 0 origin: %f, %f, %f" % origin.elems)
<commit_msg>remove cmp() function, #1190<commit_after>from __future__ import absolute_import, division, print_function

import math

import cctbx.miller
import wx
from cctbx.crystal import symmetry
from scitbx.matrix import col
from wx.lib.agw.floatspin import EVT_FLOATSPIN, FloatSpin

class UCSettingsFrame(wx.MiniFrame):
    def __init__(self, *args, **kwds):
        super(UCSettingsFrame, self).__init__(*args, **kwds)
        szr = wx.BoxSizer(wx.VERTICAL)
        self.phil_params = args[0].params
        panel = UCSettingsPanel(self)
        self.SetSizer(szr)
        szr.Add(panel, 1, wx.EXPAND)
        szr.Fit(panel)
        self.panel = panel
        self.sizer = szr
        self.Fit()
        self.Bind(wx.EVT_CLOSE, lambda evt: self.Destroy(), self)

class UCSettingsPanel(wx.Panel):
    def __init__(self, *args, **kwds):
        super(UCSettingsPanel, self).__init__(*args, **kwds)

        self.phil_params = args[0].phil_params

        # Needed to draw and delete the rings.  XXX Applies to
        # calibration_frame as well?
        self._pyslip = self.GetParent().GetParent().pyslip

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(sizer)

        # Number of decimal digits for distances.
        self.digits = 2

        # Wavelength control.
        beam = self._pyslip.tiles.raw_image.get_beam()
        self._wavelength = beam.get_wavelength()

        # Unit cell controls.
        if self.phil_params.calibrate_unitcell.unitcell is not None:
            self._cell = list(self.phil_params.calibrate_unitcell.unitcell.parameters())
        else:
            self._cell = [4.18, 4.72, 58.38, 89.44, 89.63, 75.85]

        if self.phil_params.calibrate_unitcell.spacegroup is not None:
            self._spacegroup = self.phil_params.calibrate_unitcell.spacegroup
        else:
            self._spacegroup = "P1"

        self._cell_control_names = [
            "uc_a_ctrl",
            "uc_b_ctrl",
            "uc_c_ctrl",
            "uc_alpha_ctrl",
            "uc_beta_ctrl",
            "uc_gamma_ctrl",
        ]

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_a = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[0],
            value=self._cell[0],
        )
        box.Add(
            self.uc_a, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="a"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_a)

        self.uc_alpha = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[3],
            value=self._cell[3],
        )
        box.Add(
            self.uc_alpha,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="alpha"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_alpha)

        sizer.Add(box)

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_b = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[1],
            value=self._cell[1],
        )
        box.Add(
            self.uc_b, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="b"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_b)

        self.uc_beta = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[4],
            value=self._cell[4],
        )
        box.Add(
            self.uc_beta, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(
            wx.StaticText(self, label="beta"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_beta)

        sizer.Add(box)

        box = wx.BoxSizer(wx.HORIZONTAL)

        self.uc_c = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[2],
            value=self._cell[2],
        )
        box.Add(
            self.uc_c, 0, wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 5
        )
        box.Add(wx.StaticText(self, label="c"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_c)

        self.uc_gamma = FloatSpin(
            self,
            digits=self.digits,
            name=self._cell_control_names[5],
            value=self._cell[5],
        )
        box.Add(
            self.uc_gamma,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="gamma"), 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCell, self.uc_gamma)

        sizer.Add(box)

        # Space group control
        box = wx.BoxSizer(wx.HORIZONTAL)

        self.space_group_ctrl = wx.TextCtrl(
            self, name="space group", value=self._spacegroup
        )
        box.Add(
            self.space_group_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Space group"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        self.Bind(wx.EVT_TEXT, self.OnSpaceGroup, self.space_group_ctrl)

        sizer.Add(box)

        # Distance control
        img = self.GetParent().GetParent()._img
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.distance_ctrl = FloatSpin(
            self,
            digits=self.digits,
            name="Detector Distance",
            value=img.get_detector_distance(),
        )
        self.distance_ctrl.SetIncrement(0.5)
        box.Add(
            self.distance_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtd = wx.StaticText(self, label="Detector Distance")
        box.Add(txtd, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.distance_ctrl)
        sizer.Add(box)

        # Wavelength control
        img = self.GetParent().GetParent()._img
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.wavelength_ctrl = FloatSpin(
            self, digits=4, name="Wavelength", value=img.get_wavelength()
        )
        self.wavelength_ctrl.SetIncrement(0.05)
        box.Add(
            self.wavelength_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtw = wx.StaticText(self, label="Wavelength")
        box.Add(txtw, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.wavelength_ctrl)
        sizer.Add(box)

        # d_min control
        if self.phil_params.calibrate_unitcell.d_min is not None:
            self.d_min = self.phil_params.calibrate_unitcell.d_min
        else:
            self.d_min = 3.5
        box = wx.BoxSizer(wx.HORIZONTAL)
        self.d_min_ctrl = FloatSpin(
            self, digits=self.digits, name="d_min", value=self.d_min
        )
        box.Add(
            self.d_min_ctrl,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )

        txtd = wx.StaticText(self, label="Highest resolution for ring display")
        box.Add(txtd, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpin, self.d_min_ctrl)
        sizer.Add(box)

        # Centering controls.
        self._center = [0, 0]
        box = wx.BoxSizer(wx.HORIZONTAL)

        self.spinner_fast = FloatSpin(
            self, digits=self.digits, name="fast_ctrl", value=self._center[0]
        )
        box.Add(
            self.spinner_fast,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Center fast"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        self.Bind(EVT_FLOATSPIN, self.OnSpinCenter, self.spinner_fast)

        self.spinner_slow = FloatSpin(
            self, digits=self.digits, name="slow_ctrl", value=self._center[1]
        )
        box.Add(
            self.spinner_slow,
            0,
            wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        box.Add(
            wx.StaticText(self, label="Center slow"),
            0,
            wx.ALL | wx.ALIGN_CENTER_VERTICAL,
            5,
        )
        origin_box = wx.BoxSizer(wx.HORIZONTAL)
        self.origin = wx.StaticText(self, label="")
        origin_box.Add(self.origin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
        self.Bind(EVT_FLOATSPIN, self.OnSpinCenter, self.spinner_slow)

        sizer.Add(box)
        sizer.Add(origin_box)

        self.DrawRings()

    def __del__(self):
        if hasattr(self, "_ring_layer") and self._ring_layer is not None:
            self._pyslip.DeleteLayer(self._ring_layer)

    def OnSpinCenter(self, event):
        obj = event.EventObject
        name = obj.GetName()

        if name == "fast_ctrl":
            self._center[0] = obj.GetValue()
        elif name == "slow_ctrl":
            self._center[1] = obj.GetValue()

        self.DrawRings()

    def OnSpinCell(self, event):
        obj = event.EventObject
        name = obj.GetName()

        self._cell[self._cell_control_names.index(name)] = obj.GetValue()

        self.DrawRings()

    def OnSpin(self, event):
        self.DrawRings()

    def OnSpaceGroup(self, event):
        obj = event.EventObject
        self._spacegroup = obj.GetValue()

        self.DrawRings()

    def _draw_rings_layer(self, dc, data, map_rel):
        """Draw a points layer.

        dc       the device context to draw on
        data     an iterable of point tuples:
                 (x, y, place, radius, colour, x_off, y_off, pdata)
        map_rel  points relative to map if True, MUST BE TRUE for lightweight
        Assumes all points are the same colour, saving 100's of ms.
        """

        assert map_rel is True
        if len(data) == 0:
            return
        (lon, lat, place, radius, colour, x_off, y_off, pdata) = data[0]

        scale = 2 ** self._pyslip.tiles.zoom_level

        # Draw points on map/view, using transparency if implemented.
        try:
            dc = wx.GCDC(dc)
        except NotImplementedError:
            pass
        dc.SetPen(wx.Pen(colour))
        dc.SetBrush(wx.Brush(colour, wx.TRANSPARENT))
        for (lon, lat, place, radius, colour, x_off, y_off, pdata) in data:
            (x, y) = self._pyslip.ConvertGeo2View((lon, lat))
            dc.DrawCircle(x, y, radius * scale)

    def DrawRings(self):
        frame = self.GetParent().GetParent()

        try:
            uc = symmetry(
                unit_cell=self._cell, space_group_symbol=str(self._spacegroup)
            )
            hkl_list = cctbx.miller.build_set(
                uc, False, d_min=self.d_min_ctrl.GetValue()
            )
        except Exception as e:
            frame.update_statusbar(str(e))
            return

        frame.update_statusbar(
            "%d %d %d %d %d %d, " % tuple(self._cell)
            + "number of indices: %d" % len(hkl_list.indices())
        )

        spacings = sorted(hkl_list.d_spacings(), key=lambda s: s[1], reverse=True)
        print("Printing spacings, len: %s" % len(spacings))

        for d in spacings:
            print(d)

        detector = self._pyslip.tiles.raw_image.get_detector()

        wavelength = float(self.wavelength_ctrl.GetValue())
        distance = float(self.distance_ctrl.GetValue())
        pixel_size = detector[0].get_pixel_size()[
            0
        ]  # FIXME assumes square pixels, and that all panels use same pixel size

        twotheta = hkl_list.two_theta(wavelength=wavelength)
        L_mm = []
        L_pixels = []
        for tt in twotheta:
            L_mm.append(distance * math.tan(tt[1]))
        for lmm in L_mm:
            L_pixels.append(lmm / pixel_size)

        xrayframe = self.GetParent().GetParent()
        panel_id, beam_pixel_fast, beam_pixel_slow = xrayframe.get_beam_center_px()

        if len(detector) > 1:
            (
                beam_pixel_slow,
                beam_pixel_fast,
            ) = xrayframe.pyslip.tiles.flex_image.tile_readout_to_picture(
                panel_id, beam_pixel_slow - 0.5, beam_pixel_fast - 0.5
            )

        center = self._pyslip.tiles.picture_fast_slow_to_map_relative(
            beam_pixel_fast + self._center[0], beam_pixel_slow + self._center[1]
        )

        # XXX Transparency?
        ring_data = [
            (center[0], center[1], {"colour": "red", "radius": pxl}) for pxl in L_pixels
        ]

        # Remove the old ring layer, and draw a new one.
        if hasattr(self, "_ring_layer") and self._ring_layer is not None:
            self._pyslip.DeleteLayer(self._ring_layer)
            self._ring_layer = None
        self._ring_layer = self._pyslip.AddPointLayer(
            ring_data,
            map_rel=True,
            visible=True,
            show_levels=[-3, -2, -1, 0, 1, 2, 3, 4, 5],
            renderer=self._draw_rings_layer,
            name="<ring_layer>",
        )
        panel = detector[0]
        fast = col(panel.get_fast_axis())
        slow = col(panel.get_slow_axis())
        norm = col(panel.get_normal())
        x = -panel.pixel_to_millimeter(self._center)[0]
        y = -panel.pixel_to_millimeter(self._center)[1]
        z = -(panel.get_distance() - distance)
        origin = (fast * x + slow * y + norm * z) + col(panel.get_origin())
        self.origin.SetLabel("Panel 0 origin: %f, %f, %f" % origin.elems)
<|endoftext|>


<commit_before>"""Miscellaneous types."""

from collections import namedtuple
from functools import total_ordering

# With older Python version, pathlib2 might be preferred.
try:
    from pathlib2 import Path, PurePath
except ImportError:
    from pathlib import Path, PurePath

@total_ordering
class ImageMode(object):
    """Image mode identifier."""
    def __init__(self, value, sep='-'):
        """Initialize with a string or a sequence."""
        if isinstance(value, str):
            value = value.split(sep)
        self.value = tuple(value)
        self.sep = sep

    def __iter__(self):
        return iter(self.value)

    def __getitem__(self, key):
        return self.__class__(self.value[key])

    def __len__(self):
        return len(self.value)

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, ', '.join(self))

    def __str__(self):
        return self.sep.join(iter(self))

    def __lt__(self, other):
        return tuple(self) < tuple(ImageMode(other))

    def __eq__(self, other):
        return tuple(self) == tuple(ImageMode(other))

    # def __hash__(self):
    #     return hash(tuple(self))

@total_ordering
class GleasonScore(object):
    """Gleason score is a two or three-value measure of prostate cancer
    severity.
    """
    # "Standard" thresholds for GS groups [low, intermediate, high]:
    # - low: maximum 3 anywhere;
    # - intermediate: 4 as secondary, or tertiary w/o 5;
    # - high: the rest.
    THRESHOLDS_STANDARD = ('3+3', '3+4')

    def __init__(self, score):
        """Intialize with a sequence or a string like '3+4+5' (third digit is
        optional).
        """
        if isinstance(score, str):
            s = score.split('+')
        elif isinstance(score, GleasonScore):
            s = score.score
        else:
            s = score
        s = tuple(int(x) for x in s)
        if len(s) == 2:
            s += (0,)  # Internal representation always has three digits.
        if len(s) != 3:
            raise ValueError('Invalid gleason score: {}'.format(score))
        self.score = s

    def __iter__(self):
        score = self.score
        if not score[-1]:
            score = score[0:-1]  # Drop trailing zero.
        return iter(score)

    def __repr__(self):
        return '+'.join(str(x) for x in iter(self))

    def __lt__(self, other):
        return self.score < GleasonScore(other).score

    def __eq__(self, other):
        return self.score == GleasonScore(other).score

    def __hash__(self):
        return hash(tuple(self))

class Lesion(object):
    """Lesion is a lump of cancer tissue."""
    def __init__(self, index, score, location):
        self.index = int(index)  # No. in patient.
        self.score = GleasonScore(score)  # Gleason score.
        self.location = str(location).lower()  # PZ or CZ.

    def __iter__(self):
        return iter([self.index, self.score, self.location])

    # def __hash__(self):
    #     return hash(tuple(self))

    def __repr__(self):
        return repr(tuple(self))

    def __str__(self):
        return '({})'.format(','.join(str(x) for x in self))

    def __eq__(self, other):
        return (self.score, self.location) == (other.score, other.location)

@total_ordering
class Patient(object):
    """Patient case."""
    def __init__(self, num, name, scans, lesions):
        self.num = int(num)
        self.name = str(name).lower()
        self.scans = scans
        self.lesions = lesions
        self.score = lesions[0].score  # For backwards compatibility.

    def __repr__(self):
        return repr(self._astuple())

    # def __hash__(self):
    #     return hash(self._astuple())

    def __eq__(self, other):
        return self._astuple() == other._astuple()

    def __lt__(self, other):
        return self._astuple() < other._astuple()

    def _astuple(self):
        return self.num, self.name, self.scans, self.lesions

def namedtuple_fmt(*args, **kwargs):
    """Namedtuple with added formatting and parsing."""
    sep = kwargs.pop('sep', '-')

    def _fmt_seq(seq):
        if not seq:
            return ''
        if seq[-1] is None:
            return _fmt_seq(seq[:-1])
        return sep.join(str(x) for x in seq)

    @classmethod
    def _parse(cls, s):
        return cls(*(x or None for x in s.split(sep)))

    t = namedtuple(*args, **kwargs)
    t.__str__ = _fmt_seq
    t._parse = _parse
    return t

# NOTE: Experimental.
ImageMode_NT = namedtuple_fmt('ImageMode_NT', ['modality', 'model', 'param'])
GleasonScore_NT = namedtuple_fmt('GleasonScore_NT', ['primary', 'secondary',
                                                     'tertiary'], sep='+')

ImageTarget = namedtuple_fmt('ImageTarget', ['case', 'scan', 'lesion'])
TextureSpec = namedtuple_fmt('TextureSpec', ['method', 'winsize', 'feature'])
ROISpec = namedtuple_fmt('ROISpec', ['type', 'id'])
AlgParams = namedtuple_fmt('AlgParams', ['depthmin', 'depthmax',
                                         'sidemin', 'sidemax', 'nrois'])

__all__ = list(n for n in globals() if n[:1] != '_')
<commit_msg>pycodestyle ignore pathlib/pathlib2 selection<commit_after>"""Miscellaneous types."""

from collections import namedtuple
from functools import total_ordering

# With older Python version, pathlib2 might be preferred.
try:
    from pathlib2 import Path, PurePath
except ImportError:
    from pathlib import Path, PurePath  # noqa

@total_ordering
class ImageMode(object):
    """Image mode identifier."""
    def __init__(self, value, sep='-'):
        """Initialize with a string or a sequence."""
        if isinstance(value, str):
            value = value.split(sep)
        self.value = tuple(value)
        self.sep = sep

    def __iter__(self):
        return iter(self.value)

    def __getitem__(self, key):
        return self.__class__(self.value[key])

    def __len__(self):
        return len(self.value)

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, ', '.join(self))

    def __str__(self):
        return self.sep.join(iter(self))

    def __lt__(self, other):
        return tuple(self) < tuple(ImageMode(other))

    def __eq__(self, other):
        return tuple(self) == tuple(ImageMode(other))

    # def __hash__(self):
    #     return hash(tuple(self))

@total_ordering
class GleasonScore(object):
    """Gleason score is a two or three-value measure of prostate cancer
    severity.
    """
    # "Standard" thresholds for GS groups [low, intermediate, high]:
    # - low: maximum 3 anywhere;
    # - intermediate: 4 as secondary, or tertiary w/o 5;
    # - high: the rest.
    THRESHOLDS_STANDARD = ('3+3', '3+4')

    def __init__(self, score):
        """Intialize with a sequence or a string like '3+4+5' (third digit is
        optional).
        """
        if isinstance(score, str):
            s = score.split('+')
        elif isinstance(score, GleasonScore):
            s = score.score
        else:
            s = score
        s = tuple(int(x) for x in s)
        if len(s) == 2:
            s += (0,)  # Internal representation always has three digits.
        if len(s) != 3:
            raise ValueError('Invalid gleason score: {}'.format(score))
        self.score = s

    def __iter__(self):
        score = self.score
        if not score[-1]:
            score = score[0:-1]  # Drop trailing zero.
        return iter(score)

    def __repr__(self):
        return '+'.join(str(x) for x in iter(self))

    def __lt__(self, other):
        return self.score < GleasonScore(other).score

    def __eq__(self, other):
        return self.score == GleasonScore(other).score

    def __hash__(self):
        return hash(tuple(self))

class Lesion(object):
    """Lesion is a lump of cancer tissue."""
    def __init__(self, index, score, location):
        self.index = int(index)  # No. in patient.
        self.score = GleasonScore(score)  # Gleason score.
        self.location = str(location).lower()  # PZ or CZ.

    def __iter__(self):
        return iter([self.index, self.score, self.location])

    # def __hash__(self):
    #     return hash(tuple(self))

    def __repr__(self):
        return repr(tuple(self))

    def __str__(self):
        return '({})'.format(','.join(str(x) for x in self))

    def __eq__(self, other):
        return (self.score, self.location) == (other.score, other.location)

@total_ordering
class Patient(object):
    """Patient case."""
    def __init__(self, num, name, scans, lesions):
        self.num = int(num)
        self.name = str(name).lower()
        self.scans = scans
        self.lesions = lesions
        self.score = lesions[0].score  # For backwards compatibility.

    def __repr__(self):
        return repr(self._astuple())

    # def __hash__(self):
    #     return hash(self._astuple())

    def __eq__(self, other):
        return self._astuple() == other._astuple()

    def __lt__(self, other):
        return self._astuple() < other._astuple()

    def _astuple(self):
        return self.num, self.name, self.scans, self.lesions

def namedtuple_fmt(*args, **kwargs):
    """Namedtuple with added formatting and parsing."""
    sep = kwargs.pop('sep', '-')

    def _fmt_seq(seq):
        if not seq:
            return ''
        if seq[-1] is None:
            return _fmt_seq(seq[:-1])
        return sep.join(str(x) for x in seq)

    @classmethod
    def _parse(cls, s):
        return cls(*(x or None for x in s.split(sep)))

    t = namedtuple(*args, **kwargs)
    t.__str__ = _fmt_seq
    t._parse = _parse
    return t

# NOTE: Experimental.
ImageMode_NT = namedtuple_fmt('ImageMode_NT', ['modality', 'model', 'param'])
GleasonScore_NT = namedtuple_fmt('GleasonScore_NT', ['primary', 'secondary',
                                                     'tertiary'], sep='+')

ImageTarget = namedtuple_fmt('ImageTarget', ['case', 'scan', 'lesion'])
TextureSpec = namedtuple_fmt('TextureSpec', ['method', 'winsize', 'feature'])
ROISpec = namedtuple_fmt('ROISpec', ['type', 'id'])
AlgParams = namedtuple_fmt('AlgParams', ['depthmin', 'depthmax',
                                         'sidemin', 'sidemax', 'nrois'])

__all__ = list(n for n in globals() if n[:1] != '_')
<|endoftext|>


<commit_before>import collections
import datetime
import logging
import numpy as np
import requests
import multiprocessing

from bs4 import BeautifulSoup

from django.core.management.base import BaseCommand

logger = logging.getLogger(__name__)

Station = collections.namedtuple('Station', ['station_id', 'storages', 'station_name'])
Storage = collections.namedtuple('Storage', ['date', 'level_af'])

class Command(BaseCommand):
    def handle(self, *args, **options):
        """
        Gets the historical storage levels (in acre-feet) for each reservoir in CA. Then estimates when all of them
        will reach zero, using linear regression.
        """
        stations = get_storages(get_stations())

        with multiprocessing.Pool(processes=4) as pool:
            zero_dates = pool.map(_worker, stations)

        zero_dates = list(filter(lambda x: x is not None, zero_dates))

        for zd, st in zero_dates:
            if zd > datetime.date.today():
                logger.info(st.station_name + " will dry out on " + repr(zd))
            else:
                logger.info(st.station_name + "(" + st.station_id + ") isn't drying out just yet: " + repr(zd))

        zero_dates = filter(lambda x: x[0] > datetime.date.today(), zero_dates)
        max_el = max(zero_dates, key=lambda x: x[0])
        logger.info("The last reservoir ({}) will dry out on: " .format(max_el[1].station_id) + repr(max_el))

        from catastrophe_clock.models import Catastrophe  # import here to avoid error loading django w/ multiprocessing
        Catastrophe.objects.get_or_create(
            name="California dries up",
            arrival_date=datetime.datetime(max_el[0].year, max_el[0].month, max_el[0].day)
        )

def _worker(station):
    """
    Used for our pool, but can't be a local object to the class, or something, because it must be pickleable.
    """
    try:
        return get_zero_date(station), station
    except ValueError as e:
        logger.error("Couldn't calculate a zero date for {sn} ({sid}).".format(sn=station.station_name,
                                                                               sid=station.station_id))
        logger.error(e)
        return None

def get_stations() -> [Station]:
    """
    Gets a list of stations for which data is collected.
    The `storages` property must still be filled.
    :return: A list of Station objects.
    """
    blacklist = ["own"]  # station_ids to exclude, for various reasons
    resp = requests.get("http://cdec.water.ca.gov/misc/daily_res.html")
    soup = BeautifulSoup(resp.content, "html.parser")
    trs = soup.find_all("tr")
    stations = []
    for tr in trs:
        if tr.find("th") or len(tr.find_all("td")) == 1:
            continue
        station_name = tr.td.a.text
        station_id = tr.td.next_sibling.next_sibling.b.text
        if station_id not in blacklist:
            stations.append(Station(station_id, [], station_name))
    return stations

def get_storage(station : Station, start_date=datetime.date(2012, 1, 1)) -> Station:
    """
    Takes a Station object and populates the `storages` property. Returns a new Station.
    Returns data from 2012 to present.

    :param station: A list of Stations to populate
    :param start_date: When to start getting data from. A datetime.date object.
    :return: A new Station
    """
    end_date = datetime.date.today()
    query_addr = ("http://cdec.water.ca.gov/cgi-progs/getDailyCSV?station_id={station_id}"
                  "&dur_code=D&sensor_num=15&start_date={sd_year}/{sd_month}/{sd_day}"
                  "&end_date={ed_year}/{ed_month}/{ed_day}")
    resp = requests.get(query_addr.format(
        station_id=station.station_id,
        sd_year=start_date.year,
        sd_month=start_date.month,
        sd_day=start_date.day,
        ed_year=end_date.year,
        ed_month=end_date.month,
        ed_day=end_date.day
    ))
    new_station = Station(station_id=station.station_id, storages=_parse_raw_data(resp.text),
                          station_name=station.station_name)
    return new_station

def _parse_raw_data(raw_data):
    """
    CA's site has a weird format... :(
    We'll parse it here, burning the headers/footers and returning only storage levels

    :param raw_data: a string, of the type returned by the http://cdec.water.ca.gov/ endpoints.
    :return: A list Storage objects.
    """
    storages = []
    for line in raw_data.splitlines():
        if line.startswith("'"):
            continue
        words = line.split(",")
        year = int(words[2])
        month = int(words[3])
        for day, storage in enumerate(words[4:], 1):
            day = int(day)
            if storage != "m":  # that value indicates it's missing
                storages.append(Storage(
                    date=datetime.date(year, month, day),
                    level_af=int(storage)
                ))
    if len(storages) == 0:
        raise ValueError("Couldn't parse any useful data.")
    return storages

def get_storages(stations: [Station]) -> [Station]:
    """
    Takes a list of Stations and populates the `storages` property. Returns a new list
    :param stations: A list of Stations to populate
    :return: A new list of Stations
    """
    new_stations = []
    for station in stations:
        try:
            new_stations.append(get_storage(station))
        except ValueError as e:
            logger.error("Unable to get storage data for {sn} ({sid}).".format(sn=station.station_name,
                                                                               sid=station.station_id))
            logger.error(e)
    return new_stations

def get_zero_date(station):
    """
    Perform a linear regression on the station's storages levels_af property, to determine when it will reach zero.

    :param station:
    :return:
    """
    dates_ord, levels_af = [s.date.toordinal() for s in station.storages], [s.level_af for s in station.storages]
    A = np.vstack([dates_ord, np.ones(len(dates_ord))]).T
    rate, intercept = np.linalg.lstsq(A, levels_af)[0]
    if rate > 0:
        raise ValueError("Rate must be negative.")
    zero_date_ord = int(-1*intercept / rate)
    zero_date = datetime.date.fromordinal(zero_date_ord)
    line = [rate*dates_ord[i] + intercept for i in range(0, len(dates_ord))]
    return zero_date
<commit_msg>fixup<commit_after>import collections
import datetime
import logging
import numpy as np
import requests
import multiprocessing

from bs4 import BeautifulSoup

from django.core.management.base import BaseCommand

logger = logging.getLogger(__name__)

Station = collections.namedtuple('Station', ['station_id', 'storages', 'station_name'])
Storage = collections.namedtuple('Storage', ['date', 'level_af'])

class Command(BaseCommand):
    def handle(self, *args, **options):
        """
        Gets the historical storage levels (in acre-feet) for each reservoir in CA. Then estimates when all of them
        will reach zero, using linear regression.
        """
        stations = get_storages(get_stations())

        with multiprocessing.Pool(processes=4) as pool:
            zero_dates = pool.map(_worker, stations)

        zero_dates = list(filter(lambda x: x is not None, zero_dates))

        for zd, st in zero_dates:
            if zd > datetime.date.today():
                logger.info(st.station_name + " will dry out on " + repr(zd))
            else:
                logger.info(st.station_name + "(" + st.station_id + ") isn't drying out just yet: " + repr(zd))

        zero_dates = filter(lambda x: x[0] > datetime.date.today(), zero_dates)
        max_el = max(zero_dates, key=lambda x: x[0])
        logger.info("The last reservoir ({}) will dry out on: " .format(max_el[1].station_id) + repr(max_el[0]))

        from catastrophe_clock.models import Catastrophe  # import here to avoid error loading django w/ multiprocessing
        Catastrophe.objects.get_or_create(
            name="California dries up",
            arrival_date=datetime.datetime(max_el[0].year, max_el[0].month, max_el[0].day)
        )

def _worker(station):
    """
    Used for our pool, but can't be a local object to the class, or something, because it must be pickleable.
    """
    try:
        return get_zero_date(station), station
    except ValueError as e:
        logger.error("Couldn't calculate a zero date for {sn} ({sid}).".format(sn=station.station_name,
                                                                               sid=station.station_id))
        logger.error(e)
        return None

def get_stations() -> [Station]:
    """
    Gets a list of stations for which data is collected.
    The `storages` property must still be filled.
    :return: A list of Station objects.
    """
    blacklist = ["OWN"]  # station_ids to exclude, for various reasons
    resp = requests.get("http://cdec.water.ca.gov/misc/daily_res.html")
    soup = BeautifulSoup(resp.content, "html.parser")
    trs = soup.find_all("tr")
    stations = []
    for tr in trs:
        if tr.find("th") or len(tr.find_all("td")) == 1:
            continue
        station_name = tr.td.a.text
        station_id = tr.td.next_sibling.next_sibling.b.text
        if station_id.upper() not in blacklist:
            stations.append(Station(station_id, [], station_name))
    return stations

def get_storage(station : Station, start_date=datetime.date(2012, 1, 1)) -> Station:
    """
    Takes a Station object and populates the `storages` property. Returns a new Station.
    Returns data from 2012 to present.

    :param station: A list of Stations to populate
    :param start_date: When to start getting data from. A datetime.date object.
    :return: A new Station
    """
    end_date = datetime.date.today()
    query_addr = ("http://cdec.water.ca.gov/cgi-progs/getDailyCSV?station_id={station_id}"
                  "&dur_code=D&sensor_num=15&start_date={sd_year}/{sd_month}/{sd_day}"
                  "&end_date={ed_year}/{ed_month}/{ed_day}")
    resp = requests.get(query_addr.format(
        station_id=station.station_id,
        sd_year=start_date.year,
        sd_month=start_date.month,
        sd_day=start_date.day,
        ed_year=end_date.year,
        ed_month=end_date.month,
        ed_day=end_date.day
    ))
    new_station = Station(station_id=station.station_id, storages=_parse_raw_data(resp.text),
                          station_name=station.station_name)
    return new_station

def _parse_raw_data(raw_data):
    """
    CA's site has a weird format... :(
    We'll parse it here, burning the headers/footers and returning only storage levels

    :param raw_data: a string, of the type returned by the http://cdec.water.ca.gov/ endpoints.
    :return: A list Storage objects.
    """
    storages = []
    for line in raw_data.splitlines():
        if line.startswith("'"):
            continue
        words = line.split(",")
        year = int(words[2])
        month = int(words[3])
        for day, storage in enumerate(words[4:], 1):
            day = int(day)
            if storage != "m":  # that value indicates it's missing
                storages.append(Storage(
                    date=datetime.date(year, month, day),
                    level_af=int(storage)
                ))
    if len(storages) == 0:
        raise ValueError("Couldn't parse any useful data.")
    return storages

def get_storages(stations: [Station]) -> [Station]:
    """
    Takes a list of Stations and populates the `storages` property. Returns a new list
    :param stations: A list of Stations to populate
    :return: A new list of Stations
    """
    new_stations = []
    for station in stations:
        try:
            new_stations.append(get_storage(station))
        except ValueError as e:
            logger.error("Unable to get storage data for {sn} ({sid}).".format(sn=station.station_name,
                                                                               sid=station.station_id))
            logger.error(e)
    return new_stations

def get_zero_date(station):
    """
    Perform a linear regression on the station's storages levels_af property, to determine when it will reach zero.

    :param station:
    :return:
    """
    dates_ord, levels_af = [s.date.toordinal() for s in station.storages], [s.level_af for s in station.storages]
    A = np.vstack([dates_ord, np.ones(len(dates_ord))]).T
    rate, intercept = np.linalg.lstsq(A, levels_af)[0]
    if rate > 0:
        raise ValueError("Rate must be negative.")
    zero_date_ord = int(-1*intercept / rate)
    zero_date = datetime.date.fromordinal(zero_date_ord)
    line = [rate*dates_ord[i] + intercept for i in range(0, len(dates_ord))]
    return zero_date
<|endoftext|>