class FileStimuli(Stimuli):
    """
    Manage a list of stimuli that are saved as files.
    """
    def __init__(self, filenames, cache=True, shapes=None, attributes=None):
        """
        Create a stimuli object that reads it's stimuli from files.

        The stimuli are loaded lazy: each stimulus will be opened not
        before it is accessed. At creation time, all files are opened
        to read their dimensions, however the actual image data won't
        be read.

        .. note ::

            To calculate the stimulus_ids, the stimuli have to be
            loaded. Therefore it might be a good idea to load all
            stimuli and pickle the `FileStimuli` afterwarts. Then
            the ids are pickled but the stimuli will be reloaded
            when needed again.

        Parameters
        ----------
        filenames : list of strings
            filenames of the stimuli
        cache : bool, defaults to True
            whether loaded stimuli should be cached. The cache is excluded from pickling.
        """
        self.filenames = filenames
        self.stimuli = LazyList(self.load_stimulus, len(self.filenames), cache=cache)
        if shapes is None:
            self.shapes = []
            for f in filenames:
                img = Image.open(f)
                size = img.size
                if len(img.mode) > 1:
                    # PIL uses (width, height), we use (height, width)
                    self.shapes.append((size[1], size[0], len(img.mode)))
                else:
                    self.shapes.append((size[1], size[0]))
                del img
        else:
            self.shapes = shapes

        self.stimulus_ids = LazyList(lambda n: get_image_hash(self.stimuli[n]),
                                     length=len(self.stimuli),
                                     pickle_cache=True)
        self.stimulus_objects = [StimuliStimulus(self, n) for n in range(len(self.stimuli))]
        self.sizes = LazyList(lambda n: (self.shapes[n][0], self.shapes[n][1]),
                              length = len(self.stimuli))

        if attributes is not None:
            assert isinstance(attributes, dict)
            self.attributes = attributes
            self.__attributes__ = list(attributes.keys())
        else:
            self.attributes = {}

    def load_stimulus(self, n):
        return imread(self.filenames[n])

    def __getitem__(self, index):
        if isinstance(index, slice):
            index = list(range(len(self)))[index]

        if isinstance(index, list):
            filenames = [self.filenames[i] for i in index]
            shapes = [self.shapes[i] for i in index]
            attributes = {key: [value[i] for i in index] for key, value in self.attributes.items()}
            return type(self)(filenames=filenames, shapes=shapes, attributes=attributes)
        else:
            return self.stimulus_objects[index]

    @hdf5_wrapper(mode='w')
    def to_hdf5(self, target):
        """ Write FileStimuli to hdf5 file or hdf5 group
        """

        target.attrs['type'] = np.string_('FileStimuli')
        target.attrs['version'] = np.string_('2.1')

        import h5py
        # make sure everything is unicode

        hdf5_filename = target.file.filename
        hdf5_directory = os.path.dirname(hdf5_filename)

        relative_filenames = [os.path.relpath(filename, hdf5_directory) for filename in self.filenames]
        decoded_filenames = [decode_string(filename) for filename in relative_filenames]
        encoded_filenames = [filename.encode('utf8') for filename in decoded_filenames]

        target.create_dataset(
            'filenames',
            data=np.array(encoded_filenames),
            dtype=h5py.special_dtype(vlen=str)
        )

        shape_dataset = target.create_dataset(
            'shapes',
            (len(self), ),
            dtype=h5py.special_dtype(vlen=np.dtype('int64'))
        )

        for n, shape in enumerate(self.shapes):
            shape_dataset[n] = np.array(shape)

        self._attributes_to_hdf5(target)

        target.attrs['size'] = len(self)

    @classmethod
    @hdf5_wrapper(mode='r')
    def read_hdf5(cls, source, cache=True):
        """ Read FileStimuli from hdf5 file or hdf5 group """

        data_type = decode_string(source.attrs['type'])
        data_version = decode_string(source.attrs['version'])

        if data_type != 'FileStimuli':
            raise ValueError("Invalid type! Expected 'Stimuli', got", data_type)

        valid_versions = ['1.0', '2.0', '2.1']
        if data_version not in valid_versions:
            raise ValueError("Invalid version! Expected one of {}, got {}".format(', '.join(valid_versions), data_version))

        encoded_filenames = source['filenames'][...]

        filenames = [decode_string(filename) for filename in encoded_filenames]

        if data_version >= '2.0':
            hdf5_filename = source.file.filename
            hdf5_directory = os.path.dirname(hdf5_filename)
            filenames = [os.path.join(hdf5_directory, filename) for filename in filenames]

        shapes = [list(shape) for shape in source['shapes'][...]]

        __attributes__, attributes = cls._get_attributes_from_hdf5(source, data_version, '2.1')

        stimuli = cls(filenames=filenames, cache=cache, shapes=shapes, attributes=attributes)

        return stimuli class Stimuli(Sequence):
    """
    Manages a list of stimuli (i.e. images).

    The stimuli can be given as numpy arrays. Using the class `FileStimuli`, the stimuli
    can also be saved on disk and will be loaded only when needed.

    Attributes
    ----------
    stimuli :
        The stimuli as list of numpy arrays
    shapes :
        The shapes of the stimuli. For a grayscale stimulus this will
        be a 2-tuple, for a color stimulus a 3-tuple
    sizes :
        The sizes of all stimuli in pixels as pairs (height, width). In difference
        to `shapes`, the color dimension is ignored here.
    stimulus_ids:
        A unique id for each stimulus. Can be used to cache results for stimuli
    stimulus_objects:
        A `Stimulus` instance for each stimulus. Mainly for caching.

    """
    __attributes__ = []
    def __init__(self, stimuli, attributes=None):
        self.stimuli = stimuli
        self.shapes = [s.shape for s in self.stimuli]
        self.sizes = LazyList(lambda n: (self.shapes[n][0], self.shapes[n][1]),
                              length = len(self.stimuli))
        self.stimulus_ids = LazyList(lambda n: get_image_hash(self.stimuli[n]),
                                     length=len(self.stimuli),
                                     pickle_cache=True)
        self.stimulus_objects = [StimuliStimulus(self, n) for n in range(len(self.stimuli))]

        if attributes is not None:
            assert isinstance(attributes, dict)
            self.attributes = attributes
            self.__attributes__ = list(attributes.keys())
        else:
            self.attributes = {}

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

    def __getitem__(self, index):
        if isinstance(index, slice):
            attributes = {key: value[index] for key, value in self.attributes.items()}
            return ObjectStimuli([self.stimulus_objects[i] for i in range(len(self))[index]], attributes=attributes)
        elif isinstance(index, list):
            attributes = {key: value[index] for key, value in self.attributes.items()}
            return ObjectStimuli([self.stimulus_objects[i] for i in index], attributes=attributes)
        else:
            return self.stimulus_objects[index]

    @hdf5_wrapper(mode='w')
    def to_hdf5(self, target, verbose=False, compression='gzip', compression_opts=9):
        """ Write stimuli to hdf5 file or hdf5 group
        """

        target.attrs['type'] = np.string_('Stimuli')
        target.attrs['version'] = np.string_('1.1')

        for n, stimulus in enumerate(tqdm(self.stimuli, disable=not verbose)):
            target.create_dataset(str(n), data=stimulus, compression=compression, compression_opts=compression_opts)

        self._attributes_to_hdf5(target)

        target.attrs['size'] = len(self)

    @classmethod
    @hdf5_wrapper(mode='r')
    def read_hdf5(cls, source):
        """ Read stimuli from hdf5 file or hdf5 group """

        data_type = decode_string(source.attrs['type'])
        data_version = decode_string(source.attrs['version'])

        if data_type != 'Stimuli':
            raise ValueError("Invalid type! Expected 'Stimuli', got", data_type)

        if data_version not in ['1.0', '1.1']:
            raise ValueError("Invalid version! Expected '1.0' or '1.1', got", data_version)

        size = source.attrs['size']
        stimuli = []

        for n in range(size):
            stimuli.append(source[str(n)][...])

        __attributes__, attributes = cls._get_attributes_from_hdf5(source, data_version, '1.1')

        stimuli = cls(stimuli=stimuli, attributes=attributes)

        return stimuli

    def _attributes_to_hdf5(self, target):
        for attribute_name, attribute_value in self.attributes.items():
            create_hdf5_dataset(target, attribute_name, attribute_value)
        target.attrs['__attributes__'] = np.string_(json.dumps(self.__attributes__))

    @classmethod
    def _get_attributes_from_hdf5(cls, source, data_version, data_version_for_attribute_list):
        if data_version < data_version_for_attribute_list:
            __attributes__ = []
        else:
            json_attributes = source.attrs['__attributes__']
            if not isinstance(json_attributes, string_types):
                json_attributes = json_attributes.decode('utf8')
            __attributes__ = json.loads(json_attributes)

        attributes = {}
        for attribute in __attributes__:
            attribute_value = source[attribute][...]
            if isinstance(attribute_value.flatten()[0], bytes):
                attribute_shape = attribute_value.shape
                decoded_attribute_value = [decode_string(item) for item in attribute_value.flatten()]
                attribute_value = np.array(decoded_attribute_value).reshape(attribute_shape)
            attributes[attribute] = attribute_value

        return __attributes__, attributes class FixationTrains(Fixations):
    """
    Capsules the fixations of a dataset as fixation trains.

    Additionally to `Fixations`, `FixationTrains`-instances
    have the attributes
        train_xs: 2d array (number_of_trains, maximum_length_of_train)
        train_ys: 2d array (number_of_trains, maximum_length_of_train)
        train_ts: 2d array (number_of_trains, maximum_length_of_train)
        train_ns: 1d array (number_of_trains)
        train_subjects: 1d array (number_of_trains)

    """
    def __init__(self, train_xs, train_ys, train_ts, train_ns, train_subjects, scanpath_attributes=None, attributes=None):
        self.__attributes__ = list(self.__attributes__)
        self.__attributes__.append('scanpath_index')
        self.train_xs = train_xs
        self.train_ys = train_ys
        self.train_ts = train_ts
        self.train_ns = train_ns
        self.train_subjects = train_subjects
        N_trains = self.train_xs.shape[0] * self.train_xs.shape[1] - np.isnan(self.train_xs).sum()
        max_length_trains = self.train_xs.shape[1]

        # Create conditional fixations
        self.x = np.empty(N_trains)
        self.y = np.empty(N_trains)
        self.t = np.empty(N_trains)
        self.x_hist = np.empty((N_trains, max_length_trains - 1))
        self.y_hist = np.empty((N_trains, max_length_trains - 1))
        self.t_hist = np.empty((N_trains, max_length_trains - 1))
        self.x_hist[:] = np.nan
        self.y_hist[:] = np.nan
        self.t_hist[:] = np.nan
        self.n = np.empty(N_trains, dtype=int)
        self.lengths = np.empty(N_trains, dtype=int)
        self.subjects = np.empty(N_trains, dtype=int)
        self.scanpath_index = np.empty(N_trains, dtype=int)
        out_index = 0
        for train_index in range(self.train_xs.shape[0]):
            fix_length = (1 - np.isnan(self.train_xs[train_index])).sum()
            for fix_index in range(fix_length):
                self.x[out_index] = self.train_xs[train_index][fix_index]
                self.y[out_index] = self.train_ys[train_index][fix_index]
                self.t[out_index] = self.train_ts[train_index][fix_index]
                self.n[out_index] = self.train_ns[train_index]
                self.subjects[out_index] = self.train_subjects[train_index]
                self.lengths[out_index] = fix_index
                self.scanpath_index[out_index] = train_index
                self.x_hist[out_index][:fix_index] = self.train_xs[train_index][:fix_index]
                self.y_hist[out_index][:fix_index] = self.train_ys[train_index][:fix_index]
                self.t_hist[out_index][:fix_index] = self.train_ts[train_index][:fix_index]
                out_index += 1

        if scanpath_attributes is not None:
            assert isinstance(scanpath_attributes, dict)
            self.scanpath_attributes = {key: np.array(value) for key, value in scanpath_attributes.items()}
        else:
            self.scanpath_attributes = {}

        if attributes:
            self.__attributes__ = list(self.__attributes__)
            for key, value in attributes.items():
                assert key != 'subjects'
                assert key != 'scanpath_index'
                assert len(value) == len(self.x)
                self.__attributes__.append(key)
                value = np.array(value)
                setattr(self, key, value)

        self.full_nonfixations = None

    def filter_fixation_trains(self, indices):
        """
        Create new fixations object which contains only the fixation trains indicated.
        """
        train_xs = self.train_xs[indices]
        train_ys = self.train_ys[indices]
        train_ts = self.train_ts[indices]
        train_ns = self.train_ns[indices]
        train_subjects = self.train_subjects[indices]
        scanpath_attributes = {key: np.array(value)[indices] for key, value in self.scanpath_attributes.items()}

        scanpath_indices = np.arange(len(self.train_xs), dtype=np.int)[indices]
        fixation_indices = np.in1d(self.scanpath_index, scanpath_indices)

        attributes = {
            attribute_name: getattr(self, attribute_name)[fixation_indices] for attribute_name in self.__attributes__ if attribute_name not in ['subjects', 'scanpath_index']
        }

        return type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects, attributes=attributes, scanpath_attributes=scanpath_attributes)

    def fixation_trains(self):
        """Yield for every fixation train of the dataset:
             xs, ys, ts, n, subject
        """
        for i in xrange(self.train_xs.shape[0]):
            length = (1 - np.isnan(self.train_xs[i])).sum()
            xs = self.train_xs[i][:length]
            ys = self.train_ys[i][:length]
            ts = self.train_ts[i][:length]
            n = self.train_ns[i]
            subject = self.train_subjects[i]
            yield xs, ys, ts, n, subject

    @classmethod
    def from_fixation_trains(cls, xs, ys, ts, ns, subjects, attributes=None, scanpath_attributes=None):
        """ Create Fixation object from fixation trains.
              - xs, ys, ts: Lists of array_like of double. Each array has to contain
                    the data from one fixation train.
              - ns, subjects: lists of int. ns has to contain the image index for
                    each fixation train, subjects the subject index for each
                    fixation train
        """
        maxlength = max([len(x_train) for x_train in xs])
        train_xs = np.empty((len(xs), maxlength))
        train_xs[:] = np.nan
        train_ys = np.empty((len(xs), maxlength))
        train_ys[:] = np.nan
        train_ts = np.empty((len(xs), maxlength))
        train_ts[:] = np.nan
        train_ns = np.empty(len(xs), dtype=int)
        train_subjects = np.empty(len(xs), dtype=int)
        for i in range(len(train_xs)):
            length = len(xs[i])
            train_xs[i, :length] = xs[i]
            train_ys[i, :length] = ys[i]
            train_ts[i, :length] = ts[i]
            #print ns[i], train_ns[i]
            train_ns[i] = ns[i]
            train_subjects[i] = subjects[i]
        return cls(train_xs, train_ys, train_ts, train_ns, train_subjects, attributes=attributes, scanpath_attributes=scanpath_attributes)

    def generate_crossval(self, splitcount = 10):
        train_xs_training = []
        train_xs_eval = []
        train_ys_training = []
        train_ys_eval = []
        train_ts_training = []
        train_ts_eval = []
        train_ns_training = []
        train_ns_eval = []
        train_subjects_training = []
        train_subjects_eval = []
        # We have to make the crossvalidation data
        # reproducible. Therefore we use a
        # RandomState with fixed seed for the shuffling.
        rs = np.random.RandomState(42)
        for n in range(self.n.max()+1):
            inds = np.nonzero(self.train_ns == n)[0]
            rs.shuffle(inds)
            parts = np.array_split(inds, splitcount)
            for eval_index in range(splitcount):
                for index in range(splitcount):
                    part = parts[index]
                    if len(part) == 0:
                        continue
                    xs = self.train_xs[part]
                    ys = self.train_ys[part]
                    ts = self.train_ts[part]
                    ns = self.train_ns[part]
                    subjects = self.train_subjects[part]
                    if index == eval_index:
                        train_xs_eval.append(xs)
                        train_ys_eval.append(ys)
                        train_ts_eval.append(ts)
                        train_ns_eval.append(ns * splitcount + eval_index)
                        train_subjects_eval.append(subjects)
                    else:
                        train_xs_training.append(xs)
                        train_ys_training.append(ys)
                        train_ts_training.append(ts)
                        train_ns_training.append(ns * splitcount + eval_index)
                        train_subjects_training.append(subjects)
        train_xs_eval = np.vstack(train_xs_eval)
        train_ys_eval = np.vstack(train_ys_eval)
        train_ts_eval = np.vstack(train_ts_eval)
        train_ns_eval = np.hstack(train_ns_eval)
        train_subjects_eval = np.hstack(train_subjects_eval)
        train_xs_training = np.vstack(train_xs_training)
        train_ys_training = np.vstack(train_ys_training)
        train_ts_training = np.vstack(train_ts_training)
        train_ns_training = np.hstack(train_ns_training)
        train_subjects_training = np.hstack(train_subjects_training)
        fixations_training = type(self).from_fixation_trains(train_xs_training, train_ys_training,
                                                             train_ts_training, train_ns_training,
                                                             train_subjects_training)
        fixations_evaluation = type(self).from_fixation_trains(train_xs_eval, train_ys_eval,
                                                               train_ts_eval, train_ns_eval,
                                                               train_subjects_eval)
        return fixations_training, fixations_evaluation

