diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py
new file mode 100644
index 0000000000000000000000000000000000000000..31573fd4039018ef04554422ac6a174f7da2f211
--- /dev/null
+++ b/src/imcflibs/imagej/shading.py
@@ -0,0 +1,111 @@
+"""Functions to work on shading correction / model generation."""
+
+import os
+
+from ..imagej import bioformats
+from ..imagej import projections
+from ..pathtools import listdir_matching
+
+from ij import IJ
+from ij.plugin import ImageCalculator, RGBStackMerge
+
+
+def apply_model(imps, model, merge=True):
+    """Apply a given shading model to a list of images / stacks.
+
+    The model is supposed to be a normalized 32-bit floating point 2D image that
+    will be used as a divisor to the slices of all ImagePlus objects given.
+
+    WARNING: the operation happens in-place, i.e. the original "imps" images
+    will be modified!
+    
+    Parameters
+    ----------
+    imps : list(ij.ImagePlus)
+        A list of ImagePlus objects (e.g. separate channels of a multi-channel
+        stack image) that should be corrected for shading artefacts.
+    model : ImagePlus
+        A 2D image with 32-bit float values normalized to 1.0 (i.e. no pixels
+        with higher values) to be used for dividing the input images to correct
+        for shading.
+    merge : bool, optional
+        Whether or not to combine the resulting ImagePlus objects into a single
+        multi-channel stack (default=True).
+    
+    Returns
+    -------
+    ij.ImagePlus or list(ij.ImagePlus)
+        The merged ImagePlus with all channels, or the original list of stacks
+        with the shading-corrected image planes.
+    """
+    calc = ImageCalculator()
+    for stack in imps:
+        # log.debug("Processing channel...")
+        calc.run("Divide stack", stack, model)
+
+    if not merge:
+        return imps
+
+    merger = RGBStackMerge()
+    merged_imp = merger.mergeChannels(imps, False)
+    return merged_imp
+
+
+def correct_and_project(filename, path, model, proj, fmt):
+    """Apply a shading correction model to an image file.
+    
+    Parameters
+    ----------
+    filename : str
+        The full path to a multi-channel image stack.
+    path : str
+        The full path to a directory for storing the results.
+    model : ij.ImagePlus
+        A 32-bit floating point image to be used as the shading model.
+    proj : str
+        A string describing the projections to be created. Use 'None' for not
+        creating any projections, 'ALL' to do all supported ones.
+    fmt : str
+        The file format suffix to be used for the results and projections, e.g.
+        '.ics' for ICS2 etc. See the Bio-Formats specification for details.
+    """
+    orig = bioformats.import_image(filename, split_c=True)
+    corrected = apply_model(orig, model)
+    bioformats.export_using_orig_name(corrected, path, filename, "", fmt)
+
+    if proj == 'None':
+        projs = []
+    elif proj == 'ALL':
+        projs = ['Average', 'Maximum']
+    else:
+        projs = [proj]
+    projections.create_and_save(corrected, projs, path, filename, fmt)
+
+    # corrected.show()
+    corrected.close()
+    # log.debug("Done processing [%s]" % os.path.basename(in_file))
+
+
+def process_folder(path, suffix, outpath, model_file, fmt):
+    """Run shading correction and projections on an entire folder.
+
+    Parameters
+    ----------
+    path : str
+        The input folder to be scanned for images to be processed.
+    suffix : str
+        The file name suffix of the files to be processed.
+    outpath : str
+        The output folder where results will be stored.
+    model_file : str
+        The full path to a normalized 32-bit shading model image.
+    fmt : str
+        The file format suffix for storing the results.
+    """
+    matching_files = listdir_matching(path, suffix)
+
+    imp = IJ.openImage(model_file)
+    imp.show()  # required, otherwise the IJ.run() call will ignore the imp
+    for orig_file in matching_files:
+        in_file = os.path.join(path, orig_file)
+        correct_and_project(in_file, outpath, imp, 'ALL', fmt)