class_docstr: Avalanche Dataset.
  
  Avlanche dataset are pytorch-compatible Datasets with some additional
  functionality such as:
  - management of transformation groups via :class:`AvalancheTransform`
  - support for sample attributes such as class targets and task labels
  
  Data Attributes
  ---------------
  
  Avalanche datasets manage sample-wise information such as class or task
  labels via :class:`DataAttribute`.
  
  Transformation Groups
  ---------------------
  
  Avalanche datasets manage transformation via transformation groups.
  Simply put, a transformation group is a named preprocessing function
  (as in torchvision datasets). By default, Avalanche expects
  two transformation groups:
  - 'train', which contains transformations applied to training patterns.
  - 'eval', that contain transformations applied to test patterns.
  
  Having both groups allows to use different transformations during training
  and evaluation and to seamlessly switch between them by using the
  :func:`train` and :func:`eval` methods. Arbitrary transformation groups
  can be added and used.  If you define custom groups, you can use them by
  calling the `:func:with_transforms` method.
  
  switching to a different transformation group by calling the ``train()``,
  ``eval()`` or ``with_transforms` methods always returns a new dataset,
  levaing the original one unchanged.
  
  Ttransformation groups can be manipulated by removing, freezing, or
  replacing transformations. Each operation returns a new dataset, leaving
  the original one unchanged.
class_docstr_tok: ["Avalanche","Dataset",".","Avlanche","dataset","are","pytorch-compatible","Datasets","with","some","additional","functionality","such","as",":","-","management","of","transformation","groups","via",":","class",":","`","AvalancheTransform","`","-","support","for","sample","attributes","such","as","class","targets","and","task","labels","Data","Attributes","--","--","--","--","--","--","--","-","Avalanche","datasets","manage","sample-wise","information","such","as","class","or","task","labels","via",":","class",":","`","DataAttribute","`",".","Transformation","Groups","--","--","--","--","--","--","--","--","--","--","-","Avalanche","datasets","manage","transformation","via","transformation","groups",".","Simply","put",",","a","transformation","group","is","a","named","preprocessing","function","(","as","in","torchvision","datasets",")",".","By","default",",","Avalanche","expects","two","transformation","groups",":","-","'train","'",",","which","contains","transformations","applied","to","training","patterns",".","-","'eval","'",",","that","contain","transformations","applied","to","test","patterns",".","Having","both","groups","allows","to","use","different","transformations","during","training","and","evaluation","and","to","seamlessly","switch","between","them","by","using","the",":","func",":","`","train","`","and",":","func",":","`","eval","`","methods",".","Arbitrary","transformation","groups","can","be","added","and","used",".","If","you","define","custom","groups",",","you","can","use","them","by","calling","the","`",":","func",":","with_transforms","`","method",".","switching","to","a","different","transformation","group","by","calling","the","``","train","(",")","``",",","``","eval","(",")","``","or","``","with_transforms","`","methods","always","returns","a","new","dataset",",","levaing","the","original","one","unchanged",".","Ttransformation","groups","can","be","manipulated","by","removing",",","freezing",",","or","replacing","transformations",".","Each","operation","returns","a","new","dataset",",","leaving","the","original","one","unchanged","."]
docstr: Creates a ``AvalancheDataset`` instance.
  
  :param dataset: Original dataset. Beware that
      AvalancheDataset will not overwrite transformations already
      applied by this dataset.
  :param transform_groups: Avalanche transform groups.