#    def generate_nonfixations(self, seed=42):
#        """Generate nonfixational distribution from this
#        fixation object by shuffling the images of the
#        fixation trains. The individual fixation trains
#        will be left intact"""
#        train_xs = self.train_xs.copy()
#        train_ys = self.train_ys.copy()
#        train_ts = self.train_ts.copy()
#        train_ns = self.train_ns.copy()
#        train_subjects = self.train_subjects.copy()
#        max_n = train_ns.max()
#        rs = np.random.RandomState(seed)
#        for i in range(len(train_ns)):
#            old_n = train_ns[i]
#            new_ns = range(0, old_n)+range(old_n+1, max_n+1)
#            new_n = rs.choice(new_ns)
#            train_ns[i] = new_n
#        return type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects)
#
#    def generate_more_nonfixations(self, count=1, seed=42):
#        """Generate nonfixational distribution from this
#        fixation object by assining each fixation
#        train to $count other images.
#
#        with count=0, each train will be assigned to all
#        other images"""
#        train_xs = []
#        train_ys = []
#        train_ts = []
#        train_ns = []
#        train_subjects = []
#        max_n = self.train_ns.max()
#        if count == 0:
#            count = max_n-1
#        rs = np.random.RandomState(seed)
#        for i in range(len(self.train_ns)):
#            old_n = self.train_ns[i]
#            new_ns = range(0, old_n)+range(old_n+1, max_n+1)
#            new_ns = rs.choice(new_ns, size=count, replace=False)
#            for new_n in new_ns:
#                train_xs.append(self.train_xs[i])
#                train_ys.append(self.train_ys[i])
#                train_ts.append(self.train_ts[i])
#                train_ns.append(new_n)
#                train_subjects.append(self.train_subjects[i])
#        train_xs = np.vstack(train_xs)
#        train_ys = np.vstack(train_ys)
#        train_ts = np.vstack(train_ts)
#        train_ns = np.hstack(train_ns)
#        train_subjects = np.hstack(train_subjects)
#        # reorder
#        inds = np.argsort(train_ns)
#        train_xs = train_xs[inds]
#        train_ys = train_ys[inds]
#        train_ts = train_ts[inds]
#        train_ns = train_ns[inds]
#        train_subjects = train_subjects[inds]
#        return type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects)

    def shuffle_fixations(self, stimuli=None):
        new_indices = []
        new_ns = []
        if stimuli:
            widths = np.asarray([s[1] for s in stimuli.sizes]).astype(float)
            heights = np.asarray([s[0] for s in stimuli.sizes]).astype(float)
            x_factors = []
            y_factors = []
        for n in range(self.n.max()+1):
            inds = np.nonzero(~(self.n == n))[0]
            new_indices.extend(inds)
            new_ns.extend([n]*len(inds))
            if stimuli:
                other_ns = self.n[inds]
                x_factors.extend(stimuli.sizes[n][1]/widths[other_ns])
                y_factors.extend(stimuli.sizes[n][0]/heights[other_ns])
        new_fixations = self[new_indices]
        new_fixations.n = np.asarray(new_ns)
        if stimuli:
            x_factors = np.asarray(x_factors)
            y_factors = np.asarray(y_factors)
            new_fixations.x = x_factors*new_fixations.x
            new_fixations.x_hist = x_factors[:, np.newaxis]*new_fixations.x_hist
            new_fixations.y = y_factors*new_fixations.y
            new_fixations.y_hist = y_factors[:, np.newaxis]*new_fixations.y_hist
        return new_fixations

    def shuffle_fixation_trains(self, stimuli=None):
        """

        """
        if not self.consistent_fixation_trains:
            raise ValueError('Cannot shuffle fixation trains as fixation trains not consistent!')
        train_xs = []
        train_ys = []
        train_ts = []
        train_ns = []
        train_subjects = []
        for n in range(self.n.max()+1):
            inds = ~(self.train_ns == n)
            train_xs.append(self.train_xs[inds])
            train_ys.append(self.train_ys[inds])
            train_ts.append(self.train_ts[inds])
            train_ns.append(np.ones(inds.sum(), dtype=np.int)*n)
            train_subjects.append(self.train_subjects[inds])
        train_xs = np.vstack(train_xs)
        train_ys = np.vstack(train_ys)
        train_ts = np.vstack(train_ts)
        train_ns = np.hstack(train_ns)
        train_subjects = np.hstack(train_subjects)
        full_nonfixations = type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects)
        #self.full_nonfixations = full_nonfixations
        return full_nonfixations

    def generate_full_nonfixations(self, stimuli=None):
        """
        Generate nonfixational distribution from this
        fixation object by using all fixation trains of
        other images. The individual fixation trains
        will be left intact.

        .. warning::
            This function operates on the fixation trains.
            Therefore, for filtered fixation objects it
            might return wrong results.
        """
        if self.full_nonfixations is not None:
            print("Reusing nonfixations!")
            return self.full_nonfixations
        train_xs = []
        train_ys = []
        train_ts = []
        train_ns = []
        train_subjects = []
        for n in range(self.n.max()+1):
            inds = ~(self.train_ns == n)
            train_xs.append(self.train_xs[inds])
            train_ys.append(self.train_ys[inds])
            train_ts.append(self.train_ts[inds])
            train_ns.append(np.ones(inds.sum(), dtype=np.int)*n)
            train_subjects.append(self.train_subjects[inds])
        train_xs = np.vstack(train_xs)
        train_ys = np.vstack(train_ys)
        train_ts = np.vstack(train_ts)
        train_ns = np.hstack(train_ns)
        train_subjects = np.hstack(train_subjects)
        full_nonfixations = type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects)
        self.full_nonfixations = full_nonfixations
        return full_nonfixations

    def generate_nonfixation_partners(self, seed=42):
        """Generate nonfixational distribution from this
        fixation object such that for every fixation in the
        original fixation object there is a corresponding
        fixation on the same image but on a different
        position that comes from some other fixation.

        This destroys the temporal ordering of the fixation
        trains."""
        train_xs = self.train_xs.copy()
        train_ys = self.train_ys.copy()
        train_ts = self.train_ts.copy()
        train_ns = self.train_ns.copy()
        train_subjects = self.train_subjects.copy()
        rs = np.random.RandomState(seed)
        for train_index in range(len(train_ns)):
            n = train_ns[train_index]
            inds = np.nonzero(self.n != n)[0]
            length = (1 - np.isnan(train_xs[train_index])).sum()
            for i in range(length):
                new_fix_index = rs.choice(inds)
                train_xs[train_index][i] = self.x[new_fix_index]
                train_ys[train_index][i] = self.y[new_fix_index]
                train_ts[train_index][i] = self.t[new_fix_index]
        return type(self)(train_xs, train_ys, train_ts, train_ns, train_subjects)

    @hdf5_wrapper(mode='w')
    def to_hdf5(self, target):
        """ Write fixations to hdf5 file or hdf5 group
        """

        target.attrs['type'] = np.string_('FixationTrains')
        target.attrs['version'] = np.string_('1.1')

        for attribute in ['train_xs', 'train_ys', 'train_ts', 'train_ns', 'train_subjects'] + self.__attributes__:
            if attribute in ['subjects', 'scanpath_index']:
                continue
            target.create_dataset(attribute, data=getattr(self, attribute))

        target.attrs['__attributes__'] = np.string_(json.dumps(self.__attributes__))

        scanpath_attributes_group = target.create_group('scanpath_attributes')
        for attribute_name, attribute_value in self.scanpath_attributes.items():
            scanpath_attributes_group.create_dataset(attribute_name, data=attribute_value)
        scanpath_attributes_group.attrs['__attributes__'] = np.string_(json.dumps(sorted(self.scanpath_attributes.keys())))

    @classmethod
    @hdf5_wrapper(mode='r')
    def read_hdf5(cls, source):
        """ Read train fixations from hdf5 file or hdf5 group """

        data_type = decode_string(source.attrs['type'])
        data_version = decode_string(source.attrs['version'])

        if data_type != 'FixationTrains':
            raise ValueError("Invalid type! Expected 'FixationTrains', got", data_type)

        valid_versions = ['1.0', '1.1']
        if data_version not in valid_versions:
            raise ValueError("Invalid version! Expected one of {}, got {}".format(', '.join(valid_versions), data_version))

        data = {key: source[key][...] for key in ['train_xs', 'train_ys', 'train_ts', 'train_ns', 'train_subjects']}

        json_attributes = decode_string(source.attrs['__attributes__'])

        attribute_names = list(json.loads(json_attributes))

        attributes = {}
        for key in attribute_names:
            if key in ['subjects', 'scanpath_index']:
                continue

            attributes[key] = source[key][...]

        data['attributes'] = attributes

        if data_version < '1.1':
            scanpath_attributes = {}
        else:
            scanpath_attributes_group = source['scanpath_attributes']

            json_attributes = scanpath_attributes_group.attrs['__attributes__']
            if not isinstance(json_attributes, string_types):
                json_attributes = json_attributes.decode('utf8')
            __attributes__ = json.loads(json_attributes)

            scanpath_attributes = {attribute: scanpath_attributes_group[attribute][...] for attribute in __attributes__}

        data['scanpath_attributes'] = scanpath_attributes

        fixations = cls(**data)

        return fixations class Fixations(object):
    """Capsules the fixations of a dataset and provides different methods
       of accessing them, e.g. in fixation trains, as conditional fixations
       or just all fixations at once.

       Fixations consist of:
           x: the x-position of the fixation
           y: the y-position of the fixation
           t: the time of the fixation
           x_hist: the previous x-positions in the history of this fixation
           y_hist: the previous y-positions in the history of this fixation
           t_hist: the previous times in the history of this fixation
           subject: the subject who made the fixation
           n: the number of the stimuli (optional, only needed when evaluating not on single images)

        Fixations support slicing via fixations[indices] as a shortcut for fixations.filter.

        Although all fixations have a history of previous fixations, these histories
        do not have to form a set of fixation sequences. For example, if a fixation
        has a previous fixation, this previous fixation does not have to be as a
        fixation of its on in the dataset. This is important because otherwise
        a lot of useful filtering operations would not be possible (e.g. filter
        for all fixations with at least one previous fixation to calculate
        saccade lengths). If you need fixation trains, use the subclass
        `FixationTrains`.
    """
    __attributes__ = ['subjects']

    def __init__(self, x, y, t, x_hist, y_hist, t_hist, n, subjects, attributes=None):
        x = np.asarray(x)
        y = np.asarray(y)
        t = np.asarray(t)
        n = np.asarray(n)
        self.x = x
        self.y = y
        self.t = t
        self.x_hist = x_hist
        self.y_hist = y_hist
        self.t_hist = t_hist
        self.n = n
        self.subjects = subjects
        self.lengths = (1 - np.isnan(self.x_hist)).sum(axis=-1)

        if attributes is not None:
            self.__attributes__ = list(self.__attributes__)
            for name, value in attributes.items():
                if key not in self.__attributes__:
                    self.__attributes__.append(key)
                setattr(self, name, value)

    @classmethod
    def create_without_history(cls, x, y, n, subjects=None):
        """ Create new fixation object from fixation data without time and optionally
            without subject information
        """
        N = len(x)
        t = np.zeros(N)
        x_hist = np.empty((N, 1))*np.nan
        y_hist = np.empty((N, 1))*np.nan
        t_hist = np.empty((N, 1))*np.nan
        if subjects is None:
            subjects = np.ones(N)
        return cls(x, y,  t, x_hist, y_hist, t_hist, n, subjects)

    @classmethod
    def from_fixation_matrices(cls, matrices):
        """
        create new Fixation object with fixations from fixation matrices

        Often, fixations are stored in fixation matrices: For each stimulus, there
        is a matrix of the same size as the image which ones in each fixated location
        and zero everywhere else. This method allows to construct a `Fixation` instance
        from such fixation matrices.

        >>> matrix1 = np.zeros((10,10))
        >>> matrix1[5, 2] = 1
        >>> matrix1[3, 3] = 1
        >>> matrix2  = np.zeros((20, 30))
        >>> matrix2[10, 20] = 1
        >>> fixations = pysaliency.Fixation.from_fixation_matrices(
                [matrix1,
                 matrix2])
        """
        xs = []
        ys = []
        ns = []
        for _n, matrix in enumerate(matrices):
            y, x = np.nonzero(matrix)
            n = [_n] * len(y)
            xs.append(x)
            ys.append(y)
            ns.append(n)
        x = np.hstack(xs).astype(float)
        y = np.hstack(ys).astype(float)
        n = np.hstack(ns)
        return cls.create_without_history(x, y, n)

    def __getitem__(self, indices):
        return self.filter(indices)

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

    def filter(self, inds):
        """
        Create new fixations object which contains only the fixations with indexes in inds

        .. note::
            The fixation trains of the object are left as is. Filtering the fixation trains
            is not possible as the indices may include only some fixation of a fixation train.

            The attributes `consistent_fixation_trains` tracks whether a `Fixations` instance
            still has consistent fixation trains. The return of this function will be marked
            to have inconsistent fixation trains. If you need to filter with consistent
            fixation trains, use `Fixations.filter_fixation_trains`.
        """

        kwargs = {}
        other_attributes = {}

        def filter_array(name):
            kwargs[name] = getattr(self, name)[inds].copy()
        for name in ['x', 'y', 't', 'x_hist', 'y_hist', 't_hist', 'n']:
            filter_array(name)
        for name in self.__attributes__:
            filter_array(name)
            if name != 'subjects':
                other_attributes[name] = kwargs.pop(name)

        new_fix = Fixations(**kwargs)
        for key, value in other_attributes.items():
            setattr(new_fix, key, value)
        new_fix.__attributes__ = list(self.__attributes__)
        return new_fix

    def _get_previous_values(self, name, index):
        """return fixations.name[np.arange(len(fixations.name)),index]"""
        a = getattr(self, name)
        inds = np.arange(len(a))
        if index >= 0:
            return a[inds, index]
        else:
            indexes = self.lengths + index
            return a[inds, indexes]

    def get_saccade(self, index = -1):
        """
        Return saccades for all fixations.

        @type  index: integer
        @param index: index of the saccade to return. `index==-1` returns the
                      the last saccades of all fixations etc.

        @return dx, dy, dt of the saccade

        Example:

            dx, dy, dt = fixations.get_saccade(-1)
            mean_saccade_length = np.sqrt(dx**2+dy**2).mean()
        """

        if index > 0:
            raise NotImplemented()
        if index == -1:
            x1 = self.x
            y1 = self.y
            t1 = self.t
        else:
            x1 = self._get_previous_values('x_hist', index+1)
            y1 = self._get_previous_values('y_hist', index+1)
            t1 = self._get_previous_values('t_hist', index+1)
        dx = x1 - self._get_previous_values('x_hist', index)
        dy = y1 - self._get_previous_values('y_hist', index)
        dt = t1 - self._get_previous_values('t_hist', index)
        return dx, dy, dt
        #return np.vstack((dy,dx)).T

    @property
    def x_int(self):
        """ x coordinates of the fixations, converted to integers """
        return np.asarray(self.x, np.int)

    @property
    def y_int(self):
        """ y coordinates of the fixations, converted to integers """
        return np.asarray(self.y, np.int)

    @property
    def subject_count(self):
        return self.subjects.max()+1

    def copy(self):
        cfix = Fixations(self.x.copy(), self.y.copy(), self.t.copy(),
                         self.x_hist.copy(), self.y_hist.copy(), self.t_hist.copy(),
                         self.n.copy(), self.subjects.copy())
        cfix.__attributes__ = list(self.__attributes__)
        for name in self.__attributes__:
            setattr(cfix, name, getattr(self, name).copy())
        return cfix

    @classmethod
    def FixationsWithoutHistory(cls, x, y, t, n, subjects):
        x_hist = np.empty((len(x), 1))*np.nan
        y_hist = np.empty((len(x), 1))*np.nan
        t_hist = np.empty((len(x), 1))*np.nan
        return cls(x, y, t, x_hist, y_hist, t_hist, n, subjects)

    @hdf5_wrapper(mode='w')
    def to_hdf5(self, target):
        """ Write fixations to hdf5 file or hdf5 group
        """

        target.attrs['type'] = np.string_('Fixations')
        target.attrs['version'] = np.string_('1.0')

        for attribute in ['x', 'y', 't', 'x_hist', 'y_hist', 't_hist', 'n'] + self.__attributes__:
            target.create_dataset(attribute, data=getattr(self, attribute))

        #target.create_dataset('__attributes__', data=self.__attributes__)
        target.attrs['__attributes__'] = np.string_(json.dumps(self.__attributes__))

    @classmethod
    @hdf5_wrapper(mode='r')
    def read_hdf5(cls, source):
        """ Read fixations from hdf5 file or hdf5 group """

        data_type = decode_string(source.attrs['type'])
        data_version = decode_string(source.attrs['version'])

        if data_type != 'Fixations':
            raise ValueError("Invalid type! Expected 'Fixations', got", data_type)

        if data_version != '1.0':
            raise ValueError("Invalid version! Expected '1.0', got", data_version)

        data = {key: source[key][...] for key in ['x', 'y', 't', 'x_hist', 'y_hist', 't_hist', 'n', 'subjects']}
        fixations = cls(**data)

        json_attributes = source.attrs['__attributes__']
        if not isinstance(json_attributes, string_types):
            json_attributes = json_attributes.decode('utf8')
        __attributes__ = json.loads(json_attributes)
        fixations.__attributes__ == list(__attributes__)

        for key in __attributes__:
            setattr(fixations, key, source[key][...])

        return fixations def read_hdf5(source):
    if isinstance(source, str):
        return _read_hdf5_from_file(source)

    data_type = decode_string(source.attrs['type'])

    if data_type == 'Fixations':
        return Fixations.read_hdf5(source)
    elif data_type == 'FixationTrains':
        return FixationTrains.read_hdf5(source)
    elif data_type == 'Stimuli':
        return Stimuli.read_hdf5(source)
    elif data_type == 'FileStimuli':
        return FileStimuli.read_hdf5(source)
    else:
        raise ValueError("Invalid HDF content type:", data_type) class TemporaryDirectory(object):
    """Create and return a temporary directory.  This has the same
    behavior as mkdtemp but can be used as a context manager.  For
    example:

        with TemporaryDirectory() as tmpdir:
            ...

    Upon exiting the context, the directory and everything contained
    in it are removed.

    From http://stackoverflow.com/a/19299884
    """

    def __init__(self, suffix="", prefix="tmp", dir=None, cleanup=True):
        self._closed = False
        self.name = None  # Handle mkdtemp raising an exception
        self.name = mkdtemp(suffix, prefix, dir)
        self.do_cleanup = cleanup

    def __repr__(self):
        return "<{} {!r}>".format(self.__class__.__name__, self.name)

    def __enter__(self):
        return self.name

    def cleanup(self, _warn=False):
        if not self.do_cleanup:
            return
        if self.name and not self._closed:
            try:
                self._rmtree(self.name)
            except (TypeError, AttributeError) as ex:
                # Issue #10188: Emit a warning on stderr
                # if the directory could not be cleaned
                # up due to missing globals
                if "None" not in str(ex):
                    raise
                print("ERROR: {!r} while cleaning up {!r}".format(ex, self,),
                      file=_sys.stderr)
                return
            self._closed = True
            if _warn:
                self._warn("Implicitly cleaning up {!r}".format(self))

    def __exit__(self, exc, value, tb):
        self.cleanup()

    def __del__(self):
        # Issue a ResourceWarning if implicit cleanup needed
        self.cleanup(_warn=True)

    # XXX (ncoghlan): The following code attempts to make
    # this class tolerant of the module nulling out process
    # that happens during CPython interpreter shutdown
    # Alas, it doesn't actually manage it. See issue #10188
    _listdir = staticmethod(_os.listdir)
    _path_join = staticmethod(_os.path.join)
    _isdir = staticmethod(_os.path.isdir)
    _islink = staticmethod(_os.path.islink)
    _remove = staticmethod(_os.remove)
    _rmdir = staticmethod(_os.rmdir)
    _warn = _warnings.warn

    def _rmtree(self, path):
        # Essentially a stripped down version of shutil.rmtree.  We can't
        # use globals because they may be None'ed out at shutdown.
        for name in self._listdir(path):
            fullname = self._path_join(path, name)
            try:
                isdir = self._isdir(fullname) and not self._islink(fullname)
            except OSError:
                isdir = False
            if isdir:
                self._rmtree(fullname)
            else:
                try:
                    self._remove(fullname)
                except OSError:
                    pass
        try:
            self._rmdir(path)
        except OSError:
            pass def filter_files(filenames, ignores):
    """
    Filter a list of files, excluding all filenames which contain
    an element of `ignores` as part of their path
    """
    parts = map(full_split, filenames)
    inds = [i for i, ps in enumerate(parts)
            if not any([ignore in ps for ignore in ignores])]
    return [filenames[i] for i in inds] def run_matlab_cmd(cmd, cwd=None):
    matlab = get_matlab_or_octave()
    args = []
    if os.path.basename(matlab).startswith('matlab'):
        args += ['-nodesktop', '-nosplash', '-r']
        args.append("try;{};catch exc;disp(getReport(exc));disp('__ERROR__');exit(1);end;quit".format(cmd))
    else:
        args += ['--traditional', '--eval']
        args.append("try;{};catch exc;struct_levels_to_print(10);print_struct_array_contents(true);disp(lasterror);for i=1:size(lasterror.stack);disp(lasterror.stack(i));end;disp('__ERROR__');exit(1);end;quit".format(cmd))
    sp.check_call([matlab] + args, cwd=cwd) def download_and_check(url, target, md5_hash):
    """Download url to target and check for correct md5_hash. Prints warning if hash is not correct."""
    download_file(url, target)
    check_file_hash(target, md5_hash) def download_file_from_google_drive(id, destination):
    """adapted from https://drive.google.com/uc?id=0B2hsWbciDVedWHFiMUVVWFRZTE0&export=download"""
    def get_confirm_token(response):
        for key, value in response.cookies.items():
            if key.startswith('download_warning'):
                return value

        return None

    def save_response_content(response, destination):
        CHUNK_SIZE = 32768

        with tqdm(unit='B', unit_scale=True) as pbar:
            with open(destination, "wb") as f:
                for chunk in response.iter_content(CHUNK_SIZE):
                    if chunk: # filter out keep-alive new chunks
                        f.write(chunk)
                        pbar.update(CHUNK_SIZE)

    URL = "https://docs.google.com/uc?export=download"

    session = requests.Session()

    response = session.get(URL, params = { 'id' : id }, stream = True)
    token = get_confirm_token(response)

    if token:
        params = { 'id' : id, 'confirm' : token }
        response = session.get(URL, params = params, stream = True)

    save_response_content(response, destination) def check_file_hash(filename, md5_hash):
    """
    Check a file's hash and issue a warning it is has not the expected value.
    """
    print('Checking md5 sum...')
    hasher = hashlib.md5()
    with open(filename, 'rb') as f:
        # read file in chunks to avoid "invalid argument" error
        # see https://stackoverflow.com/a/48123430
        for block in iter(partial(f.read, 64 * (1 << 20)), b''):
            hasher.update(block)

    file_hash = hasher.hexdigest()
    if file_hash != md5_hash:
        warnings.warn("MD5 sum of {} has changed. Expected {} but got {}. This might lead to"
                      " this code producing wrong data.".format(filename, md5_hash, file_hash)) @contextmanager
