User Tools

Site Tools


Negative scan with GNU/Linux and Canon CanoScan 9000F Mark II

See also the page Canon CanoScan 9000F Mark II.

The first negative scan

I purchased the scanner mainly to acquire my old negatives, so I put two strips of Fuji negatives into the rails and I did my first test.

After starting the xsane program, from the preview window, I selected the area of negatives rails (about 70 x 240 mm). You can add a preset area using the context menu, which add the following section into $HOME/.sane/xsane/xsane.rc:

"CanoScan 9000F - Negative rails"

Negative scan at 2400 dpi

Then I selected Transparency Unit as the scan source, Color as the scan mode, Fuji as the medium type (that film was a Fuji), 2400 dpi, clicked the Negative button, clicked the Set default enhancment values (shortcut Ctrl-0) to reset gamma controls to their central position. The acquire process tooks about 3.5 minutes (206 seconds), and produced a 16 bit/channel TIFF file of 650 Mb. I used a very low-end desktop with 2 Gb of RAM and and Atom CPU D510 at 1.6 GHz.

A standard 135 mm negative acquired at 2400 dpi produces an image of about 3400 x 2200 pixels (i.e. about 7 Mpixels), the result is very light, as you can see. The scanner is capable up to 9600 dpi.

Xsane and medium definition

See the Xsane documentation about medium definition.

With the Xsane program it is possible to set varius levels for gamma correction, luminosity and contrasts or choose from some presets, including some brand of negative films. It is also possible to define new profiles, e.g. to get best results with this scanner and some type of negatives.

Neverthless it seems that none of that controls influence the way the scanner acquire from the transparency unit, so it is preferible to acquire without any correction (sort of raw scanning), and do postprocessing later, with some software.

PIXMA backend options for transparencies

Sane PIXMA backend allows several options to be passed using scanimage, you can see all of them with:

scanimage --device-name pixma:04A9190D --all-options

this is a summary (run the command to get some extra explanation):

  Scan mode:
    --resolution auto||75|150|300|600|1200|2400|4800dpi [75]
    --mode auto|Color|Gray|48 bits color|16 bits gray|Lineart [Color]
    --source Flatbed|Transparency Unit [Flatbed]
    --button-controlled[=(yes|no)] [no]
    --custom-gamma[=(auto|yes|no)] [yes]
    --gamma-table auto|0..255,...
    --gamma auto|0.299988..5 [2.2]
    -l auto|0..216.069mm [0]
    -t auto|0..297.011mm [0]
    -x auto|0..216.069mm [216.069]
    -y auto|0..297.011mm [297.011]
    --button-1 <int> [0] [read-only]
    --button-2 <int> [0] [read-only]
    --original <int> [0] [read-only]
    --target <int> [0] [read-only]
    --scan-resolution <int> [0] [read-only]
    --threshold auto|0..100% (in steps of 1) [inactive]
    --threshold-curve auto|0..127 (in steps of 1) [inactive]

WARNING: The order of parameters does matter! For example if you select --source "Transparency Unit", the --resolution parameters will be resetted to the default value of 300, so you must set the resolution after the source! Tip: run the program with the options you want and append the --all-options: you will see all the actual and available options.

I was mainly interested into gamma and color correction, which is the real problem when scanning negative films. I played with all the gamma options, but I get always the same image as result. After being confirmed by this post pixma: no gamma table in TPU mode, I'm rather sure that this scanner does not support gamma when scanning transparencies (Transparency Profile Unit).

We can assume that this scanner (at least with libsane 1.0.24) does a raw scan of transparencies, which is a good thing in my opinion. At scan time we have not to bother with gamma correction and color calibration (e.g. using different profiles with different brand of negatives) and we can proceed in quick batch. Then we will do all the postprocessing via software, keeping the original raw data for future enhancements.

Scanning from command line and benchmarking

WARNING! The order of parameters passed to scanimage does matter! See above.

WARNING! With images acquired at 4800 dpi, 64 bit software is probably required (Imagemagick, tiffinfo, etc.). Otherwise integer overflow may occurr.

With this command we will scan the entire transparency surface, containing two strips of 135 mm negatives:

scanimage --device-name pixma:04A9190D \
    --source 'Transparency Unit' \
    --resolution 2400 \
    --format tiff \
    --mode Color \
    -l 76 -x 66 \
    > negative.tiff

In the following table we report the scan time and the size of the resulting file, using different resolutions:

Resolution Scan time Scan size File size (tiff) Single shot size Mpixel
1200 0m 48s 3118 x 11840 212M 1692 x 1128 ~ 2
2400 2m 39s 6236 x 23680 845M 3384 x 2256 ~ 7
4800 5m 54s 12473 x 47360 3.4G 6768 x 4512 ~ 29


Infrared scan of negative film

The CanoScan 9000F Mark II scanner has an infrared sensor, useful to remove scratches and dust from the scanned image. With xsane it is possible to acquire the infrared image, just select Infrared as scan mode. The result is a grayscale image, where only dust and scratches are visibles. This image should be used togeter with the color scan, to obtain an interpolated image. FIXME How to do that?

Batch processing


The scanner accomodates two stripes of 135 mm negatives. This is the script used to acquire them:

filename="$(date +%Y-%m-%d_%H%M%S)"
scanimage --device-name pixma:04A9190D \
    --source 'Transparency Unit' \
    --resolution "$resolution" \
    --format "$format" \
    --mode Color \
    -l 76 -x 66 \
    > "${filename}-${resolution}.$format"

