Natural Image Noise Dataset

An open dataset of real photographs with real noise, from identical scenes captured with varying ISO values.

Droids introducing the Natural Image Noise Dataset

Most images are taken with a Fujifilm X-T1 and XF18-55mm, other photographers are encouraged to contribute images for a more diverse crowdsourced effort.

See also: paper, code

How to contribute? edit

Each image should be captured with at least the camera's base and highest ISO, and some other (varying/random) ISO value.

The files should be named NIND_<imagesname>_ISO<ISO_value> (eg: NIND_droid_ISO200.jpg), without any space in <imagesname>

Ensure the same lighting (eg no blinking lights, avoid moving clouds if outside) and position (preferably use a stable tripod away from cat, even a remote if available). There cannot be any raw overexposed area (crop it out of the set if necessary)

Export the image without denoising or sharpening (which would amplify the noise and should always be applied last), ensure the exposure and white balance is the same in all shots (eg use a fixed auto exposure value on the camera and in the raw development, copy the white balance from one of the shots)

Do whatever you can to ensure the image is pixel-aligned. That is, a non-moving subject and a non-moving camera.

Ideally align the images using a dedicated software. For example using the programs provided by Hugin and Imagemagick, you may use the following Bash script:

EXT="png"
IMAGESNAME=${PWD##*/}
INIMAGES=(*)

if [[ ${IMAGESNAME} != *"_"* ]]; then
    # align images
    align_image_stack -C --use-given-order -a NIND_${IMAGESNAME} ${INIMAGES[*]}
    convert NIND*.tif -set filename: "%t" %[filename:].${EXT}
    rm NIND*.tif
    # restore lost exif data and rename
    for i in `seq 0 $((${#INIMAGES[@]}-1))`; do
      ISO=`exiftool -ISO ${INIMAGES[i]} -T`
      if [ "$i" -lt 10 ]; then ZEROS="000"; else ZEROS="00"; fi
      exiftool -TagsFromFile ${INIMAGES[i]} "NIND_${IMAGESNAME}${ZEROS}$i.${EXT}" -overwrite_original;
      mv -vn "NIND_${IMAGESNAME}${ZEROS}$i.${EXT}" "NIND_${IMAGESNAME}_ISO${ISO}.${EXT}"
    done
    # rm *.tif # uncomment this to remove the input images
else
    echo "Error: directory name (${IMAGESNAME}) cannot contain underscore"
fi

The end result of this script will be a set of files NIND_droid_ISO200.jpg, NIND_droid_ISO800.jpg, ...

Categorize your images in "Category:ISO<ISO_value> images from NIND" (eg: Category:ISO200 images from NIND)

Gallery edit

Download edit

This dataset may be downloaded using the following python script:

#!/usr/bin/env python3
# NIND download script. Use --use_wget is recommended
import os
import requests
import argparse
import subprocess
import sys
import hashlib

TARGET_DPATH = os.path.join('..', '..', 'datasets', 'NIND')

last_update = '2021-07-17'
imageslist = {
    'XT1_8bit': {
        'images': [
            'droid,200,800,3200,6400',
            'gnome,200,800,1600,6400',
            'Ottignies,200,640,3200,6400',
            'MuseeL-turtle,200,800,1250,6400',
            'MuseeL-centrifuge,200,800,2000,6400',
            'MuseeL-shell,200,400,800,6400',
            'MuseeL-coral,200,800,5000,6400',
            'MuseeL-head,200,640,3200,6400',
            'MuseeL-heads,200,400,3200,6400',
            'MuseeL-mask,200,640,4000,6400',
            'MuseeL-pig,200,500,2000,6400',
            'MuseeL-inspiredlotus,200,640,2500,6400',
            'MuseeL-pinklotus,200,800,4000,6400',
            'MuseeL-Armlessness,200,800,2000,6400',
            'MuseeL-byMarcGroessens,200,400,3200,6400',
            'MuseeL-Moschophore,200,500,4000,6400',
            'MuseeL-AraPacis,200,800,4000,6400',
            'MuseeL-stele,200,640,1000,6400',
            'MuseeL-cross,200,500,4000,6400',
            'MuseeL-fuite,200,320,4000,6400',
            'MuseeL-RGB,200,250,1000,6400',
            'MuseeL-Vincent,200,400,2500,6400',
            'MuseeL-ambon,200,640,2500,6400',
            'MuseeL-ram,200,800,1000,6400',
            'MuseeL-pedestal,200,1250,5000,6400',
            'MuseeL-theatre,200,400,2500,6400',
            'MuseeL-text,200,400,5000,6400',
            'MuseeL-painting,200,800,3200,6400',
            'MuseeL-yombe,200,640,3200,6400',
            'MuseeL-hanging,200,500,4000,6400',
            'MuseeL-snakeAndMask,200,2000,6400,H1',
            'MuseeL-coral2,200,6400,H1,H2',
            'MuseeL-Vanillekipferl,200,6400,H1',
            'MuseeL-clam,200,6400,H1',
            'MuseeL-Ndengese,200,6400,H1',
            'MuseeL-Bobo,200,2500,6400,H1,H2',
            'threebicycles,200,6400',
            'sevenbicycles,200,1600,6400',
            'Stevin,200,4000,6400',
            'wall,200,640,6400',
            'Saint-Remi,200,6400,H1,H2,H3',
            'Saint-Remi-C,200,6400,H1,H2',
            'books,200,1600,6400,H1,H2',
            'bloop,200,3200,6400,H1',
            'schooltop,200,800,6400,H1,H2',
            'Sint-Joris,200,1000,2500,6400,H1,H2,H3',
            'claycreature,200,4000,6400,H1',
            'claycreatures,200,1600,5000,6400,H1,H2,H3',
            'claytools,200,5000,6400,H1,H2,H3',
            'CourtineDeVillersDebris,200,2500,6400,H1,H2',
            'Leonidas,200,400,3200,6400,H1',
            'pastries,200,3200,6400,H1,H2',
            'mugshot,200,6400,H1',
            'holywater,200,1600,4000,6400,H1,H2,H3',
            'chapel,200,1000,6400,H1,H2,H3',
            'directions,200,640,640-2,1250,6400,6400-2,H1,H2,H3',
            'drowning,200,800,6400,H1,H2,H3',
            'parking-keyboard,200,400,800,1600,3200,6400,H1,H2,H3',
            'semicircle,200,320,640,1250,2500,5000,6400,H1,H2,H3',
            'stairs,200,250,320,640,1250,2500,5000,6400,H1,H2',
            'stefantiek,200,250,500,2000,6400,H1,H2',
            'tree1,200,400,1600,3200,6400,H1,H2,H3',
            'tree2,200,800,1600,3200,6400,H1,H2,H3',
            'ursulines-building,200,250,400,1000,4000,6400,H1',
            'ursulines-can,200,200-2,400,800,1600,3200,6400,H1,H2',
            'ursulines-red,200,250,500,4000,6400,H1,H2',
            'vlc,200,250,500,1000,3200,6400,H1,H2,H3',
            'whistle,200,250,500,1000,2000,4000,6400,H1,H2,H3,H4',
            'Homarus-americanus,200,200-2,250,400,800,2000,3200,5000,6400,H1,H2',
            'fruits,200,200-2,800,3200,5000,6400',
            'MVB-Sainte-Anne,200,200-2,250,640,4000,6400,H1',
            'MVB-JardinBotanique,200,200-2,400,1000,2500,3200,6400',
            'MVB-Urania,200,320,500,1000,2500,5000,6400,H1,H2',
            'MVB-1887GrandPlace,200,200-2,400,640,2000,5000,6400,H1,H2',
            'MVB-heraldicLion,200,200-2,320,1000,3200,6400,H1,H2',
            'MVB-LouveFire,200,200-2,400,800,1600,3200,6400,6400-2,H1,H2',
            'MVB-Bombardement,200,200-2,320,800,5000,6400,H1,H2,H3',
            'beads,200,500,1000,3200,6400',
            'shells,200,200-2,250,320,1000,1600,2500,3200,5000,6400,H1,H2,H3',
        ], 'ext': 'jpg'},
    'XT1_16bit': {
        'images': [
            'soap,200,200-2,400,800,3200,6400,H1,H2,H3,H4',
            'kibbles,200,200-2,800,5000,6400,H1,H2,H3',
            'bertrixtree,200,400,640,2500,4000,6400,H1',
            'BruegelLibraryS1,200,400,1000,2500,3200,5000,6400,H1,H2',
            'BruegelLibraryS2,200,500,1250,2500,5000,6400,H1,H2,H3,H4',
            'LaptopInLibrary,200,500,800,1600,2500,6400,H1,H2,H3',
            'banana,200,250,500,800,1250,2000,4000,6400,H1,H2,H3',
            'dustyrubberduck,200,1000,1250,2500,5000,6400,H1,H2',
            'partiallyeatenbanana,200,640,1250,2500,4000,5000,6400,H1,H2,H3',
            'corkboard,200,320,1000,2500,5000,6400,H1,H2,H3',
            'fireextinguisher,200,200-2,200-3,800,3200,6400,H1,H2,H3',
            'colorscreen,200,201,202,400,1000,3200,6400,H1'
        ], 'ext': 'png'},
    'C500D_8bit': {
        'images': [
            'MuseeL-Bobo-C500D,100,200,400,800,1600,3200,H1',
            'MuseeL-yombe-C500D,100,400,800,1600,3200,H1',
            'MuseeL-sol-C500D,100,200,400,800,3200,H1',
            'MuseeL-skull-C500D,100,200,400,800,1600,3200,H1',
            'MuseeL-Sepik-C500D,100,200,800,1600,3200,H1',
            'MuseeL-Saint-Pierre-C500D,100,100-2,200,400,800,1600,3200,H1',
            'MuseeL-mammal-C500D,100,200,400,800,1600,3200,H1',
            'MuseeL-idole-C500D,100,100-2,200,400,800,3200,H1',
            'MuseeL-CopteArch-C500D,100,100-2,200,400,1600,3200',
            'MuseeL-cross-C500D,100,200,400,800,1600,3200,H1',
            'MuseeL-fuite-C500D,100,200,400,800,1600,3200,H1',
       ], 'ext': 'jpg'},
    'Z6_16bit': {
        'images': [
            'sewingmachine,50,63,79,100,125,160,200,400,800,3200,6400,12800,25600,32000,51200',
            'bananapi,50,63,100,125,200,400,800,1600,3200,6400,12800,25600,51200',
            'couch,50,63,79,100,160,250,400,800,1600,3200,5000,10000,16000,25600,40000',
        ], 'ext': 'png'},
    'Peter_16bit': {
        'images': [
            'Bark,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Blombukett,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Elplint,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Kortlek,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Kyckling-i-kruka,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Metalldel,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Spydercheckr,100,200,400,800,1600,3200,6400,12800,25600,51200,65535',
            'Vaxt-i-trad,100,200,400,800,1600,3200',
            '7D-1,100,200,400,800,1600,3200,6400,12800',
            '7D-2,100,200,400,800,1600,3200,6400,12800',
            '7D-3,100,200,400,800,1600,3200,6400,12800',
            '7D-4,100,200,400,800,1600,3200,6400,12800',
            '7D-5,100,200,400,800,1600,3200,6400,12800',
            '7D-6,100,200,400,800,1600,3200,6400,12800',
            '7D-7,100,200,400,800,1600,3200,6400,12800',
            'D60-1,100,200,400,800,1037',
            'D60-2,100,200,400,800,1037',
            'D60-3,100,200,400,800,1037',
        ], 'ext': 'png'},
    'Contrib':  {
        'images': [
            'Pen-pile,100,200,400,800,1600,3200',
        ], 'ext': 'jpg'},
    'ContribPNG': {
        'images': [
            'Iain01,200,200-2,200-3,200-4,400,800,1600,3200',
            'Iain02,200,200-2,200-3,200-4,400,800,1600,3200',
        ], 'ext': 'png'},
}

dlerrors = []

apiurl = 'https://commons.wikimedia.org/w/api.php'
session = requests.Session()

def get_img(bname, isoval, ext, attempts_left, datelimit, use_wget, custom_program = None):
    def get_latest_reqimg_info(imname, datelimit):
        rparams = {
            'action': 'query',
            'format': 'json',
            'prop': 'imageinfo',
            'titles': 'File:'+imname.replace('_', ' '),
            'iistart': datelimit+'T23:59:59Z',
            'iiprop': 'timestamp|url|sha1',
        }
        request = session.get(url=apiurl, params=rparams)
        try:
            imageinfo = next(iter(request.json()['query']['pages'].values()))['imageinfo'][0]
            return imageinfo
        except:
            print('File not found: %s'%imname)
            return 404
    def checkfile(path, reqsha1):
        if not os.path.isfile(path):
            return False
        with open(path, 'rb') as file:
            h = hashlib.sha1(file.read())
        if h.hexdigest() != reqsha1:
            print('Invalid file: %s'%path)
            return False
        print('Validated %s'%(path))
        return True
    def download(path, url, use_wget, custom_program = None):
        if use_wget:
            subprocess.run(['wget', url, '-O', path])
        elif custom_program:
            subprocess.run([custom_program, url, '-O', path])
        else:
            with open(path, 'wb') as f:
                response = requests.get(url, headers={'user-agent': 'NIND-download-script/0.0.1'})
                if response.status_code != 200:
                    print("Error: %s (hint: try with --use_wget)" % response.reason)
                    return
                f.write(response.content)
                print('Downloaded %s'%(path))
                f.flush()
    imname = 'NIND_%s_ISO%s.%s'%(bname, isoval, ext)
    imageinfo = get_latest_reqimg_info(imname, datelimit)
    if imageinfo == 404:
        return dlerrors.append('Error: %s not found prior to %s'%(imname, datelimit))
    fpath = os.path.join(bname, imname)
    reqsha1 = imageinfo['sha1']
    url = imageinfo['url']
    while not checkfile(fpath, reqsha1):
        if attempts_left == 0:  # negative max_attempts -> unlimited attempts
            return dlerrors.append('Error: Unable to download %s (source: %s)'%(fpath, url))
        download(fpath, url, use_wget, custom_program)
        attempts_left -= 1

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='NIND download script')
    parser.add_argument('--datelimit', default=last_update, type=str, help='Latest date of upload, used to get a specific version of the dataset (default: %s)'%(last_update))
    parser.add_argument('--use_wget', action='store_true', help="Use wget instead of python's request library (more likely to succeed)")
    parser.add_argument('--custom_program', help="Custom program (alternative to wget), must follow the pattern custom_program url -O path")
    parser.add_argument('--target_dir', default=TARGET_DPATH, type=str, help=f"Target directory (default: {TARGET_DPATH})")
    parser.add_argument('--sets2dl', nargs='*', help='Space separated list of image sets to download (default: %s)'%(' '.join(imageslist.keys())))
    parser.add_argument('--max_attempts', default=3, type=int, help='Maximum download attempts (default: 3)')
    args = parser.parse_args()
    os.makedirs(args.target_dir, exist_ok=True)
    os.chdir(args.target_dir)

    dlsets = imageslist.keys() if args.sets2dl is None else args.sets2dl
    for aset in dlsets:
        if aset not in imageslist.keys():
            dlerrors.append('Error: %s not defined.'%(aset))
        ext = imageslist[aset]['ext']
        for img in imageslist[aset]['images']:
            bname, *isos = img.split(',')
            os.makedirs(bname, exist_ok=True)
            for isoval in isos:
                get_img(bname, isoval, ext, attempts_left=args.max_attempts, datelimit=args.datelimit, use_wget=args.use_wget, custom_program=args.custom_program)

    if sum(['Unable to download' in error for error in dlerrors]) > 0:
        dlerrors.append('Some errors were encountered and corrupted files may be present, you should remove them manually or run this script again.')
        if not args.use_wget:
            dlerrors.append('hint: the --use_wget option may help.')
    for error in dlerrors:
        print(error, file=sys.stderr)
    sys.exit(len(dlerrors))

Tools edit

  • Source code to train a neural network for image denoising (including dataset download and pre-processing), and inference tools to denoise (unsharpened) images with a provided pre-trained model: https://github.com/trougnouf/nind-denoise

See also: Category:Natural Image Noise Dataset