def atomic_directory_setup(directory):
    """ context manager that makes sure that directory is deleted in case of exceptions. """
    with ExitStack() as stack:
        if directory is not None:
            stack.callback(lambda: shutil.rmtree(directory))
        yield
        stack.pop_all() def build_padded_2d_array(arrays, max_length=None, padding_value=np.nan):
    if max_length is None:
        max_length = np.max([len(a) for a in arrays])

    output = np.ones((len(arrays), max_length), dtype=np.asarray(arrays[0]).dtype)
    output *= padding_value
    for i, array in enumerate(arrays):
        output[i, :len(array)] = array

    return output def progressinfo(seq, verbose=True, length=None, prefix=''):
	"""Yields from seq while displaying progress information.
	   Unlike mdp.utils.progessinfo, this routine does not
	   display the progress after each iteration but tries
	   to approximate adaequate stepsizes in order to print
	   the progress information roughly ones per second.

       -verbose: if False, the function behaves like `yield from seq`
       -length: can be used to give the length of sequences that
             have no __len__ attribute or to overwrite the length
       -prefix: Will be printed before the status information.
	"""
	if not verbose:
		for item in seq:
			yield item
		return
	next_step = 1
	step_size = 1
	last_time = time.time()
	start_time = last_time
	if length is None:
		if hasattr(seq, '__len__'):
			length = len(seq)
	if length is not None:
		prec = int(math.ceil(math.log10(length)))
		out_string = "\r{prefix}{{count:{prec}d}} ({{ratio:3.1f}}%)".format(prec=prec, prefix=prefix)
	else:
		length = 1
		out_string = "\r{prefix}{{count:d}}".format(prefix=prefix)
	steps = 0
	for i, item in enumerate(seq):
		yield item
		if i == next_step:
			this_time = time.time()
			time_diff = this_time - last_time + 0.0001
			normed_timediff = time_diff / (step_size)
			new_step_size = int(math.ceil(1.0/normed_timediff))
			#In order to avoid overshooting the right stepsize, we take
			#a convex combination with the old stepsize (that will be
			#too small at the beginning)
			step_size = int(math.ceil(0.8*step_size+0.2*new_step_size))
			last_time = this_time
			next_step = i+step_size
			print(out_string.format(count=i, ratio=1.0*i/length*100), end='')
			sys.stdout.flush()
			#steps += 1
	print(out_string.format(count=length, ratio=100.0))
	#end_time = time.time()
	#print "Needed Steps: ", steps
	#print "Last stepsize", step_size
	#print "Needed time", end_time - start_time
	#print "Avg time per step", (end_time - start_time) / steps
