From 6bcd06bdd9b2c8709cb5623625e6ea966729ff7f Mon Sep 17 00:00:00 2001 From: Mohammad Hijjawi Date: Wed, 21 Oct 2026 16:57:52 +0100 Subject: [PATCH 1/2] [datasets] Keep relative image folders readable in RecognitionDataset.merge_dataset merge_dataset moves the root of the merged dataset to "/" and joins each image path to its former root, but a relative root yields relative paths, which are then read from the filesystem root (e.g. "train/part_a/images/0.png" is read as "/train/part_a/images/0.png"). This breaks the recognition training script as soon as --train_path is a relative folder with several subfolders. The roots are now made absolute before being joined. --- doctr/datasets/recognition.py | 6 +++--- tests/pytorch/test_datasets_pt.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/doctr/datasets/recognition.py b/doctr/datasets/recognition.py index 714be882ba..15011251b6 100644 --- a/doctr/datasets/recognition.py +++ b/doctr/datasets/recognition.py @@ -47,10 +47,10 @@ def __init__( self.data.append((img_name, label)) def merge_dataset(self, ds: AbstractDataset) -> None: - # Update data with new root for self - self.data = [(str(Path(self.root).joinpath(img_path)), label) for img_path, label in self.data] + # Update data with new root for self (absolute paths, a relative one would be read from the new root) + self.data = [(str(Path(self.root).absolute().joinpath(img_path)), label) for img_path, label in self.data] # Define new root self.root = Path("/") # Merge with ds data for img_path, label in ds.data: - self.data.append((str(Path(ds.root).joinpath(img_path)), label)) + self.data.append((str(Path(ds.root).absolute().joinpath(img_path)), label)) diff --git a/tests/pytorch/test_datasets_pt.py b/tests/pytorch/test_datasets_pt.py index 7c0030ea00..a65986751c 100644 --- a/tests/pytorch/test_datasets_pt.py +++ b/tests/pytorch/test_datasets_pt.py @@ -378,6 +378,22 @@ def test_recognition_dataset(mock_image_folder, mock_recognition_label): move(os.path.join(ds.root, "tmp_file"), os.path.join(ds.root, img_name)) +def test_recognition_dataset_merge(mock_image_folder, mock_recognition_label, monkeypatch): + # Relative image folders, e.g. the subfolders of a relative `--train_path` of the recognition training script + monkeypatch.chdir(os.path.dirname(mock_image_folder)) + img_folder = os.path.basename(mock_image_folder) + ds = datasets.RecognitionDataset(img_folder, mock_recognition_label) + labels = [label for _, label in ds.data] + + ds.merge_dataset(datasets.RecognitionDataset(img_folder, mock_recognition_label)) + ds.merge_dataset(datasets.RecognitionDataset(img_folder, mock_recognition_label)) + assert len(ds) == 15 + assert [label for _, label in ds.data] == 3 * labels + for idx in range(len(ds)): + sample = ds[idx] + assert isinstance(sample.image, torch.Tensor) and sample.target == ds.data[idx][1] + + @pytest.mark.parametrize( "use_polygons", [False, True], From 77d79e5be57308573bc13327c3a387b6a8e40d92 Mon Sep 17 00:00:00 2001 From: Mohammad Hijjawi Date: Thu, 22 Oct 2026 21:46:59 +0100 Subject: [PATCH 2/2] [tests] Fold the merge check into test_recognition_dataset and iterate a DataLoader over the merged dataset --- tests/pytorch/test_datasets_pt.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/pytorch/test_datasets_pt.py b/tests/pytorch/test_datasets_pt.py index a65986751c..a5040cb1fe 100644 --- a/tests/pytorch/test_datasets_pt.py +++ b/tests/pytorch/test_datasets_pt.py @@ -350,7 +350,7 @@ def test_table_dataset(mock_image_folder, mock_table_label, use_polygons): move(os.path.join(mock_image_folder, "tmp_file"), os.path.join(mock_image_folder, img_name)) -def test_recognition_dataset(mock_image_folder, mock_recognition_label): +def test_recognition_dataset(mock_image_folder, mock_recognition_label, monkeypatch): input_size = (32, 128) ds = datasets.RecognitionDataset( img_folder=mock_image_folder, @@ -377,21 +377,25 @@ def test_recognition_dataset(mock_image_folder, mock_recognition_label): datasets.RecognitionDataset(mock_image_folder, mock_recognition_label) move(os.path.join(ds.root, "tmp_file"), os.path.join(ds.root, img_name)) - -def test_recognition_dataset_merge(mock_image_folder, mock_recognition_label, monkeypatch): - # Relative image folders, e.g. the subfolders of a relative `--train_path` of the recognition training script + # Merge datasets with a relative image folder monkeypatch.chdir(os.path.dirname(mock_image_folder)) img_folder = os.path.basename(mock_image_folder) - ds = datasets.RecognitionDataset(img_folder, mock_recognition_label) + ds = datasets.RecognitionDataset( + img_folder=img_folder, + labels_path=mock_recognition_label, + img_transforms=Resize(input_size, preserve_aspect_ratio=True), + ) labels = [label for _, label in ds.data] - ds.merge_dataset(datasets.RecognitionDataset(img_folder, mock_recognition_label)) ds.merge_dataset(datasets.RecognitionDataset(img_folder, mock_recognition_label)) assert len(ds) == 15 - assert [label for _, label in ds.data] == 3 * labels - for idx in range(len(ds)): - sample = ds[idx] - assert isinstance(sample.image, torch.Tensor) and sample.target == ds.data[idx][1] + + loader = DataLoader(ds, batch_size=5, collate_fn=ds.collate_fn) + merged_labels = [] + for images, batch_labels in loader: + assert isinstance(images, torch.Tensor) and images.shape == (5, 3, *input_size) + merged_labels.extend(batch_labels) + assert merged_labels == 3 * labels @pytest.mark.parametrize(