docstr_tok: ["Creates","a","``","AvalancheDataset","``","instance",".",":","param","dataset",":","Original","dataset",".","Beware","that","AvalancheDataset","will","not","overwrite","transformations","already","applied","by","this","dataset",".",":","param","transform_groups",":","Avalanche","transform","groups","."]
code: def __init__(
      self,
      datasets: Sequence[IDataset[T_co]],
      *,
      indices: Optional[List[int]] = None,
      data_attributes: Optional[List[DataAttribute]] = None,
      transform_groups: Optional[TransformGroups] = None,
      frozen_transform_groups: Optional[TransformGroups] = None,
      collate_fn: Optional[Callable[[List], Any]] = None,
  ):
      """Creates a ``AvalancheDataset`` instance.
  
      :param dataset: Original dataset. Beware that
          AvalancheDataset will not overwrite transformations already
          applied by this dataset.
      :param transform_groups: Avalanche transform groups.
      """
      if issubclass(type(datasets), TorchDataset) or issubclass(
          type(datasets), AvalancheDataset
      ):
          datasets = [datasets]  # type: ignore
  
      # NOTES on implementation:
      # - raw datasets operations are implemented by _FlatData
      # - data attributes are implemented by DataAttribute
      # - transformations are implemented by TransformGroups
      # AvalancheDataset just takes care to manage all of these attributes
      # together and decides how the information propagates through
      # operations (e.g. how to pass attributes after concat/subset
      # operations).
      flat_datas = []
      for d in datasets:
          if len(d) > 0:
              if isinstance(d, AvalancheDataset):
                  flat_datas.append(d._flat_data)
              elif not isinstance(d, _FlatDataWithTransform):
                  flat_datas.append(_FlatDataWithTransform([d]))
              else:
                  flat_datas.append(d)
      if (
          transform_groups is None
          and frozen_transform_groups is None
          and indices is not None
          and len(flat_datas) == 1
      ):
          # TODO: remove. shouldn't be needed but helps with flattening
          assert len(flat_datas) == 1
          self._flat_data = flat_datas[0].subset(indices)
      elif (
          transform_groups is None
          and frozen_transform_groups is None
          and indices is None
          and len(flat_datas) >= 1
      ):
          # TODO: remove. shouldn't be needed but helps with flattening
          if len(flat_datas) == 0:
              self._flat_data = _FlatDataWithTransform([])
          self._flat_data = flat_datas[0]
          if not isinstance(self._flat_data, _FlatDataWithTransform):
              self._flat_data = _FlatDataWithTransform(
                  [self._flat_data]
              )
  
          for d in flat_datas[1:]:
              if not isinstance(d, _FlatDataWithTransform):
                  d = _FlatDataWithTransform([d])
              self._flat_data = self._flat_data.concat(d)
      else:
          self._flat_data: _FlatDataWithTransform[
              T_co
          ] = _FlatDataWithTransform(
              flat_datas,
              indices=indices,
              transform_groups=transform_groups,
              frozen_transform_groups=frozen_transform_groups,
          )
      self.collate_fn = collate_fn
  
      ####################################
      # Init collate_fn
      ####################################
      if len(datasets) > 0:
          self.collate_fn = self._init_collate_fn(
              datasets[0], collate_fn
          )
      else:
          self.collate_fn = default_collate
      """
          The collate function to use when creating mini-batches from this
          dataset.
          """
  
      ####################################
      # Init data attributes
      ####################################
      # concat attributes from child datasets
      new_data_attributes: Dict[str, DataAttribute] = dict()
      if data_attributes is not None:
          new_data_attributes = {da.name: da for da in data_attributes}
          ld = sum(len(d) for d in datasets)
          for da in data_attributes:
              if len(da) != ld:
                  raise ValueError(
                      "Data attribute {} has length {} but the dataset "
                      "has length {}".format(da.name, len(da), ld)
                  )
  
      self._data_attributes: Dict[str, DataAttribute] = OrderedDict()
      first_dataset = datasets[0] if len(datasets) > 0 else None
      if isinstance(first_dataset, AvalancheDataset):
          for attr in first_dataset._data_attributes.values():
              if attr.name in new_data_attributes:
                  # Keep overridden attributes in their previous position
                  self._data_attributes[
                      attr.name
                  ] = new_data_attributes.pop(attr.name)
                  continue
  
              acat = attr
              found_all = True
              for d2 in datasets[1:]:
                  if hasattr(d2, attr.name):
                      acat = acat.concat(getattr(d2, attr.name))
                  elif (
                      len(d2) > 0
                  ):  # if empty we allow missing attributes
                      found_all = False
                      break
              if found_all:
                  self._data_attributes[attr.name] = acat
  
      # Insert new data attributes after inherited ones
      for da in new_data_attributes.values():
          self._data_attributes[da.name] = da
  
      if indices is not None:  # subset operation for attributes
          for da in self._data_attributes.values():
              # TODO: this was the old behavior. How do we know what to do if
              # we permute the entire dataset?
              # DEPRECATED! always subset attributes
              # we keep this behavior only for `classification_subset`
              # if len(da) != sum([len(d) for d in datasets]):
              #     self._data_attributes[da.name] = da
              # else:
              #     self._data_attributes[da.name] = da.subset(self._indices)
              #
              #     dasub = da.subset(indices)
              #     self._data_attributes[da.name] = dasub
              dasub = da.subset(indices)
              self._data_attributes[da.name] = dasub
  
      # set attributes dynamically
      for el in self._data_attributes.values():
          assert len(el) == len(
              self
          ), f"BUG: Wrong size for attribute {el.name}"
  
          is_property = False
          if hasattr(self, el.name):
              is_property = True
              # Do not raise an error if a property.
              # Any check related to the property will be done
              # in the property setter method.
              if not isinstance(
                  getattr(type(self), el.name, None), property
              ):
                  raise ValueError(
                      f"Trying to add DataAttribute `{el.name}` to "
                      f"AvalancheDataset but the attribute name is "
                      f"already used."
                  )
          if not is_property:
              setattr(self, el.name, el)
  
imports: import copy
  import warnings
  import numpy
  from torch.utils.data.dataloader import default_collate
  from avalanche.benchmarks.utils.dataset_definitions import IDataset
  from .data_attribute import DataAttribute
  from typing import Dict, List, Any, Optional, Sequence, TypeVar, Callable, Union, overload
  from .flat_data import FlatData
  from .transform_groups import TransformGroups, EmptyTransformGroups
  from torch.utils.data import Dataset
  from collections import OrderedDict
label_desc: Use image node_id 1 to create a new AvalancheDataset object from inherited base classes: IDataset with example: obj = AvalancheDataset(datasets)