import zipfile
import os
import shutil
import warnings
import glob
import itertools
import numpy as np
import dill
                import piexif
            import h5py  # we don't import globally to avoid depending on h5py
from six.moves import urllib
from scipy.io import loadmat
from natsort import natsorted
from pkg_resources import resource_string
from PIL import Image
from boltons.fileutils import mkdir_p
from tqdm import tqdm
from .datasets import FileStimuli, Stimuli, FixationTrains, Fixations, read_hdf5
from .utils import (
    TemporaryDirectory,
    filter_files,
    run_matlab_cmd,
    download_and_check,
    download_file_from_google_drive,
    check_file_hash,
    atomic_directory_setup,
    build_padded_2d_array)
from .generics import progressinfo
    from salicon.salicon import SALICON
        if fixation_url is not None:
            # FixationTrains
            print('Creating fixations')

            ns = []
            train_xs = []
            train_ys = []
            train_ts = []
            train_subjects = []

            for n, imgId in progressinfo(enumerate(imgIds)):
                annIds = salicon.getAnnIds(imgIds=imgId)
                anns = salicon.loadAnns(annIds)
                for ann in anns:
                    fs = ann['fixations']
                    # SALICON annotations are 1-indexed, not 0-indexed.
                    xs = np.array([f[1] - 1 for f in fs])
                    ys = np.array([f[0] - 1 for f in fs])
                    ns.append(np.ones(len(xs), dtype=int) * n)
                    train_xs.append(xs)
                    train_ys.append(ys)
                    train_ts.append(range(len(xs)))
                    train_subjects.append(np.ones(len(xs), dtype=int) * ann['worker_id'])

            xs = np.hstack(train_xs)
            ys = np.hstack(train_ys)
            ts = np.hstack(train_ts)

            ns = np.hstack(ns)
            subjects = np.hstack(train_subjects)

            fixations = Fixations.FixationsWithoutHistory(xs, ys, ts, ns, subjects)
        else:
            fixations = None

    if location:
        stimuli.to_hdf5(os.path.join(location, 'stimuli.hdf5'))
        if fixations is not None:
            fixations.to_hdf5(os.path.join(location, 'fixations.hdf5'))
    return stimuli, fixations


