diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b9e0bfdb54d389f70521c44da970bd48f1552279..94dffe1a3e0f0de5dec1ce01e6962d660612c462 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,6 +11,7 @@ image: python:latest
 stages:
   - linting
   - testing
+  - docs
 
 # Change pip's cache directory to be inside the project directory since we can
 # only cache local items.
@@ -27,28 +28,27 @@ cache:
 #    - .cache/pip  # 
 #    - venv/   #
 
-
-
   
 before_script:
   - python --version  # For debugging
   - pip install -r requirements.txt  # install dependencies from file
-  - pip install flake8
-  - pip install pylint
 #  - pip install virtualenv
 #  - virtualenv venv
 #  - source venv/bin/activate
 
 PEP8:
   stage: linting
-  # allow_failure: true
-  script: flake8 --count . # PEP8 linting
+  script: 
+    - pip install flake8
+    - flake8 --count . # PEP8 linting
 
 
 Pylint:
   stage: linting
   # allow_failure: true
-  script: find . -type f -name '*.py' | xargs pylint -rn --rcfile='plotid/.pylintrc' # Find all python files and check the code with pylint.
+  script: 
+    - pip install pylint
+    - find . -type f -name '*.py' | xargs pylint -rn --rcfile='plotid/.pylintrc' # Find all python files and check the code with pylint.
 
 test:
   stage: testing
@@ -61,6 +61,19 @@ test:
 #    - pip install tox flake8  # you can also use tox
 #    - tox -e py36,flake8
 
+pages:
+  stage: docs
+  when: manual
+  script:
+  - pip install -U sphinx sphinx-autoapi sphinx_rtd_theme # sphinx_panels
+  - cd docs
+  - make html
+  - mv build/html/ ../public
+  artifacts:
+    paths:
+    - public
+
+
 # Commenting out all other stages and jobs
 #run:
 #  script:
@@ -72,14 +85,3 @@ test:
 #    paths:
 #      - dist/*.whl
 
