Scanning 35mm film negatives

What scanner settings work best?

I have sat at my iMac for weeks, scanning old "found" negatives. I adopted a scanning workflow starting with scanning 35mm film negatives using my Epson Perfection V600, SilverFast 9 Plus, and the Negative Lab Pro plugin for Adobe Lightroom Classic. This workflow is adapted from Matt Wright's illuminating article in 35mmc. While scanning my 35mm film negatives myself saves me some money (about $5/roll), my recent focus has been on digitising decades-old photographs in an attempt to preserve memories.

For my most recent 35mm film captures, I have scanned the negatives to 1800x1200 DNG files at 300 pixels per inch (PPI). I have kept the PPI low to keep the image file sizes small when I export them on my blog or for sharing on social media. Most people have switched to using smartphones to share and view images. On such tiny screens, anything beyond 1000 pixels is mostly a waste of bandwidth. I am uploading 1800x1200 images to my blog for those who prefer to use a computer or tablet screen to view photographs.

But prompted by a recent blog post by Jim Grey, I have been thinking quite a bit about what resolution to use for archiving of older 35mm film negatives. I searched the web for information about the resolution of 35mm film but found mostly articles debating the benefits of film versus digital. But from websites like dppreviw and others, I eventfully put together what I think is a correct answer.

A 35mm film negative frame is 24mm (height) by 36mm (length). Most full-frame digital sensors are approximately the same size. Film resolution is based on the ability of film to resolve "line pairs per mm" (lp/mm). I then took a look at the resolution specifications for some modern films. I did not consider the resolving capability of lenses. Some 35m films were rated at nearly 200 lines/mm, but most were less. According to the specification notes, Fujichrome Provia 100F film has about 140 lines per mm (l/mm) and Superia X-TRA 400 has a resolution of 125 lines/mm. There is a tradeoff between sensitivity (aka ISO) and grain size. The higher the ISO, the larger the grain size. Grain size increases noise and lowers the spatial resolution of more sensitive films. This assumes the frame is exposed at the optimum f-stop, and the camera is mounted on a tripod to minimise the camera shake.

To be clear, I am not comparing 35mm film to digital; I am comparing scans of 35mm film to digital.

While photographic film grains are randomly distributed and have size variations, digital image photocells on a specific sensor will be the same size and arranged in a grid. Direct comparison of film and digital resolutions is not straightforward. The ISO setting on a digital camera controls the gain of the electronic amplifier on the circuitry of the chip sensor. High ISO settings on a digital camera operating in low light conditions result in a noisy image, but the visual appearance differs from traditional photographic film grain.

To better compare these values with digital photography, we must transform this information into DPI (dots per inch) or pixels per inch. Doing some quick math, I calculated a frame of Fujichrome Provia 100F 35mm film has a maximum resolution of 5040 x 3500 lines or 16 megapixels when exposed at box speed. In comparison, a Sony α7 full-frame (35.8 mm x 23.9 mm) sensor has a maximum resolution of 6000 x 4000 pixels (24 megapixels) and a native ISO range of 50 - 25600. The modern digital full-frame sensor has better resolution. But this didn't answer my question. What scan settings do I want to use to extract the "best" archive scans from my Epson Perfection V600.

The dimension of a single frame of 35m film is 36mm x 25mm or approximately 1.42 inches x 09.98 inches. So it seems that to make archival scans of a negative of a single frame of Fujichrome Provia 100F 35mm film, I need to set my Epson Perfection V600 to scan at about 3571 PPI. If I scan lower-resolution 35mm film negatives, such Superia X-TRA 400, scanning at 3189 PPI is more appropriate.

The table below lists popular 35mm film stock and my approximate recommended scan resolution for each film stock (MTF in lines/mm at 1000:1 contrast) in pixels per inch (PPI). These calculations are for a 1000:1 contrast ratio. Fujifilm’s website was full of helpful information, but Kodak Alaris was less forthcoming. While For example, while KODAK PROFESSIONAL PORTRA 160 datasheet mentions that the film is "ideal for scanning", the document provides no suggestions. However, Kodak Alaris did include a Modulation Transfer Function graph that shows the cycles per millimetre. According to Ken Rockwell, "Cycles per millimeter is also called lines per millimeter". Assuming that is true, I have included the data for some popular 35mm film stock in the table below.1 NOTE: Some of these film stocks are discontinued.

Manufacturer Film Stock Film Resolution Recomeded Scan Resolution
Fujifilm FUJICOLOR 200 125 3188
Fujifilm SUPERIA X-TRA 400 125 3188
Fujifilm FUJICHROME Velvia 50 160 4082
Fujifilm FUJICHROME Velvia 100 160 4082
Fujifilm FUJICHROME Provia 100F 140 3571
Fujifilm FUJICOLOR PRO 400H 125 3188
Fujifilm NEOPAN 100 ACROS II 200 5102
Kodak KODAK PROFESSIONAL PORTRA 160 80 2041
Kodak KODAK PROFESSIONAL PORTRA 400 80 2041
Kodak KODAK PROFESSIONAL EKTAR 100 80 2041
Kodak KODAK PROFESSIONAL T-MAX 400 200 5102
Kodak KODAK PROFESSIONAL T-MAX 100 200 5102
Kodak KODAK PROFESSIONAL TRI-X 400 60 1530
Kodak KODAK PROFESSIONAL EKTAR 100 90 2296
Kodak KODAK PROFESSIONAL EKTACHROME E100 90 2296
FPP X2 100 2551
Rollei RPX 25 260 6633
Rollei RPX 100 160 4082
Rollei RPX 100 100 2041

I don't know much about other devices, but modern Macs, iPads and iPhones displays have a resolution between 220 (macOS) and 480 (iOS) pixels per inch. I think scanning negatives at very high resolutions (anything beyond 300 PPI) to downsample those scans to 1800x1200 or 960x640 for display on a website or mobile device screen is a wasted effort. However, scanning at higher resolutions seems wise when I scan images for archive when the original film negative is damaged (like so many of my wife's college travel photographs are).

For comparison, I have compiled a table of instant films.

Manufacturer Film Stock Film Resolution Recomeded Scan Resolution
Fujifilm Instax Square 10 320

  1. Of course, Ken Rockwell will also tell you that it's MUCH more complicated

Publish untappd check-ins to micro.blog

Carl Rustung wrote his original post, Zapping Instagram pics to micro.blog on Wed. January 30th, 2019. I wanted my Untappd check-ins automatically posted to my micro.blog. With some trial and error, I was able to tweak his Zap to do it for me.

Here’s how:

  • Set up a new access token for Zapier in your micro.blog account settings.
micro.blog screen shot
Set up a new [access token](https://micro.blog/account/apps) for Zapier in your micro.blog account settings.
  • Make a new Zap. Choose Untappd for your Trigger App and trigger the zap on “New Check-In”.
  • Connect your Untappd account.
  • Select a sample post.
  • Add “Webhooks” for your Action step, and select the POST request.
Zapier screen shot
Add “Webhooks” for your Action step, and select the POST request.
  • Use micro.blog/micropub as the URL and “Form” as the Payload Type
  • Under Data, you’re going to need 4 key-value pairs:
    • h: entry (literally, write “entry”)
    • access_token: [your access token from step 1]
    • content: Use the Zapier tokens to create the content you want. I used the following.
screen shot from Zapier
Use the Zapier tokens to create the content you want


<p><a href="https://untappd.com/user/khurtwilliams/checkin/{{110699014__checkin_id}}">{{110699014__beer__beer_name}}</a> ({{110699014__beer__beer_abv}}% ABV {{110699014__beer__beer_style}}) by <a href="https://untappd.com/{{110699014__brewery__brewery_page_url}}" />{{110699014__brewery__brewery_name}}</a> ({{110699014__brewery__location__brewery_city}}, {{110699014__brewery__location__brewery_state}}). Rating:{{110699014__rating_score}}/5</p> <p>{{110699014__checkin_comment}}</p> <p><figure><img src="{{110699014__media__items[]photo__photo_img_og}}" alt="{{110699014__beer__beer_name}}" /><figcaption>{{110699014__beer__beer_name}}</figcaption></figure></p>
  • Leave the rest of the inputs alone, and click Continue.
  • Click the “Send Test...”-button, and your sample Untappd post should appear on your micro.blog timeline.

You can see examples of this in my beer category. page on micro.blog.

Camel POOP - Perl Object Oriented Programming

I wrote this article for the CodeProject back in 2002 during my first consulting career as a web developer as a way to boost my visibility. I am capturing it here for my archives. The article is based on Perl 5.

Perl Object-Oriented Programming

Most people are not aware of the fact that Perl has support for object-oriented programming. If you've used another object-oriented programming language such as Java or C++ or been exposed to object-orientation, then object-oriented programming in Perl is nothing like that. To do real useful object-oriented programming in Perl, you only need to use a few simple rules as put forth by Damian Conway in his book Object Oriented Perl: A Comprehensive Guide to Concepts and Programming Techniques.

Object-oriented programmers are familiar with the concept of object and classes, but I will review that here quickly. An object is a thing that provides access to or modification of data. A class is the description of the attributes of a particular kind of object and how those objects can be accessed and modified. A method is a means by which an object's data is accessed or modified. An object is an instance of a class.

An example would be a Person class in an HR system. The Person class describes the attributes of a Person such as a name, address, title, social security number, id etc. A particular class instance or object would encapsulate data about a specific Person, e.g. name, title, social security number, address, etc. Some methods to access that object's data would be, name, address etc.

Perl Package delivery

To create a class in Perl, we first build a package. A package is a self-contained unit of user-defined variables and subroutines, which can be reused over and over again. They provide a separate namespace within a Perl program that keeps subroutines and variables from conflicting with those in other packages.

To declare a class named Person in Perl we do: package Person; That's it. The scope of the package definition extends to the end of the file, or until another package keyword is encountered. Not very useful yet but on to the next section.

There's a method to this madness

A method is a means by which an object's data is accessed or modified. In Perl, a method is just a subroutine defined within a particular package. So to define a method to print our Person object we do:

sub print { my ($self) = @_;
    #print Person info
    printf("Name:%s %s", $self->firstName, $self->lastName );
}

The subroutine print is now associated with the package Person. To call the method print on a Person object, we use the Perl "arrow" notation. If the variable $khurt contains a Person object we would call print on that object by writing: $khurt->print(); When the object method is invoked, a reference to the object is passed in along with any other arguments. This is important since the method now has access to the object on which it to operate.

How do we create the invoking object?

Bless me, father

To create an instance of a class (an object) we need an object constructor. This constructor is a method defined within the package. Most programmers choose to name this object constructor method new, but in Perl, one can use any name.

One can use any kind of Perl variable as an object in Perl. Most Perl programmers choose either references to arrays or hashes. Let's create our constructor for our Person class using a Perl hash reference;

#constructor
sub new {
    my $self = { _firstName => undef, _lastName => undef, _ssn => undef, _address => undef };
    bless $self, 'Person';
    return $self;
}

What have we done? We created a subroutine called new associated with the package Person. The entries of the hash reference $self become the attributes of our object.

We then use the bless function on the hash reference. The bless function take two arguments; a reference to the variable to be marked and a string containing the name of the class.

This indicates that the variable now belongs to the class Person. To create an instance of our Person object: my $khurt = new Person(); We have not defined accessor methods or done any error checking on the input values or keys or the anonymous hash reference, but we have the start of a Perl Person OO framework. To make our constructor more flexible and to make our class inheritable (more on that later) we can define it to use the $class variable to bless the hash reference.

#constructor
sub new {
    my ($class) = @_;
    my $self = { _firstName => undef, _lastName => undef, _ssn => undef, _address => undef };
    bless $self, $class;
    return $self;
}

Other object-oriented languages have the concept of security of the data to prevent a programmer from changing an object's data directly and so provide accessor methods to modify object data. Perl does not have private variables, but we can still use the concept of accessor methods and ask programmers to not mess with our object innards.

For our Person class, we should provide accessor methods for our object attributes; name, address, title, SSN.

package Person; #class Person
use strict;

use Address; #Person class will contain an Address

sub new { #constructor
    my ($class) = @_;
    my $self = { _firstName => undef, _lastName => undef, _ssn => undef, _address => undef };
    bless $self, $class; return $self;
}

#accessor method for Person first name
sub firstName {
    my ( $self, $firstName ) = @_;
    $self->{_firstName} = $firstName if defined($firstName);
    return $self->{_firstName};
}

#accessor method for Person last name
sub lastName {
    my ( $self, $lastName ) = @_;
    $self->{_lastName} = $lastName if defined($lastName);
    return $self->{_lastName};
}

#accessor method for Person address
sub address {
    my ( $self, $address ) = @_;
    $self->{_address} = $address if defined($address);
    return $self->{_address};
}

#accessor method for Person social security number
sub ssn {
    my ( $self, $ssn ) = @_;
    $self->{_ssn} = $ssn if defined($ssn); return $self->{_ssn};
}

sub print {
    my ($self) = @_; #print Person info
    printf("Name:%s %s ", $self->firstName, $self->lastName );
}
1;

Making babies

Object-oriented programming sometimes involves inheritance. Inheritance simply means allowing one class called the Child to inherit methods and attributes from another, called the Parent one, so you don't have to write the same code again and again. For example, we can have a class Employee which inherits from Person. This is referred to as an "isa" relationship because an Employee is a Person. Perl has a special variable, @ISA, to help with this. @ISA which governs (method) inheritance. So to create a new Employee class that will inherit methods and attributes from our Person class we simply code:

package Employee; # class Employee
use Person;
use strict;
our @ISA = qw(Person); # inherits from Person

What we have done is load the Person class and declare that Employee class inherits methods from it. We have declared no methods for Employee, but an Employee object will behave just like a Person object. We should be able to write code:

my $khurt = new Employee(); #create Employee class instance

#set object attributes
$khurt->firstName('Khurt');
$khurt->lastName('Williams');

# without any other changes.

Now let's add some methods.

package Employee; # class Employee
use Person;
use strict;
our @ISA = qw(Person); # inherits from Person

sub new { # constructor

    my ($class) = @_;

    #call the constructor of the parent class, Person.
     my $self = $class->SUPER::new();
     $self->{_id} = undef;
     $self->{_title} = undef;
     bless $self, $class;
     return $self; 
 }

sub id {  #accessor method for id
    my ( $self, $id ) = @_;
    $self->{_id} = $id if defined($id);
    return ( $self->{_id} );
}

sub title { #accessor method for title
    my ( $self, $title ) = @_;
    $self->{_title} = $title if defined($title);
    return ( $self->{_title} );
}

sub print {
    my ($self) = @_;
    $self->SUPER::print; # call the print method of the parent class
    $self->address->print;
}
1;

Looking at the code, you will notice that we have a new method and a print method. Both a Child class and its Parent class have the same method defined. We have overridden the Parent class' methods with the ones from the Child. When those methods are called on an Employee object, we will get the Employee class' version of the method. This concept of using the methods of an existing object and modifying them is known as polymorphism.

Putting it together

So now we have a complete set of classes we can write a small program to test them.

use strict; 
use warnings;
use diagnostics; use Employee;

my $khurt = eval { new Employee(); } or die ($@); #create Employee class instance

# set object attributes
$khurt->firstName('Khurt');
$khurt->lastName('Williams'); $khurt->id(1001);
$khurt->title('Executive Director');
$khurt->address( new Address() );
$khurt->address->street('10 Anywhere Lane');
$khurt->address->city('Anytown');
$khurt->address->state('NJ');
$khurt->address->zip('12345');

$khurt->print(); #display Employee info

Let's execute our code and see the output.

$ ./test.pl
Name: Khurt Williams
Address: 10 Anywhere Lane
Anytown, NJ 12345

It works! We covered the basics of object-oriented programming in Perl.

I hope this article was informative and useful.

The post Camel POOP - Perl Object Oriented Programming first appeared 11/12/2002 on Code Project.