Repository: openvinotoolkit/datumaro

Issue:
Troubles running 'remap_labels' on ProjectDataset
In this example, the dataset given from `project.make_dataset()` returns a ProjectDataset, I am not able to run `remap_labels` on this dataset as it just returns a `TypeError: 'str' object is not callable`. I looked through the tests and it looks like all the tests are running `remap_labels` on `datumaro.components.dataset.Dataset` objects and I don't see any tests/examples running on `datumaro.components.project.ProjectDataset`.

I'm not sure if I'm doing something wrong, but just running this example on the master and development branch seems to give me the same `TypeError: 'str' object is not callable` error.

If I use the `dataset.Dataset` class it works fine, but I'm not sure how to take a ProjectDataset and turn it into just a `Dataset`

```python
from datumaro.components.project import Project

# load a Datumaro project
project = Project.load('directory')

# create a dataset
dataset = project.make_dataset()

# keep only annotated images
dataset.select(lambda item: len(item.annotations) != 0)

# change dataset labels
dataset.transform('remap_labels',
  {'cat': 'dog', # rename cat to dog
    'truck': 'car', # rename truck to car
    'person': '', # remove this label
  }, default='delete') # remove everything else
```


Discussion:
As a workaround I am running this instead
```python
import datumaro.plugins.transforms as transforms

dataset = transforms.RemapLabels(dataset,
  mapping={'cat': 'dog', # rename cat to dog
    'truck': 'car', # rename truck to car
    'person': '', # remove this label
  }, default='delete')
```

This seems to get me past that step, but it changes the type of the dataset to a `datumaro.plugins.transforms.RemapLabels`
Alright, well that bombed when I tried to export the dataset. I'll keep looking for a solution. Any help on what I'm doing wrong or what I could correct would be great. Thanks!
Hi, thanks for reporting the problem with the example. Yes, it looks like `ProjectDataset` was not updated after recent `Dataset` changes and it can accept only a transform class instead of allowing a string name in the `transform` method. The workaround you tried was correct, it just represents how it works under the hood. To make everything working do any of the following:
```python
# Just pass the transform class in the example code instead of string
dataset.transform(project.env.transforms['remap_labels'],
# or
from datumaro.plugins.transforms import RemapLabels
dataset.transform(RemapLabels,
# but it will just do what you did in the workaround, i.e. it will not return a Dataset instance

# Continue the workaround you tried with
dataset = Dataset.from_extractors(dataset)
dataset.export('path/', 'format_name')

# or (answering your question about getting a Dataset from ProjectDataset)
dataset = Dataset.from_extractors(project.make_dataset())
... # the example code
```
All of this will produce the same results.

Resolving patch:
diff --git a/datumaro/components/project.py b/datumaro/components/project.py
--- a/datumaro/components/project.py
+++ b/datumaro/components/project.py
@@ -257,6 +257,9 @@ def _save_branch_project(self, extractor, save_dir=None):
         dst_dataset.save(save_dir=save_dir, merge=True)
 
     def transform(self, method, *args, **kwargs):
+        if isinstance(method, str):
+            method = self.env.make_transform(method)
+
         return method(self, *args, **kwargs)
 
     def filter(self, expr: str, filter_annotations: bool = False,
@@ -292,9 +295,6 @@ def define_categories(self, categories):
 
     def transform_project(self, method, save_dir=None, **method_kwargs):
         # NOTE: probably this function should be in the ViewModel layer
-        if isinstance(method, str):
-            method = self.env.make_transform(method)
-
         transformed = self.transform(method, **method_kwargs)
         self._save_branch_project(transformed, save_dir=save_dir)
 


Test patch:
diff --git a/tests/requirements.py b/tests/requirements.py
--- a/tests/requirements.py
+++ b/tests/requirements.py
@@ -30,6 +30,7 @@ class Requirements:
     DATUM_BUG_219 = "Return format is not uniform"
     DATUM_BUG_257 = "Dataset.filter doesn't count removed items"
     DATUM_BUG_259 = "Dataset.filter fails on merged datasets"
+    DATUM_BUG_402 = "Troubles running 'remap_labels' on ProjectDataset"
     DATUM_BUG_404 = "custom importer/extractor not loading"
 
 
diff --git a/tests/test_project.py b/tests/test_project.py
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -430,6 +430,32 @@ def __iter__(self):
 
         compare_datasets(self, CustomExtractor(), dataset)
 
+    @mark_requirement(Requirements.DATUM_BUG_402)
+    def test_can_transform_by_name(self):
+        class CustomExtractor(Extractor):
+            def __iter__(self):
+                return iter([
+                    DatasetItem('a'),
+                    DatasetItem('b'),
+                ])
+
+        extractor_name = 'ext1'
+        project = Project()
+        project.env.extractors.register(extractor_name, CustomExtractor)
+        project.add_source('src1', {
+            'url': '',
+            'format': extractor_name,
+        })
+        dataset = project.make_dataset()
+
+        dataset = dataset.transform('reindex')
+
+        expected = Dataset.from_iterable([
+            DatasetItem(1),
+            DatasetItem(2),
+        ])
+        compare_datasets(self, expected, dataset)
+
     @mark_requirement(Requirements.DATUM_BUG_404)
     def test_can_add_plugin(self):
         with TestDir() as test_dir: