<commit_before>import os
import yaml
import ephem

# from astropy import units as u
# from astropy.coordinates import SkyCoord

import panoptes.utils.config as config
import panoptes.utils.logger as logger
import panoptes.utils.serial as serial
import panoptes.utils.error as error

@logger.has_logger
@config.has_config
class AbstractMount(object):

    """
    Abstract Base class for controlling a mount

    Methods to be implemented:
        - check_coordinates
        - sync_coordinates
        - slew_to_coordinates
        - slew_to_park
        - echo

    """

    def __init__(self,
                 config=dict(),
                 commands=dict(),
                 site=None,
                 connect_on_startup=True,
                 ):
        """
        Create a new mount class. Sets the following properies:

            - self.non_sidereal_available = False
            - self.PEC_available = False
            - self.is_connected = False
            - self.is_slewing = False
            - self.is_initialized = False

        After setting, calls the following:

            - _setup_commands
            - _setup_site
        """

        # Create an object for just the mount config items
        self.mount_config = config if len(config) else dict()

        # Check the config for required items
        assert self.mount_config.get('port') is not None, self.logger.error('No mount port specified, cannot create mount\n {}'.format(self.mount_config))

        self.logger.info('Creating mount')

        # Setup commands for mount
        self.commands = self._setup_commands(commands)

        # We set some initial mount properties. May come from config
        self.non_sidereal_available = self.mount_config.setdefault('non_sidereal_available', False)
        self.PEC_available = self.mount_config.setdefault('PEC_available', False)

        # Initial states
        self.is_initialized = False

        self.site = site

        # Setup our serial connection at the given port
        self.port = self.mount_config.get('port')
        self.serial = serial.SerialData(port=self.port)

        # Setup connection
        self.logger.info('connect_on_startup status: {}'.format(connect_on_startup))
        if connect_on_startup:
            self.initialize_mount()
            self._setup_site(site=self.site)

        self.logger.info('Mount created')

    @property
    def is_connected(self):
        """
        Checks the serial connection on the mount to determine if connection is open
        """
        self.logger.info('Mount is_connected: {}'.format(self.serial.is_connected))
        return self.serial.is_connected

    @property
    def is_slewing(self):
        """
        Class property that determines if mount is slewing.
        For some mounts, this is a built in function. For mount which do not have it we will have to
        write something based on how the coordinates are changing.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized, cannot check slewing')

        # Make sure response matches what it should for slewing
        if self.serial_query('is_slewing') == '1':
            self._is_slewing = True
        else:
            self._is_slewing = False

        self.logger.info('Mount is_slewing: {}'.format(self._is_slewing))
        return self._is_slewing

    @property
    def is_parked(self):
        """
        Class property that determines if mount is parked.
        For some mounts, this is a built in function. For mount which do not have it we will have to
        write something based on how the coordinates are changing.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized, cannot check parked')

        # Make sure response matches what it should for parked
        if self.serial_query('is_parked') == self._get_expected_response('is_parked'):
            self._is_slewing = True
        else:
            self._is_slewing = False

        self.logger.info('Mount is_parked: {}'.format(self._is_slewing))
        return self._is_slewing

    def get_target_coordinates(self):
        """
        Gets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount.

        @retval         astropy.coordinates.SkyCoord
        """

        if self._target_coordinates is None:
            self.logger.info("Target coordinates not set")
        else:
            self.logger.info('Mount target_coordinates: {}'.format(self._target_coordinates))

        return self._target_coordinates

    def set_target_coordinates(self, coords):
        """
        Sets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount.

        @retval         Boolean indicating success
        """
        target_set = False

        # Save the coordinates
        self._target_coordinates = coords

        # Send coordinates to mount
        self.serial_query('set_ra', mount_ra)
        self.serial_query('set_dec', mount_dec)

        return target_set

    def get_current_coordinates(self):
        """
        Reads out the current RA/Dec from the mount.

        @retval         astropy.coordinates.SkyCoord
        """
        self.logger.info('Mount current_coordinates')

        mount_ra = self.serial_query('get_ra')
        mount_dec = self.serial_query('get_dec')

        self._current_coordinates = self._mount_coord_to_skycoord(mount_ra, mount_dec)

        return self._current_coordinates

    def sync_coordinates(self):
        """
        Takes as input, the actual coordinates (J2000) of the mount and syncs the mount on them.
        Used after a plate solve.
        Once we have a mount model, we would use sync only initially,
        then subsequent plate solves would be used as input to the model.
        """
        raise NotImplementedError()

    ### Movement Methods ###

    def slew_to_coordinates(self, coords, ra_rate=None, dec_rate=None):
        """
        Inputs:
            RA and Dec
            RA tracking rate (in arcsec per second, use 15.0 in absence of tracking model).
            Dec tracking rate (in arcsec per second, use 0.0 in absence of tracking model).
        """
        assert isinstance(coords, tuple), self.logger.warning('slew_to_coordinates expects RA-Dec coords')

        # Check the existing guide rate
        # rate = self.serial_query('get_guide_rate')
        # self.logger.debug("slew_to_coordinates: coords: {} \t rate: {} {}".format(coords,ra_rate,dec_rate))

        # Set the coordinates
        ra, dec = coords
        self.ra = ra
        self.dec = dec

        if self.serial_query('slew_to_target'):
            self.logger.debug('Slewing to target')

    def slew_to_target(self):
        """
        Slews to the current _target_coordinates
        """
        assert self._target_coordinates is not None, self.logger.warning("_target_coordinates not set")

        if self.serial_query('slew_to_target'):
            self.logger.debug('Slewing to target')
        else:
            self.logger.warning('Problem with slew_to_target')

    def slew_to_park(self):
        """
        No inputs, the park position should be defined in configuration
        """
        return self.serial_query('goto_park')

    ### Utility Methods ###
    def connect(self):
        """
        Connects to the mount via the serial port (self.port). Opens a serial connection
        and calls initialize_mount
        """
        self.logger.info('Connecting to mount')

        if self.serial.ser.isOpen() is False:
            try:
                self._connect_serial()
            except OSError as err:
                self.logger.error("OS error: {0}".format(err))
            except:
                self.logger.warning('Could not create serial connection to mount.')
                self.logger.warning('NO MOUNT CONTROL AVAILABLE')
                # raise error.BadSerialConnection('Cannot create serial connect for mount at port {}'.format(self.port))

        self.logger.debug('Mount connected: {}'.format(self.is_connected))

        return self.is_connected

    def serial_query(self, cmd, *args):
        """
        Performs a send and then returns response. Will do a translate on cmd first. This should
        be the major serial utility for commands. Accepts an additional args that is passed
        along with the command. Checks for and only accepts one args param.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')
        assert len(args) <=1, self.logger.warning('Ignoring additional arguments for {}'.format(cmd))

        params = args[0] if args else None

        self.logger.debug('Mount Query & Params: {} {}'.format(cmd, params))

        self.serial.clear_buffer()

        full_command = self._get_command(cmd,params=params)

        self.serial_write(full_command)

        return self.serial_read()

    def serial_write(self, string_command):
        """
            Sends a string command to the mount via the serial port. First 'translates'
            the message into the form specific mount can understand
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')

        self.logger.debug("Mount Send: {}".format(string_command))
        self.serial.write(string_command)

    def serial_read(self):
        """
        Reads from the serial connection.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')

        response = ''

        # while response == '':
        response = self.serial.read()

        self.logger.debug("Mount Read: {}".format(response))

        # Strip the line ending (#) and return
        return response.rstrip('#')

    def check_coordinates(self):
        """
        Query the mount for the current position of the mount.
        This will be useful in comparing the position of the mount to the orientation
        indicated by the accelerometer or by an astrometric plate solve.
        """
        self.logger.info('Mount check_coordinates')

        ra = self.serial_query('get_ra')
        dec = self.serial_query('get_dec')

        alt = self.serial_query('get_alt')
        az = self.serial_query('get_az')

        self.logger.debug('Mount check_coordinates: \nRA/Dec: \t {} {}\nAlt/Az: {} {}'.format(ra,dec, alt, az))

        return (ra, dec)

    def ping(self):
        """ Pings the mount by returning time """
        return self.serial_query('get_local_time')

    ### Private Methods ###

    def _setup_commands(self, commands):
        """
        Does any setup for the commands needed for this mount. Mostly responsible for
        setting the pre- and post-commands. We could also do some basic checking here
        to make sure required commands are in fact available.
        """
        self.logger.info('Setting up commands for mount')
        # If commands are not passed in, look for configuration file
        # self.logger.debug('commands: {}'.format(commands))

        if len(commands) == 0:
            model = self.mount_config.get('model')
            if model is not None:
                conf_file = "{}/{}/{}.yaml".format(self.config.get('base_dir', os.getcwd()), 'panoptes/mount/', model)

                self.logger.debug("Loading mount commands file: {}".format(conf_file))
                if os.path.isfile(conf_file):
                    try:
                        with open(conf_file, 'r') as f:
                            commands.update(yaml.load(f.read()))
                            self.logger.debug("Mount commands updated from {}".format(conf_file))
                    except OSError as err:
                        self.logger.warning(
                            'Cannot load commands config file: {} \n {}'.format(conf_file, err))
                    except:
                        self.logger.warning("Problem loading mount command file")

        # Get the pre- and post- commands
        self._pre_cmd = commands.setdefault('cmd_pre', ':')
        self._post_cmd = commands.setdefault('cmd_post', '#')

        # Commands to check
        # NOTE: We might want to slim this down and decide which ones fail
        required_commands = [
            'cmd_post', 'cmd_pre', 'get_alt', 'get_az', 'get_dec', 'get_guide_rate', 'get_lat', 'get_local_date',
            'get_local_time', 'get_long', 'get_ra', 'goto_home', 'goto_park', 'is_home', 'is_parked', 'is_sidereal',
            'is_slewing', 'is_tracking', 'mount_info', 'set_alt', 'set_az', 'set_dec', 'set_guide_rate', 'set_lat',
            'set_local_date', 'set_local_time', 'set_long', 'set_ra', 'set_sidereal_rate', 'set_sidereal_tracking',
            'slew_to_target', 'start_tracking', 'stop_slewing', 'stop_tracking', 'unpark', 'version',
        ]

        # Give a warning if command not available
        for cmd in required_commands:
            assert commands.get(cmd) is not None, self.logger.warning(
                'No {} command available for mount'.format(cmd))

        self.logger.info('Mount commands set up')
        return commands

    def _setup_site(self, site=None):
        """
        Sets the mount up to the current site. Includes:
        * Latitude set_long
        * Longitude set_lat
        * Universal Time Offset set_gmt_offset
        * Daylight Savings disable_daylight_savings
        * Current Date set_local_date
        * Current Time set_local_time
        """
        assert site is not None, self.logger.warning('_setup_site requires a site in the config')
        self.logger.info('Setting up mount for site')

        # Location
        self.serial_query('set_long', site.lon)
        self.serial_query('set_lat', site.lat)

        # Time
        self.serial_query('disable_daylight_savings')

        self.serial_query('set_gmt_offset', self.config.get('site').get('gmt_offset', 0))

        dt = ephem.localtime(site.date)

        t = "{:02d}:{:02d}:{:02d}".format(dt.hour, dt.minute, dt.second)
        d = "{:02d}/{:02d}/{:02d}".format(dt.month, dt.day, dt.year-2000)

        self.serial_query('set_local_time', t)
        self.serial_query('set_local_date', d)

    def _connect_serial(self):
        """Gets up serial connection """
        self.logger.info('Making serial connection for mount at {}'.format(self.port))

        self.serial.connect()

        self.logger.info('Mount connected via serial')

    def _get_command(self, cmd, params=''):
        """ Looks up appropriate command for telescope """
        self.logger.debug('Mount Command Lookup: {}'.format(cmd))

        full_command = ''

        # Get the actual command
        cmd_info = self.commands.get(cmd)

        if cmd_info is not None:

            # Check if this command needs params
            if 'params' in cmd_info:
                if params is '':
                    raise error.InvalidMountCommand('{} expects params: {}'.format(cmd, cmd_info.get('params')))

                full_command = "{}{} {}{}".format( self._pre_cmd, cmd_info.get('cmd'), params, self._post_cmd)
            else:
                full_command = "{}{}{}".format( self._pre_cmd, cmd_info.get('cmd'), self._post_cmd)

            self.logger.debug('Mount Full Command: {}'.format(full_command))
        else:
            raise error.InvalidMountCommand('No command for {}'.format(cmd))

        return full_command

    def _get_expected_response(self, cmd):
        """ Looks up appropriate response for command for telescope """
        self.logger.debug('Mount Response Lookup: {}'.format(cmd))

        response = ''

        # Get the actual command
        cmd_info = self.commands.get(cmd)

        if cmd_info is not None:
            response = cmd_info.get('response')
            self.logger.debug('Mount Command Response: {}'.format(response))
        else:
            raise error.InvalidMountCommand('No result for command {}'.format(cmd))

        return response

    ### NotImplemented methods ###
    def _mount_coord_to_skycoord(self):
        raise NotImplemented()

    def echo(self):
        """ mount-specific echo command """
        raise NotImplemented()

    def initialize_mount(self):
        raise NotImplemented()

<commit_msg>Remove property<commit_after>import os
import yaml
import ephem

# from astropy import units as u
# from astropy.coordinates import SkyCoord

import panoptes.utils.config as config
import panoptes.utils.logger as logger
import panoptes.utils.serial as serial
import panoptes.utils.error as error

@logger.has_logger
@config.has_config
class AbstractMount(object):

    """
    Abstract Base class for controlling a mount

    Methods to be implemented:
        - check_coordinates
        - sync_coordinates
        - slew_to_coordinates
        - slew_to_park
        - echo

    """

    def __init__(self,
                 config=dict(),
                 commands=dict(),
                 site=None,
                 connect_on_startup=True,
                 ):
        """
        Create a new mount class. Sets the following properies:

            - self.non_sidereal_available = False
            - self.PEC_available = False
            - self.is_connected = False
            - self.is_slewing = False
            - self.is_initialized = False

        After setting, calls the following:

            - _setup_commands
            - _setup_site
        """

        # Create an object for just the mount config items
        self.mount_config = config if len(config) else dict()

        # Check the config for required items
        assert self.mount_config.get('port') is not None, self.logger.error('No mount port specified, cannot create mount\n {}'.format(self.mount_config))

        self.logger.info('Creating mount')

        # Setup commands for mount
        self.commands = self._setup_commands(commands)

        # We set some initial mount properties. May come from config
        self.non_sidereal_available = self.mount_config.setdefault('non_sidereal_available', False)
        self.PEC_available = self.mount_config.setdefault('PEC_available', False)

        # Initial states
        self.is_initialized = False

        self.site = site

        # Setup our serial connection at the given port
        self.port = self.mount_config.get('port')
        self.serial = serial.SerialData(port=self.port)

        # Setup connection
        self.logger.info('connect_on_startup status: {}'.format(connect_on_startup))
        if connect_on_startup:
            self.initialize_mount()
            self._setup_site(site=self.site)

        self.logger.info('Mount created')

    def is_connected(self):
        """
        Checks the serial connection on the mount to determine if connection is open
        """
        self.logger.info('Mount is_connected: {}'.format(self.serial.is_connected))
        return self.serial.is_connected

    def is_slewing(self):
        """
        Class property that determines if mount is slewing.
        For some mounts, this is a built in function. For mount which do not have it we will have to
        write something based on how the coordinates are changing.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized, cannot check slewing')

        # Make sure response matches what it should for slewing
        if self.serial_query('is_slewing') == '1':
            self._is_slewing = True
        else:
            self._is_slewing = False

        self.logger.info('Mount is_slewing: {}'.format(self._is_slewing))
        return self._is_slewing

    def is_parked(self):
        """
        Class property that determines if mount is parked.
        For some mounts, this is a built in function. For mount which do not have it we will have to
        write something based on how the coordinates are changing.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized, cannot check parked')

        # Make sure response matches what it should for parked
        if self.serial_query('is_parked') == self._get_expected_response('is_parked'):
            self._is_slewing = True
        else:
            self._is_slewing = False

        self.logger.info('Mount is_parked: {}'.format(self._is_slewing))
        return self._is_slewing

    def get_target_coordinates(self):
        """
        Gets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount.

        @retval         astropy.coordinates.SkyCoord
        """

        if self._target_coordinates is None:
            self.logger.info("Target coordinates not set")
        else:
            self.logger.info('Mount target_coordinates: {}'.format(self._target_coordinates))

        return self._target_coordinates

    def set_target_coordinates(self, coords):
        """
        Sets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount.

        @retval         Boolean indicating success
        """
        target_set = False

        # Save the coordinates
        self._target_coordinates = coords

        # Send coordinates to mount
        self.serial_query('set_ra', mount_ra)
        self.serial_query('set_dec', mount_dec)

        return target_set

    def get_current_coordinates(self):
        """
        Reads out the current RA/Dec from the mount.

        @retval         astropy.coordinates.SkyCoord
        """
        self.logger.info('Mount current_coordinates')

        mount_ra = self.serial_query('get_ra')
        mount_dec = self.serial_query('get_dec')

        self._current_coordinates = self._mount_coord_to_skycoord(mount_ra, mount_dec)

        return self._current_coordinates

    def sync_coordinates(self):
        """
        Takes as input, the actual coordinates (J2000) of the mount and syncs the mount on them.
        Used after a plate solve.
        Once we have a mount model, we would use sync only initially,
        then subsequent plate solves would be used as input to the model.
        """
        raise NotImplementedError()

    ### Movement Methods ###

    def slew_to_coordinates(self, coords, ra_rate=None, dec_rate=None):
        """
        Inputs:
            RA and Dec
            RA tracking rate (in arcsec per second, use 15.0 in absence of tracking model).
            Dec tracking rate (in arcsec per second, use 0.0 in absence of tracking model).
        """
        assert isinstance(coords, tuple), self.logger.warning('slew_to_coordinates expects RA-Dec coords')

        # Check the existing guide rate
        # rate = self.serial_query('get_guide_rate')
        # self.logger.debug("slew_to_coordinates: coords: {} \t rate: {} {}".format(coords,ra_rate,dec_rate))

        # Set the coordinates
        ra, dec = coords
        self.ra = ra
        self.dec = dec

        if self.serial_query('slew_to_target'):
            self.logger.debug('Slewing to target')

    def slew_to_target(self):
        """
        Slews to the current _target_coordinates
        """
        assert self._target_coordinates is not None, self.logger.warning("_target_coordinates not set")

        if self.serial_query('slew_to_target'):
            self.logger.debug('Slewing to target')
        else:
            self.logger.warning('Problem with slew_to_target')

    def slew_to_park(self):
        """
        No inputs, the park position should be defined in configuration
        """
        return self.serial_query('goto_park')

    ### Utility Methods ###
    def connect(self):
        """
        Connects to the mount via the serial port (self.port). Opens a serial connection
        and calls initialize_mount
        """
        self.logger.info('Connecting to mount')

        if self.serial.ser.isOpen() is False:
            try:
                self._connect_serial()
            except OSError as err:
                self.logger.error("OS error: {0}".format(err))
            except:
                self.logger.warning('Could not create serial connection to mount.')
                self.logger.warning('NO MOUNT CONTROL AVAILABLE')
                # raise error.BadSerialConnection('Cannot create serial connect for mount at port {}'.format(self.port))

        self.logger.debug('Mount connected: {}'.format(self.is_connected))

        return self.is_connected

    def serial_query(self, cmd, *args):
        """
        Performs a send and then returns response. Will do a translate on cmd first. This should
        be the major serial utility for commands. Accepts an additional args that is passed
        along with the command. Checks for and only accepts one args param.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')
        assert len(args) <=1, self.logger.warning('Ignoring additional arguments for {}'.format(cmd))

        params = args[0] if args else None

        self.logger.debug('Mount Query & Params: {} {}'.format(cmd, params))

        self.serial.clear_buffer()

        full_command = self._get_command(cmd,params=params)

        self.serial_write(full_command)

        return self.serial_read()

    def serial_write(self, string_command):
        """
            Sends a string command to the mount via the serial port. First 'translates'
            the message into the form specific mount can understand
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')

        self.logger.debug("Mount Send: {}".format(string_command))
        self.serial.write(string_command)

    def serial_read(self):
        """
        Reads from the serial connection.
        """
        assert self.is_initialized, self.logger.warning('Mount has not been initialized')

        response = ''

        # while response == '':
        response = self.serial.read()

        self.logger.debug("Mount Read: {}".format(response))

        # Strip the line ending (#) and return
        return response.rstrip('#')

    def check_coordinates(self):
        """
        Query the mount for the current position of the mount.
        This will be useful in comparing the position of the mount to the orientation
        indicated by the accelerometer or by an astrometric plate solve.
        """
        self.logger.info('Mount check_coordinates')

        ra = self.serial_query('get_ra')
        dec = self.serial_query('get_dec')

        alt = self.serial_query('get_alt')
        az = self.serial_query('get_az')

        self.logger.debug('Mount check_coordinates: \nRA/Dec: \t {} {}\nAlt/Az: {} {}'.format(ra,dec, alt, az))

        return (ra, dec)

    def ping(self):
        """ Pings the mount by returning time """
        return self.serial_query('get_local_time')

    ### Private Methods ###

    def _setup_commands(self, commands):
        """
        Does any setup for the commands needed for this mount. Mostly responsible for
        setting the pre- and post-commands. We could also do some basic checking here
        to make sure required commands are in fact available.
        """
        self.logger.info('Setting up commands for mount')
        # If commands are not passed in, look for configuration file
        # self.logger.debug('commands: {}'.format(commands))

        if len(commands) == 0:
            model = self.mount_config.get('model')
            if model is not None:
                conf_file = "{}/{}/{}.yaml".format(self.config.get('base_dir', os.getcwd()), 'panoptes/mount/', model)

                self.logger.debug("Loading mount commands file: {}".format(conf_file))
                if os.path.isfile(conf_file):
                    try:
                        with open(conf_file, 'r') as f:
                            commands.update(yaml.load(f.read()))
                            self.logger.debug("Mount commands updated from {}".format(conf_file))
                    except OSError as err:
                        self.logger.warning(
                            'Cannot load commands config file: {} \n {}'.format(conf_file, err))
                    except:
                        self.logger.warning("Problem loading mount command file")

        # Get the pre- and post- commands
        self._pre_cmd = commands.setdefault('cmd_pre', ':')
        self._post_cmd = commands.setdefault('cmd_post', '#')

        # Commands to check
        # NOTE: We might want to slim this down and decide which ones fail
        required_commands = [
            'cmd_post', 'cmd_pre', 'get_alt', 'get_az', 'get_dec', 'get_guide_rate', 'get_lat', 'get_local_date',
            'get_local_time', 'get_long', 'get_ra', 'goto_home', 'goto_park', 'is_home', 'is_parked', 'is_sidereal',
            'is_slewing', 'is_tracking', 'mount_info', 'set_alt', 'set_az', 'set_dec', 'set_guide_rate', 'set_lat',
            'set_local_date', 'set_local_time', 'set_long', 'set_ra', 'set_sidereal_rate', 'set_sidereal_tracking',
            'slew_to_target', 'start_tracking', 'stop_slewing', 'stop_tracking', 'unpark', 'version',
        ]

        # Give a warning if command not available
        for cmd in required_commands:
            assert commands.get(cmd) is not None, self.logger.warning(
                'No {} command available for mount'.format(cmd))

        self.logger.info('Mount commands set up')
        return commands

    def _setup_site(self, site=None):
        """
        Sets the mount up to the current site. Includes:
        * Latitude set_long
        * Longitude set_lat
        * Universal Time Offset set_gmt_offset
        * Daylight Savings disable_daylight_savings
        * Current Date set_local_date
        * Current Time set_local_time
        """
        assert site is not None, self.logger.warning('_setup_site requires a site in the config')
        self.logger.info('Setting up mount for site')

        # Location
        self.serial_query('set_long', site.lon)
        self.serial_query('set_lat', site.lat)

        # Time
        self.serial_query('disable_daylight_savings')

        self.serial_query('set_gmt_offset', self.config.get('site').get('gmt_offset', 0))

        dt = ephem.localtime(site.date)

        t = "{:02d}:{:02d}:{:02d}".format(dt.hour, dt.minute, dt.second)
        d = "{:02d}/{:02d}/{:02d}".format(dt.month, dt.day, dt.year-2000)

        self.serial_query('set_local_time', t)
        self.serial_query('set_local_date', d)

    def _connect_serial(self):
        """Gets up serial connection """
        self.logger.info('Making serial connection for mount at {}'.format(self.port))

        self.serial.connect()

        self.logger.info('Mount connected via serial')

    def _get_command(self, cmd, params=''):
        """ Looks up appropriate command for telescope """
        self.logger.debug('Mount Command Lookup: {}'.format(cmd))

        full_command = ''

        # Get the actual command
        cmd_info = self.commands.get(cmd)

        if cmd_info is not None:

            # Check if this command needs params
            if 'params' in cmd_info:
                if params is '':
                    raise error.InvalidMountCommand('{} expects params: {}'.format(cmd, cmd_info.get('params')))

                full_command = "{}{} {}{}".format( self._pre_cmd, cmd_info.get('cmd'), params, self._post_cmd)
            else:
                full_command = "{}{}{}".format( self._pre_cmd, cmd_info.get('cmd'), self._post_cmd)

            self.logger.debug('Mount Full Command: {}'.format(full_command))
        else:
            raise error.InvalidMountCommand('No command for {}'.format(cmd))

        return full_command

    def _get_expected_response(self, cmd):
        """ Looks up appropriate response for command for telescope """
        self.logger.debug('Mount Response Lookup: {}'.format(cmd))

        response = ''

        # Get the actual command
        cmd_info = self.commands.get(cmd)

        if cmd_info is not None:
            response = cmd_info.get('response')
            self.logger.debug('Mount Command Response: {}'.format(response))
        else:
            raise error.InvalidMountCommand('No result for command {}'.format(cmd))

        return response

    ### NotImplemented methods ###
    def _mount_coord_to_skycoord(self):
        raise NotImplemented()

    def echo(self):
        """ mount-specific echo command """
        raise NotImplemented()

    def initialize_mount(self):
        raise NotImplemented()

<|endoftext|>


<commit_before><commit_msg>pep8 fix<commit_after><|endoftext|>


<commit_before>import os
import argparse
import pandas as pd
import numpy as np
from unet3d.train import run_training
from unet3d.utils.filenames import wrapped_partial, generate_filenames, load_bias, load_sequence
from unet3d.utils.sequences import (WholeVolumeToSurfaceSequence, HCPRegressionSequence, ParcelBasedSequence,
                                    WindowedAutoEncoderSequence)
from unet3d.utils.pytorch.dataset import (WholeBrainCIFTI2DenseScalarDataset, HCPRegressionDataset, AEDataset,
                                          WholeVolumeSegmentationDataset, WindowedAEDataset)
from unet3d.utils.utils import load_json, in_config, dump_json
from unet3d.utils.custom import get_metric_data_from_config
from unet3d.scripts.predict import format_parser as format_prediction_args
from unet3d.scripts.predict import run_inference
from unet3d.scripts.script_utils import get_machine_config, add_machine_config_to_parser

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--config_filename", required=True,
                        help="JSON configuration file specifying the parameters for model training.")
    parser.add_argument("--model_filename",
                        help="Location to save the model during and after training. If this filename exists "
                             "prior to training, the model will be loaded from the filename.",
                        required=True)
    parser.add_argument("--training_log_filename",
                        help="CSV filename to save the to save the training and validation results for each epoch.",
                        required=True)
    parser.add_argument("--fit_gpu_mem", type=float,
                        help="Specify the amount of gpu memory available on a single gpu and change the image size to "
                             "fit into gpu memory automatically. Will try to find the largest image size that will fit "
                             "onto a single gpu. The batch size is overwritten and set to the number of gpus available."
                             " The new image size will be written to a new config file ending named "
                             "'<original_config>_auto.json'. This option is experimental and only works with the UNet "
                             "model. It has only been tested with gpus that have 12GB and 32GB of memory.")
    parser.add_argument("--group_average_filenames")
    parser.add_argument("--batch_size", help="Override the batch size from the config file.", type=int)
    parser.add_argument("--debug", type=bool, action="store_true", default=False,
                        help="Raises an error if a training file is not found. The default is to silently skip"
                             "any training files that cannot be found. Use this flag to debug the config for finding"
                             "the data.")
    add_machine_config_to_parser(parser)
    subparsers = parser.add_subparsers(help="sub-commands", dest='sub_command')
    prediction_parser = subparsers.add_parser(name="predict",
                                              help="Run prediction after the model has finished training")
    format_prediction_args(prediction_parser, sub_command=True)
    args = parser.parse_args()

    return args

def check_hierarchy(config):
    if in_config("labels", config["sequence_kwargs"]) and in_config("use_label_hierarchy", config["sequence_kwargs"]):
        config["sequence_kwargs"].pop("use_label_hierarchy")
        labels = config["sequence_kwargs"].pop("labels")
        new_labels = list()
        while len(labels):
            new_labels.append(labels)
            labels = labels[1:]
        config["sequence_kwargs"]["labels"] = new_labels

def compute_unet_number_of_voxels(window, channels, n_layers):
    n_voxels = 0
    for i in range(n_layers):
        n_voxels = n_voxels + ((1/(2**(3*i))) * window[0] * window[1] * window[2] * channels * 2**i * 2)
    return n_voxels

def compute_window_size(step, step_size, ratios):
    step_ratios = np.asarray(ratios) * step * step_size
    mod = np.mod(step_ratios, step_size)
    return np.asarray(step_ratios - mod + np.round(mod / step_size) * step_size, dtype=int)

def update_config_to_fit_gpu_memory(config, n_gpus, gpu_memory, output_filename, voxels_per_gb=17000000.0,
                                    ratios=(1.22, 1.56, 1.0)):
    max_voxels = voxels_per_gb * gpu_memory
    n_layers = len(config["model_kwargs"]["encoder_blocks"])
    step_size = 2**(n_layers - 1)
    step = 1
    window = compute_window_size(step, step_size, ratios)
    n_voxels = compute_unet_number_of_voxels(window, config["model_kwargs"]["base_width"], n_layers)
    while n_voxels <= max_voxels:
        step = step + 1
        window = compute_window_size(step, step_size, ratios)
        n_voxels = compute_unet_number_of_voxels(window, config["model_kwargs"]["base_width"], n_layers)
    window = compute_window_size(step - 1, step_size, ratios).tolist()
    print("Setting window size to {} x {} x {}".format(*window))
    print("Setting batch size to", n_gpus)
    config["window"] = window
    config["model_kwargs"]["input_shape"] = window
    config["batch_size"] = n_gpus
    config["validation_batch_size"] = n_gpus
    print("Writing new configuration file:", output_filename)
    dump_json(config, output_filename)

def main():
    import nibabel as nib
    nib.imageglobals.logger.level = 40

    namespace = parse_args()

    print("Config: ", namespace.config_filename)
    config = load_json(namespace.config_filename)

    if "package" in config:
        package = config["package"]
    else:
        package = "keras"

    if "metric_names" in config and not config["n_outputs"] == len(config["metric_names"]):
        raise ValueError("n_outputs set to {}, but number of metrics is {}.".format(config["n_outputs"],
                                                                                len(config["metric_names"])))

    print("Model: ", namespace.model_filename)
    print("Log: ", namespace.training_log_filename)
    system_config = get_machine_config(namespace)
    training_function_kwargs = in_config("training_function_kwargs", config, dict())

    # Override the batch size from the config file
    if namespace.batch_size:
        config["batch_size"] = namespace.batch_size

    if namespace.fit_gpu_mem and namespace.fit_gpu_mem > 0:
        update_config_to_fit_gpu_memory(config=config, n_gpus=system_config["n_gpus"], gpu_memory=namespace.fit_gpu_mem,
                                        output_filename=namespace.config_filename.replace(".json", "_auto.json"))

    if namespace.group_average_filenames is not None:
        group_average = get_metric_data_from_config(namespace.group_average_filenames, namespace.config_filename)
        from unet3d.models.keras.resnet.resnet import compare_scores
        model_metrics = [wrapped_partial(compare_scores, comparison=group_average)]
        metric_to_monitor = "compare_scores"
    else:
        model_metrics = []
        if config['skip_validation']:
            metric_to_monitor = "loss"
        else:
            metric_to_monitor = "val_loss"

    if config["skip_validation"]:
        groups = ("training",)
    else:
        groups = ("training", "validation")

    for name in groups:
        key = name + "_filenames"
        if key not in config:
            config[key] = generate_filenames(config, name, system_config,
                                             raise_if_not_exists=namespace.debug)
    if "directory" in system_config:
        directory = system_config.pop("directory")
    else:
        directory = "."

    if "sequence" in config:
        sequence_class = load_sequence(config["sequence"])
    elif "_wb_" in os.path.basename(namespace.config_filename):
        if "package" in config and config["package"] == "pytorch":
            if config["sequence"] == "AEDataset":
                sequence_class = AEDataset
            elif config["sequence"] == "WholeVolumeSegmentationDataset":
                sequence_class = WholeVolumeSegmentationDataset
            else:
                sequence_class = WholeBrainCIFTI2DenseScalarDataset
        else:
            sequence_class = WholeVolumeToSurfaceSequence
    elif config["sequence"] == "WindowedAutoEncoderSequence":
        sequence_class = WindowedAutoEncoderSequence
    elif config["sequence"] == "WindowedAEDataset":
        sequence_class = WindowedAEDataset
    elif "_pb_" in os.path.basename(namespace.config_filename):
        sequence_class = ParcelBasedSequence
        config["sequence_kwargs"]["parcellation_template"] = os.path.join(
            directory, config["sequence_kwargs"]["parcellation_template"])
    else:
        if config["package"] == "pytorch":
            sequence_class = HCPRegressionDataset
        else:
            sequence_class = HCPRegressionSequence

    if "bias_filename" in config and config["bias_filename"] is not None:
        bias = load_bias(config["bias_filename"])
    else:
        bias = None

    check_hierarchy(config)

    if in_config("add_contours", config["sequence_kwargs"], False):
        config["n_outputs"] = config["n_outputs"] * 2

    if sequence_class == ParcelBasedSequence:
        target_parcels = config["sequence_kwargs"].pop("target_parcels")
        for target_parcel in target_parcels:
            config["sequence_kwargs"]["target_parcel"] = target_parcel
            print("Training on parcel: {}".format(target_parcel))
            if type(target_parcel) == list:
                parcel_id = "-".join([str(i) for i in target_parcel])
            else:
                parcel_id = str(target_parcel)
            _training_log_filename = namespace.training_log_filename.replace(".csv", "_{}.csv".format(parcel_id))
            if os.path.exists(_training_log_filename):
                _training_log = pd.read_csv(_training_log_filename)
                if (_training_log[metric_to_monitor].values.argmin()
                        <= len(_training_log) - int(config["early_stopping_patience"])):
                    print("Already trained")
                    continue
            run_training(package,
                         config,
                         namespace.model_filename.replace(".h5", "_{}.h5".format(parcel_id)),
                         _training_log_filename,
                         sequence_class=sequence_class,
                         model_metrics=model_metrics,
                         metric_to_monitor=metric_to_monitor,
                         **training_function_kwargs,
                         **system_config)

    else:
        run_training(package, config, namespace.model_filename, namespace.training_log_filename,
                     sequence_class=sequence_class,
                     model_metrics=model_metrics,
                     metric_to_monitor=metric_to_monitor,
                     bias=bias,
                     **training_function_kwargs,
                     **system_config)

    if namespace.sub_command == "predict":
        run_inference(namespace)

if __name__ == '__main__':
    main()
<commit_msg>FIX: debugging<commit_after>import os
import argparse
import pandas as pd
import numpy as np
from unet3d.train import run_training
from unet3d.utils.filenames import wrapped_partial, generate_filenames, load_bias, load_sequence
from unet3d.utils.sequences import (WholeVolumeToSurfaceSequence, HCPRegressionSequence, ParcelBasedSequence,
                                    WindowedAutoEncoderSequence)
from unet3d.utils.pytorch.dataset import (WholeBrainCIFTI2DenseScalarDataset, HCPRegressionDataset, AEDataset,
                                          WholeVolumeSegmentationDataset, WindowedAEDataset)
from unet3d.utils.utils import load_json, in_config, dump_json
from unet3d.utils.custom import get_metric_data_from_config
from unet3d.scripts.predict import format_parser as format_prediction_args
from unet3d.scripts.predict import run_inference
from unet3d.scripts.script_utils import get_machine_config, add_machine_config_to_parser

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--config_filename", required=True,
                        help="JSON configuration file specifying the parameters for model training.")
    parser.add_argument("--model_filename",
                        help="Location to save the model during and after training. If this filename exists "
                             "prior to training, the model will be loaded from the filename.",
                        required=True)
    parser.add_argument("--training_log_filename",
                        help="CSV filename to save the to save the training and validation results for each epoch.",
                        required=True)
    parser.add_argument("--fit_gpu_mem", type=float,
                        help="Specify the amount of gpu memory available on a single gpu and change the image size to "
                             "fit into gpu memory automatically. Will try to find the largest image size that will fit "
                             "onto a single gpu. The batch size is overwritten and set to the number of gpus available."
                             " The new image size will be written to a new config file ending named "
                             "'<original_config>_auto.json'. This option is experimental and only works with the UNet "
                             "model. It has only been tested with gpus that have 12GB and 32GB of memory.")
    parser.add_argument("--group_average_filenames")
    parser.add_argument("--batch_size", help="Override the batch size from the config file.", type=int)
    parser.add_argument("--debug", action="store_true", default=False,
                        help="Raises an error if a training file is not found. The default is to silently skip"
                             "any training files that cannot be found. Use this flag to debug the config for finding"
                             "the data.")
    add_machine_config_to_parser(parser)
    subparsers = parser.add_subparsers(help="sub-commands", dest='sub_command')
    prediction_parser = subparsers.add_parser(name="predict",
                                              help="Run prediction after the model has finished training")
    format_prediction_args(prediction_parser, sub_command=True)
    args = parser.parse_args()

    return args

def check_hierarchy(config):
    if in_config("labels", config["sequence_kwargs"]) and in_config("use_label_hierarchy", config["sequence_kwargs"]):
        config["sequence_kwargs"].pop("use_label_hierarchy")
        labels = config["sequence_kwargs"].pop("labels")
        new_labels = list()
        while len(labels):
            new_labels.append(labels)
            labels = labels[1:]
        config["sequence_kwargs"]["labels"] = new_labels

def compute_unet_number_of_voxels(window, channels, n_layers):
    n_voxels = 0
    for i in range(n_layers):
        n_voxels = n_voxels + ((1/(2**(3*i))) * window[0] * window[1] * window[2] * channels * 2**i * 2)
    return n_voxels

def compute_window_size(step, step_size, ratios):
    step_ratios = np.asarray(ratios) * step * step_size
    mod = np.mod(step_ratios, step_size)
    return np.asarray(step_ratios - mod + np.round(mod / step_size) * step_size, dtype=int)

def update_config_to_fit_gpu_memory(config, n_gpus, gpu_memory, output_filename, voxels_per_gb=17000000.0,
                                    ratios=(1.22, 1.56, 1.0)):
    max_voxels = voxels_per_gb * gpu_memory
    n_layers = len(config["model_kwargs"]["encoder_blocks"])
    step_size = 2**(n_layers - 1)
    step = 1
    window = compute_window_size(step, step_size, ratios)
    n_voxels = compute_unet_number_of_voxels(window, config["model_kwargs"]["base_width"], n_layers)
    while n_voxels <= max_voxels:
        step = step + 1
        window = compute_window_size(step, step_size, ratios)
        n_voxels = compute_unet_number_of_voxels(window, config["model_kwargs"]["base_width"], n_layers)
    window = compute_window_size(step - 1, step_size, ratios).tolist()
    print("Setting window size to {} x {} x {}".format(*window))
    print("Setting batch size to", n_gpus)
    config["window"] = window
    config["model_kwargs"]["input_shape"] = window
    config["batch_size"] = n_gpus
    config["validation_batch_size"] = n_gpus
    print("Writing new configuration file:", output_filename)
    dump_json(config, output_filename)

def main():
    import nibabel as nib
    nib.imageglobals.logger.level = 40

    namespace = parse_args()

    print("Config: ", namespace.config_filename)
    config = load_json(namespace.config_filename)

    if "package" in config:
        package = config["package"]
    else:
        package = "keras"

    if "metric_names" in config and not config["n_outputs"] == len(config["metric_names"]):
        raise ValueError("n_outputs set to {}, but number of metrics is {}.".format(config["n_outputs"],
                                                                                len(config["metric_names"])))

    print("Model: ", namespace.model_filename)
    print("Log: ", namespace.training_log_filename)
    system_config = get_machine_config(namespace)
    training_function_kwargs = in_config("training_function_kwargs", config, dict())

    # Override the batch size from the config file
    if namespace.batch_size:
        config["batch_size"] = namespace.batch_size

    if namespace.fit_gpu_mem and namespace.fit_gpu_mem > 0:
        update_config_to_fit_gpu_memory(config=config, n_gpus=system_config["n_gpus"], gpu_memory=namespace.fit_gpu_mem,
                                        output_filename=namespace.config_filename.replace(".json", "_auto.json"))

    if namespace.group_average_filenames is not None:
        group_average = get_metric_data_from_config(namespace.group_average_filenames, namespace.config_filename)
        from unet3d.models.keras.resnet.resnet import compare_scores
        model_metrics = [wrapped_partial(compare_scores, comparison=group_average)]
        metric_to_monitor = "compare_scores"
    else:
        model_metrics = []
        if config['skip_validation']:
            metric_to_monitor = "loss"
        else:
            metric_to_monitor = "val_loss"

    if config["skip_validation"]:
        groups = ("training",)
    else:
        groups = ("training", "validation")

    for name in groups:
        key = name + "_filenames"
        if key not in config:
            config[key] = generate_filenames(config, name, system_config,
                                             raise_if_not_exists=namespace.debug)
    if "directory" in system_config:
        directory = system_config.pop("directory")
    else:
        directory = "."

    if "sequence" in config:
        sequence_class = load_sequence(config["sequence"])
    elif "_wb_" in os.path.basename(namespace.config_filename):
        if "package" in config and config["package"] == "pytorch":
            if config["sequence"] == "AEDataset":
                sequence_class = AEDataset
            elif config["sequence"] == "WholeVolumeSegmentationDataset":
                sequence_class = WholeVolumeSegmentationDataset
            else:
                sequence_class = WholeBrainCIFTI2DenseScalarDataset
        else:
            sequence_class = WholeVolumeToSurfaceSequence
    elif config["sequence"] == "WindowedAutoEncoderSequence":
        sequence_class = WindowedAutoEncoderSequence
    elif config["sequence"] == "WindowedAEDataset":
        sequence_class = WindowedAEDataset
    elif "_pb_" in os.path.basename(namespace.config_filename):
        sequence_class = ParcelBasedSequence
        config["sequence_kwargs"]["parcellation_template"] = os.path.join(
            directory, config["sequence_kwargs"]["parcellation_template"])
    else:
        if config["package"] == "pytorch":
            sequence_class = HCPRegressionDataset
        else:
            sequence_class = HCPRegressionSequence

    if "bias_filename" in config and config["bias_filename"] is not None:
        bias = load_bias(config["bias_filename"])
    else:
        bias = None

    check_hierarchy(config)

    if in_config("add_contours", config["sequence_kwargs"], False):
        config["n_outputs"] = config["n_outputs"] * 2

    if sequence_class == ParcelBasedSequence:
        target_parcels = config["sequence_kwargs"].pop("target_parcels")
        for target_parcel in target_parcels:
            config["sequence_kwargs"]["target_parcel"] = target_parcel
            print("Training on parcel: {}".format(target_parcel))
            if type(target_parcel) == list:
                parcel_id = "-".join([str(i) for i in target_parcel])
            else:
                parcel_id = str(target_parcel)
            _training_log_filename = namespace.training_log_filename.replace(".csv", "_{}.csv".format(parcel_id))
            if os.path.exists(_training_log_filename):
                _training_log = pd.read_csv(_training_log_filename)
                if (_training_log[metric_to_monitor].values.argmin()
                        <= len(_training_log) - int(config["early_stopping_patience"])):
                    print("Already trained")
                    continue
            run_training(package,
                         config,
                         namespace.model_filename.replace(".h5", "_{}.h5".format(parcel_id)),
                         _training_log_filename,
                         sequence_class=sequence_class,
                         model_metrics=model_metrics,
                         metric_to_monitor=metric_to_monitor,
                         **training_function_kwargs,
                         **system_config)

    else:
        run_training(package, config, namespace.model_filename, namespace.training_log_filename,
                     sequence_class=sequence_class,
                     model_metrics=model_metrics,
                     metric_to_monitor=metric_to_monitor,
                     bias=bias,
                     **training_function_kwargs,
                     **system_config)

    if namespace.sub_command == "predict":
        run_inference(namespace)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4

from django.template.loader import render_to_string
from datetime import datetime, timedelta
from apps.reports.models import Case
from apps.hq.utils import get_dates
import logging
'''Report file for custom Grameen reports'''
# see mvp.py for an explanation of how these are used.

def government(request):
    '''Government Report'''
    context = {}
    case_name = "Pathfinder CHBCP"
    startdate, enddate = get_dates(request, 30)
    maxrows = 10
    show_test = False
    if "showtest" in request.GET:
        show_test = True
    if "maxrows" in request.GET:
        try:
            maxrows = int(request.GET["maxrows"])
        except Exception:
            # just default to the above
            pass
    if enddate < startdate:
        return '''
The date range you selected is not valid.
                  End date of %s must fall after start date of %s.
'''\
                  % (enddate, startdate)

    try:
        case = Case.objects.get(name=case_name)
    except Case.DoesNotExist:
        return '''Sorry, it doesn't look like the forms that this report
                  depends on have been uploaded.'''
    all_data = case.get_all_data_maps()
    # the case ids come back as chwid|case
    # we want to bucketize them by chw first
    data_by_chw = {}
    for id, map in all_data.items():
        chw_id = id.split("|")[0]
        if not chw_id in data_by_chw:
            data_by_chw[chw_id] = {}
        data_by_chw[chw_id][id] = map
    chw_data_list = []
    for chw_id, chw_data in data_by_chw.items():
        chw_obj = PathfinderCHWData(case, chw_id, chw_data, startdate, enddate)
        chw_data_list.append(chw_obj)
    context["all_data"] = chw_data_list[:maxrows]
    context["startdate"] = startdate
    context["enddate"] = enddate
    context["showtest"] = show_test
    # todo: stop hard coding these
    context["supervisor_name"] = "Neal Lesh"
    context["supervisor_code"] = 1234
    context["ward"] = "A Ward"
    context["district"] = "A District"
    return render_to_string("custom/pathfinder/government_report.html", context)

class PathfinderCHWData(object):
    '''Class to store all the data we need to access in a single
       row of the government report'''

    # initialize all the fields
    case = None
    chw_id = None
    chw_name = None
    deaths = 0
    transfers = 0
    not_at_home = 0
    new_plha = 0
    new_chron = 0
    first_visit_plha = 0
    first_visit_chron = 0
    freq_visits_plha = 0
    freq_visits_chron = 0
    male = 0
    female = 0
    ref_vct = 0
    ref_oi = 0
    ref_ctc = 0
    ref_ctc_arv = 0
    ref_pmtct = 0
    ref_fp = 0
    ref_sg = 0
    ref_ovc = 0
    ref_tb = 0
    drug_counts = {}
    itns_waterguard_supplied = {}
    pcg_new_male = 0
    pcg_new_female = 0
    pcg_existing_male = 0
    pcg_existing_female = 0
    reg_forms = 0
    followup_forms = 0
    clients = 0
    reg_forms_in_period = 0
    followup_forms_in_period = 0
    clients_in_period = 0

    def __init__(self, case, chw_id, data_map, startdate, enddate):
        self.case = case
        self.chw_id = chw_id
        self.startdate = startdate
        self.enddate = enddate

        self.drug_counts = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0 }
        self.itns_waterguard_supplied = { 1:0, 2:0 }
        for client_id, client_data in data_map.items():
            self.clients += 1

            [reg_forms, followup_forms] =\
                [client_data[form] for form in case.form_identifiers]

            self.reg_forms += len(reg_forms)
            self.followup_forms += len(followup_forms)
            matching_reg_forms = []
            matching_followups = []
            # filter on dates, set usernames
            for reg in reg_forms:
                date = reg['meta_timeend']
                if date and date.date() >= startdate and date.date() <= enddate:
                    matching_reg_forms.append(reg)

                if not self.chw_name and reg["meta_username"]:
                    self.chw_name = reg["meta_username"]
                elif self.chw_name and reg["meta_username"]:
                    if self.chw_name != reg["meta_username"]:
                        logging.debug("Warning, multiple ids found for %s: %s and %s" %\
                            (self.chw_id, self.chw_name, reg["meta_username"]))
            for follow in followup_forms:
                date = follow['meta_timeend']
                if date and date.date() >= startdate and date.date() <= enddate:
                    matching_followups.append(follow)

                if not self.chw_name and follow["meta_username"]:
                    self.chw_name = follow["meta_username"]
                elif self.chw_name and follow["meta_username"]:
                    if self.chw_name != follow["meta_username"]:
                        logging.debug("Warning, multiple ids found for %s: %s and %s" %\
                            (self.chw_id, self.chw_name, follow["meta_username"]))

            self.reg_forms_in_period += len(matching_reg_forms)
            self.followup_forms_in_period += len(matching_followups)
            if matching_reg_forms or matching_followups:
                self.clients_in_period += 1

            reg_form = None
            if matching_reg_forms:
                if len(matching_reg_forms)!= 1:
                    logging.debug("Warning, multiple registration forms found for %s" %\
                                  client_id)
                reg_form = matching_reg_forms[0]
                status = reg_form["pathfinder_registration_patient_status"]
                if status == "People_living_with_HIV_(PLHA)":
                    self.new_plha += 1
                # should we explicitly check?  these could be empty
                #elif status == "Chronic_Ill":
                else:
                    self.new_chron += 1

                if reg_form["pathfinder_registration_patient_existing_female_pcg"]:
                    self.pcg_new_female += reg_form["pathfinder_registration_patient_existing_female_pcg"]
                if reg_form["pathfinder_registration_patient_existing_male_pcg"]:
                    self.pcg_new_male += reg_form["pathfinder_registration_patient_existing_male_pcg"]

            elif reg_forms:
                if len(reg_forms)!= 1:
                    logging.debug("Warning, multiple registration forms found for %s" %\
                                  client_id)
                reg_form = reg_forms[0]

            if matching_followups:
                first_followup = followup_forms[len(followup_forms)- 1]

                matching_follow = matching_followups[0]
                # use this one to do counts for pcgs
                if matching_follow["pathfinder_followup_new_male_pcg"]:
                    self.pcg_existing_male += matching_follow["pathfinder_followup_new_male_pcg"]
                if matching_follow["pathfinder_followup_existing_male_pcg"]:
                    self.pcg_existing_male += matching_follow["pathfinder_followup_existing_male_pcg"]
                if matching_follow["pathfinder_followup_new_female_pcg"]:
                    self.pcg_existing_female += matching_follow["pathfinder_followup_new_female_pcg"]
                if matching_follow["pathfinder_followup_existing_female_pcg"]:
                    self.pcg_existing_female += matching_follow["pathfinder_followup_existing_female_pcg"]

                count = 0
                if reg_form:
                    sex = reg_form["pathfinder_registration_patient_sex"]
                    if sex == "M":
                        self.male += 1
                    elif sex == "F":
                        self.female += 1
                for followup in matching_followups:
                    count += 1
                    counted_second_followup = False
                    type = followup["pathfinder_followup_type_of_client"]
                    if followup == first_followup:
                        # first followup ever, and it was in the report period
                        if type == "HIV":
                            self.first_visit_plha += 1
                        else:
                            self.first_visit_chron += 1
                    elif not counted_second_followup:
                        # second or later followup
                        if type == "HIV":
                            self.freq_visits_plha += 1
                        else:
                            self.freq_visits_chron += 1
                        counted_second_followup = True

                    found_not_home = False

                    # why missing?
                    why_missing = followup["pathfinder_followup_why_missing"]
                    if why_missing == "dead":
                        self.deaths += 1
                    elif why_missing == "moved":
                        self.transfers += 1
                    elif not found_not_home and why_missing == "not_home":
                        # only count the first instance of this
                        self.not_at_home += 1
                        found_not_home = True

                    # referrals
                    for key, property_name in REFERRAL_MAPPING:
                        if follow[key]:
                            prev_val = getattr(self, property_name)
                            setattr(self, property_name, prev_val + 1)

                    for key, code in MED_MAPPING:
                        if follow[key]:
                            self.drug_counts[code] += 1

                    for key, code in WATER_ITN_MAPPING:
                        if follow[key]:
                            self.itns_waterguard_supplied[code] += 1

    def get_drug_counts_string(self):
        """Gets the drug counts as a displayable string, showing only
           those values with counts"""
        return self._get_map_string(self.drug_counts)

    def get_water_itn_counts_string(self):
        """Gets the water/itn counts as a displayable string, showing only
           those values with counts"""
        return self._get_map_string(self.itns_waterguard_supplied)

    def _get_map_string(self, dict, delim="
"):
        keys = dict.keys()
        keys.sort()
        vals = []
        for key in keys:
            if dict[key]:
                vals.append("%s=%s" % (key, dict[key]))
        return delim.join(vals)

# maps fields in the xform to properties on the model
REFERRAL_MAPPING = (("pathfinder_followup_referral_hiv_test", "ref_vct"),
                    ("pathfinder_followup_referral_hiv_other_illness", "ref_oi"),
                    ("pathfinder_followup_referral_health_facility", "ref_ctc"),
                    ("pathfinder_followup_referral_prevention_from_mother_to_child", "ref_pmtct"),
                    ("pathfinder_followup_referral_aid_from_other_groups", "ref_sg"),
                    ("pathfinder_followup_referral_family_planning", "ref_fp"),
                    ("pathfinder_followup_referral_tb", "ref_tb"),
                    ("pathfinder_followup_referral_ophans", "ref_ovc"))

# maps meds to their number codes
MED_MAPPING = (("pathfinder_followup_type_med_septrini", 1),
               ("pathfinder_followup_type_med_arv", 2),
               ("pathfinder_followup_type_med_anti_tb", 3),
               ("pathfinder_followup_type_med_kinga_ya_tb", 4),
               ("pathfinder_followup_type_med_local_medicine", 5),
               ("pathfinder_followup_type_med_other", 6))

# maps meds to their number codes
WATER_ITN_MAPPING = (("pathfinder_followup_mosquito_net", 1),
                     ("pathfinder_followup_water_guard", 2))
               <commit_msg>Fix a typo in the custom pathfinder reports file.  Domain was listed in the doc string as "Grameen"<commit_after>#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4

from django.template.loader import render_to_string
from datetime import datetime, timedelta
from apps.reports.models import Case
from apps.hq.utils import get_dates
import logging
'''Report file for custom Pathfinder reports'''
# see mvp.py for an explanation of how these are used.

def government(request):
    '''Government Report'''
    context = {}
    case_name = "Pathfinder CHBCP"
    startdate, enddate = get_dates(request, 30)
    maxrows = 10
    show_test = False
    if "showtest" in request.GET:
        show_test = True
    if "maxrows" in request.GET:
        try:
            maxrows = int(request.GET["maxrows"])
        except Exception:
            # just default to the above
            pass
    if enddate < startdate:
        return '''
The date range you selected is not valid.
                  End date of %s must fall after start date of %s.
'''\
                  % (enddate, startdate)

    try:
        case = Case.objects.get(name=case_name)
    except Case.DoesNotExist:
        return '''Sorry, it doesn't look like the forms that this report
                  depends on have been uploaded.'''
    all_data = case.get_all_data_maps()
    # the case ids come back as chwid|case
    # we want to bucketize them by chw first
    data_by_chw = {}
    for id, map in all_data.items():
        chw_id = id.split("|")[0]
        if not chw_id in data_by_chw:
            data_by_chw[chw_id] = {}
        data_by_chw[chw_id][id] = map
    chw_data_list = []
    for chw_id, chw_data in data_by_chw.items():
        chw_obj = PathfinderCHWData(case, chw_id, chw_data, startdate, enddate)
        chw_data_list.append(chw_obj)
    context["all_data"] = chw_data_list[:maxrows]
    context["startdate"] = startdate
    context["enddate"] = enddate
    context["showtest"] = show_test
    # todo: stop hard coding these
    context["supervisor_name"] = "Neal Lesh"
    context["supervisor_code"] = 1234
    context["ward"] = "A Ward"
    context["district"] = "A District"
    return render_to_string("custom/pathfinder/government_report.html", context)

class PathfinderCHWData(object):
    '''Class to store all the data we need to access in a single
       row of the government report'''

    # initialize all the fields
    case = None
    chw_id = None
    chw_name = None
    deaths = 0
    transfers = 0
    not_at_home = 0
    new_plha = 0
    new_chron = 0
    first_visit_plha = 0
    first_visit_chron = 0
    freq_visits_plha = 0
    freq_visits_chron = 0
    male = 0
    female = 0
    ref_vct = 0
    ref_oi = 0
    ref_ctc = 0
    ref_ctc_arv = 0
    ref_pmtct = 0
    ref_fp = 0
    ref_sg = 0
    ref_ovc = 0
    ref_tb = 0
    drug_counts = {}
    itns_waterguard_supplied = {}
    pcg_new_male = 0
    pcg_new_female = 0
    pcg_existing_male = 0
    pcg_existing_female = 0
    reg_forms = 0
    followup_forms = 0
    clients = 0
    reg_forms_in_period = 0
    followup_forms_in_period = 0
    clients_in_period = 0

    def __init__(self, case, chw_id, data_map, startdate, enddate):
        self.case = case
        self.chw_id = chw_id
        self.startdate = startdate
        self.enddate = enddate

        self.drug_counts = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0 }
        self.itns_waterguard_supplied = { 1:0, 2:0 }
        for client_id, client_data in data_map.items():
            self.clients += 1

            [reg_forms, followup_forms] =\
                [client_data[form] for form in case.form_identifiers]

            self.reg_forms += len(reg_forms)
            self.followup_forms += len(followup_forms)
            matching_reg_forms = []
            matching_followups = []
            # filter on dates, set usernames
            for reg in reg_forms:
                date = reg['meta_timeend']
                if date and date.date() >= startdate and date.date() <= enddate:
                    matching_reg_forms.append(reg)

                if not self.chw_name and reg["meta_username"]:
                    self.chw_name = reg["meta_username"]
                elif self.chw_name and reg["meta_username"]:
                    if self.chw_name != reg["meta_username"]:
                        logging.debug("Warning, multiple ids found for %s: %s and %s" %\
                            (self.chw_id, self.chw_name, reg["meta_username"]))
            for follow in followup_forms:
                date = follow['meta_timeend']
                if date and date.date() >= startdate and date.date() <= enddate:
                    matching_followups.append(follow)

                if not self.chw_name and follow["meta_username"]:
                    self.chw_name = follow["meta_username"]
                elif self.chw_name and follow["meta_username"]:
                    if self.chw_name != follow["meta_username"]:
                        logging.debug("Warning, multiple ids found for %s: %s and %s" %\
                            (self.chw_id, self.chw_name, follow["meta_username"]))

            self.reg_forms_in_period += len(matching_reg_forms)
            self.followup_forms_in_period += len(matching_followups)
            if matching_reg_forms or matching_followups:
                self.clients_in_period += 1

            reg_form = None
            if matching_reg_forms:
                if len(matching_reg_forms)!= 1:
                    logging.debug("Warning, multiple registration forms found for %s" %\
                                  client_id)
                reg_form = matching_reg_forms[0]
                status = reg_form["pathfinder_registration_patient_status"]
                if status == "People_living_with_HIV_(PLHA)":
                    self.new_plha += 1
                # should we explicitly check?  these could be empty
                #elif status == "Chronic_Ill":
                else:
                    self.new_chron += 1

                if reg_form["pathfinder_registration_patient_existing_female_pcg"]:
                    self.pcg_new_female += reg_form["pathfinder_registration_patient_existing_female_pcg"]
                if reg_form["pathfinder_registration_patient_existing_male_pcg"]:
                    self.pcg_new_male += reg_form["pathfinder_registration_patient_existing_male_pcg"]

            elif reg_forms:
                if len(reg_forms)!= 1:
                    logging.debug("Warning, multiple registration forms found for %s" %\
                                  client_id)
                reg_form = reg_forms[0]

            if matching_followups:
                first_followup = followup_forms[len(followup_forms)- 1]

                matching_follow = matching_followups[0]
                # use this one to do counts for pcgs
                if matching_follow["pathfinder_followup_new_male_pcg"]:
                    self.pcg_existing_male += matching_follow["pathfinder_followup_new_male_pcg"]
                if matching_follow["pathfinder_followup_existing_male_pcg"]:
                    self.pcg_existing_male += matching_follow["pathfinder_followup_existing_male_pcg"]
                if matching_follow["pathfinder_followup_new_female_pcg"]:
                    self.pcg_existing_female += matching_follow["pathfinder_followup_new_female_pcg"]
                if matching_follow["pathfinder_followup_existing_female_pcg"]:
                    self.pcg_existing_female += matching_follow["pathfinder_followup_existing_female_pcg"]

                count = 0
                if reg_form:
                    sex = reg_form["pathfinder_registration_patient_sex"]
                    if sex == "M":
                        self.male += 1
                    elif sex == "F":
                        self.female += 1
                for followup in matching_followups:
                    count += 1
                    counted_second_followup = False
                    type = followup["pathfinder_followup_type_of_client"]
                    if followup == first_followup:
                        # first followup ever, and it was in the report period
                        if type == "HIV":
                            self.first_visit_plha += 1
                        else:
                            self.first_visit_chron += 1
                    elif not counted_second_followup:
                        # second or later followup
                        if type == "HIV":
                            self.freq_visits_plha += 1
                        else:
                            self.freq_visits_chron += 1
                        counted_second_followup = True

                    found_not_home = False

                    # why missing?
                    why_missing = followup["pathfinder_followup_why_missing"]
                    if why_missing == "dead":
                        self.deaths += 1
                    elif why_missing == "moved":
                        self.transfers += 1
                    elif not found_not_home and why_missing == "not_home":
                        # only count the first instance of this
                        self.not_at_home += 1
                        found_not_home = True

                    # referrals
                    for key, property_name in REFERRAL_MAPPING:
                        if follow[key]:
                            prev_val = getattr(self, property_name)
                            setattr(self, property_name, prev_val + 1)

                    for key, code in MED_MAPPING:
                        if follow[key]:
                            self.drug_counts[code] += 1

                    for key, code in WATER_ITN_MAPPING:
                        if follow[key]:
                            self.itns_waterguard_supplied[code] += 1

    def get_drug_counts_string(self):
        """Gets the drug counts as a displayable string, showing only
           those values with counts"""
        return self._get_map_string(self.drug_counts)

    def get_water_itn_counts_string(self):
        """Gets the water/itn counts as a displayable string, showing only
           those values with counts"""
        return self._get_map_string(self.itns_waterguard_supplied)

    def _get_map_string(self, dict, delim="
"):
        keys = dict.keys()
        keys.sort()
        vals = []
        for key in keys:
            if dict[key]:
                vals.append("%s=%s" % (key, dict[key]))
        return delim.join(vals)

# maps fields in the xform to properties on the model
REFERRAL_MAPPING = (("pathfinder_followup_referral_hiv_test", "ref_vct"),
                    ("pathfinder_followup_referral_hiv_other_illness", "ref_oi"),
                    ("pathfinder_followup_referral_health_facility", "ref_ctc"),
                    ("pathfinder_followup_referral_prevention_from_mother_to_child", "ref_pmtct"),
                    ("pathfinder_followup_referral_aid_from_other_groups", "ref_sg"),
                    ("pathfinder_followup_referral_family_planning", "ref_fp"),
                    ("pathfinder_followup_referral_tb", "ref_tb"),
                    ("pathfinder_followup_referral_ophans", "ref_ovc"))

# maps meds to their number codes
MED_MAPPING = (("pathfinder_followup_type_med_septrini", 1),
               ("pathfinder_followup_type_med_arv", 2),
               ("pathfinder_followup_type_med_anti_tb", 3),
               ("pathfinder_followup_type_med_kinga_ya_tb", 4),
               ("pathfinder_followup_type_med_local_medicine", 5),
               ("pathfinder_followup_type_med_other", 6))

# maps meds to their number codes
WATER_ITN_MAPPING = (("pathfinder_followup_mosquito_net", 1),
                     ("pathfinder_followup_water_guard", 2))
               <|endoftext|>