<commit_before>from datetime import datetime

from django.db import models
from django.contrib.auth.models import User

import tagging
from tagging.fields import TagField

TASK_STATUS_CHOICES = (
        ("Unpublished", "Unpublished"),
        ("Open", "Open"),
        ("Locked", "Locked"),
        ("Working", "Working"),
        ("Closed", "Closed"),
        ("Deleted", "Deleted"),
        ("Completed", "Completed"))

TB_STATUS_CHOICES = (
    ("Unpublished", "Unpublished"),
    ("Open", "Open"),
    ("All tasks have users selected", "All tasks have users selected"),
    ("Completed", "Completed"))

UPLOADS_DIR = "/pytask/static/uploads"

class Task(models.Model):

    title = models.CharField(
      max_length=1024, verbose_name=u"Title",
      help_text=u"Keep it simple and below 100 chars.")

    desc = models.TextField(verbose_name=u"Description")

    status = models.CharField(max_length=255,
                              choices=TASK_STATUS_CHOICES,
                              default="Unpublished")

    tags_field = TagField(verbose_name=u"Tags",
                          help_text=u"Give tags separated by commas")

    pynts = models.PositiveSmallIntegerField(
      help_text=u"Number of Pynts a user gets on completing the task")

    created_by = models.ForeignKey(User,
                                   related_name="created_tasks")

    approved_by = models.ForeignKey(User, blank=True, null=True,
                                    related_name="approved_tasks")

    reviewers = models.ManyToManyField(User, blank=True, null=True,
                                       related_name="reviewing_tasks")

    claimed_users = models.ManyToManyField(User, blank=True, null=True,
                                           related_name="claimed_tasks")

    selected_users = models.ManyToManyField(User, blank=True, null=True,
                                            related_name="selected_tasks")

    creation_datetime = models.DateTimeField(auto_now_add=True)

    approval_datetime = models.DateTimeField(blank=True, null=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

    def __unicode__(self):
        return unicode(self.title)

class TaskComment(models.Model):

    task = models.ForeignKey('Task', related_name="comments")

    data = models.TextField(verbose_name='Comment')

    commented_by = models.ForeignKey(User,
                                     related_name="commented_taskcomments")

    deleted_by = models.ForeignKey(User, null=True, blank=True,
                                   related_name="deleted_taskcomments")

    comment_datetime = models.DateTimeField(auto_now_add=True)

    is_deleted = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

    def __unicode__(self):
        return unicode(self.task.title)

class TaskClaim(models.Model):

    task = models.ForeignKey('Task', related_name="claims")

    claimed_by = models.ForeignKey(User,
                                   related_name="claimed_claims")
    proposal = models.TextField()

    claim_datetime = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return unicode(self.task.title)

class WorkReport(models.Model):

    task = models.ForeignKey(Task, related_name="reports")

    submitted_by = models.ForeignKey(User, null=True, blank=True,
                                     related_name="submitted_reports")

    approved_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="approved_reports")

    data = models.TextField(verbose_name="Report")

    summary = models.CharField(max_length=1024, verbose_name="Summary",
                               help_text="A one line summary")

    attachment = models.FileField(upload_to=UPLOADS_DIR)

    revision = models.PositiveIntegerField(default=0)

    submitted_at = models.DateTimeField(auto_now_add=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

class ReportComment(models.Model):

    report = models.ForeignKey('WorkReport', related_name="%(class)s_report")

    data = models.TextField()

    commented_by = models.ForeignKey(User,
                                     related_name="commented_reportcomments")

    deleted_by = models.ForeignKey(User, null=True, blank=True,
                                   related_name="deleted_reportcomments")

    comment_datetime = models.DateTimeField(auto_now_add=True)

    is_deleted = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

class PyntRequest(models.Model):
    task = models.ForeignKey(Task, related_name="pynt_requests")

    pynts = models.PositiveIntegerField(default=0, help_text="No.of pynts")

    requested_by = models.ForeignKey(User,
                                     related_name="requested_by_pynts")

    requested_for = models.ForeignKey(User,
                                     related_name="requested_for_pynts")

    responded_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="responded_requests")

    is_accepted = models.BooleanField(default=False)

    remarks = models.CharField(max_length=100, blank=True,
                               help_text="Reason in case of rejection")

    request_datetime = models.DateTimeField(auto_now_add=True)

    is_responded = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

class TextBook(models.Model):

    name = models.CharField(max_length=1024)

    chapters = models.ManyToManyField(Task, related_name="textbooks")

    tags_field = TagField(verbose_name="Tags")

    created_by = models.ForeignKey(User, related_name="created_textbooks")

    approved_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="approved_textbooks")

    status = models.CharField(max_length=255,
                              choices=TB_STATUS_CHOICES,
                              default="Unpublished")

    creation_datetime = models.DateTimeField(auto_now_add=True)

    approval_datetime = models.DateTimeField(blank=True, null=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

tagging.register(Task)
tagging.register(TextBook)
<commit_msg>Adding deprecation warning to Textbook model.<commit_after>from datetime import datetime

from django.db import models
from django.contrib.auth.models import User

import tagging
from tagging.fields import TagField

TASK_STATUS_CHOICES = (
        ("Unpublished", "Unpublished"),
        ("Open", "Open"),
        ("Locked", "Locked"),
        ("Working", "Working"),
        ("Closed", "Closed"),
        ("Deleted", "Deleted"),
        ("Completed", "Completed"))

TB_STATUS_CHOICES = (
    ("Unpublished", "Unpublished"),
    ("Open", "Open"),
    ("All tasks have users selected", "All tasks have users selected"),
    ("Completed", "Completed"))

UPLOADS_DIR = "/pytask/static/uploads"

class Task(models.Model):

    title = models.CharField(
      max_length=1024, verbose_name=u"Title",
      help_text=u"Keep it simple and below 100 chars.")

    desc = models.TextField(verbose_name=u"Description")

    status = models.CharField(max_length=255,
                              choices=TASK_STATUS_CHOICES,
                              default="Unpublished")

    tags_field = TagField(verbose_name=u"Tags",
                          help_text=u"Give tags separated by commas")

    pynts = models.PositiveSmallIntegerField(
      help_text=u"Number of Pynts a user gets on completing the task")

    created_by = models.ForeignKey(User,
                                   related_name="created_tasks")

    approved_by = models.ForeignKey(User, blank=True, null=True,
                                    related_name="approved_tasks")

    reviewers = models.ManyToManyField(User, blank=True, null=True,
                                       related_name="reviewing_tasks")

    claimed_users = models.ManyToManyField(User, blank=True, null=True,
                                           related_name="claimed_tasks")

    selected_users = models.ManyToManyField(User, blank=True, null=True,
                                            related_name="selected_tasks")

    creation_datetime = models.DateTimeField(auto_now_add=True)

    approval_datetime = models.DateTimeField(blank=True, null=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

    def __unicode__(self):
        return unicode(self.title)

class TaskComment(models.Model):

    task = models.ForeignKey('Task', related_name="comments")

    data = models.TextField(verbose_name='Comment')

    commented_by = models.ForeignKey(User,
                                     related_name="commented_taskcomments")

    deleted_by = models.ForeignKey(User, null=True, blank=True,
                                   related_name="deleted_taskcomments")

    comment_datetime = models.DateTimeField(auto_now_add=True)

    is_deleted = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

    def __unicode__(self):
        return unicode(self.task.title)

class TaskClaim(models.Model):

    task = models.ForeignKey('Task', related_name="claims")

    claimed_by = models.ForeignKey(User,
                                   related_name="claimed_claims")
    proposal = models.TextField()

    claim_datetime = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return unicode(self.task.title)

class WorkReport(models.Model):

    task = models.ForeignKey(Task, related_name="reports")

    submitted_by = models.ForeignKey(User, null=True, blank=True,
                                     related_name="submitted_reports")

    approved_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="approved_reports")

    data = models.TextField(verbose_name="Report")

    summary = models.CharField(max_length=1024, verbose_name="Summary",
                               help_text="A one line summary")

    attachment = models.FileField(upload_to=UPLOADS_DIR)

    revision = models.PositiveIntegerField(default=0)

    submitted_at = models.DateTimeField(auto_now_add=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

class ReportComment(models.Model):

    report = models.ForeignKey('WorkReport', related_name="%(class)s_report")

    data = models.TextField()

    commented_by = models.ForeignKey(User,
                                     related_name="commented_reportcomments")

    deleted_by = models.ForeignKey(User, null=True, blank=True,
                                   related_name="deleted_reportcomments")

    comment_datetime = models.DateTimeField(auto_now_add=True)

    is_deleted = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

class PyntRequest(models.Model):
    task = models.ForeignKey(Task, related_name="pynt_requests")

    pynts = models.PositiveIntegerField(default=0, help_text="No.of pynts")

    requested_by = models.ForeignKey(User,
                                     related_name="requested_by_pynts")

    requested_for = models.ForeignKey(User,
                                     related_name="requested_for_pynts")

    responded_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="responded_requests")

    is_accepted = models.BooleanField(default=False)

    remarks = models.CharField(max_length=100, blank=True,
                               help_text="Reason in case of rejection")

    request_datetime = models.DateTimeField(auto_now_add=True)

    is_responded = models.BooleanField(default=False)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

# THIS MODEL IS DEPRECATED AND WILL BE REMOVED IN FUTURE VERSIONS.
class TextBook(models.Model):
    """THIS MODEL IS DEPRECATED AND WILL BE REMOVED IN FUTURE VERSIONS.
    """

    name = models.CharField(max_length=1024)

    chapters = models.ManyToManyField(Task, related_name="textbooks")

    tags_field = TagField(verbose_name="Tags")

    created_by = models.ForeignKey(User, related_name="created_textbooks")

    approved_by = models.ForeignKey(User, null=True, blank=True,
                                    related_name="approved_textbooks")

    status = models.CharField(max_length=255,
                              choices=TB_STATUS_CHOICES,
                              default="Unpublished")

    creation_datetime = models.DateTimeField(auto_now_add=True)

    approval_datetime = models.DateTimeField(blank=True, null=True)

    last_modified = models.DateTimeField(auto_now=True,
                                         default=datetime.now())

tagging.register(Task)
tagging.register(TextBook)
<|endoftext|>


<commit_before>"""
Created on Nov 8, 2016

@author: Chris Smith -- csmith55@utk.edu
"""
import os
import numpy as np
import array
from skimage.io import imread
from .dm3_image_utils import parse_dm_header, imagedatadict_to_ndarray
from . import dm4reader

def read_image(image_path, *args, **kwargs):
    """
    Read the image file at `image_path` into a numpy array

    Parameters
    ----------
    image_path : str
        Path to the image file
    greyscale : Boolean, optional
        Should the image be converted to greyscale after reading.
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    """
    ext = os.path.splitext(image_path)[1]
    if ext == '.dm3':
        return read_dm3(image_path, *args, **kwargs)
    elif ext == '.dm4':
        return read_dm4(image_path, *args, **kwargs)
    elif ext == '.txt':
        return read_txt(image_path, *args, **kwargs), dict()
    else:
        return imread(image_path, *args, **kwargs), dict()

def read_dm3(image_path, get_parms=True):
    """
    Read an image from a dm3 file into a numpy array

    image_path : str
        Path to the image file
    get_parms : Boolean, optional
        Should the parameters from the dm3 file be returned
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    """
    image_file = open(image_path, 'rb')
    dmtag = parse_dm_header(image_file)
    img_index = -1
    image = imagedatadict_to_ndarray(dmtag['ImageList'][img_index]['ImageData'])
    image_parms = dmtag['ImageList'][img_index]['ImageTags']

    if get_parms:
        image_parms = _parse_dm3_parms(image_parms)
    else:
        image_parms = dict()

    return image, image_parms

def _parse_dm3_parms(image_parms, prefix=''):
    """
    Parses the nested image parameter dictionary and converts it to a single
    level dictionary, prepending the name of inner dictionaries to their
    keys to denote level.

    Parameters
    ----------
    image_parms : dict

    Returns
    -------

    """
    new_parms = dict()
    for name, val in image_parms.iteritems():
        # print 'name',name,'val',val
        name = '-'.join([prefix]+name.split()).strip('-')
        if isinstance(val, dict):
            new_parms.update(_parse_dm3_parms(val, name))
        elif isinstance(val, list) and isinstance(val[0], dict):
            for thing in val:
                new_parms.update(_parse_dm3_parms(thing, name))
        else:
            new_parms[name] = try_tag_to_string(val)

    return new_parms

def read_dm4(file_path, *args, **kwargs):
    """
    Read dm4 file

    :param file_path:
    :return:
    """
    get_parms = kwargs.pop('get_parms', True)
    header = kwargs.pop('header', None)

    file_parms = dict()
    dm4_file = dm4reader.DM4File.open(file_path)
    if header is None:
        tags = dm4_file.read_directory()
        header = tags.named_subdirs['ImageList'].dm4_tag
        image_list = tags.named_subdirs['ImageList'].unnamed_subdirs
    else:
        dm4_file.hfile.seek(header.offset)
        image_list = dm4_file.read_directory(header)

    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']

        x_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[0])
        y_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[1])

        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.uint16)
        image_array = np.reshape(image_array, (y_dim, x_dim))

    if get_parms:
        file_parms = parse_dm4_parms(dm4_file, tags, '')
        file_parms['Image_Tag'] = header

    return image_array, file_parms