Strip cut and rotate

Then we used this script to cut the two stripes and rotate them by 270 degrees. The png compression seems to be the best lossless method:

# Cut two strips from a 135 mm negative scan, CanoScan 9000F Mark II
case "$resolution" in
        echo "Usage: $(basename $0) file.tiff"
        exit 1
# To avoid "the PNG file specifies an offset" problem, use the +repage option.
set -x
convert "${filename}" \
    -crop "$strip1" \
    -rotate 270 \
    +repage \
convert "${filename}" \
    -crop "$strip2" \
    -rotate 270 \
    +repage \

Invert and color balance

This is an empirical recipe to invert and balance colors from a scanned negative strip. It is based on a three pass process: negate, gamma correction and color level adjust. The values for gamma correction and color adjust, make a specific profile suitable for just one brand and type of negative (e.g. Kodak VR 100-3).

Scanner softwares often contain several negative profiles based only on color adjust. Gamma correction is defined for each RGB channel along the black and white point. We instead prefer to apply an uniform gamma correction before color level adjust, so we can deal with under-exposed or over-exposed frames just touching a single pre-process parameter.

First of all we make several test from a negative scan, setting different gamma correction:

!/bin/sh -x
test_values="0.002 0.003 0.004 0.005 0.006 0.007 0.008"
for gamma in $test_values; do
  convert -colorspace RGB "$1" \
    -negate -gamma $gamma \

We choose by visual inspection the best value for gamma; 0.007 in this example.

Thake a film strip (negated and gamma-corrected with the above recipe) and open it with the Gimp. We need a strip with good black, gray and white spots. The process is:

  1. Apply a pixelize blur filter on the black, gray and white spots. Make it large enough so you can easly pick the color from it in the next steps.
  2. Open menu Colors, Levels:
    1. From All Channels, click Pick gray point and then click on the gray spot.
    2. From Channel select Red, click Pick black point and click on the black spot. Click Pick white point and click on the white spot.
    3. Repeat the above step for Red, Green and Blue.
  3. Before closing the dialog box, annotate the values of black point, gamma, and white point for each RGB channel.

Suppose that the values are:

Channel Black Gamma White
Red 22 1.06 193
Green 20 1.01 190
Blue 30 0.78 197

The black and white points are integers 0-255, we need to convert it into a percent value; e.g. for the red white point: 22 / 255 * 100 = 8.63%.

This is the Imagemagick convert recipe:

for file in $@; do
  convert -colorspace RGB "$file" \
    -negate \
    -gamma 0.007 \
    -channel R -level 08.63%,75.69%,1.06 \
    -channel G -level 07.84%,74.51%,1.01 \
    -channel B -level 11.76%,77.25%,0.78 \

Cutting frames using the GIMP

Now we have several strips of frames, acquired with batch procedures and very few manual work. The last step indeed requires some manual work and human intervention: cut the single frames, eventually rotate them, and save in our preferred format.

We use the GIMP with a preset for the Rectangle select tool and a plugin script named Export selection as jpeg. Prepare the GIMP:

  1. Save the file 135-mm-frame-select-2400.gtp into your $HOME/.gimp-2.8/tool-presets/ directory.
  2. Save the file into your $HOME/.gimp-2.8/plug-ins/ directory.
  3. Assign a keyboard shortcut the the Export selections as jpeg plugin, eg. Ctrl-Alt-E (menu EditPreferencesInterfaceConfigure Keyboard ShortcutsSearch Export selection…).

Now the workflow will be:

  1. Open the strip image
  2. Press R to activate the Rectangle select tool.
  3. From the Tool Options dialog, click Restore Tool Preset and choose “135 mm frame select @2400” (there is a bug? You may need to choose it two times): you see the fixed size for selection.
  4. Select the frame you want to save (the preset will force you with the fixed size).
  5. Press Ctrl-Alt-E to run the script: insert the filename and the required rotation.
  6. Repeat the last two steps.

ImageMagick convert, memory issue

Executing convert on very large images can lead to memory exhausted issue. I experienced it converting a TIF scan into PNG (because I wish to avoid 24 bit Gimp limitation with TIF images):

convert very-big.tif very-big.png
convert-im6.q16: DistributedPixelCache '' @ error/distribute-cache.c/ConnectPixelCacheServer/244.
convert-im6.q16: cache resources exhausted `very-big.png' @ error/cache.c/OpenPixelCache/3945.
convert-im6.q16: No IDATs written into file `very-big.png' @ error/png.c/MagickPNGErrorHandler/1628.

It seems that ImageMagick is self-limiting the disk resource it will use, you can fix the configuration file /etc/ImageMagick-6/policy.xml, increasing the 1GiB limit:

  <policy domain="resource" name="memory" value="256MiB"/>
  <policy domain="resource" name="map" value="512MiB"/>
  <policy domain="resource" name="width" value="16KP"/>
  <policy domain="resource" name="height" value="16KP"/>
  <policy domain="resource" name="area" value="128MB"/>
  <policy domain="resource" name="disk" value="1GiB"/>

May be you can also increase the Memory resource, if you have enough:

convert -list resource
Resource limits:
  Width: 16KP
  Height: 16KP
  Area: 128MP
  Memory: 256MiB
  Map: 512MiB
  Disk: 1GiB
  File: 768
  Thread: 2
  Throttle: 0
  Time: unlimited

Web references

doc/appunti/hardware/canoscan_9000f_mark_ii_negative_film.txt · Last modified: 2019/12/23 08:28 by niccolo