Repository: johnnv1/CCAgT-utils

Issue:
Create subdatasets
- Split image into small tiles
- Generate one image by nucleus

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim) Report
> Merging [#24](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim) (8bbd17c) into [main](https://codecov.io/gh/johnnv1/CCAgT-utils/commit/17931026b56dd29d756d996f3c3da6363707ca84?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim) (1793102) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/graphs/tree.svg?width=650&height=150&src=pr&token=HB8P4BKTZ7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim)](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim)

```diff
@@            Coverage Diff             @@
##              main       #24    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           34        42     +8     
  Lines         1872      2404   +532     
  Branches       204       264    +60     
==========================================
+ Hits          1872      2404   +532     
```


| [Impacted Files](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim) | Coverage Δ | |
|---|---|---|
| [CCAgT\_utils/checkers.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvY2hlY2tlcnMucHk=) | `100.00% <100.00%> (ø)` | |
| [CCAgT\_utils/main.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvbWFpbi5weQ==) | `100.00% <100.00%> (ø)` | |
| [CCAgT\_utils/prepare.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvcHJlcGFyZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [CCAgT\_utils/slice.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvc2xpY2UucHk=) | `100.00% <100.00%> (ø)` | |
| [CCAgT\_utils/types/annotation.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvdHlwZXMvYW5ub3RhdGlvbi5weQ==) | `100.00% <100.00%> (ø)` | |
| [CCAgT\_utils/utils.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-Q0NBZ1RfdXRpbHMvdXRpbHMucHk=) | `100.00% <100.00%> (ø)` | |
| [testing/create.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-dGVzdGluZy9jcmVhdGUucHk=) | `100.00% <100.00%> (ø)` | |
| [tests/checkers\_test.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-dGVzdHMvY2hlY2tlcnNfdGVzdC5weQ==) | `100.00% <100.00%> (ø)` | |
| [tests/conftest.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-dGVzdHMvY29uZnRlc3QucHk=) | `100.00% <100.00%> (ø)` | |
| [tests/main\_test.py](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim#diff-dGVzdHMvbWFpbl90ZXN0LnB5) | `100.00% <100.00%> (ø)` | |
| ... and [5 more](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim). Last update [1793102...8bbd17c](https://codecov.io/gh/johnnv1/CCAgT-utils/pull/24?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jo%C3%A3o+Gustavo+A.+Amorim).


Patch:
diff --git a/CCAgT_utils/__main__.py b/CCAgT_utils/__main__.py
new file mode 100644
index 0000000..a3c5e37
--- /dev/null
+++ b/CCAgT_utils/__main__.py
@@ -0,0 +1,7 @@
+from __future__ import annotations
+
+from CCAgT_utils.main import main
+
+
+if __name__ == '__main__':
+    raise SystemExit(main())
diff --git a/CCAgT_utils/checkers.py b/CCAgT_utils/checkers.py
new file mode 100644
index 0000000..3d4e6bd
--- /dev/null
+++ b/CCAgT_utils/checkers.py
@@ -0,0 +1,51 @@
+from __future__ import annotations
+
+import multiprocessing
+from typing import Any
+
+import numpy as np
+from PIL import Image
+
+from CCAgT_utils.utils import basename
+from CCAgT_utils.utils import find_files
+from CCAgT_utils.utils import get_traceback
+
+
+def has_all_into_dir(dir_images: str, filenames: list[str], **kwargs: Any) -> bool:
+    files = find_files(dir_images, **kwargs)
+
+    return all(filename in files for filename in filenames)
+
+
+@get_traceback
+def single_core_mask_has(filenames: set[str],
+                         categories: set[int]) -> set[str]:
+    return {basename(filename) for filename in filenames
+            if any(v in categories for v in np.unique(Image.open(filename).convert('L')))}
+
+
+def masks_that_has(dir_masks: str,
+                   categories: set[int],
+                   extension: str | tuple[str, ...] = '.png',
+                   look_recursive: bool = True) -> set[str]:
+
+    files = find_files(dir_masks, extension, look_recursive)
+    cpu_num = multiprocessing.cpu_count()
+    workers = multiprocessing.Pool(processes=cpu_num)
+
+    filenames_splitted = np.array_split(list(files), cpu_num)
+    print(f'Start the checker if the masks have at least one of the categories ({categories}) using {cpu_num} cores with '
+          f'{len(filenames_splitted[0])} masks per core...')
+    processes = []
+    for filenames in filenames_splitted:
+        msk_filenames = {files[f] for f in filenames}
+
+        p = workers.apply_async(single_core_mask_has, (msk_filenames,
+                                                       categories))
+        processes.append(p)
+
+    masks_with_categories: set[str] = set()
+    for p in processes:
+        masks_with_categories = masks_with_categories.union(p.get())
+
+    return masks_with_categories
diff --git a/CCAgT_utils/main.py b/CCAgT_utils/main.py
new file mode 100644
index 0000000..126cf3c
--- /dev/null
+++ b/CCAgT_utils/main.py
@@ -0,0 +1,222 @@
+from __future__ import annotations
+
+import argparse
+import ast
+import os
+import shutil
+import sys
+from typing import Sequence
+
+from CCAgT_utils import slice
+from CCAgT_utils.checkers import masks_that_has
+from CCAgT_utils.constants import VERSION
+from CCAgT_utils.prepare import clean_images_and_masks
+from CCAgT_utils.prepare import extract_image_and_mask_by_category
+from CCAgT_utils.utils import basename
+from CCAgT_utils.utils import find_files
+
+
+def _add_create_subdataset_options(parser: argparse.ArgumentParser) -> None:
+
+    parser.add_argument('--name',
+                        metavar='SUBDATASET NAME',
+                        required=True,
+                        help='The name of the subdataset that will be generated')
+
+    parser.add_argument('--original',
+                        metavar='PATH_FOR_ORIGINAL_DATASET',
+                        required=True,
+                        help=('Path for the original dataset. It is expected that this directory has the subdirectories '
+                              '`images/` and `masks/`'))
+    parser.add_argument('--output',
+                        metavar='PATH_TO_WRITE_THE_SUBDATASETS',
+                        required=True,
+                        help=('Path to write the new subdataset. It will create a directory with this subdataset name, and '
+                              'subdirectories `images/` and `masks/`'))
+
+    group_ex = parser.add_mutually_exclusive_group(required=False)
+    group_ex.add_argument('--slice-images',
+                          nargs=2,
+                          metavar='HORIZONTAL VERTICAL',
+                          type=int,
+                          help=('Define that wants slice the images into smaller parts. Needs to pass the amount of slice '
+                                'desired for the horizontal and vertical split of the images.'))
+    group_extract = group_ex.add_argument_group('Extract options')
+
+    group_extract.add_argument('--extract',
+                               nargs='*',
+                               type=int,
+                               metavar='CATEGORIES IDs',
+                               help=('Define that wants extract based on one category. Will generate one image for each '
+                                     'instance of the desired category.'))
+    group_extract.add_argument('--labels',
+                               help='Path for the CCAgT file with the labels. Just works with --extract')
+
+    group_extract.add_argument('--paddings',
+                               default=0,
+                               help=('In percent (float values) or pixels (integer values) select, the size of paddings to '
+                                     'apply. Just works with --extract'))
+
+    parser.add_argument('--remove-images-without',
+                        nargs='*',
+                        type=int,
+                        metavar='CATEGORIES IDs',
+                        help=('Define that you wants remove of this subdataset the images that does not have the categories '
+                              'passed as argument.'))
+
+    group = parser.add_argument_group('Check if all images have at least one of the desired categories.')
+    group.add_argument('--check-if-all-have-at-least-one-of',
+                       nargs='*',
+                       metavar='CATEGORIES IDs',
+                       type=int,
+                       help=('Define that you wants check if all images have at least one of the categories passed as '
+                             'argument.'))
+    group.add_argument('--delete',
+                       action='store_true',
+                       help='To remove all images that do not have at least one of desired categories.')
+
+    parser.add_argument('--extensions',
+                        nargs='*',
+                        default=('.jpg', '.png'),
+                        help='The extensions of the images and masks.')
+
+
+def create_subdataset(name: str,
+                      original_dir: str,
+                      output_base: str,
+                      slice_images: tuple[int, ...] | None,
+                      extract: set[int] | None,
+                      categories_to_keep: set[int] | None,
+                      check_if_all_have_one: set[int] | None,
+                      delete: bool,
+                      CCAgT_path: str | None,
+                      paddings: str,
+                      extensions: tuple[str, ...] = ('.jpg', '.png')) -> int:
+    output_dir = os.path.join(output_base, name)
+    output_images_dir = os.path.join(output_dir, 'images/')
+    output_masks_dir = os.path.join(output_dir, 'masks/')
+
+    if os.path.isdir(output_images_dir) or os.path.isdir(output_masks_dir):
+        print(f'Already exist a dataset with name={name} at {output_base}!', file=sys.stderr)
+        return 1
+
+    input_images_dir = os.path.join(original_dir, 'images/')
+    input_masks_dir = os.path.join(original_dir, 'masks/')
+
+    if not os.path.isdir(input_images_dir) or not os.path.isdir(input_masks_dir):
+        print(f'Do not found the original dataset at {input_images_dir} or {input_masks_dir}!', file=sys.stderr)
+        return 1
+
+    print('\n\n------------------------')
+    if slice_images:
+        slice_images = tuple(slice_images)
+        print(f'Create images and masks splitting then into {slice_images} (horizontal, vertical) parts')
+        slice.images_and_masks(input_images_dir,
+                               input_masks_dir,
+                               output_dir,
+                               slice_images[0], slice_images[1],
+                               extension=extensions,
+                               look_recursive=True)
+    elif extract:
+        if CCAgT_path:
+            print(f'Create images and masks for each instance of the categories {extract}')
+            extract_image_and_mask_by_category(input_images_dir,
+                                               input_masks_dir,
+                                               output_dir,
+                                               extract,
+                                               CCAgT_path,
+                                               ast.literal_eval(paddings),
+                                               extensions,
+                                               True)
+        else:
+            print('When using `--extract`, please specify the labels files with `--labels` argument', file=sys.stderr)
+            return 1
+    else:
+        print('The images and masks of the subdataset will be copied from the original dataset!')
+        print('Coping images files...')
+        shutil.copytree(input_images_dir, output_images_dir)
+        print('Coping masks files...')
+        shutil.copytree(input_masks_dir, output_masks_dir)
+
+    if categories_to_keep:
+        print('------------------------')
+        print(f'Delete images that do not have the categories: {categories_to_keep} ')
+        clean_images_and_masks(output_images_dir, output_masks_dir, set(categories_to_keep), extensions)
+
+    if check_if_all_have_one:
+        print('------------------------')
+        check_if_all_have_one = set(check_if_all_have_one)
+
+        masks_with = masks_that_has(output_masks_dir, check_if_all_have_one, extensions, True)
+        all_masks = {basename(k): v for k, v in find_files(output_masks_dir, extensions, True).items()}
+        diff = set(all_masks.keys()) - masks_with
+
+        if len(diff) > 0:
+            print(f'A total of {len(diff)} files there is not at least one of the categories {check_if_all_have_one}',
+                  file=sys.stderr)
+
+            if delete:
+                all_images = {basename(k): v for k, v in find_files(output_images_dir, extensions, True).items()}
+                print('Deleting images and masks that do not have at least one of the selected categories...')
+                for bn in diff:
+                    os.remove(all_masks[bn])
+                    os.remove(all_images[bn])
+        else:
+            print(f'Everything is ok, having 0 files that do not have any of the categories {check_if_all_have_one}')
+    print('\n------------------------')
+    print('Creation of the subdataset finished!')
+    print(f'Dataset name: `{name}` | Location: `{output_dir}`')
+    images_quantity = len(find_files(output_images_dir, extensions, True))
+    masks_quantity = len(find_files(output_masks_dir, extensions, True))
+    print(f'Total of images: {images_quantity}')
+    print(f'Total of images: {masks_quantity}')
+    return 0
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+    argv = argv if argv is not None else sys.argv[1:]
+    parser = argparse.ArgumentParser(prog='CCAgT_converter')
+
+    # https://stackoverflow.com/a/8521644/812183
+    parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {VERSION}')
+
+    subparsers = parser.add_subparsers(dest='command')
+
+    create_subdataset_parser = subparsers.add_parser('create-subdataset',
+                                                     help=('Based on the original dataset create a personalized version of '
+                                                           'the dataset with the desired modifications. Examples: slice the '
+                                                           'images into smaller parts, select just images with some category,'
+                                                           ' images of a specific category.'))
+    _add_create_subdataset_options(create_subdataset_parser)
+
+    help = subparsers.add_parser('help', help='Show help for a specific command.')
+    help.add_argument('help_cmd', nargs='?', help='Command to show help for.')
+
+    if len(argv) == 0:
+        argv = ['help']
+
+    args = parser.parse_args(argv)
+
+    if args.command == 'help' and args.help_cmd:
+        parser.parse_args([args.help_cmd, '--help'])
+    elif args.command == 'help':
+        parser.parse_args(['--help'])
+
+    if args.command == 'create-subdataset' and args.name != '':
+        return create_subdataset(str(args.name),
+                                 str(args.original),
+                                 str(args.output),
+                                 args.slice_images,
+                                 args.extract,
+                                 args.remove_images_without,
+                                 args.check_if_all_have_at_least_one_of,
+                                 args.delete,
+                                 args.labels,
+                                 args.paddings,
+                                 tuple(args.extensions))
+
+    return 1
+
+
+if __name__ == '__main__':
+    raise SystemExit(main())
diff --git a/CCAgT_utils/prepare.py b/CCAgT_utils/prepare.py
new file mode 100644
index 0000000..0ed4d66
--- /dev/null
+++ b/CCAgT_utils/prepare.py
@@ -0,0 +1,139 @@
+from __future__ import annotations
+
+import multiprocessing
+import os
+
+import numpy as np
+import pandas as pd
+from PIL import Image
+
+from CCAgT_utils.checkers import masks_that_has
+from CCAgT_utils.converters import CCAgT
+from CCAgT_utils.types.annotation import Annotation
+from CCAgT_utils.utils import basename
+from CCAgT_utils.utils import create_structure
+from CCAgT_utils.utils import find_files
+from CCAgT_utils.utils import get_traceback
+from CCAgT_utils.utils import slide_from_filename
+
+
+def clean_images_and_masks(dir_images: str,
+                           dir_masks: str,
+                           categories: set[int],
+                           extension: str | tuple[str, ...] = ('.png', '.jpg'),
+                           look_recursive: bool = True) -> None:
+
+    basenames_matches = masks_that_has(dir_masks, categories, extension, look_recursive)
+
+    image_filenames_to_remove = {v for k, v in find_files(dir_images, extension, look_recursive).items()
+                                 if basename(k) not in basenames_matches}
+
+    mask_filenames_to_remove = {v for k, v in find_files(dir_masks, extension, look_recursive).items()
+                                if basename(k) not in basenames_matches}
+
+    print(f'Deleting {len(image_filenames_to_remove)} images files...')
+    for filename in image_filenames_to_remove:
+        os.remove(filename)
+
+    print(f'Deleting {len(mask_filenames_to_remove)} masks files...')
+    for filename in mask_filenames_to_remove:
+        os.remove(filename)
+
+
+def extract_category_from_image_file(input_path: str,
+                                     output_path: str,
+                                     annotations: list[Annotation],
+                                     padding: int | float) -> int:
+    im = np.asarray(Image.open(input_path))
+
+    bn, ext = os.path.splitext(basename(input_path, with_extension=True))
+
+    height, width = im.shape[:2]
+
+    count = 1
+    for ann in annotations:
+        bb = ann.bbox
+        bb.add_padding(padding, (0, 0, width, height))
+        part = im[bb.slice_y, bb.slice_x]
+        Image.fromarray(part).save(os.path.join(output_path, f'{bn}_{ann.category_id}_{count}{ext}'),
+                                   quality=100,
+                                   subsampling=0)
+        count += 1
+
+    return len(annotations)
+
+
+@get_traceback
+def single_core_extract_image_and_masks(image_filenames: dict[str, str],
+                                        mask_filenames: dict[str, str],
+                                        df_annotations: pd.DataFrame,
+                                        base_dir_output: str,
+                                        padding: int | float) -> tuple[int, int]:
+    image_counter = 0
+    mask_counter = 0
+    for bn, sub_df in df_annotations.groupby('image_name'):
+
+        anns = [Annotation(row['geometry'], row['category_id']) for _, row in sub_df.iterrows()]
+
+        if bn in image_filenames:
+            image_counter += extract_category_from_image_file(image_filenames[bn],
+                                                              os.path.join(base_dir_output, 'images/', slide_from_filename(bn)),
+                                                              anns,
+                                                              padding)
+        if bn in mask_filenames:
+            mask_counter += extract_category_from_image_file(mask_filenames[bn],
+                                                             os.path.join(base_dir_output, 'masks/', slide_from_filename(bn)),
+                                                             anns,
+                                                             padding)
+    return (image_counter, mask_counter)
+
+
+def extract_image_and_mask_by_category(dir_images: str,
+                                       dir_masks: str,
+                                       dir_output: str,
+                                       categories: set[int],
+                                       CCAgT_path: str,
+                                       paddings: int | float,
+                                       extension: str | tuple[str, ...] = ('.png', '.jpg'),
+                                       look_recursive: bool = True) -> None:
+    ccagt_annotations = CCAgT.read_parquet(CCAgT_path)
+
+    df = ccagt_annotations.df.loc[ccagt_annotations.df['category_id'].isin(categories),
+                                  ['image_name', 'geometry', 'category_id']]
+
+    if df.empty:
+        print(f'Nothing to process with categories {categories} from {CCAgT_path}')
+        return
+
+    image_filenames = {basename(k): v for k, v in find_files(dir_images, extension, look_recursive).items()}
+    mask_filenames = {basename(k): v for k, v in find_files(dir_masks, extension, look_recursive).items()}
+
+    slides = {slide_from_filename(i) for i in image_filenames}
+    create_structure(dir_output, slides)
+
+    cpu_num = multiprocessing.cpu_count()
+    workers = multiprocessing.Pool(processes=cpu_num)
+
+    filenames_splitted = np.array_split(df['image_name'].unique().tolist(), cpu_num)
+    print(f'Start the extraction of each category instance at {len(image_filenames)} images/masks using {cpu_num} cores with '
+          f'{len(filenames_splitted[0])} images/masks per core...')
+
+    processes = []
+    for filenames in filenames_splitted:
+        img_filenames = {k: image_filenames[k] for k in filenames}
+        msk_filenames = {k: mask_filenames[k] for k in filenames}
+        p = workers.apply_async(single_core_extract_image_and_masks, (img_filenames,
+                                                                      msk_filenames,
+                                                                      df[df['image_name'].isin(filenames)],
+                                                                      dir_output,
+                                                                      paddings))
+        processes.append(p)
+
+    image_counter = 0
+    mask_counter = 0
+    for p in processes:
+        im_counter, msk_counter = p.get()
+        image_counter += im_counter
+        mask_counter += msk_counter
+
+    print(f'Successful create {image_counter}/{mask_counter} images/masks for the instances of the categories {categories}')
diff --git a/CCAgT_utils/slice.py b/CCAgT_utils/slice.py
new file mode 100644
index 0000000..5d7dbac
--- /dev/null
+++ b/CCAgT_utils/slice.py
@@ -0,0 +1,102 @@
+from __future__ import annotations
+
+import multiprocessing
+import os
+from typing import Any
+
+import numpy as np
+from PIL import Image
+
+from CCAgT_utils.utils import basename
+from CCAgT_utils.utils import create_structure
+from CCAgT_utils.utils import find_files
+from CCAgT_utils.utils import get_traceback
+from CCAgT_utils.utils import slide_from_filename
+
+
+def image(input_path: str,
+          output_path: str,
+          h_quantity: int = 4,
+          v_quantity: int = 4) -> int:
+    im = np.asarray(Image.open(input_path))
+
+    bn, ext = os.path.splitext(basename(input_path, with_extension=True))
+
+    height, width = im.shape[:2]
+
+    tile_h = height // v_quantity
+    tile_w = width // h_quantity
+
+    count = 1
+    for y in range(0, height, tile_h):
+        for x in range(0, width, tile_w):
+            part = im[y:y + tile_h, x:x + tile_w]
+            Image.fromarray(part).save(os.path.join(output_path, f'{bn}_{count}{ext}'),
+                                       quality=100,
+                                       subsampling=0)
+            count += 1
+
+    return count - 1
+
+
+@get_traceback
+def single_core_image_and_masks(image_filenames: dict[str, str],
+                                mask_filenames: dict[str, str],
+                                base_dir_output: str,
+                                h_quantity: int = 4,
+                                v_quantity: int = 4) -> tuple[int, int]:
+    image_counter = 0
+    mask_counter = 0
+    for bn in image_filenames:
+        image_counter += image(image_filenames[bn],
+                               os.path.join(base_dir_output, 'images/', slide_from_filename(bn)),
+                               h_quantity,
+                               v_quantity)
+
+        if bn in mask_filenames:
+            mask_counter += image(mask_filenames[bn],
+                                  os.path.join(base_dir_output, 'masks/', slide_from_filename(bn)),
+                                  h_quantity,
+                                  v_quantity)
+
+    return (image_counter, mask_counter)
+
+
+def images_and_masks(dir_images: str,
+                     dir_masks: str,
+                     dir_output: str,
+                     h_quantity: int = 4,
+                     v_quantity: int = 4,
+                     **kwargs: Any) -> None:
+
+    image_filenames = {basename(k): v for k, v in find_files(dir_images, **kwargs).items()}
+    mask_filenames = {basename(k): v for k, v in find_files(dir_masks, **kwargs).items()}
+
+    slides = {slide_from_filename(i) for i in image_filenames}
+    create_structure(dir_output, slides)
+
+    cpu_num = multiprocessing.cpu_count()
+    workers = multiprocessing.Pool(processes=cpu_num)
+    filenames_splitted = np.array_split(list(image_filenames), cpu_num)
+    print(f'Start the split of images and masks into {h_quantity}x{v_quantity} parts using {cpu_num} cores with '
+          f'{len(filenames_splitted[0])} images and masks per core...')
+    processes = []
+    for filenames in filenames_splitted:
+        img_filenames = {k: image_filenames[k] for k in filenames}
+        msk_filenames = {k: mask_filenames[k] for k in filenames}
+        p = workers.apply_async(single_core_image_and_masks, (img_filenames,
+                                                              msk_filenames,
+                                                              dir_output,
+                                                              h_quantity,
+                                                              v_quantity))
+        processes.append(p)
+
+    image_counter = 0
+    mask_counter = 0
+    for p in processes:
+        im_counter, msk_counter = p.get()
+        image_counter += im_counter
+        mask_counter += msk_counter
+
+    print(f'Successful splitted {len(image_filenames)}/{len(mask_filenames)} images/masks into {image_counter}/{mask_counter}'
+          ' images/masks')
diff --git a/CCAgT_utils/types/annotation.py b/CCAgT_utils/types/annotation.py
index e364c7c..12d645a 100644
--- a/CCAgT_utils/types/annotation.py
+++ b/CCAgT_utils/types/annotation.py
@@ -85,6 +85,14 @@ class BBox:
         _x, _y = zip(*self.coords)
         return (list(_x), list(_y))
 
+    @property
+    def slice_x(self) -> slice:
+        return slice(self.x_init, self.x_end)
+
+    @property
+    def slice_y(self) -> slice:
+        return slice(self.y_init, self.y_end)
+
     def center_point(self) -> tuple[int, int]:
         return (self.x_init + self.width // 2,
                 self.y_init + self.height // 2)
@@ -95,6 +103,31 @@ class BBox:
     def to_polygon(self) -> Polygon:
         return Polygon(self.coords)
 
+    def fit_inside(self, bounds: tuple[int, int, int, int]) -> None:
+        min_x, min_y, max_x, max_y = bounds
+        self.x_init = min_x if self.x_init < min_x else self.x_init
+        self.y_init = min_y if self.y_init < min_y else self.y_init
+        self.width = max_x - self.x_init if self.x_end > max_x else self.width
+        self.height = max_y - self.y_init if self.y_end > max_y else self.height
+
+    def add_padding(self, padding: int | float = 0, bounds: tuple[int, int, int, int] = (0, 0, 0, 0)) -> None:
+
+        if padding != 0:
+            if isinstance(padding, int):
+                self.width += padding * 2
+                self.height += padding * 2
+                self.x_init -= padding
+                self.y_init -= padding
+            elif isinstance(padding, float):
+                self.x_init = int(self.x_init - (self.width * padding))
+                self.y_init = int(self.y_init - (self.height * padding))
+                self.width = int(self.width * (1 + padding * 2))
+                self.height = int(self.height * (1 + padding * 2))
+            else:
+                raise TypeError('Unexpected value for the padding! Use int or float values')
+
+            self.fit_inside(bounds)
+
 
 def bounds_to_BBox(bounds: tuple[float], category_id: int) -> BBox:
     b = tuple(int(i) for i in bounds)
diff --git a/CCAgT_utils/utils.py b/CCAgT_utils/utils.py
index eda7ba0..278992d 100644
--- a/CCAgT_utils/utils.py
+++ b/CCAgT_utils/utils.py
@@ -108,3 +108,42 @@ def slide_from_filename(filename: str) -> str:
         The slide ID of the filename
     """
     return items_from_filename(filename)[FILENAME_ITEM.slide.value]
+
+
+def find_files(dir_path: str,
+               extension: str | tuple[str, ...],
+               look_recursive: bool = False,
+               selection: list[str] = []) -> dict[str, str]:
+    """Find all files into at the path and subdirectories
+
+    Parameters
+    ----------
+    dir_path : str
+        Path of the base directory to look
+    extension : str | tuple[str]
+        Extension of the dessired files
+
+    Returns
+    -------
+    dict[str, str]
+        A dict with the filename as key and the relative path for the
+        file
+    """
+    if look_recursive:
+        files = {file: os.path.join(path, file) for path, _, files in os.walk(dir_path) for file in files
+                 if file.endswith(extension) and (selection == [] or file in selection)}
+    else:
+        files = {file: os.path.join(dir_path, file) for file in os.listdir(dir_path)
+                 if file.endswith(extension) and (selection == [] or file in selection)}
+
+    return files
+
+
+def create_structure(dir_path: str, slides: set[str]) -> None:
+
+    dir_images = os.path.join(dir_path, 'images/')
+    dir_masks = os.path.join(dir_path, 'masks/')
+
+    for slide in slides:
+        os.makedirs(os.path.join(dir_images, slide), exist_ok=True)
+        os.makedirs(os.path.join(dir_masks, slide), exist_ok=True)
diff --git a/setup.cfg b/setup.cfg
index 93d3952..cb8e12e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -42,6 +42,7 @@ exclude =
 
 [options.entry_points]
 console_scripts =
+    CCAgT-utils = CCAgT_utils.main:main
     CCAgT-converter = CCAgT_utils.converters.main:main
     CCAgT-visualization = CCAgT_utils.visualization.main:main
 


Test patch:
diff --git a/testing/create.py b/testing/create.py
index b5980d8..caa982b 100644
--- a/testing/create.py
+++ b/testing/create.py
@@ -59,22 +59,35 @@ class RawAuxFiles():
 
 
 class ImageMaskFiles():
-    def __init__(self, height: int = 1000, width: int = 1000, names: list[str] = ['example']) -> None:
+    def __init__(self,
+                 height: int = 1000,
+                 width: int = 1000,
+                 names: list[str] = ['example'],
+                 create_image: bool = True,
+                 create_mask: bool = True) -> None:
         self.tmp_dir = tempfile.TemporaryDirectory('_ImageMaskFiles', 'CCAgTutils_')
-        self.mask_dir = os.path.join(self.tmp_dir.name, 'masks/')
+
         self.image_dir = os.path.join(self.tmp_dir.name, 'images/')
-        os.makedirs(self.mask_dir, exist_ok=True)
-        os.makedirs(self.image_dir, exist_ok=True)
+        self.mask_dir = os.path.join(self.tmp_dir.name, 'masks/')
+
+        if create_mask:
+            os.makedirs(self.mask_dir, exist_ok=True)
+            self.__mask = mask_categorical((height, width))
+            msk = Image.fromarray(self.__mask)
+
+        if create_image:
+            os.makedirs(self.image_dir, exist_ok=True)
+            self.__image = mask_colorized((height, width))
+            img = Image.fromarray(self.__image)
 
-        self.__mask = mask_categorical((height, width))
-        self.__image = mask_colorized((height, width))
-        img = Image.fromarray(self.__image)
-        msk = Image.fromarray(self.__mask)
         for name in names:
-            self.mask_path = os.path.join(self.mask_dir, f'{name}.png')
-            self.image_path = os.path.join(self.image_dir, f'{name}.jpg')
-            img.save(self.image_path)
-            msk.save(self.mask_path)
+            if create_mask:
+                self.mask_path = os.path.join(self.mask_dir, f'{name}.png')
+                msk.save(self.mask_path)
+
+            if create_image:
+                self.image_path = os.path.join(self.image_dir, f'{name}.jpg')
+                img.save(self.image_path)
 
     def __enter__(self) -> tuple[str, str, str]:
 
diff --git a/tests/checkers_test.py b/tests/checkers_test.py
new file mode 100644
index 0000000..8e2913e
--- /dev/null
+++ b/tests/checkers_test.py
@@ -0,0 +1,40 @@
+from __future__ import annotations
+
+import os
+import tempfile
+
+from CCAgT_utils import checkers
+from testing import create
+
+
+def test_has_all_into_dir():
+    with tempfile.TemporaryDirectory() as tmpdir:
+        f = open(os.path.join(tmpdir, 'file.png'), 'w')
+        f.close()
+
+        f = open(os.path.join(tmpdir, 'file.jpg'), 'w')
+        f.close()
+
+        assert checkers.has_all_into_dir(tmpdir, ['file.png'], extension=('.png'))
+
+
+def test_single_core_mask_has(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['test'], create_image=False) as paths:
+        _, mask_dir, _ = paths
+        o = checkers.single_core_mask_has({os.path.join(mask_dir, 'test.png')}, {0})
+        assert len(o) == 0
+
+        o = checkers.single_core_mask_has({os.path.join(mask_dir, 'test.png')}, {0, 1})
+        assert len(o) == 1
+        assert o == {'test'}
+
+
+def test_masks_that_has(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['test'], create_image=False) as paths:
+        _, mask_dir, _ = paths
+        o = checkers.masks_that_has(mask_dir, {0})
+        assert len(o) == 0
+
+        o = checkers.masks_that_has(mask_dir, {0, 1})
+        assert len(o) == 1
+        assert o == {'test'}
diff --git a/tests/conftest.py b/tests/conftest.py
index 5cda500..7df73a0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,6 +8,7 @@ from shapely.geometry import Polygon
 
 from CCAgT_utils.converters import CCAgT
 from CCAgT_utils.converters.LabelBox import LabelBox
+from CCAgT_utils.types.annotation import Annotation
 from CCAgT_utils.types.mask import Mask
 from CCAgT_utils.visualization import colors
 from testing import create
@@ -112,6 +113,11 @@ def cluster_ex():
     return Polygon([(10, 10), (10, 15), (15, 15), (15, 10)])
 
 
+@pytest.fixture
+def annotations_ex(nucleus_ex, cluster_ex):
+    return [Annotation(nucleus_ex, 1), Annotation(cluster_ex, 2)]
+
+
 @pytest.fixture
 def cluster_mask_ex():
     out = np.zeros((20, 20), dtype=np.uint8)
@@ -211,6 +217,11 @@ def ccagt_df_multi(nucleus_ex, cluster_ex, satellite_ex):
     return pd.DataFrame(d)
 
 
+@pytest.fixture
+def ccagt_df_single_nucleus(nucleus_ex):
+    return pd.DataFrame([create.row_CCAgT(nucleus_ex, 1, 'C_xx1')])
+
+
 @pytest.fixture
 def ccagt_ann_single_nucleus(nucleus_ex):
     d = pd.DataFrame([create.row_CCAgT(nucleus_ex, 1, 'C_xx1')])
diff --git a/tests/main_test.py b/tests/main_test.py
new file mode 100644
index 0000000..e773f9f
--- /dev/null
+++ b/tests/main_test.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from CCAgT_utils import main
+from testing import create
+
+
+def test_create_subdataset(capsys, shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, _ = paths
+        out = main.create_subdataset('test', tmp_dir, tmp_dir, None, None, None, None, False, None, '0')
+        assert out == 0
+        _, _ = capsys.readouterr()
+
+        out = main.create_subdataset('test', tmp_dir, tmp_dir, None, None, None, None, False, None, '0')
+        _, err = capsys.readouterr()
+        assert out == 1
+        assert err[:33] == 'Already exist a dataset with name'
+
+        out = main.create_subdataset('test1', masks_dir, tmp_dir, None, None, None, None, False, None, '0')
+        _, err = capsys.readouterr()
+        assert out == 1
+        assert err[:36] == 'Do not found the original dataset at'
+
+
+def test_create_subdataset_with_slice(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, _ = paths
+        out = main.create_subdataset('test', tmp_dir, tmp_dir, (1, 2), None, {1}, {99}, True, None, '0')
+        assert out == 0
+
+        out = main.create_subdataset('test1', tmp_dir, tmp_dir, (1, 2), None, {1}, {1}, True, None, '0')
+        assert out == 0
+
+        out = main.create_subdataset('test2', tmp_dir, tmp_dir, (1, 2), None, {1}, {99}, False, None, '0')
+        assert out == 0
+
+
+def test_create_subdataset_with_extraction(capsys, shape, ccagt_ann_single_nucleus):
+    img_name = ccagt_ann_single_nucleus.df.iloc[0]['image_name']
+    with create.ImageMaskFiles(shape[0], shape[1], [img_name]) as paths:
+        tmp_dir, masks_dir, _ = paths
+        labels_path = os.path.join(tmp_dir, 'labels.parquet.gzip')
+        ccagt_ann_single_nucleus.to_parquet(labels_path)
+        out = main.create_subdataset('test', tmp_dir, tmp_dir, None, {1}, None, None, None, labels_path, '0')
+        assert out == 0
+        _, _ = capsys.readouterr()
+
+        out = main.create_subdataset('test1', tmp_dir, tmp_dir, None, {1}, None, None, None, None, '0')
+        assert out == 1
+        _, err = capsys.readouterr()
+        assert err[:80] == 'When using `--extract`, please specify the labels files with `--labels` argument'
+
+
+def test_not_implemented_command():
+    exit_code = main.main(['create-subdataset', '--name', '', '--original', '', '--output', ''])
+    assert exit_code == 1
+
+
+def test_main_help():
+    with pytest.raises(SystemExit):
+        main.main(['help'])
+
+    with pytest.raises(SystemExit):
+        main.main([])
+
+
+def test_main_help_other_command():
+    with pytest.raises(SystemExit):
+        main.main(['help', 'create-subdataset'])
+
+
+def test_main_create_subdataset(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, _ = paths
+        out = main.main(['create-subdataset', '--name', 'example', '--original', tmp_dir, '--output', tmp_dir])
+
+        assert out == 0
diff --git a/tests/prepare_test.py b/tests/prepare_test.py
new file mode 100644
index 0000000..1e3e44d
--- /dev/null
+++ b/tests/prepare_test.py
@@ -0,0 +1,70 @@
+from __future__ import annotations
+
+import os
+
+from CCAgT_utils import prepare
+from testing import create
+
+
+def test_clean_images_and_masks(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, images_dir = paths
+        prepare.clean_images_and_masks(images_dir, masks_dir, {0})
+
+        assert os.path.isfile(os.path.join(masks_dir, 'A', 'A_example_1.png')) is False
+        assert os.path.isfile(os.path.join(masks_dir, 'A', 'A_example_2.png')) is False
+        assert os.path.isfile(os.path.join(images_dir, 'A', 'A_example_1.jpg')) is False
+        assert os.path.isfile(os.path.join(images_dir, 'A', 'A_example_2.jpg')) is False
+
+
+def test_extract_category_from_image_file(shape, annotations_ex):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example'], create_image=False) as paths:
+        tmp_dir, masks_dir, _ = paths
+        msk_path = os.path.join(masks_dir, 'A_example.png')
+
+        extracted_quantity = prepare.extract_category_from_image_file(msk_path, tmp_dir, [annotations_ex[0]], 0)
+
+        assert extracted_quantity == 1
+        assert os.path.isfile(os.path.join(tmp_dir, 'A_example_1_1.png'))
+
+
+def test_single_core_extract_image_and_masks(shape, ccagt_df_single_nucleus):
+    img_name = ccagt_df_single_nucleus.iloc[0]['image_name']
+    slide = img_name.split('_')[0]
+    with create.ImageMaskFiles(shape[0], shape[1], [img_name]) as paths:
+        tmp_dir, masks_dir, images_dir = paths
+
+        imgs = {img_name: os.path.join(images_dir, f'{img_name}.jpg')}
+        msks = {img_name: os.path.join(masks_dir, f'{img_name}.png')}
+
+        masks_dir_out = os.path.join(masks_dir, f'{slide}')
+        os.makedirs(masks_dir_out)
+
+        images_dir_out = os.path.join(images_dir, f'{slide}')
+        os.makedirs(images_dir_out)
+
+        extracted_quantity = prepare.single_core_extract_image_and_masks(imgs, msks, ccagt_df_single_nucleus, tmp_dir, 0)
+
+        assert extracted_quantity == (1, 1)
+
+        extracted_quantity = prepare.single_core_extract_image_and_masks({}, {}, ccagt_df_single_nucleus, tmp_dir, 0)
+
+        assert extracted_quantity == (0, 0)
+
+
+def test_extract_image_and_mask_by_category(capsys, shape, ccagt_ann_single_nucleus):
+    img_name = ccagt_ann_single_nucleus.df.iloc[0]['image_name']
+    slide = img_name.split('_')[0]
+    with create.ImageMaskFiles(shape[0], shape[1], [img_name]) as paths:
+        tmp_dir, masks_dir, images_dir = paths
+        labels_path = os.path.join(tmp_dir, 'labels.parquet.gzip')
+        ccagt_ann_single_nucleus.to_parquet(labels_path)
+
+        prepare.extract_image_and_mask_by_category(images_dir, masks_dir, tmp_dir, {1}, labels_path, 0)
+
+        assert os.path.isfile(os.path.join(masks_dir, slide, f'{img_name}_1_1.png'))
+        _, _ = capsys.readouterr()
+
+        prepare.extract_image_and_mask_by_category(images_dir, masks_dir, tmp_dir, {99}, labels_path, 0)
+        out, _ = capsys.readouterr()
+        assert out[:34] == 'Nothing to process with categories'
diff --git a/tests/slice_test.py b/tests/slice_test.py
new file mode 100644
index 0000000..80450c9
--- /dev/null
+++ b/tests/slice_test.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+import os
+
+from CCAgT_utils import slice
+from testing import create
+
+
+def test_slice_image(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['example'], create_image=False) as paths:
+        tmp_dir, masks_dir, _ = paths
+        mask_path = os.path.join(masks_dir, 'example.png')
+        sliced_quantity = slice.image(mask_path, tmp_dir, 2, 1)
+
+        assert sliced_quantity == 2
+        assert os.path.isfile(os.path.join(tmp_dir, 'example_1.png'))
+        assert os.path.isfile(os.path.join(tmp_dir, 'example_2.png'))
+
+
+def test_single_core_image_and_masks(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, images_dir = paths
+
+        imgs = {'A_example': os.path.join(images_dir, 'A_example.jpg')}
+        msks = {'A_example': os.path.join(masks_dir, 'A_example.png')}
+
+        masks_dir_out = os.path.join(masks_dir, 'A')
+        os.makedirs(masks_dir_out)
+
+        images_dir_out = os.path.join(images_dir, 'A')
+        os.makedirs(images_dir_out)
+
+        sliced_quantity = slice.single_core_image_and_masks(imgs, msks, tmp_dir, 1, 2)
+
+        assert sliced_quantity == (2, 2)
+        assert os.path.isfile(os.path.join(masks_dir_out, 'A_example_1.png'))
+        assert os.path.isfile(os.path.join(masks_dir_out, 'A_example_2.png'))
+        assert os.path.isfile(os.path.join(images_dir_out, 'A_example_1.jpg'))
+        assert os.path.isfile(os.path.join(images_dir_out, 'A_example_2.jpg'))
+
+        msks = {}
+        sliced_quantity = slice.single_core_image_and_masks(imgs, msks, tmp_dir, 1, 2)
+        assert sliced_quantity == (2, 0)
+
+
+def test_slice_image_and_masks(shape):
+    with create.ImageMaskFiles(shape[0], shape[1], ['A_example']) as paths:
+        tmp_dir, masks_dir, images_dir = paths
+        slice.images_and_masks(images_dir, masks_dir, tmp_dir, 1, 2, extension=('.png', '.jpg'))
+
+        assert os.path.isfile(os.path.join(masks_dir, 'A', 'A_example_1.png'))
+        assert os.path.isfile(os.path.join(masks_dir, 'A', 'A_example_2.png'))
+        assert os.path.isfile(os.path.join(images_dir, 'A', 'A_example_1.jpg'))
+        assert os.path.isfile(os.path.join(images_dir, 'A', 'A_example_2.jpg'))
diff --git a/tests/types/annotation_test.py b/tests/types/annotation_test.py
index 8bea8a2..02e2ed7 100644
--- a/tests/types/annotation_test.py
+++ b/tests/types/annotation_test.py
@@ -101,6 +101,11 @@ def test_bounds_to_BBox(bbox_example, bbox_params):
     assert bbox_example == annotation.bounds_to_BBox(bounds, bbox_params['category_id'])
 
 
+def test_slices(bbox_example, bbox_params):
+    assert bbox_example.slice_y == slice(bbox_params['y_init'], bbox_params['y_end'])
+    assert bbox_example.slice_x == slice(bbox_params['x_init'], bbox_params['x_end'])
+
+
 def test_count_BBox_categories(bbox_example):
     cat_id_example = bbox_example.category_id
     bbox_example1 = copy.copy(bbox_example)
@@ -119,6 +124,62 @@ def test_count_BBox_categories(bbox_example):
     assert counter == {f'cat {cat_id_example}': 3, f'cat {cat_id_example1}': 2}
 
 
+def test_fit_inside(bbox_example, bbox_params):
+    x_init, y_init, x_end, y_end = (bbox_params['x_init'], bbox_params['y_init'],
+                                    int(bbox_params['x_end'] * 0.8), int(bbox_params['y_end'] * 0.8))
+
+    bbox_example.fit_inside((x_init, y_init, x_end, y_end))
+
+    assert bbox_example.x_init == x_init
+    assert bbox_example.y_init == y_init
+    assert bbox_example.x_end == x_end
+    assert bbox_example.y_end == y_end
+
+
+def test_add_padding(bbox_example):
+    bbox_example1 = copy.copy(bbox_example)
+    bbox_example1.add_padding()
+    assert bbox_example1.coords == bbox_example.coords
+
+    with pytest.raises(TypeError):
+        bbox_example.add_padding('1')
+
+
+@pytest.mark.parametrize('padding', [1, 5, 50, 5000])
+def test_add_padding_in_pixel(bbox_example, bbox_params, padding):
+    x_init_expected = bbox_params['x_init'] - padding
+    y_init_expected = bbox_params['y_init'] - padding
+    x_end_expected = bbox_params['x_end'] + padding
+    y_end_expected = bbox_params['y_end'] + padding
+    bbox_example.add_padding(padding, (x_init_expected - 100,
+                                       y_init_expected - 100,
+                                       x_end_expected + 100,
+                                       y_end_expected + 100))
+
+    assert bbox_example.x_init == x_init_expected
+    assert bbox_example.y_init == y_init_expected
+    assert bbox_example.x_end == x_end_expected
+    assert bbox_example.y_end == y_end_expected
+
+
+@pytest.mark.parametrize('padding', [.1, .5, 5.0, 50.00])
+def test_add_padding_in_percentage(bbox_example, bbox_params, padding):
+    x_init_expected = int(bbox_params['x_init'] - (bbox_params['width'] * padding))
+    y_init_expected = int(bbox_params['y_init'] - (bbox_params['height'] * padding))
+    x_end_expected = int(bbox_params['x_end'] + (bbox_params['width'] * padding))
+    y_end_expected = int(bbox_params['y_end'] + (bbox_params['height'] * padding))
+
+    bbox_example.add_padding(padding, (x_init_expected - 100,
+                                       y_init_expected - 100,
+                                       x_end_expected + 100,
+                                       y_end_expected + 100))
+
+    assert bbox_example.x_init == x_init_expected
+    assert bbox_example.y_init == y_init_expected
+    assert bbox_example.x_end == x_end_expected
+    assert bbox_example.y_end == y_end_expected
+
+
 def test_annotation_bbox(nucleus_ex):
     ann = annotation.Annotation(nucleus_ex, 1)
 
diff --git a/tests/utils_test.py b/tests/utils_test.py
index f062a71..88a8f9a 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -1,5 +1,8 @@
 from __future__ import annotations
 
+import os
+import tempfile
+
 import pytest
 
 from CCAgT_utils import utils
@@ -36,3 +39,34 @@ def test_slide_from_filename():
     slide_id = utils.slide_from_filename(filename)
 
     assert slide_id == 'G'
+
+
+def test_find_files():
+
+    with tempfile.TemporaryDirectory() as tmpdir:
+        subdir = os.path.join(tmpdir, 'test')
+        os.makedirs(subdir)
+        filename_txt = os.path.join(tmpdir, 'file.txt')
+        f = open(filename_txt, 'w')
+        f.close()
+
+        filename_bin = os.path.join(subdir, 'file.bin')
+        f = open(filename_bin, 'w')
+        f.close()
+
+        files = utils.find_files(tmpdir, ('.txt', '.bin'), False)
+        expected = {'file.txt': filename_txt}
+        assert files == expected
+
+        files = utils.find_files(tmpdir, ('.txt', '.bin'), True)
+
+        expected['file.bin'] = filename_bin
+        assert files == expected
+
+
+def test_create_structure():
+    with tempfile.TemporaryDirectory() as tmpdir:
+
+        utils.create_structure(tmpdir, {'A'})
+        assert os.path.isdir(os.path.join(tmpdir, 'images', 'A'))
+        assert os.path.isdir(os.path.join(tmpdir, 'masks', 'A'))
diff --git a/tests/visualization/_show_test.py b/tests/visualization/_show_test.py
index 388edbc..714fb03 100644
--- a/tests/visualization/_show_test.py
+++ b/tests/visualization/_show_test.py
@@ -56,7 +56,7 @@ def test_image_and_mask_not_found(capsys, ccagt_ann_multi, categories_aux_data,
 # @pytest.mark.slow
 def test_image_with_boxes(ccagt_ann_multi, categories_aux_data, shape, remove_plt_show):
     names = ccagt_ann_multi.df['image_name'].unique()
-    with create.ImageMaskFiles(shape[0], shape[1], names) as paths:
+    with create.ImageMaskFiles(shape[0], shape[1], names, create_mask=False) as paths:
         _, _, image_dir = paths
         _params = {'CCAgT_ann': ccagt_ann_multi,
                    'CCAgT_helper': Helper(categories_aux_data),
@@ -76,7 +76,7 @@ def test_image_with_boxes(ccagt_ann_multi, categories_aux_data, shape, remove_pl
 # @pytest.mark.slow
 def test_image_with_boxes_not_found(capsys, ccagt_ann_multi, categories_aux_data, shape, remove_plt_show):
     names = ccagt_ann_multi.df['image_name'].unique()
-    with create.ImageMaskFiles(shape[0], shape[1], names) as paths:
+    with create.ImageMaskFiles(shape[0], shape[1], names, create_mask=False) as paths:
         _, _, image_dir = paths
         _params = {'CCAgT_ann': ccagt_ann_multi,
                    'CCAgT_helper': Helper(categories_aux_data),