def parse_dm4_parms(dm4_file, tag_dir, base_name=''):
    """
    Recursive function to trace the dictionary tree of the Image Data
    and build a single dictionary of all parameters

    Parameters
    ----------
    dm4_file : DM4File
        File object of the dm4 file to be parsed.
    tag_dir : dict
        Dictionary to be traced.  Has the following attributes:
            tag_dir.name : str
                Name of the directory
            tag_dir.dm4_tag : str
    base_name : str
        Base name of parameters.  Tag and subdirectory names will be appended
        for named tags and subdirectories.  Unnamed ones will recieve a number.
        Default ''.  'Root' is automatically prepended to the name.

    Returns
    -------
    parm_dict : dict()
        Dictionary containing the name:value pairs of all parameters `dm4_file`
    """
    parm_dict = dict()

    '''
    Loop over named tags
    '''
    for name in tag_dir.named_tags.keys():
        '''
        Skip Data tags.  These will be handled elseware.
        '''
        if name == 'Data':
            continue
        tag_name = '_'.join([base_name, name.replace(' ', '_')])
        if base_name == '':
            tag_name = 'Root'+tag_name
        tag_data = dm4_file.read_tag_data(tag_dir.named_tags[name])

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over unnamed tags
    '''
    for itag, tag in enumerate(tag_dir.unnamed_tags):
        tag_name = '_'.join([base_name, 'Tag_{:03d}'.format(itag)])
        if base_name == '':
            tag_name = 'Root'+tag_name

        tag_data = dm4_file.read_tag_data(tag)

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over named subdirectories
    '''
    for name in tag_dir.named_subdirs.keys():
        dir_name = '_'.join([base_name, name.replace(' ', '_')])
        sub_dir = tag_dir.named_subdirs[name]
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    '''
    Loop over unnamed subdirectories
    '''
    for idir, sub_dir in enumerate(tag_dir.unnamed_subdirs):
        dir_name = '_'.join([base_name, 'SubDir_{:03d}'.format(idir)])
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    return parm_dict

def try_tag_to_string(tag_data):
    """
    Attempt to convert array of integers into a string

    Parameters
    ----------
    tag_data : array.array
        Array of 16-bit integers

    """
    if not isinstance(tag_data, array.array):
        return tag_data

    if tag_data.typecode == 'H':
        try:
            tag_data = str(tag_data.tostring().decode('utf-16'))
        except UnicodeDecodeError:
            pass
        except UnicodeEncodeError:
            pass
        except:
            raise

    return tag_data

def read_txt(image_path, *args, **kwargs):
    """

    :param image_path:
    :param args:
    :param kwargs:
    :return:
    """

    header_lines = kwargs.pop('header_lines', 0)
    delimiter = kwargs.pop('delimiter', None)

    image = np.loadtxt(image_path,
                       skiprows=header_lines,
                       delimiter=delimiter)

    return image<commit_msg>Default to grey_scale when reading images<commit_after>"""
Created on Nov 8, 2016

@author: Chris Smith -- csmith55@utk.edu
"""
import os
import numpy as np
import array
from skimage.io import imread
from .dm3_image_utils import parse_dm_header, imagedatadict_to_ndarray
from . import dm4reader

def read_image(image_path, *args, **kwargs):
    """
    Read the image file at `image_path` into a numpy array

    Parameters
    ----------
    image_path : str
        Path to the image file

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    image_parms : dict
        Dictionary containing image parameters.  If image type does not have
        parameters then an empty dictionary is returned.
    """
    ext = os.path.splitext(image_path)[1]
    if ext == '.dm3':
        return read_dm3(image_path, *args, **kwargs)
    elif ext == '.dm4':
        return read_dm4(image_path, *args, **kwargs)
    elif ext == '.txt':
        return read_txt(image_path, *args, **kwargs), dict()
    else:
        # Set the as_grey argument to True is not already provided.
        kwargs['as_grey'] = (kwargs.pop('as_grey', True))
        return imread(image_path, *args, **kwargs), dict()

def read_dm3(image_path, get_parms=True):
    """
    Read an image from a dm3 file into a numpy array

    image_path : str
        Path to the image file
    get_parms : Boolean, optional
        Should the parameters from the dm3 file be returned
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    """
    image_file = open(image_path, 'rb')
    dmtag = parse_dm_header(image_file)
    img_index = -1
    image = imagedatadict_to_ndarray(dmtag['ImageList'][img_index]['ImageData'])
    image_parms = dmtag['ImageList'][img_index]['ImageTags']

    if get_parms:
        image_parms = _parse_dm3_parms(image_parms)
    else:
        image_parms = dict()

    return image, image_parms