def get_SALICON(edition='2015', fixation_type='mouse', location=None):
    """
    Loads or downloads and caches the SALICON dataset as used in the LSUN challenge prior to 2017.
    For memory reasons no fixation trains  are provided.
    @type edition: string, defaults to '2015'
    @param edition: whether to provide the original SALICON dataset from 2015 or the update from 2017

    @type fixation_type: string, defaults to 'mouse'
    @param fixation_type: whether to use the mouse gaze postion ('mouse')
                          or the inferred fixations ('fixations').
                          For more details see the SALICON challenge homepage (below).

    @type  location: string, defaults to `None`
    @param location: If and where to cache the dataset. The dataset
                     will be stored in the subdirectory `SALICON` of
                     location and read from there, if already present.
    @return: Training stimuli, validation stimuli, testing stimuli, training fixation trains, validation fixation trains

    .. seealso::
        Ming Jiang, Shengsheng Huang, Juanyong Duan*, Qi Zhao: SALICON: Saliency in Context, CVPR 2015

        http://salicon.net/

    .. note:
        prior to version 0.2.0 of pysaliency the data was stored in a way that allowed
        accessing the subject ids. This is not possible anymore and each scanpath on
        each image is just assigned an auto-incrementing id.
    """
    if edition not in ['2015', '2017']:
        raise ValueError('edition has to be \'2015\' or \'2017\', not \'{}\''.format(edition))

    if fixation_type not in ['mouse', 'fixations']:
        raise ValueError('fixation_type has to be \'mouse\' or \'fixations\', not \'{}\''.format(fixation_type))

    name = _get_SALICON_name(edition=edition, fixation_type=fixation_type)

    stimuli_train, stimuli_val, stimuli_test = _get_SALICON_stimuli(
        location=location, name='SALICON', edition=edition, fixation_type=fixation_type)
    fixations_train, fixations_val = _get_SALICON_fixations(
        location=location, name=name, edition=edition, fixation_type=fixation_type)

    return stimuli_train, stimuli_val, stimuli_test, fixations_train, fixations_val