-#pages:
-#  script:
-#    - pip install sphinx sphinx-rtd-theme
-#    - cd doc
-#    - make html
-#    - mv build/html/ ../public/
-#  artifacts:
-#    paths:
-#      - public
-#  rules:
-#    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
diff --git a/Contribute.md b/Contribute.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d409fba9fa924986df452f0e5f685e65c31c220
--- /dev/null
+++ b/Contribute.md
@@ -0,0 +1,79 @@
+## Implement a new plot engine
+If you want to add another plot engine "$engine" to plotID, this document helps you to do it. 
+
+### tagplot
+Create a new module named "tagplot_$engine.py". Paste the following code and replace every "$engine" with the name of your engine:  
+```python
+"""
+Tag your picture with an ID.
+
+Functions:
+    tagplot_$engine(PlotOptions instance) -> list
+"""
+import $engine
+from plotid.create_id import create_id
+from plotid.plotoptions import PlotOptions
+
+
+def tagplot_$engine(plotid_object):
+    """
+    Add IDs to plots with $engine.
+
+    The ID is placed visual on the figure window and returned as string in a
+    list together with the figures.
+    TagPlot can tag multiple figures at once.
+    """
+    # Check if plotid_object is a valid instance of PlotOptions
+    if not isinstance(plotid_object, PlotOptions):
+        raise TypeError('The given options container is not an instance'
+                        'of PlotOptions.')
+
+    ids_as_list = []
+
+    for fig in plotid_object.figs:
+        figure_id = create_id(plotid_object.id_method)
+        ids_as_list.append(figure_id)
+
+        """
+        Insert here the tagging with $engine:
+        Open the figure fig.
+        Place the string figure_id on it.
+        Use plotid_object.position and plotid_object.rotation for position and rotation.
+        Save the tagged figure to plotid_object.figs.
+        """
+
+    return [plotid_object.figs, ids_as_list]
+
+
+Last step:
+Add the following code in tagplot.py:
+match engine:
+    [...]
+    case '$engine':
+        return tagplot_$engine(option_container)
+    case _:
+        [...]
+```
+
+### publish
+To include a new plot engine in the publish function only save_plot.py has to be touched.  
+Import the plot engine at the top of the file.  
+In the beginning of the function save_plot() create the following line:
+```python
+if isinstance(figures, $type_of_figure):
+    figures = [figures]
+[...]
+if not isinstance(figures, list):
+    raise TypeError('Figures are not given as list.')
+```
+This allows to iterate through all figures, even if it is only one. It must be placed before the line that checks if figures is a list.  
+
+Create a new elif condition in the for loop:
+```python
+for i, fig in enumerate(figures):
+    [...]
+    elif isinstance(fig, $type_of_figure):
+            # Read/Open the figure fig if necessary
+            plot_path.append(plot_names[i] + '.' + extension)
+            # Save the figure fig to plot_path[i]
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index ae2a53cd332f27bb0bf00295a835515f94663bbb..adda52cc748c5ade9fbc2f7a136f462f67be8f3a 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,8 @@ Tag your figure/plot with an ID. It is possible to tag multiple figures at once.
 `tagplot(figures, plot_engine)`  
 The variable "figures" can be a single figure or a list of multiple figures.  
 The argument "plot_engine" defines which plot engine was used to create the figures. It also determines which plot engine plotID uses to place the ID on the plot. Currently supported plot engines are:
-- 'matplotlib'
+- 'matplotlib', which processes figures created by matplotlib.
+- 'image', which processes pictures with common extensions (jpg, png, etc.)
 
 tagplot returns a list that contains two lists each with as many entries as figures were given. The first list contains the tagged figures. The second list contains the corresponding IDs as strings
 
@@ -74,7 +75,7 @@ Save plot, data and measuring script. It is possible to export multiple figures
 
 - "src_datapath" specifies the path to (raw) data that should be published. It can be a string or a list of strings that specifies all files and directories which will be published.  
 - "dst_path" is the path to the destination directory, where all the data should be copied/exported to.  
-- "figure" expects the figure or a list of figures that were tagged and now should be saved as pictures.  
+- "figure" expects the figure or a list of figures that were tagged and now should be saved as pictures. If image files were tagged, all of them need to have the same file extension.  
 - "plot_names" will be the file names for the exported plots. If you give only one plot name but several figures, plotID will name the exported plots with an appended number, e.g. example_fig1.png, example_fig2.png, ...  
 
 Optional parameters can be used to customize the publish process.
@@ -98,5 +99,5 @@ If you want to build plotID yourself, follow these steps:
 
 
 ## Documentation
-If you have more questions about plotID, please have a look at the [documentation](link-to-docs).  
+If you have more questions about plotID, please have a look at the [documentation](https://plotid.pages.rwth-aachen.de/plotid_python).  
 Also have a look at the example.py that is shipped with plotID.
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 7ca2459f4a60345e7602be2281fb6ed590a463be..fe798ee200872ed2cb3cc542b544190727241b97 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,28 +1,32 @@
+"""
 # Configuration file for the Sphinx documentation builder.
-#
+
 # This file only contains a selection of the most common options. For a full
 # list see the documentation:
 # https://www.sphinx-doc.org/en/master/usage/configuration.html
+"""
 
 # -- Path setup --------------------------------------------------------------
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('../../plotid'))
+
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../..'))
+
+from plotid import __version__, __author__  # noqa: E402
 
 
 # -- Project information -----------------------------------------------------
 
-project = 'PlotID'
-copyright = '2022, Example Author'
-author = 'Example Author'
+project = 'plotID'
+copyright = '2022, ' + __author__
+author = __author__
 
 # The full version, including alpha/beta/rc tags
-release = '0.0.6'
+release = __version__
 
 
 # -- General configuration ---------------------------------------------------
@@ -31,6 +35,7 @@ release = '0.0.6'
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
+    'sphinx.ext.napoleon',
     'autoapi.extension'
 ]
 autoapi_type = 'python'
@@ -50,7 +55,7 @@ exclude_patterns = []
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'alabaster'
+html_theme = 'sphinx_rtd_theme'
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
diff --git a/plotid/__init__.py b/plotid/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b9b886cfe7247b5ffb940f20a022b85dff449a5f 100644
--- a/plotid/__init__.py
+++ b/plotid/__init__.py
@@ -0,0 +1,14 @@
+"""
+plotID for Python.
+
+This is the python plotID project.
+plotID is a program connected to Research Data Management (RDM).
+It has two main functionalities:
+1. Tag your plot with an identifier.
+2. Export the resulting file to a specified directory along the corresponding
+research data, the plot is based on. Additionally, the script that created the
+plot will also be copied to the directory.
+"""
+
+__version__ = '0.1.2'
+__author__ = 'Institut Fluidsystemtechnik within nfdi4ing at TU Darmstadt'
diff --git a/plotid/example.py b/plotid/example.py
index e7fdce69b3195b1b77904cfac7f5c8699e50f56b..7e41ff8141faee4152dfb48dd15f945518da4fa6 100644
--- a/plotid/example.py
+++ b/plotid/example.py
@@ -2,14 +2,13 @@
 """
 Example workflow for integrating plotID.
 
-With tagplot an ID can be generated an printed on the plot. To export the plot
-along with the corresponding research data and the plot generating script use
-the function publish.
+With tagplot() an ID can be generated and printed on the plot. To export the
+plot along with the corresponding research data and the plot generating
+script use the function publish().
 """
 
 # %% Import modules
 import numpy as np
-# import h5py as h5
 import matplotlib.pyplot as plt
 from plotid.tagplot import tagplot
 from plotid.publish import publish
@@ -17,44 +16,45 @@ from plotid.publish import publish
 # %% Set Project ID
 PROJECT_ID = "MR04_"
 
-# %% Choose Plot engine
-PLOT_ENGINE = "matplotlib"
-
 # %% Create sample data
 x = np.linspace(0, 10, 100)
 y = np.random.rand(100) + 2
 y_2 = np.sin(x) + 2
 
-# %% Create figures
+# %% Create sample figures
 
 # 1. figure
+IMG1 = 'image1.png'
 FIG1 = plt.figure()
 plt.plot(x, y, color='black')
 plt.plot(x, y_2, color='yellow')
+plt.savefig(IMG1)
 
 # 2. figure
+IMG2 = 'image2.png'
 FIG2 = plt.figure()
 plt.plot(x, y, color='blue')
 plt.plot(x, y_2, color='red')
+plt.savefig(IMG2)
 
 # %% TagPlot
 
 # If multiple figures should be tagged, figures must be provided as list.
 FIGS_AS_LIST = [FIG1, FIG2]
+IMGS_AS_LIST = [IMG1, IMG2]
 
-[TAGGED_FIGS, ID] = tagplot(FIGS_AS_LIST, PLOT_ENGINE, prefix=PROJECT_ID,
-                            id_method='random', location='west')
+# Example for how to use tagplot with matplotlib figures
+[TAGGED_FIGS, ID] = tagplot(FIGS_AS_LIST, 'matplotlib', prefix=PROJECT_ID,
+                            id_method='time', location='west')
 
-# %% Save figure as tiff-file, but publish also exports the plot to a picture
-# file in the destination folder.
-# for i, figure in enumerate(TAGGED_FIGS):
-#     NAME = "Test"+str(i)+".tiff"
-#     figure.savefig(NAME)
+# Example for how to use tagplot with image files
+# [TAGGED_FIGS, ID] = tagplot(IMGS_AS_LIST, 'image', prefix=PROJECT_ID,
+#                            id_method='random', location='west')
 
 # %% Publish
-# Arguments: Source directory, destination directory, figure, plot name,
-# publish-mode).
+# Arguments: Source directory or files as list, destination directory, figures,
+# plots or images.
 
-publish(['../README.md', '../tests', '../LICENSE'],
+publish(['../README.md', '../docs', '../LICENSE'],
         '/home/chief/Dokumente/fst/plotid_python/data',
-        FIGS_AS_LIST, 'Bild', 'individual')
+        TAGGED_FIGS, 'Bild')
diff --git a/plotid/filecompare.py b/plotid/filecompare.py
deleted file mode 100644
index 2ccc81a36933b07cbcf6897a8112843e7f7c02a2..0000000000000000000000000000000000000000
--- a/plotid/filecompare.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Compare if data already exists in published folder to avoid redundancy."""
-
-# Test des Moduls filecmp — File and Directory Comparisons
-
-import filecmp
-
-a = filecmp.cmp("2.py", "test_git.py")
-print(a)
diff --git a/plotid/hdf5_external_link.py b/plotid/hdf5_external_link.py
deleted file mode 100644
index 09b96fa9ccd5b9fa84faa19826410a29ac413701..0000000000000000000000000000000000000000
--- a/plotid/hdf5_external_link.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Handling of hdf5 data files."""
-
-import h5py
-
-# Shows how to create an externalLink (symbolic) to a hdf5 File
-# Documentation available at:
-#    https://docs.h5py.org/en/stable/high/group.html#external-links
-myfile = h5py.File('./example.h5', 'w')
-myfile['ext link'] = h5py.ExternalLink("testdata_2.h5", "/")
-
-myfile.close()
diff --git a/plotid/plotoptions.py b/plotid/plotoptions.py
index cab0b8be8a588da25990ba04840488086e619c73..163a1c1bfc622fc48fc18011e1a7e1b8e4062b9a 100644
--- a/plotid/plotoptions.py
+++ b/plotid/plotoptions.py
@@ -41,7 +41,7 @@ class PlotOptions:
         Raises
         ------
         TypeError
-            TypeError is thrown if one of the attributes is not the correct
+            TypeError is thrown if one of the attributes is not of correct
             type.
 
         Returns
@@ -50,14 +50,14 @@ class PlotOptions:
 
         """
         # Input validation for figs is done in submodules tagplot_$engine.py
-        if isinstance(self.prefix, str):
-            pass
-        else:
+        if not isinstance(self.prefix, str):
             raise TypeError("Prefix is not a string.")
 
-        if isinstance(self.id_method, str):
-            pass
-        else:
+        if not isinstance(self.id_method, str):
             raise TypeError('The chosen id_method is not a string.')
 
+        # Store figs in a list, even if it is only one.
+        if not isinstance(self.figs, list):
+            self.figs = [self.figs]
+
         return 0
diff --git a/plotid/publish.py b/plotid/publish.py
index ce479a3bb22952ed80d09b480e791e4ecad1f4cd..79a926bd592b2061d913c2f686d76379a1191db7 100644
--- a/plotid/publish.py
+++ b/plotid/publish.py
@@ -46,7 +46,7 @@ class PublishOptions:
     """
 
     def __init__(self, src_datapaths, dst_path, figure, plot_names,
-                 data_storage):
+                 data_storage='individual'):
 
         self.src_datapaths = src_datapaths
         self.dst_path = dst_path
@@ -72,6 +72,11 @@ class PublishOptions:
         None.
 
         """
+        if not os.path.isfile(sys.argv[0]):
+            raise FileNotFoundError('Cannot copy original python script. '
+                                    'Running publish from a shell is not '
+                                    'possible.')
+
         if isinstance(self.src_datapaths, str):
             self.src_datapaths = [self.src_datapaths]
         if isinstance(self.src_datapaths, list):
@@ -137,7 +142,7 @@ class PublishOptions:
             if overwrite_dir in ('yes', 'y'):
                 shutil.rmtree(self.dst_path)
             else:
-                raise RuntimeError('PlotID has finished without an export.\n'
+                raise RuntimeError('publish has finished without an export.\n'
                                    'Rerun TagPlot if you need a new ID or '
                                    'consider overwriting.')
 
@@ -145,19 +150,25 @@ class PublishOptions:
         dst_path_invisible = os.path.join(self.dst_path_head,
                                           '.' + self.dst_dirname)
 
-        # If invisible Folder exists, delete it
-        # (publish was not successful before)
-        if os.path.isdir(dst_path_invisible):
-            shutil.rmtree(dst_path_invisible)
-
         match self.data_storage:
             case 'centralized':
                 self.centralized_data_storage()
             case 'individual':
-                self.individual_data_storage(dst_path_invisible, plot_paths)
+                try:
+                    self.individual_data_storage(dst_path_invisible,
+                                                 plot_paths)
+                except Exception as exc:
+                    delete_dir = input('There was an error while publishing'
+                                       ' the data. Should the partially copied'
+                                       f' data at {dst_path_invisible} be'
+                                       ' removed? (yes/no[default])\n')
+                    if delete_dir in ('yes', 'y'):
+                        shutil.rmtree(dst_path_invisible)
+                    raise RuntimeError('Publishing was unsuccessful. '
+                                       'Try re-running publish.') from exc
             case _:
-                raise ValueError('The data storage method {data_storage} '
-                                 'is not available.')
+                raise ValueError(f'The data storage method {self.data_storage}'
+                                 ' is not available.')
 
         # If export was successful, make the directory visible
         os.rename(dst_path_invisible, self.dst_path)
@@ -167,6 +178,8 @@ class PublishOptions:
 
     def centralized_data_storage(self):
         """
+        [not implemented yet].
+
         Store the data only in one directory and link all others to it.
 
         Returns
@@ -175,7 +188,6 @@ class PublishOptions:
 
         """
         # Does nothing, not implemented yet
-        pass
 
     def individual_data_storage(self, destination, pic_paths):
         """
@@ -183,10 +195,10 @@ class PublishOptions:
 
         Parameters
         ----------
-        destination : path-like
+        destination : string
             Directory where the data should be stored.
-        pic_path : path-like
-            Path to the picture file that will be stored in destination.
+        pic_paths : list
+            Paths to the picture file that will be stored in destination.
 
         Returns
         -------
diff --git a/plotid/save_plot.py b/plotid/save_plot.py
index 5522fce64e04043f49708be7184d6a2ef1d5bb64..8642c4aa2470599b8207ea11b5c659463638609c 100644
--- a/plotid/save_plot.py
+++ b/plotid/save_plot.py
@@ -34,13 +34,14 @@ def save_plot(figures, plot_names, extension='png'):
     # Check if figs is a valid figure or a list of valid figures
     if isinstance(figures, matplotlib.figure.Figure):
         figures = [figures]
-    if isinstance(figures, list):
-        for fig in figures:
-            if isinstance(fig, matplotlib.figure.Figure):
-                pass
-    else:
-        raise TypeError('Figures is neither a valid matplotlib-figure nor'
-                        ' a list of matplotlib-figures.')
+    # PIL has different classes for different file formats. Therefore, the
+    # type is checked to contain 'PIL' and 'ImageFile'.
+    if all(x in str(type(figures)) for x in ['PIL', 'ImageFile']):
+        figures = [figures]
+    if not isinstance(figures, list):
+        raise TypeError('Figures are not given as list.')
+    if isinstance(plot_names, str):
+        plot_names = [plot_names]
 
     if len(plot_names) < len(figures):
         warnings.warn('There are more figures than plot names. The first name'
@@ -53,13 +54,16 @@ def save_plot(figures, plot_names, extension='png'):
         raise IndexError('There are more plot names than figures.')
 
     plot_path = []
-    # match type(figures):
-    #     case matplotlib.figure.Figure:
+
     for i, fig in enumerate(figures):
-        plt.figure(fig)
-        plot_path.append(plot_names[i] + '.' + extension)
-        plt.savefig(plot_path[i])
-        # case _:
-        #     raise TypeError('The given figure is not a valid figure object.')
+        if isinstance(fig, matplotlib.figure.Figure):
+            plt.figure(fig)
+            plot_path.append(plot_names[i] + '.' + extension)
+            plt.savefig(plot_path[i])
+        elif all(x in str(type(fig)) for x in ['PIL', 'ImageFile']):
+            plot_path.append(plot_names[i] + '.' + extension)
+            fig.save(plot_path[i])
+        else:
+            raise TypeError(f'Figure number {i} is not a valid figure object.')
 
     return plot_path
diff --git a/plotid/tagplot.py b/plotid/tagplot.py
index ba660b153fdfbd6fc6b17d02d9a5d156710a67bf..596679a5c335558641b63ac2aa0be4156d351a48 100644
--- a/plotid/tagplot.py
+++ b/plotid/tagplot.py
@@ -7,12 +7,13 @@ For publishing the tagged plot along your research data have a look at the
 module publish.
 
 Functions:
-    TagPlot(figure object, string) -> list
+    tagplot(figure object, string) -> list
 """
 
 import warnings
 from plotid.plotoptions import PlotOptions
 from plotid.tagplot_matplotlib import tagplot_matplotlib
+from plotid.tagplot_image import tagplot_image
 
 
 def tagplot(figs, engine, prefix='', id_method='time', location='east'):
@@ -39,8 +40,10 @@ def tagplot(figs, engine, prefix='', id_method='time', location='east'):
 
     Raises
     ------
-    RuntimeWarning
-        DESCRIPTION.
+    TypeError
+        If specified location is not given as string.
+    ValueError
+        If an unsupported plot engine is given.
 
     Returns
     -------
@@ -88,6 +91,8 @@ def tagplot(figs, engine, prefix='', id_method='time', location='east'):
     match engine:
         case 'matplotlib' | 'pyplot':
             return tagplot_matplotlib(option_container)
+        case 'image' | 'fallback':
+            return tagplot_image(option_container)
         case _:
             raise ValueError(
                 f'The plot engine "{engine}" is not supported.')
diff --git a/plotid/tagplot_image.py b/plotid/tagplot_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..104352c9066ccd4631c7e8117926a4863c1a8ce5
--- /dev/null
+++ b/plotid/tagplot_image.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+Tag your picture with an ID.
+
+Functions:
+    tagplot_image(PlotOptions instance) -> list
+"""
+import os
+from PIL import Image, ImageDraw, ImageFont, ImageOps
+from plotid.create_id import create_id
+from plotid.plotoptions import PlotOptions
+
+
+def tagplot_image(plotid_object):
+    """
+    Add IDs to images/pictures with pillow.
+
+    The ID is placed visual on the figure window and returned as string in a
+    list together with the figures.
+    TagPlot can tag multiple figures at once.
+    """
+    # Check if plotid_object is a valid instance of PlotOptions
+    if not isinstance(plotid_object, PlotOptions):
+        raise TypeError('The given options container is not an instance'
+                        'of PlotOptions.')
+
+    # Check if figs is a list of files
+    for img in plotid_object.figs:
+        if not isinstance(img, str):
+            raise TypeError('Name of the image is not a string.')
+        if not os.path.isfile(img):
+            raise TypeError('File does not exist.')
+            # Check if figs is a valid file is done by pillow internally
+
+    ids_as_list = []
+    color = (128, 128, 128)  # grey
+    font = ImageFont.load_default()
+
+    for i, img in enumerate(plotid_object.figs):
+        img_id = plotid_object.prefix + create_id(plotid_object.id_method)
+        ids_as_list.append(img_id)
+        img = Image.open(img)
+
+        img_txt = Image.new('L', font.getsize(img_id))
+        draw_txt = ImageDraw.Draw(img_txt)
+        draw_txt.text((0, 0), img_id, font=font, fill=255)
+        txt = img_txt.rotate(plotid_object.rotation, expand=1)
+        img.paste(ImageOps.colorize(txt, (0, 0, 0), color),
+                  (int(img.width*plotid_object.position[0]),
+                   int(img.height*(1-plotid_object.position[1]))), txt)
+
+        plotid_object.figs[i] = img
+    return [plotid_object.figs, ids_as_list]
diff --git a/plotid/tagplot_matplotlib.py b/plotid/tagplot_matplotlib.py
index 916ee5b0e306f4fabee9ade93a5a05aa13cc6429..34f06cfe8629238a893ea64b4d7a55ecf8da6c21 100644
--- a/plotid/tagplot_matplotlib.py
+++ b/plotid/tagplot_matplotlib.py
@@ -3,7 +3,7 @@
 Tag your matplotlib plot with an ID.
 
 Functions:
-    tagplot_matplotlib(figure object, string) -> list
+    tagplot_matplotlib(PlotOptions instance) -> list
 """
 
 import matplotlib
@@ -24,16 +24,11 @@ def tagplot_matplotlib(plotid_object):
     if not isinstance(plotid_object, PlotOptions):
         raise TypeError('The given options container is not an instance'
                         'of PlotOptions.')
-    # Check if figs is a valid figure or a list of valid figures
-    if isinstance(plotid_object.figs, matplotlib.figure.Figure):
-        plotid_object.figs = [plotid_object.figs]
-    if isinstance(plotid_object.figs, list):
-        for figure in plotid_object.figs:
-            if isinstance(figure, matplotlib.figure.Figure):
-                pass
-    else:
-        raise TypeError('Figures is neither a valid matplotlib-figure nor'
-                        ' a list of matplotlib-figures.')
+
+    # Check if figs is a list of valid figures
+    for figure in plotid_object.figs:
+        if not isinstance(figure, matplotlib.figure.Figure):
+            raise TypeError('Figure is not a valid matplotlib-figure.')
 
     fontsize = 'small'
     color = 'grey'
@@ -45,7 +40,7 @@ def tagplot_matplotlib(plotid_object):
         figure_id = plotid_object.prefix + str(figure_id)
         ids_as_list.append(figure_id)
 
-        plt.figure(fig.number)
+        plt.figure(fig)
         plt.figtext(x=plotid_object.position[0], y=plotid_object.position[1],
                     s=figure_id, ha='left', wrap=True,
                     rotation=plotid_object.rotation,
diff --git a/requirements.txt b/requirements.txt
index 6891c1fbed005067ba464b8b0882c55b2a87425f..a70ca0a9a91df50dc4885d797a31f7c128ce6f35 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@ coverage==6.3.2
 cycler==0.11.0
 fonttools==4.32.0
 kiwisolver==1.4.2
-matplotlib==3.5.1
+matplotlib==3.5.2
 numpy==1.22.3
 packaging==21.3
 Pillow==9.1.0
diff --git a/setup.cfg b/setup.cfg
index 784c27c13aaaa300b6cf43f549c6153aed579fe4..5abba63e6c6766a3357e166a00c8a76d91a5ee3e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
 [metadata]
 name = plotID
-version = 0.1.0
-author = Institut Fluidsystemtechnik within nfdi4ing at TU Darmstadt
+version = attr: plotid.__version__
+author = attr: plotid.__author__
 author_email = nfdi4ing@fst.tu-darmstadt.de
 description = The plotID toolkit supports researchers in tracking and storing relevant data in plots. Plots are labelled with an ID and the corresponding data is stored.
 license = Apache License, Version 2.0
@@ -12,6 +12,8 @@ url = https://git.rwth-aachen.de/plotid/plotid_python
 project_urls =
     Bug Tracker = https://git.rwth-aachen.de/plotid/plotid_python/-/issues
 classifiers =
+    Programming Language :: Python
+    Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: 3.10
     License :: OSI Approved :: Apache Software License
     Operating System :: OS Independent
@@ -23,6 +25,7 @@ classifiers =
 packages = plotid
 python_requires = >=3.10
 install_requires =
+    Pillow
     matplotlib
     numpy
    
diff --git a/tests/test_create_id.py b/tests/test_create_id.py
index 589405785640b5cd8ba74f69b9c8245ddfb92190..b47ae91a384be0845f55c758648d8f5e4feaee5e 100644
--- a/tests/test_create_id.py
+++ b/tests/test_create_id.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
-Unittests for CreateID
+Unittests for create_id
 """
 
 import unittest
diff --git a/tests/test_publish.py b/tests/test_publish.py
index a797b0114eb84f6448156ad2c8061785ef21fbb5..159f2be0c0ab8f57d7e1055d24035f3c663d1a6c 100644
--- a/tests/test_publish.py
+++ b/tests/test_publish.py
@@ -7,7 +7,9 @@ Unittests for publish
 import unittest
 import os
 import sys
+import platform
 import shutil
+from subprocess import run, CalledProcessError
 from unittest.mock import patch
 import matplotlib.pyplot as plt
 from plotid.publish import publish
@@ -92,6 +94,39 @@ class TestPublish(unittest.TestCase):
             publish(SRC_DIR, 'not_existing_folder',
                     FIG, PIC_NAME, 'individual')
 
+    def test_script_exists(self):
+        """
+        Test if Error is raised when publish is called from a shell.
+
+        First publish is called from a python shell with correct parameters.
+        It is checked if the subprocess returns an Error. Since the type of
+        exception is not passed but a CalledProcessError is raised, publish
+        is called again and it is checked if the output contains the correct
+        exception type.
+        """
+        if platform.system() == "Linux":
+            python = "python3"
+        else:
+            python = "python"
+
+        with self.assertRaises(CalledProcessError):
+            run([python, "-c",
+                 "import matplotlib.pyplot as plt\n"
+                 "from plotid.publish import publish\n"
+                 "publish('test_src_folder', 'test_parent/test_dst_folder',"
+                 " plt.figure(), 'test_picture')"],
+                capture_output=True, check=True)
+        process = run([python, "-c",
+                       "import matplotlib.pyplot as plt\n"
+                       "from plotid.publish import publish\n"
+                       "publish('test_src_folder', "
+                       "'test_parent/test_dst_folder', plt.figure(), "
+                       "'test_picture')"],
+                      capture_output=True)
+        assert ("FileNotFoundError: Cannot copy original python script. "
+                "Running publish from a shell"
+                " is not possible.") in process.stderr.decode()
+
     # Skip test if tests are run from command line.
     @unittest.skipIf(not os.path.isfile(sys.argv[0]), 'Publish is not called '
                      'from a Python script. Therefore, the script cannot be '
@@ -140,13 +175,18 @@ class TestPublish(unittest.TestCase):
     @unittest.skipIf(not os.path.isfile(sys.argv[0]), 'Publish is not called '
                      'from a Python script. Therefore, the script cannot be '
                      'copied.')
-    def test_dst__invisible_already_exists(self):
+    def test_dst_invisible_already_exists(self):
         """
-        Test if publish succeeds when there is already an invisible
-        directory from a previous run (delete the folder and proceed).
+        Test if after a crash of publish the partially copied data is
+        removed.
+        To mock this, the invisible directory already exists.
         """
         os.mkdir(INVISIBLE_PATH)
-        publish(SRC_DIR, DST_PATH, FIG, PIC_NAME, 'individual')
+        # Mock user input as 'yes'
+        with patch('builtins.input', return_value='yes'):
+            with self.assertRaises(RuntimeError):
+                publish(SRC_DIR, DST_PATH, FIG, PIC_NAME, 'individual')
+        assert not os.path.isdir(INVISIBLE_PATH)
 
     def test_plot_names(self):
         """ Test if Error is raised if plot_name is not a string. """
diff --git a/tests/test_save_plot.py b/tests/test_save_plot.py
index dcf9e39f7354235479e4c2edd81e1256abda8c03..0541724117cc28f0483a827fbc1e1fd5e5333cd5 100644
--- a/tests/test_save_plot.py
+++ b/tests/test_save_plot.py
@@ -7,10 +7,13 @@ Unittests for save_plot
 import os
 import unittest
 import matplotlib.pyplot as plt
+from PIL import Image
 from plotid.save_plot import save_plot
 
 FIGURE = plt.figure()
 PLOT_NAME = 'PLOT_NAME'
+IMG1 = 'image1.png'
+IMG2 = 'image2.jpg'
 
 
 class TestSavePlot(unittest.TestCase):
@@ -18,12 +21,41 @@ class TestSavePlot(unittest.TestCase):
     Class for all unittests of the save_plot module.
     """
 
-    def test_save_plot(self):
-        """  Test if save_plot succeeds with valid arguments. """
+    def setUp(self):
+        plt.savefig(IMG1)
+        plt.savefig(IMG2)
+
+    def test_save_plot_matplotlib(self):
+        """
+        Test if save_plot succeeds with valid arguments
+        using the matplotlib engine.
+        """
         plot_paths = save_plot(FIGURE, [PLOT_NAME], extension='jpg')
         self.assertIsInstance(plot_paths, list)
         os.remove(PLOT_NAME + '.jpg')
 
+    def test_save_plot_image_png(self):
+        """
+        Test if save_plot succeeds with valid arguments using the image engine
+        and a png file.
+        """
+        img1 = Image.open(IMG1)
+        plot_paths = save_plot(img1, [PLOT_NAME])
+        self.assertIsInstance(plot_paths, list)
+        os.remove(PLOT_NAME + '.png')
+
+    def test_save_plot_image_jpg(self):
+        """
+        Test if save_plot succeeds with valid arguments using the image engine
+        and a list of jpg files.
+        """
+        img2 = Image.open(IMG2)
+        imgs_as_list = [img2, img2]
+        plot_paths = save_plot(imgs_as_list, [PLOT_NAME], extension='jpg')
+        self.assertIsInstance(plot_paths, list)
+        os.remove(PLOT_NAME + '1.jpg')
+        os.remove(PLOT_NAME + '2.jpg')
+
     def test_more_figs_than_names(self):
         """
         Test if a warning is raised if more figures than plot names are given.
@@ -45,6 +77,18 @@ class TestSavePlot(unittest.TestCase):
         with self.assertRaises(TypeError):
             save_plot('figure', 'PLOT_NAME', extension='jpg')
 
+    def test_wrong_fig_in_list(self):
+        """
+        Test if Error is raised when one figure is invalid in a list of
+        valid figures.
+        """
+        with self.assertRaises(TypeError):
+            save_plot([FIGURE, 'figure', FIGURE], 'PLOT_NAME', extension='jpg')
+
+    def tearDown(self):
+        os.remove(IMG1)
+        os.remove(IMG2)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_tagplot.py b/tests/test_tagplot.py
index e08084fbd4b5cfc563e0f169c77ade1acd29b159..ddbdd163f6c8546fe5b49e76371d8c4c3e9543e7 100644
--- a/tests/test_tagplot.py
+++ b/tests/test_tagplot.py
@@ -4,6 +4,7 @@
 Unittests for tagplot
 """
 
+import os
 import unittest
 import matplotlib.pyplot as plt
 from plotid.tagplot import tagplot
@@ -12,6 +13,9 @@ from plotid.tagplot import tagplot
 FIG1 = plt.figure()
 FIG2 = plt.figure()
 FIGS_AS_LIST = [FIG1, FIG2]
+IMG1 = 'image1.png'
+IMG2 = 'image2.jpg'
+IMGS_AS_LIST = [IMG1, IMG2]
 
 PROJECT_ID = "MR01"
 PLOT_ENGINE = "matplotlib"
@@ -23,6 +27,17 @@ class TestTagplot(unittest.TestCase):
     Class for all unittests of the tagplot module.
     """
 
+    def setUp(self):
+        plt.savefig(IMG1)
+        plt.savefig(IMG2)
+
+    def test_tagplot(self):
+        """
+        Test if tagplot runs successful.
+        """
+        tagplot(FIGS_AS_LIST, PLOT_ENGINE, PROJECT_ID, METHOD)
+        tagplot(IMGS_AS_LIST, 'image', PROJECT_ID, METHOD, location='north')
+
     def test_prefix(self):
         """ Test if Error is raised if prefix is not a string. """
         with self.assertRaises(TypeError):
@@ -59,6 +74,10 @@ class TestTagplot(unittest.TestCase):
             tagplot(FIGS_AS_LIST, PLOT_ENGINE, PROJECT_ID, METHOD,
                     location='up')
 
+    def tearDown(self):
+        os.remove(IMG1)
+        os.remove(IMG2)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_tagplot_image.py b/tests/test_tagplot_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..f899abca9b030682e26905ebcf327c2b2eb3622b
--- /dev/null
+++ b/tests/test_tagplot_image.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Unittests for tagplot_image
+"""
+
+import os
+import unittest
+import matplotlib.pyplot as plt
+from PIL import PngImagePlugin, JpegImagePlugin
+from plotid.tagplot_image import tagplot_image
+from plotid.plotoptions import PlotOptions
+
+
+FIG1 = plt.figure()
+IMG1 = 'image1.png'
+FIG2 = plt.figure()
+IMG2 = 'image2.jpg'
+IMGS_AS_LIST = [IMG1, IMG2]
+
+# Constants for tests
+PROJECT_ID = "MR01"
+METHOD = 'time'
+ROTATION = 90
+POSITION = (0.975, 0.35)
+
+
+class TestTagplotImage(unittest.TestCase):
+    """
+    Class for all unittests of the tagplot_image module.
+    """
+
+    def setUp(self):
+        plt.savefig(IMG1)
+        plt.savefig(IMG2)
+
+    def test_images(self):
+        """
+        Test of returned objects. Check if they are png and jpg files,
+        respectively.
+        """
+        options = PlotOptions(IMGS_AS_LIST, PROJECT_ID, METHOD,
+                              ROTATION, POSITION)
+        options.validate_input()
+        [figs, _] = tagplot_image(options)
+        self.assertIsInstance(figs[0], PngImagePlugin.PngImageFile)
+        self.assertIsInstance(figs[1], JpegImagePlugin.JpegImageFile)
+
+    def test_single_image(self):
+        """
+        Test of returned objects. Check if png files are returned,
+        if a single matplot figure is given (not as a list).
+        """
+        options = PlotOptions(IMG1, PROJECT_ID, METHOD, ROTATION,
+                              POSITION)
+        options.validate_input()
+        [figs, _] = tagplot_image(options)
+        self.assertIsInstance(figs[0], PngImagePlugin.PngImageFile)
+
+    def test_image_not_str(self):
+        """ Test if Error is raised if wrong type of image is given. """
+        options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION,
+                              POSITION)
+        options.validate_input()
+        with self.assertRaises(TypeError):
+            tagplot_image(options)
+
+    def test_image_not_file(self):
+        """ Test if Error is raised if the image file does not exist. """
+        options = PlotOptions('xyz', PROJECT_ID, METHOD, ROTATION,
+                              POSITION)
+        options.validate_input()
+        with self.assertRaises(TypeError):
+            tagplot_image(options)
+
+    def test_image_plotoptions(self):
+        """
+        Test if Error is raised if not an instance of PlotOptions is passed.
+        """
+        with self.assertRaises(TypeError):
+            tagplot_image('wrong_object')
+
+    def tearDown(self):
+        os.remove(IMG1)
+        os.remove(IMG2)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test_tagplot_matplotlib.py b/tests/test_tagplot_matplotlib.py
index 8f81b6f742b3c6f0b8d176341a9e643afec44f8e..604bc79d4f88b076b04b1c71c1d7972baa2ea3e4 100644
--- a/tests/test_tagplot_matplotlib.py
+++ b/tests/test_tagplot_matplotlib.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
-Unittests for TagPlot_matplotlib
+Unittests for tagplot_matplotlib
 """
 
 import unittest
@@ -29,8 +29,9 @@ class TestTagplotMatplotlib(unittest.TestCase):
 
     def test_mplfigures(self):
         """ Test of returned objects. Check if they are matplotlib figures. """
-        options = PlotOptions(FIGS_AS_LIST, PROJECT_ID, METHOD, ROTATION,
-                              POSITION)
+        options = PlotOptions(FIGS_AS_LIST, PROJECT_ID, METHOD,
+                              ROTATION, POSITION)
+        options.validate_input()
         [figs, _] = tagplot_matplotlib(options)
         self.assertIsInstance(figs[0], Figure)
         self.assertIsInstance(figs[1], Figure)
@@ -40,13 +41,17 @@ class TestTagplotMatplotlib(unittest.TestCase):
         Test of returned objects. Check if matplotlib figures are returned,
         if a single matplot figure is given (not as a list).
         """
-        options = PlotOptions(FIG1, PROJECT_ID, METHOD, ROTATION, POSITION)
+        options = PlotOptions(FIG1, PROJECT_ID, METHOD, ROTATION,
+                              POSITION)
+        options.validate_input()
         [figs, _] = tagplot_matplotlib(options)
         self.assertIsInstance(figs[0], Figure)
 
     def test_mplerror(self):
         """ Test if Error is raised if wrong type of figures is given. """
-        options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION, POSITION)
+        options = PlotOptions(3, PROJECT_ID, METHOD, ROTATION,
+                              POSITION)
+        options.validate_input()
         with self.assertRaises(TypeError):
             tagplot_matplotlib(options)