def _parse_dm3_parms(image_parms, prefix=''):
    """
    Parses the nested image parameter dictionary and converts it to a single
    level dictionary, prepending the name of inner dictionaries to their
    keys to denote level.

    Parameters
    ----------
    image_parms : dict

    Returns
    -------

    """
    new_parms = dict()
    for name, val in image_parms.iteritems():
        # print 'name',name,'val',val
        name = '-'.join([prefix]+name.split()).strip('-')
        if isinstance(val, dict):
            new_parms.update(_parse_dm3_parms(val, name))
        elif isinstance(val, list) and isinstance(val[0], dict):
            for thing in val:
                new_parms.update(_parse_dm3_parms(thing, name))
        else:
            new_parms[name] = try_tag_to_string(val)

    return new_parms

def read_dm4(file_path, *args, **kwargs):
    """
    Read dm4 file

    :param file_path:
    :return:
    """
    get_parms = kwargs.pop('get_parms', True)
    header = kwargs.pop('header', None)

    file_parms = dict()
    dm4_file = dm4reader.DM4File.open(file_path)
    if header is None:
        tags = dm4_file.read_directory()
        header = tags.named_subdirs['ImageList'].dm4_tag
        image_list = tags.named_subdirs['ImageList'].unnamed_subdirs
    else:
        dm4_file.hfile.seek(header.offset)
        image_list = dm4_file.read_directory(header)

    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']

        x_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[0])
        y_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[1])

        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.uint16)
        image_array = np.reshape(image_array, (y_dim, x_dim))

    if get_parms:
        file_parms = parse_dm4_parms(dm4_file, tags, '')
        file_parms['Image_Tag'] = header

    return image_array, file_parms

def parse_dm4_parms(dm4_file, tag_dir, base_name=''):
    """
    Recursive function to trace the dictionary tree of the Image Data
    and build a single dictionary of all parameters

    Parameters
    ----------
    dm4_file : DM4File
        File object of the dm4 file to be parsed.
    tag_dir : dict
        Dictionary to be traced.  Has the following attributes:
            tag_dir.name : str
                Name of the directory
            tag_dir.dm4_tag : str
    base_name : str
        Base name of parameters.  Tag and subdirectory names will be appended
        for named tags and subdirectories.  Unnamed ones will recieve a number.
        Default ''.  'Root' is automatically prepended to the name.

    Returns
    -------
    parm_dict : dict()
        Dictionary containing the name:value pairs of all parameters `dm4_file`
    """
    parm_dict = dict()

    '''
    Loop over named tags
    '''
    for name in tag_dir.named_tags.keys():
        '''
        Skip Data tags.  These will be handled elseware.
        '''
        if name == 'Data':
            continue
        tag_name = '_'.join([base_name, name.replace(' ', '_')])
        if base_name == '':
            tag_name = 'Root'+tag_name
        tag_data = dm4_file.read_tag_data(tag_dir.named_tags[name])

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over unnamed tags
    '''
    for itag, tag in enumerate(tag_dir.unnamed_tags):
        tag_name = '_'.join([base_name, 'Tag_{:03d}'.format(itag)])
        if base_name == '':
            tag_name = 'Root'+tag_name

        tag_data = dm4_file.read_tag_data(tag)

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over named subdirectories
    '''
    for name in tag_dir.named_subdirs.keys():
        dir_name = '_'.join([base_name, name.replace(' ', '_')])
        sub_dir = tag_dir.named_subdirs[name]
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    '''
    Loop over unnamed subdirectories
    '''
    for idir, sub_dir in enumerate(tag_dir.unnamed_subdirs):
        dir_name = '_'.join([base_name, 'SubDir_{:03d}'.format(idir)])
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    return parm_dict

def try_tag_to_string(tag_data):
    """
    Attempt to convert array of integers into a string

    Parameters
    ----------
    tag_data : array.array
        Array of 16-bit integers

    """
    if not isinstance(tag_data, array.array):
        return tag_data

    if tag_data.typecode == 'H':
        try:
            tag_data = str(tag_data.tostring().decode('utf-16'))
        except UnicodeDecodeError:
            pass
        except UnicodeEncodeError:
            pass
        except:
            raise

    return tag_data

def read_txt(image_path, *args, **kwargs):
    """

    :param image_path:
    :param args:
    :param kwargs:
    :return:
    """

    header_lines = kwargs.pop('header_lines', 0)
    delimiter = kwargs.pop('delimiter', None)

    image = np.loadtxt(image_path,
                       skiprows=header_lines,
                       delimiter=delimiter)

    return image<|endoftext|>


<commit_before>import os
import sys
import types
import inspect
import datetime
import pytest
import tornado
import tornado.gen
import tornado.testing
import tornado.httpserver
import tornado.httpclient

if sys.version_info[:2] >= (3, 5):
    iscoroutinefunction = inspect.iscoroutinefunction
else:
    iscoroutinefunction = lambda f: False

try:
    with_timeout = tornado.gen.with_timeout
except AttributeError:
    from tornado.ioloop import IOLoop
    from tornado.concurrent import Future, chain_future

    # simplified version of 'with_timeout' from tornado 4.0
    # to work with tornado 3
    def with_timeout(timeout, future, io_loop=None):
        result = Future()
        chain_future(future, result)
        if io_loop is None:
            io_loop = IOLoop.current()
        timeout_handle = io_loop.add_timeout(
            timeout,
            lambda: result.set_exception(TimeoutError("Timeout")))
        future.add_done_callback(
            lambda future: io_loop.remove_timeout(timeout_handle))
        return result

def _get_async_test_timeout():
    try:
        return float(os.environ.get('ASYNC_TEST_TIMEOUT'))
    except (ValueError, TypeError):
        return 5

def pytest_addoption(parser):
    parser.addoption('--async-test-timeout', type=float,
                     default=_get_async_test_timeout(),
                     help='timeout in seconds before failing the test')
    parser.addoption('--app-fixture', default='app',
                     help='fixture name returning a tornado application')

def pytest_configure(config):
    config.addinivalue_line("markers",
                            "gen_test(timeout=None): "
                            "mark the test as asynchronous, it will be "
                            "run using tornado's event loop")

def _argnames(func):
    spec = inspect.getargspec(func)
    if spec.defaults:
        return spec.args[:-len(spec.defaults)]
    if isinstance(func, types.FunctionType):
        return spec.args
    # Func is a bound method, skip "self"
    return spec.args[1:]

def _timeout(item):
    default_timeout = item.config.getoption('async_test_timeout')
    gen_test = item.get_marker('gen_test')
    if gen_test:
        return gen_test.kwargs.get('timeout', default_timeout)
    return default_timeout

@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
    if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
        item = pytest.Function(name, parent=collector)
        if 'gen_test' in item.keywords:
            return list(collector._genfunctions(name, obj))

def pytest_runtest_setup(item):
    if 'gen_test' in item.keywords and 'io_loop' not in item.fixturenames:
        # inject an event loop fixture for all async tests
        item.fixturenames.append('io_loop')

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    gen_test_mark = pyfuncitem.keywords.get('gen_test')
    if gen_test_mark:
        io_loop = pyfuncitem.funcargs.get('io_loop')
        run_sync = gen_test_mark.kwargs.get('run_sync', True)

        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        if iscoroutinefunction(pyfuncitem.obj):
            coroutine = pyfuncitem.obj
            future = tornado.gen.convert_yielded(coroutine(**funcargs))
        else:
            coroutine = tornado.gen.coroutine(pyfuncitem.obj)
            future = coroutine(**funcargs)
        if run_sync:
            io_loop.run_sync(lambda: future, timeout=_timeout(pyfuncitem))
        else:
            # Run this test function as a coroutine, until the timeout. When completed, stop the IOLoop
            # and reraise any exceptions

            future_with_timeout = with_timeout(
                    datetime.timedelta(seconds=_timeout(pyfuncitem)),
                    future)
            io_loop.add_future(future_with_timeout, lambda f: io_loop.stop())
            io_loop.start()

            # This will reraise any exceptions that occurred.
            future_with_timeout.result()

        # prevent other pyfunc calls from executing
        return True

@pytest.fixture
def io_loop(request):
    """Create an instance of the `tornado.ioloop.IOLoop` for each test case.
    """
    io_loop = tornado.ioloop.IOLoop()
    io_loop.make_current()

    def _close():
        io_loop.clear_current()
        if (not tornado.ioloop.IOLoop.initialized() or
                io_loop is not tornado.ioloop.IOLoop.instance()):
            io_loop.close(all_fds=True)

    request.addfinalizer(_close)
    return io_loop

@pytest.fixture
def _unused_port():
    return tornado.testing.bind_unused_port()