def _get_SALICON_name(edition='2015', fixation_type='mouse'):
    if edition not in ['2015', '2017']:
        raise ValueError('edition has to be \'2015\' or \'2017\', not \'{}\''.format(edition))

    if fixation_type not in ['mouse', 'fixations']:
        raise ValueError('fixation_type has to be \'mouse\' or \'fixations\', not \'{}\''.format(fixation_type))

    if edition == '2015':
        if fixation_type == 'mouse':
            name = 'SALICON'
        elif fixation_type == 'fixations':
            name = 'SALICON2015_fixations'
    elif edition == '2017':
        if fixation_type == 'mouse':
            name = 'SALICON2017_mouse'
        elif fixation_type == 'fixations':
            name = 'SALICON2017_fixations'

    return name


def _get_SALICON_stimuli(location, name, edition='2015', fixation_type='mouse'):
    if edition not in ['2015', '2017']:
        raise ValueError('edition has to be \'2015\' or \'2017\', not \'{}\''.format(edition))

    if fixation_type not in ['mouse', 'fixations']:
        raise ValueError('fixation_type has to be \'mouse\' or \'fixations\', not \'{}\''.format(fixation_type))

    if location:
        location = os.path.join(location, name)
        if os.path.exists(location):
            if all(os.path.exists(os.path.join(location, filename)) for filename in ['stimuli_train.hdf5', 'stimuli_val.hdf5', 'stimuli_test.hdf5']):
                stimuli_train = read_hdf5(os.path.join(location, 'stimuli_train.hdf5'))
                stimuli_val = read_hdf5(os.path.join(location, 'stimuli_val.hdf5'))

                stimuli_test = read_hdf5(os.path.join(location, 'stimuli_test.hdf5'))