@pytest.fixture
def http_port(_unused_port):
    """Get a port used by the test server.
    """
    return _unused_port[1]

@pytest.fixture
def base_url(http_port):
    """Create an absolute base url (scheme://host:port)
    """
    return 'http://localhost:%s' % http_port

@pytest.fixture
def http_server(request, io_loop, _unused_port):
    """Start a tornado HTTP server.

    You must create an `app` fixture, which returns
    the `tornado.web.Application` to be tested.

    Raises:
        FixtureLookupError: tornado application fixture not found
    """
    http_app = request.getfuncargvalue(request.config.option.app_fixture)
    server = tornado.httpserver.HTTPServer(http_app, io_loop=io_loop)
    server.add_socket(_unused_port[0])

    def _stop():
        server.stop()

        if hasattr(server, 'close_all_connections'):
            io_loop.run_sync(server.close_all_connections,
                             timeout=request.config.option.async_test_timeout)

    request.addfinalizer(_stop)
    return server

@pytest.fixture
def http_client(request, http_server):
    """Get an asynchronous HTTP client.
    """
    client = tornado.httpclient.AsyncHTTPClient(io_loop=http_server.io_loop)

    def _close():
        if (not tornado.ioloop.IOLoop.initialized() or
                client.io_loop is not tornado.ioloop.IOLoop.instance()):
            client.close()

    request.addfinalizer(_close)
    return client
<commit_msg>Adding support for HTTPS testing in tornado<commit_after>import os
import sys
import types
import inspect
import datetime
import pytest
import tornado
import tornado.gen
import tornado.testing
import tornado.httpserver
import tornado.httpclient

if sys.version_info[:2] >= (3, 5):
    iscoroutinefunction = inspect.iscoroutinefunction
else:
    iscoroutinefunction = lambda f: False

try:
    with_timeout = tornado.gen.with_timeout
except AttributeError:
    from tornado.ioloop import IOLoop
    from tornado.concurrent import Future, chain_future

    # simplified version of 'with_timeout' from tornado 4.0
    # to work with tornado 3
    def with_timeout(timeout, future, io_loop=None):
        result = Future()
        chain_future(future, result)
        if io_loop is None:
            io_loop = IOLoop.current()
        timeout_handle = io_loop.add_timeout(
            timeout,
            lambda: result.set_exception(TimeoutError("Timeout")))
        future.add_done_callback(
            lambda future: io_loop.remove_timeout(timeout_handle))
        return result

def _get_async_test_timeout():
    try:
        return float(os.environ.get('ASYNC_TEST_TIMEOUT'))
    except (ValueError, TypeError):
        return 5

def pytest_addoption(parser):
    parser.addoption('--async-test-timeout', type=float,
                     default=_get_async_test_timeout(),
                     help='timeout in seconds before failing the test')
    parser.addoption('--app-fixture', default='app',
                     help='fixture name returning a tornado application')

def pytest_configure(config):
    config.addinivalue_line("markers",
                            "gen_test(timeout=None): "
                            "mark the test as asynchronous, it will be "
                            "run using tornado's event loop")

def _argnames(func):
    spec = inspect.getargspec(func)
    if spec.defaults:
        return spec.args[:-len(spec.defaults)]
    if isinstance(func, types.FunctionType):
        return spec.args
    # Func is a bound method, skip "self"
    return spec.args[1:]

def _timeout(item):
    default_timeout = item.config.getoption('async_test_timeout')
    gen_test = item.get_marker('gen_test')
    if gen_test:
        return gen_test.kwargs.get('timeout', default_timeout)
    return default_timeout

@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
    if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
        item = pytest.Function(name, parent=collector)
        if 'gen_test' in item.keywords:
            return list(collector._genfunctions(name, obj))

def pytest_runtest_setup(item):
    if 'gen_test' in item.keywords and 'io_loop' not in item.fixturenames:
        # inject an event loop fixture for all async tests
        item.fixturenames.append('io_loop')

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    gen_test_mark = pyfuncitem.keywords.get('gen_test')
    if gen_test_mark:
        io_loop = pyfuncitem.funcargs.get('io_loop')
        run_sync = gen_test_mark.kwargs.get('run_sync', True)

        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        if iscoroutinefunction(pyfuncitem.obj):
            coroutine = pyfuncitem.obj
            future = tornado.gen.convert_yielded(coroutine(**funcargs))
        else:
            coroutine = tornado.gen.coroutine(pyfuncitem.obj)
            future = coroutine(**funcargs)
        if run_sync:
            io_loop.run_sync(lambda: future, timeout=_timeout(pyfuncitem))
        else:
            # Run this test function as a coroutine, until the timeout. When completed, stop the IOLoop
            # and reraise any exceptions

            future_with_timeout = with_timeout(
                    datetime.timedelta(seconds=_timeout(pyfuncitem)),
                    future)
            io_loop.add_future(future_with_timeout, lambda f: io_loop.stop())
            io_loop.start()

            # This will reraise any exceptions that occurred.
            future_with_timeout.result()

        # prevent other pyfunc calls from executing
        return True

@pytest.fixture
def io_loop(request):
    """Create an instance of the `tornado.ioloop.IOLoop` for each test case.
    """
    io_loop = tornado.ioloop.IOLoop()
    io_loop.make_current()

    def _close():
        io_loop.clear_current()
        if (not tornado.ioloop.IOLoop.initialized() or
                io_loop is not tornado.ioloop.IOLoop.instance()):
            io_loop.close(all_fds=True)

    request.addfinalizer(_close)
    return io_loop

@pytest.fixture
def _unused_port():
    return tornado.testing.bind_unused_port()

@pytest.fixture
def http_port(_unused_port):
    """Get a port used by the test server.
    """
    return _unused_port[1]

@pytest.fixture
def base_url(http_port):
    """Create an absolute base url (scheme://host:port)
    """
    return 'http://localhost:%s' % http_port

@pytest.fixture
def http_server(request, io_loop, _unused_port):
    """Start a tornado HTTP server.

    You must create an `app` fixture, which returns
    the `tornado.web.Application` to be tested.

    Raises:
        FixtureLookupError: tornado application fixture not found
    """
    http_app = request.getfuncargvalue(request.config.option.app_fixture)
    server = tornado.httpserver.HTTPServer(http_app, io_loop=io_loop)
    server.add_socket(_unused_port[0])

    def _stop():
        server.stop()

        if hasattr(server, 'close_all_connections'):
            io_loop.run_sync(server.close_all_connections,
                             timeout=request.config.option.async_test_timeout)

    request.addfinalizer(_stop)
    return server

@pytest.fixture
def http_client(request, http_server):
    """Get an asynchronous HTTP client.
    """
    client = tornado.httpclient.AsyncHTTPClient(io_loop=http_server.io_loop)

    def _close():
        if (not tornado.ioloop.IOLoop.initialized() or
                client.io_loop is not tornado.ioloop.IOLoop.instance()):
            client.close()

    request.addfinalizer(_close)
    return client

@pytest.fixture
def https_server(request, io_loop, _unused_port):
    """Start a tornado HTTPS server.

    You must create an `app` fixture, which returns
    the `tornado.web.Application` to be tested.

    Raises:
        FixtureLookupError: tornado application fixture not found
    """
    http_app = request.getfuncargvalue(request.config.option.app_fixture)
    ssl_options = {}
    # I dont know to get server cert & key from user
    """
    http_server = HTTPServer(application, ssl_options={
            "certfile": settings["server_certfile"],
            "keyfile": settings["server_keyfile"]
            })
    """
    server = tornado.httpserver.HTTPServer(http_app, ssl_options=ssl_options, io_loop=io_loop)
    server.add_socket(_unused_port[0])

    def _stop():
        server.stop()

        if hasattr(server, 'close_all_connections'):
            io_loop.run_sync(server.close_all_connections,
                             timeout=request.config.option.async_test_timeout)

    request.addfinalizer(_stop)
    return server

@pytest.fixture
def https_client(request, http_server):
    """Get an asynchronous HTTPS client.
    """
    # How does on get ca_certs from the user
    client = tornado.httpclient.AsyncHTTPClient(ca_certs=ca_certs, io_loop=https_server.io_loop)

    def _close():
        if (not tornado.ioloop.IOLoop.initialized() or
                client.io_loop is not tornado.ioloop.IOLoop.instance()):
            client.close()

    request.addfinalizer(_close)
    return client
<|endoftext|>


<commit_before>"""Support for KNX/IP climate devices."""
from typing import List, Optional

import voluptuous as vol
from xknx.devices import Climate as XknxClimate, ClimateMode as XknxClimateMode
from xknx.devices.climate import SetpointShiftMode
from xknx.dpt import HVACOperationMode

from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
from homeassistant.components.climate.const import (
    HVAC_MODE_AUTO,
    HVAC_MODE_COOL,
    HVAC_MODE_DRY,
    HVAC_MODE_FAN_ONLY,
    HVAC_MODE_HEAT,
    HVAC_MODE_OFF,
    PRESET_AWAY,
    PRESET_COMFORT,
    PRESET_ECO,
    PRESET_SLEEP,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from . import ATTR_DISCOVER_DEVICES, DATA_XKNX

CONF_SETPOINT_SHIFT_ADDRESS = "setpoint_shift_address"
CONF_SETPOINT_SHIFT_STATE_ADDRESS = "setpoint_shift_state_address"
CONF_SETPOINT_SHIFT_MODE = "setpoint_shift_mode"
CONF_SETPOINT_SHIFT_STEP = "setpoint_shift_step"
CONF_SETPOINT_SHIFT_MAX = "setpoint_shift_max"
CONF_SETPOINT_SHIFT_MIN = "setpoint_shift_min"
CONF_TEMPERATURE_ADDRESS = "temperature_address"
CONF_TARGET_TEMPERATURE_ADDRESS = "target_temperature_address"
CONF_TARGET_TEMPERATURE_STATE_ADDRESS = "target_temperature_state_address"
CONF_OPERATION_MODE_ADDRESS = "operation_mode_address"
CONF_OPERATION_MODE_STATE_ADDRESS = "operation_mode_state_address"
CONF_CONTROLLER_STATUS_ADDRESS = "controller_status_address"
CONF_CONTROLLER_STATUS_STATE_ADDRESS = "controller_status_state_address"
CONF_CONTROLLER_MODE_ADDRESS = "controller_mode_address"
CONF_CONTROLLER_MODE_STATE_ADDRESS = "controller_mode_state_address"
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = "operation_mode_frost_protection_address"
CONF_OPERATION_MODE_NIGHT_ADDRESS = "operation_mode_night_address"
CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address"
CONF_OPERATION_MODE_STANDBY_ADDRESS = "operation_mode_standby_address"
CONF_OPERATION_MODES = "operation_modes"
CONF_ON_OFF_ADDRESS = "on_off_address"
CONF_ON_OFF_STATE_ADDRESS = "on_off_state_address"
CONF_ON_OFF_INVERT = "on_off_invert"
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"

DEFAULT_NAME = "KNX Climate"
DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010"
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEFAULT_ON_OFF_INVERT = False
# Map KNX operation modes to HA modes. This list might not be complete.
OPERATION_MODES = {
    # Map DPT 20.105 HVAC control modes
    "Auto": HVAC_MODE_AUTO,
    "Heat": HVAC_MODE_HEAT,
    "Cool": HVAC_MODE_COOL,
    "Off": HVAC_MODE_OFF,
    "Fan only": HVAC_MODE_FAN_ONLY,
    "Dry": HVAC_MODE_DRY,
}

OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items())

PRESET_MODES = {
    # Map DPT 20.102 HVAC operating modes to HA presets
    "Frost Protection": PRESET_ECO,
    "Night": PRESET_SLEEP,
    "Standby": PRESET_AWAY,
    "Comfort": PRESET_COMFORT,
}

PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items())

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_MODE, default=DEFAULT_SETPOINT_SHIFT_MODE): cv.enum(
            SetpointShiftMode
        ),
        vol.Optional(
            CONF_SETPOINT_SHIFT_STEP, default=DEFAULT_SETPOINT_SHIFT_STEP
        ): vol.All(float, vol.Range(min=0, max=2)),
        vol.Optional(
            CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX
        ): vol.All(int, vol.Range(min=0, max=32)),
        vol.Optional(
            CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN
        ): vol.All(int, vol.Range(min=-32, max=0)),
        vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
        vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT): cv.boolean,
        vol.Optional(CONF_OPERATION_MODES): vol.All(
            cv.ensure_list, [vol.In({**OPERATION_MODES, **PRESET_MODES})]
        ),
        vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
        vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
    }
)

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up climate(s) for KNX platform."""
    if discovery_info is not None:
        async_add_entities_discovery(hass, discovery_info, async_add_entities)
    else:
        async_add_entities_config(hass, config, async_add_entities)

@callback
def async_add_entities_discovery(hass, discovery_info, async_add_entities):
    """Set up climates for KNX platform configured within platform."""
    entities = []
    for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
        device = hass.data[DATA_XKNX].xknx.devices[device_name]
        entities.append(KNXClimate(device))
    async_add_entities(entities)

@callback
def async_add_entities_config(hass, config, async_add_entities):
    """Set up climate for KNX platform configured within platform."""
    climate_mode = XknxClimateMode(
        hass.data[DATA_XKNX].xknx,
        name=f"{config[CONF_NAME]} Mode",
        group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
        group_address_operation_mode_state=config.get(
            CONF_OPERATION_MODE_STATE_ADDRESS
        ),
        group_address_controller_status=config.get(CONF_CONTROLLER_STATUS_ADDRESS),
        group_address_controller_status_state=config.get(
            CONF_CONTROLLER_STATUS_STATE_ADDRESS
        ),
        group_address_controller_mode=config.get(CONF_CONTROLLER_MODE_ADDRESS),
        group_address_controller_mode_state=config.get(
            CONF_CONTROLLER_MODE_STATE_ADDRESS
        ),
        group_address_operation_mode_protection=config.get(
            CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
        ),
        group_address_operation_mode_night=config.get(
            CONF_OPERATION_MODE_NIGHT_ADDRESS
        ),
        group_address_operation_mode_comfort=config.get(
            CONF_OPERATION_MODE_COMFORT_ADDRESS
        ),
        group_address_operation_mode_standby=config.get(
            CONF_OPERATION_MODE_STANDBY_ADDRESS
        ),
        operation_modes=config.get(CONF_OPERATION_MODES),
    )
    hass.data[DATA_XKNX].xknx.devices.add(climate_mode)

    climate = XknxClimate(
        hass.data[DATA_XKNX].xknx,
        name=config[CONF_NAME],
        group_address_temperature=config[CONF_TEMPERATURE_ADDRESS],
        group_address_target_temperature=config.get(CONF_TARGET_TEMPERATURE_ADDRESS),
        group_address_target_temperature_state=config[
            CONF_TARGET_TEMPERATURE_STATE_ADDRESS
        ],
        group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
        group_address_setpoint_shift_state=config.get(
            CONF_SETPOINT_SHIFT_STATE_ADDRESS
        ),
        setpoint_shift_mode=config[CONF_SETPOINT_SHIFT_MODE],
        setpoint_shift_step=config[CONF_SETPOINT_SHIFT_STEP],
        setpoint_shift_max=config[CONF_SETPOINT_SHIFT_MAX],
        setpoint_shift_min=config[CONF_SETPOINT_SHIFT_MIN],
        group_address_on_off=config.get(CONF_ON_OFF_ADDRESS),
        group_address_on_off_state=config.get(CONF_ON_OFF_STATE_ADDRESS),
        min_temp=config.get(CONF_MIN_TEMP),
        max_temp=config.get(CONF_MAX_TEMP),
        mode=climate_mode,
        on_off_invert=config[CONF_ON_OFF_INVERT],
    )
    hass.data[DATA_XKNX].xknx.devices.add(climate)

    async_add_entities([KNXClimate(climate)])

class KNXClimate(ClimateEntity):
    """Representation of a KNX climate device."""

    def __init__(self, device):
        """Initialize of a KNX climate device."""
        self.device = device
        self._unit_of_measurement = TEMP_CELSIUS

    @property
    def supported_features(self) -> int:
        """Return the list of supported features."""
        return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

    async def async_added_to_hass(self) -> None:
        """Register callbacks to update hass after device was changed."""

        async def after_update_callback(device):
            """Call after device was updated."""
            self.async_write_ha_state()

        self.device.register_device_updated_cb(after_update_callback)
        self.device.mode.register_device_updated_cb(after_update_callback)

    async def async_update(self):
        """Request a state update from KNX bus."""
        await self.device.sync()
        await self.device.mode.sync()

    @property
    def name(self) -> str:
        """Return the name of the KNX device."""
        return self.device.name

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return self.hass.data[DATA_XKNX].connected

    @property
    def should_poll(self) -> bool:
        """No polling needed within KNX."""
        return False

    @property
    def temperature_unit(self):
        """Return the unit of measurement."""
        return self._unit_of_measurement

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self.device.temperature.value

    @property
    def target_temperature_step(self):
        """Return the supported step of target temperature."""
        return self.device.temperature_step

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self.device.target_temperature.value

    @property
    def min_temp(self):
        """Return the minimum temperature."""
        return self.device.target_temperature_min

    @property
    def max_temp(self):
        """Return the maximum temperature."""
        return self.device.target_temperature_max

    async def async_set_temperature(self, **kwargs) -> None:
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if temperature is None:
            return
        await self.device.set_target_temperature(temperature)
        self.async_write_ha_state()

    @property
    def hvac_mode(self) -> Optional[str]:
        """Return current operation ie. heat, cool, idle."""
        if self.device.supports_on_off and not self.device.is_on:
            return HVAC_MODE_OFF
        if self.device.supports_on_off and self.device.is_on:
            return HVAC_MODE_HEAT
        if self.device.mode.supports_operation_mode:
            return OPERATION_MODES.get(
                self.device.mode.operation_mode.value, HVAC_MODE_HEAT
            )
        # default to "heat"
        return HVAC_MODE_HEAT

    @property
    def hvac_modes(self) -> Optional[List[str]]:
        """Return the list of available operation modes."""
        _operations = [
            OPERATION_MODES.get(operation_mode.value)
            for operation_mode in self.device.mode.operation_modes
        ]

        if self.device.supports_on_off:
            _operations.append(HVAC_MODE_HEAT)
            _operations.append(HVAC_MODE_OFF)

        _modes = list(filter(None, _operations))
        # default to ["heat"]
        return _modes if _modes else [HVAC_MODE_HEAT]

    async def async_set_hvac_mode(self, hvac_mode: str) -> None:
        """Set operation mode."""
        if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
            await self.device.turn_off()
        elif self.device.supports_on_off and hvac_mode == HVAC_MODE_HEAT:
            await self.device.turn_on()
        elif self.device.mode.supports_operation_mode:
            knx_operation_mode = HVACOperationMode(OPERATION_MODES_INV.get(hvac_mode))
            await self.device.mode.set_operation_mode(knx_operation_mode)
            self.async_write_ha_state()

    @property
    def preset_mode(self) -> Optional[str]:
        """Return the current preset mode, e.g., home, away, temp.

        Requires SUPPORT_PRESET_MODE.
        """
        if self.device.mode.supports_operation_mode:
            return PRESET_MODES.get(self.device.mode.operation_mode.value, PRESET_AWAY)
        return None

    @property
    def preset_modes(self) -> Optional[List[str]]:
        """Return a list of available preset modes.

        Requires SUPPORT_PRESET_MODE.
        """
        _presets = [
            PRESET_MODES.get(operation_mode.value)
            for operation_mode in self.device.mode.operation_modes
        ]

        return list(filter(None, _presets))

    async def async_set_preset_mode(self, preset_mode: str) -> None:
        """Set new preset mode."""
        if self.device.mode.supports_operation_mode:
            knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode))
            await self.device.mode.set_operation_mode(knx_operation_mode)
            self.async_write_ha_state()
<commit_msg>climate.is_on does not override operation mode<commit_after>"""Support for KNX/IP climate devices."""
from typing import List, Optional

import voluptuous as vol
from xknx.devices import Climate as XknxClimate, ClimateMode as XknxClimateMode
from xknx.devices.climate import SetpointShiftMode
from xknx.dpt import HVACOperationMode

from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
from homeassistant.components.climate.const import (
    HVAC_MODE_AUTO,
    HVAC_MODE_COOL,
    HVAC_MODE_DRY,
    HVAC_MODE_FAN_ONLY,
    HVAC_MODE_HEAT,
    HVAC_MODE_OFF,
    PRESET_AWAY,
    PRESET_COMFORT,
    PRESET_ECO,
    PRESET_SLEEP,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from . import ATTR_DISCOVER_DEVICES, DATA_XKNX

CONF_SETPOINT_SHIFT_ADDRESS = "setpoint_shift_address"
CONF_SETPOINT_SHIFT_STATE_ADDRESS = "setpoint_shift_state_address"
CONF_SETPOINT_SHIFT_MODE = "setpoint_shift_mode"
CONF_SETPOINT_SHIFT_STEP = "setpoint_shift_step"
CONF_SETPOINT_SHIFT_MAX = "setpoint_shift_max"
CONF_SETPOINT_SHIFT_MIN = "setpoint_shift_min"
CONF_TEMPERATURE_ADDRESS = "temperature_address"
CONF_TARGET_TEMPERATURE_ADDRESS = "target_temperature_address"
CONF_TARGET_TEMPERATURE_STATE_ADDRESS = "target_temperature_state_address"
CONF_OPERATION_MODE_ADDRESS = "operation_mode_address"
CONF_OPERATION_MODE_STATE_ADDRESS = "operation_mode_state_address"
CONF_CONTROLLER_STATUS_ADDRESS = "controller_status_address"
CONF_CONTROLLER_STATUS_STATE_ADDRESS = "controller_status_state_address"
CONF_CONTROLLER_MODE_ADDRESS = "controller_mode_address"
CONF_CONTROLLER_MODE_STATE_ADDRESS = "controller_mode_state_address"
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = "operation_mode_frost_protection_address"
CONF_OPERATION_MODE_NIGHT_ADDRESS = "operation_mode_night_address"
CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address"
CONF_OPERATION_MODE_STANDBY_ADDRESS = "operation_mode_standby_address"
CONF_OPERATION_MODES = "operation_modes"
CONF_ON_OFF_ADDRESS = "on_off_address"
CONF_ON_OFF_STATE_ADDRESS = "on_off_state_address"
CONF_ON_OFF_INVERT = "on_off_invert"
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"

DEFAULT_NAME = "KNX Climate"
DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010"
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEFAULT_ON_OFF_INVERT = False
# Map KNX operation modes to HA modes. This list might not be complete.
OPERATION_MODES = {
    # Map DPT 20.105 HVAC control modes
    "Auto": HVAC_MODE_AUTO,
    "Heat": HVAC_MODE_HEAT,
    "Cool": HVAC_MODE_COOL,
    "Off": HVAC_MODE_OFF,
    "Fan only": HVAC_MODE_FAN_ONLY,
    "Dry": HVAC_MODE_DRY,
}

OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items())

PRESET_MODES = {
    # Map DPT 20.102 HVAC operating modes to HA presets
    "Frost Protection": PRESET_ECO,
    "Night": PRESET_SLEEP,
    "Standby": PRESET_AWAY,
    "Comfort": PRESET_COMFORT,
}

PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items())

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_MODE, default=DEFAULT_SETPOINT_SHIFT_MODE): cv.enum(
            SetpointShiftMode
        ),
        vol.Optional(
            CONF_SETPOINT_SHIFT_STEP, default=DEFAULT_SETPOINT_SHIFT_STEP
        ): vol.All(float, vol.Range(min=0, max=2)),
        vol.Optional(
            CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX
        ): vol.All(int, vol.Range(min=0, max=32)),
        vol.Optional(
            CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN
        ): vol.All(int, vol.Range(min=-32, max=0)),
        vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
        vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
        vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
        vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
        vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
        vol.Optional(CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT): cv.boolean,
        vol.Optional(CONF_OPERATION_MODES): vol.All(
            cv.ensure_list, [vol.In({**OPERATION_MODES, **PRESET_MODES})]
        ),
        vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
        vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
    }
)

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up climate(s) for KNX platform."""
    if discovery_info is not None:
        async_add_entities_discovery(hass, discovery_info, async_add_entities)
    else:
        async_add_entities_config(hass, config, async_add_entities)

@callback
def async_add_entities_discovery(hass, discovery_info, async_add_entities):
    """Set up climates for KNX platform configured within platform."""
    entities = []
    for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
        device = hass.data[DATA_XKNX].xknx.devices[device_name]
        entities.append(KNXClimate(device))
    async_add_entities(entities)

@callback
def async_add_entities_config(hass, config, async_add_entities):
    """Set up climate for KNX platform configured within platform."""
    climate_mode = XknxClimateMode(
        hass.data[DATA_XKNX].xknx,
        name=f"{config[CONF_NAME]} Mode",
        group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
        group_address_operation_mode_state=config.get(
            CONF_OPERATION_MODE_STATE_ADDRESS
        ),
        group_address_controller_status=config.get(CONF_CONTROLLER_STATUS_ADDRESS),
        group_address_controller_status_state=config.get(
            CONF_CONTROLLER_STATUS_STATE_ADDRESS
        ),
        group_address_controller_mode=config.get(CONF_CONTROLLER_MODE_ADDRESS),
        group_address_controller_mode_state=config.get(
            CONF_CONTROLLER_MODE_STATE_ADDRESS
        ),
        group_address_operation_mode_protection=config.get(
            CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
        ),
        group_address_operation_mode_night=config.get(
            CONF_OPERATION_MODE_NIGHT_ADDRESS
        ),
        group_address_operation_mode_comfort=config.get(
            CONF_OPERATION_MODE_COMFORT_ADDRESS
        ),
        group_address_operation_mode_standby=config.get(
            CONF_OPERATION_MODE_STANDBY_ADDRESS
        ),
        operation_modes=config.get(CONF_OPERATION_MODES),
    )
    hass.data[DATA_XKNX].xknx.devices.add(climate_mode)

    climate = XknxClimate(
        hass.data[DATA_XKNX].xknx,
        name=config[CONF_NAME],
        group_address_temperature=config[CONF_TEMPERATURE_ADDRESS],
        group_address_target_temperature=config.get(CONF_TARGET_TEMPERATURE_ADDRESS),
        group_address_target_temperature_state=config[
            CONF_TARGET_TEMPERATURE_STATE_ADDRESS
        ],
        group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
        group_address_setpoint_shift_state=config.get(
            CONF_SETPOINT_SHIFT_STATE_ADDRESS
        ),
        setpoint_shift_mode=config[CONF_SETPOINT_SHIFT_MODE],
        setpoint_shift_step=config[CONF_SETPOINT_SHIFT_STEP],
        setpoint_shift_max=config[CONF_SETPOINT_SHIFT_MAX],
        setpoint_shift_min=config[CONF_SETPOINT_SHIFT_MIN],
        group_address_on_off=config.get(CONF_ON_OFF_ADDRESS),
        group_address_on_off_state=config.get(CONF_ON_OFF_STATE_ADDRESS),
        min_temp=config.get(CONF_MIN_TEMP),
        max_temp=config.get(CONF_MAX_TEMP),
        mode=climate_mode,
        on_off_invert=config[CONF_ON_OFF_INVERT],
    )
    hass.data[DATA_XKNX].xknx.devices.add(climate)

    async_add_entities([KNXClimate(climate)])

class KNXClimate(ClimateEntity):
    """Representation of a KNX climate device."""

    def __init__(self, device):
        """Initialize of a KNX climate device."""
        self.device = device
        self._unit_of_measurement = TEMP_CELSIUS

    @property
    def supported_features(self) -> int:
        """Return the list of supported features."""
        return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

    async def async_added_to_hass(self) -> None:
        """Register callbacks to update hass after device was changed."""

        async def after_update_callback(device):
            """Call after device was updated."""
            self.async_write_ha_state()

        self.device.register_device_updated_cb(after_update_callback)
        self.device.mode.register_device_updated_cb(after_update_callback)

    async def async_update(self):
        """Request a state update from KNX bus."""
        await self.device.sync()
        await self.device.mode.sync()

    @property
    def name(self) -> str:
        """Return the name of the KNX device."""
        return self.device.name

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return self.hass.data[DATA_XKNX].connected

    @property
    def should_poll(self) -> bool:
        """No polling needed within KNX."""
        return False

    @property
    def temperature_unit(self):
        """Return the unit of measurement."""
        return self._unit_of_measurement

    @property
    def current_temperature(self):
        """Return the current temperature."""
        return self.device.temperature.value

    @property
    def target_temperature_step(self):
        """Return the supported step of target temperature."""
        return self.device.temperature_step

    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self.device.target_temperature.value

    @property
    def min_temp(self):
        """Return the minimum temperature."""
        return self.device.target_temperature_min

    @property
    def max_temp(self):
        """Return the maximum temperature."""
        return self.device.target_temperature_max

    async def async_set_temperature(self, **kwargs) -> None:
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        if temperature is None:
            return
        await self.device.set_target_temperature(temperature)
        self.async_write_ha_state()

    @property
    def hvac_mode(self) -> Optional[str]:
        """Return current operation ie. heat, cool, idle."""
        if self.device.supports_on_off and not self.device.is_on:
            return HVAC_MODE_OFF
        if self.device.mode.supports_operation_mode:
            return OPERATION_MODES.get(
                self.device.mode.operation_mode.value, HVAC_MODE_HEAT
            )
        # default to "heat"
        return HVAC_MODE_HEAT

    @property
    def hvac_modes(self) -> Optional[List[str]]:
        """Return the list of available operation modes."""
        _operations = [
            OPERATION_MODES.get(operation_mode.value)
            for operation_mode in self.device.mode.operation_modes
        ]

        if self.device.supports_on_off:
            _operations.append(HVAC_MODE_HEAT)
            _operations.append(HVAC_MODE_OFF)

        _modes = list(filter(None, _operations))
        # default to ["heat"]
        return _modes if _modes else [HVAC_MODE_HEAT]

    async def async_set_hvac_mode(self, hvac_mode: str) -> None:
        """Set operation mode."""
        if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
            await self.device.turn_off()
        elif self.device.supports_on_off and hvac_mode == HVAC_MODE_HEAT:
            await self.device.turn_on()
        elif self.device.mode.supports_operation_mode:
            knx_operation_mode = HVACOperationMode(OPERATION_MODES_INV.get(hvac_mode))
            await self.device.mode.set_operation_mode(knx_operation_mode)
            self.async_write_ha_state()

    @property
    def preset_mode(self) -> Optional[str]:
        """Return the current preset mode, e.g., home, away, temp.

        Requires SUPPORT_PRESET_MODE.
        """
        if self.device.mode.supports_operation_mode:
            return PRESET_MODES.get(self.device.mode.operation_mode.value, PRESET_AWAY)
        return None

    @property
    def preset_modes(self) -> Optional[List[str]]:
        """Return a list of available preset modes.

        Requires SUPPORT_PRESET_MODE.
        """
        _presets = [
            PRESET_MODES.get(operation_mode.value)
            for operation_mode in self.device.mode.operation_modes
        ]

        return list(filter(None, _presets))

    async def async_set_preset_mode(self, preset_mode: str) -> None:
        """Set new preset mode."""
        if self.device.mode.supports_operation_mode:
            knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode))
            await self.device.mode.set_operation_mode(knx_operation_mode)
            self.async_write_ha_state()
<|endoftext|>


<commit_before>"""
Created on Nov 8, 2016

@author: Chris Smith -- csmith55@utk.edu
"""
import os
import numpy as np
import array
from skimage.io import imread
from .dm3_image_utils import parse_dm_header, imagedatadict_to_ndarray
from . import dm4reader

def read_image(image_path, *args, **kwargs):
    """
    Read the image file at `image_path` into a numpy array

    Parameters
    ----------
    image_path : str
        Path to the image file

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    image_parms : dict
        Dictionary containing image parameters.  If image type does not have
        parameters then an empty dictionary is returned.
    """
    ext = os.path.splitext(image_path)[1]
    if ext == '.dm3':
        return read_dm3(image_path, *args, **kwargs)
    elif ext == '.dm4':
        return read_dm4(image_path, *args, **kwargs)
    elif ext == '.txt':
        return read_txt(image_path, *args, **kwargs), dict()
    else:
        # Set the as_grey argument to True is not already provided.
        kwargs['as_grey'] = (kwargs.pop('as_grey', True))
        return imread(image_path, *args, **kwargs), dict()

def read_dm3(image_path, get_parms=True):
    """
    Read an image from a dm3 file into a numpy array

    image_path : str
        Path to the image file
    get_parms : Boolean, optional
        Should the parameters from the dm3 file be returned
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    """
    image_file = open(image_path, 'rb')
    dmtag = parse_dm_header(image_file)
    img_index = -1
    image = imagedatadict_to_ndarray(dmtag['ImageList'][img_index]['ImageData'])
    image_parms = dmtag['ImageList'][img_index]['ImageTags']

    if get_parms:
        image_parms = _parse_dm3_parms(image_parms)
    else:
        image_parms = dict()

    return image, image_parms

def _parse_dm3_parms(image_parms, prefix=''):
    """
    Parses the nested image parameter dictionary and converts it to a single
    level dictionary, prepending the name of inner dictionaries to their
    keys to denote level.

    Parameters
    ----------
    image_parms : dict

    Returns
    -------

    """
    new_parms = dict()
    for name, val in image_parms.iteritems():
        # print 'name',name,'val',val
        name = '-'.join([prefix]+name.split()).strip('-')
        if isinstance(val, dict):
            new_parms.update(_parse_dm3_parms(val, name))
        elif isinstance(val, list) and isinstance(val[0], dict):
            for thing in val:
                new_parms.update(_parse_dm3_parms(thing, name))
        else:
            new_parms[name] = try_tag_to_string(val)

    return new_parms

def read_dm4(file_path, *args, **kwargs):
    """
    Read dm4 file

    :param file_path:
    :return:
    """
    get_parms = kwargs.pop('get_parms', True)
    header = kwargs.pop('header', None)

    file_parms = dict()
    dm4_file = dm4reader.DM4File.open(file_path)
    if header is None:
        tags = dm4_file.read_directory()
        header = tags.named_subdirs['ImageList'].dm4_tag
        image_list = tags.named_subdirs['ImageList'].unnamed_subdirs
    else:
        dm4_file.hfile.seek(header.offset)
        image_list = dm4_file.read_directory(header)

    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']

        x_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[0])
        y_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[1])

        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.uint16)
        image_array = np.reshape(image_array, (y_dim, x_dim))

    if get_parms:
        file_parms = parse_dm4_parms(dm4_file, tags, '')
        file_parms['Image_Tag'] = header

    return image_array, file_parms

def parse_dm4_parms(dm4_file, tag_dir, base_name=''):
    """
    Recursive function to trace the dictionary tree of the Image Data
    and build a single dictionary of all parameters

    Parameters
    ----------
    dm4_file : DM4File
        File object of the dm4 file to be parsed.
    tag_dir : dict
        Dictionary to be traced.  Has the following attributes:
            tag_dir.name : str
                Name of the directory
            tag_dir.dm4_tag : str
    base_name : str
        Base name of parameters.  Tag and subdirectory names will be appended
        for named tags and subdirectories.  Unnamed ones will recieve a number.
        Default ''.  'Root' is automatically prepended to the name.

    Returns
    -------
    parm_dict : dict()
        Dictionary containing the name:value pairs of all parameters `dm4_file`
    """
    parm_dict = dict()

    '''
    Loop over named tags
    '''
    for name in tag_dir.named_tags.keys():
        '''
        Skip Data tags.  These will be handled elseware.
        '''
        if name == 'Data':
            continue
        tag_name = '_'.join([base_name, name.replace(' ', '_')])
        if base_name == '':
            tag_name = 'Root'+tag_name
        tag_data = dm4_file.read_tag_data(tag_dir.named_tags[name])

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over unnamed tags
    '''
    for itag, tag in enumerate(tag_dir.unnamed_tags):
        tag_name = '_'.join([base_name, 'Tag_{:03d}'.format(itag)])
        if base_name == '':
            tag_name = 'Root'+tag_name

        tag_data = dm4_file.read_tag_data(tag)

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over named subdirectories
    '''
    for name in tag_dir.named_subdirs.keys():
        dir_name = '_'.join([base_name, name.replace(' ', '_')])
        sub_dir = tag_dir.named_subdirs[name]
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    '''
    Loop over unnamed subdirectories
    '''
    for idir, sub_dir in enumerate(tag_dir.unnamed_subdirs):
        dir_name = '_'.join([base_name, 'SubDir_{:03d}'.format(idir)])
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    return parm_dict

def try_tag_to_string(tag_data):
    """
    Attempt to convert array of integers into a string

    Parameters
    ----------
    tag_data : array.array
        Array of 16-bit integers

    """
    if not isinstance(tag_data, array.array):
        return tag_data

    if tag_data.typecode == 'H':
        try:
            tag_data = str(tag_data.tostring().decode('utf-16'))
        except UnicodeDecodeError:
            pass
        except UnicodeEncodeError:
            pass
        except:
            raise

    return tag_data

def read_txt(image_path, *args, **kwargs):
    """

    :param image_path:
    :param args:
    :param kwargs:
    :return:
    """

    header_lines = kwargs.pop('header_lines', 0)
    delimiter = kwargs.pop('delimiter', None)

    image = np.loadtxt(image_path,
                       skiprows=header_lines,
                       delimiter=delimiter)

    return image<commit_msg>Rename _parse_dm3_parms to unnest_parms_dicts<commit_after>"""
Created on Nov 8, 2016

@author: Chris Smith -- csmith55@utk.edu
"""
import os
import numpy as np
import array
from skimage.io import imread
from .dm3_image_utils import parse_dm_header, imagedatadict_to_ndarray
from . import dm4reader

def read_image(image_path, *args, **kwargs):
    """
    Read the image file at `image_path` into a numpy array

    Parameters
    ----------
    image_path : str
        Path to the image file

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    image_parms : dict
        Dictionary containing image parameters.  If image type does not have
        parameters then an empty dictionary is returned.
    """
    ext = os.path.splitext(image_path)[1]
    if ext == '.dm3':
        return read_dm3(image_path, *args, **kwargs)
    elif ext == '.dm4':
        return read_dm4(image_path, *args, **kwargs)
    elif ext == '.txt':
        return read_txt(image_path, *args, **kwargs), dict()
    else:
        # Set the as_grey argument to True is not already provided.
        kwargs['as_grey'] = (kwargs.pop('as_grey', True))
        return imread(image_path, *args, **kwargs), dict()

def read_dm3(image_path, get_parms=True):
    """
    Read an image from a dm3 file into a numpy array

    image_path : str
        Path to the image file
    get_parms : Boolean, optional
        Should the parameters from the dm3 file be returned
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
    """
    image_file = open(image_path, 'rb')
    dmtag = parse_dm_header(image_file)
    img_index = -1
    image = imagedatadict_to_ndarray(dmtag['ImageList'][img_index]['ImageData'])
    image_parms = dmtag['ImageList'][img_index]['ImageTags']

    if get_parms:
        image_parms = unnest_parm_dicts(image_parms)
    else:
        image_parms = dict()

    return image, image_parms

def unnest_parm_dicts(image_parms, prefix=''):
    """
    Parses the nested image parameter dictionary and converts it to a single
    level dictionary, prepending the name of inner dictionaries to their
    keys to denote level.

    Parameters
    ----------
    image_parms : dict

    Returns
    -------

    """
    new_parms = dict()
    for name, val in image_parms.iteritems():
        # print 'name',name,'val',val
        name = '-'.join([prefix]+name.split()).strip('-')
        if isinstance(val, dict):
            new_parms.update(unnest_parm_dicts(val, name))
        elif isinstance(val, list) and isinstance(val[0], dict):
            for thing in val:
                new_parms.update(unnest_parm_dicts(thing, name))
        else:
            new_parms[name] = try_tag_to_string(val)

    return new_parms

def read_dm4(file_path, *args, **kwargs):
    """
    Read dm4 file

    :param file_path:
    :return:
    """
    get_parms = kwargs.pop('get_parms', True)
    header = kwargs.pop('header', None)

    file_parms = dict()
    dm4_file = dm4reader.DM4File.open(file_path)
    if header is None:
        tags = dm4_file.read_directory()
        header = tags.named_subdirs['ImageList'].dm4_tag
        image_list = tags.named_subdirs['ImageList'].unnamed_subdirs
    else:
        dm4_file.hfile.seek(header.offset)
        image_list = dm4_file.read_directory(header)

    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']

        x_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[0])
        y_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[1])

        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.uint16)
        image_array = np.reshape(image_array, (y_dim, x_dim))

    if get_parms:
        file_parms = parse_dm4_parms(dm4_file, tags, '')
        file_parms['Image_Tag'] = header

    return image_array, file_parms

def parse_dm4_parms(dm4_file, tag_dir, base_name=''):
    """
    Recursive function to trace the dictionary tree of the Image Data
    and build a single dictionary of all parameters

    Parameters
    ----------
    dm4_file : DM4File
        File object of the dm4 file to be parsed.
    tag_dir : dict
        Dictionary to be traced.  Has the following attributes:
            tag_dir.name : str
                Name of the directory
            tag_dir.dm4_tag : str
    base_name : str
        Base name of parameters.  Tag and subdirectory names will be appended
        for named tags and subdirectories.  Unnamed ones will recieve a number.
        Default ''.  'Root' is automatically prepended to the name.

    Returns
    -------
    parm_dict : dict()
        Dictionary containing the name:value pairs of all parameters `dm4_file`
    """
    parm_dict = dict()

    '''
    Loop over named tags
    '''
    for name in tag_dir.named_tags.keys():
        '''
        Skip Data tags.  These will be handled elseware.
        '''
        if name == 'Data':
            continue
        tag_name = '_'.join([base_name, name.replace(' ', '_')])
        if base_name == '':
            tag_name = 'Root'+tag_name
        tag_data = dm4_file.read_tag_data(tag_dir.named_tags[name])

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over unnamed tags
    '''
    for itag, tag in enumerate(tag_dir.unnamed_tags):
        tag_name = '_'.join([base_name, 'Tag_{:03d}'.format(itag)])
        if base_name == '':
            tag_name = 'Root'+tag_name

        tag_data = dm4_file.read_tag_data(tag)

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over named subdirectories
    '''
    for name in tag_dir.named_subdirs.keys():
        dir_name = '_'.join([base_name, name.replace(' ', '_')])
        sub_dir = tag_dir.named_subdirs[name]
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    '''
    Loop over unnamed subdirectories
    '''
    for idir, sub_dir in enumerate(tag_dir.unnamed_subdirs):
        dir_name = '_'.join([base_name, 'SubDir_{:03d}'.format(idir)])
        if base_name == '':
            dir_name = 'Root'+dir_name
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    return parm_dict

def try_tag_to_string(tag_data):
    """
    Attempt to convert array of integers into a string

    Parameters
    ----------
    tag_data : array.array
        Array of 16-bit integers

    """
    if not isinstance(tag_data, array.array):
        return tag_data

    if tag_data.typecode == 'H':
        try:
            tag_data = str(tag_data.tostring().decode('utf-16'))
        except UnicodeDecodeError:
            pass
        except UnicodeEncodeError:
            pass
        except:
            raise

    return tag_data

def read_txt(image_path, *args, **kwargs):
    """

    :param image_path:
    :param args:
    :param kwargs:
    :return:
    """

    header_lines = kwargs.pop('header_lines', 0)
    delimiter = kwargs.pop('delimiter', None)

    image = np.loadtxt(image_path,
                       skiprows=header_lines,
                       delimiter=delimiter)

    return image<|endoftext|>