Leica Experiments in Leica M8 and M9 Conversions

Brian

Product of the Fifties
I have been experimenting with using color contrast filters with my M8 and writing my own conversion software to work with the channels based on their altered spectral response. With a Yellow Y48 filter, the channels are mostly balanced. With an O56 filter, the Blue channel is at about 1/4th the sensitivity of Green. My code starts with an uncompressed DNG from Arvid's M8RAW2DNG, but I can adapt to to work with the M9. Files must be uncompressed.

What the image looks like in color,

16920711011_15e15bf299_b.jpg
L1015755s
by fiftyonepointsix, on Flickr

16930964225_5bc046f518_b.jpg
M1015769_S
by fiftyonepointsix, on Flickr

For this conversion, the lens has an Orange filter over it, and the post-processor converts it to two colors- then adds the two channels. Blue/Green are "equalized" and used uninterpolated for 3 of the 4 pixels in the Bayer cells, 4th is interpolated from green. The Red channel is interpolated, two channels added.

16747498677_4065be5aa3_b.jpg
M1015790
by fiftyonepointsix, on Flickr
 
I have a new routine for interpreting and modifying the Tags in the "Image File Directory". The function looks up the Tag and keeps it unchanged, changes as necessary, or eliminates it. This converts the color-DNG to a Monochrome-Linear DNG. "Long story Short", I can apply the same process to the M9 uncompressed files.

16955243312_7ef793ae9e_b.jpg
M1015792
by fiftyonepointsix, on Flickr

Another two-channel conversion.
 
I modified the algorithm that equalizes the blue and green channel. The spectral response of the two have about the same shape, but Blue is less sensitive. It's not linear, and the prior algorithm worked on the average values of the two channels before merging them. This algorithm computes the histogram for Blue and Green, then equalizes the Blue values to the corresponding Green value of it's histogram. I thought of this last night, in a dream. So I have color/monochrome conversion dreams.

SUBROUTINE MONOMOSAIC3( IMAGE, RED, GREEN, BLUE,
1 MONOCHROME, COLUMNS, ROWS, FILE)
IMPLICIT NONE
CHARACTER* 12 FILE
C DEMOSAIC ROUTINE OPTIMIZED FOR MONOCHROME CONVERSION.
INTEGER* 4 COLUMNS, ROWS
INTEGER* 2 IMAGE( COLUMNS, ROWS)
INTEGER* 2 RED( COLUMNS, ROWS)
INTEGER* 2 GREEN( COLUMNS, ROWS)
INTEGER* 2 BLUE( COLUMNS, ROWS)
INTEGER* 2 MONOCHROME( COLUMNS, ROWS)
INTEGER* 4 COLUMN, ROW
C+++ BEGIN DOS I/O FUNCTION CODES.
C FILE I/O PARAMETERS AND CODES.
INTEGER* 2 CRTNRM, CRTRD, CRTHDN, CRTSYS
INTEGER* 2 OPNRD, OPNWRT, OPNRDW
INTEGER* 2 FRMBGN, FRMCRN, FRMEND
INTEGER* 2 BADFIL, NORMAL
PARAMETER ( CRTNRM= 0, CRTRD= 1, CRTHDN= 2, CRTSYS= 3)
PARAMETER ( OPNRD= 0, OPNWRT= 1, OPNRDW= 2)
PARAMETER ( FRMBGN= 0, FRMCRN= 1, FRMEND= 2)
PARAMETER ( BADFIL= 0, NORMAL= -1)
C--- END DOS I/O FUNCTION CODES.
INTEGER* 4 MAXPIXEL
PARAMETER ( MAXPIXEL= Z'3FFF')
C HISTOGRAM.
INTEGER* 4 BLUEHIST( 0: MAXPIXEL), GREENHIST( 0: MAXPIXEL)
C INTEGRATE THE CURVE.
REAL* 8 BLUETOTAL( 0: MAXPIXEL), GREENTOTAL( 0: MAXPIXEL)
C MAP BLUE TOTALS TO GREEN TOTALS, SHOULD EQUALIZE THE HISTOGRAM.
REAL* 8 GAINCURVE( 0: MAXPIXEL)
INTEGER* 2 BYTES, HANDLE, STATUS, LENGTH
REAL* 8 SUMGREEN, SUMBLUE, AVERAGE_GREEN, AVERAGE_BLUE, GAIN
INTEGER* 4 PIXELS, MINBLUE, MINGREEN, MAXBLUE, MAXGREEN
INTEGER* 4 I, J, K, L
INTEGER* 4 BIGVALUE
INTEGER* 2 VALUE( 2)
C EXTERNALS.
REAL* 8 DFLOAT
EQUIVALENCE ( BIGVALUE, VALUE)
WRITE( *, *) ' COLUMNS= ', COLUMNS, ', ROWS= ', ROWS
C EQUALIZE BLUE AND GREEN CHANNELS.
SUMBLUE= 0.0D0
SUMGREEN= 0.0D0
MINBLUE= 32768
MINGREEN= 32768
MAXGREEN= 0
MAXBLUE= 0
PIXELS= COLUMNS* ROWS
DO 1 I= 0, MAXPIXEL
BLUEHIST( I)= 0
GREENHIST( I)= 0
BLUETOTAL( I)= 0.0D0
GREENTOTAL( I)= 0.0D0
GAINCURVE( I)= 0.0D0
1 CONTINUE
DO 2 J= 2, ROWS, 2
DO 2 I= 2, COLUMNS, 2
BLUEHIST( IMAGE( I, J))= BLUEHIST( IMAGE( I, J))+ 1
GREENHIST( IMAGE( I- 1, J))= GREENHIST( IMAGE( I- 1, J))+ 1
GREENHIST( IMAGE( I, J- 1))= GREENHIST( IMAGE( I, J- 1))+ 1
IF( IMAGE( I, J) .LT. MINBLUE) MINBLUE= IMAGE( I, J)
IF( IMAGE( I- 1, J) .LT. MINGREEN) MINGREEN= IMAGE( I- 1, J)
IF( IMAGE( I, J- 1) .LT. MINGREEN) MINGREEN= IMAGE( I, J- 1)
IF( IMAGE( I, J) .GT. MAXBLUE) MAXBLUE= IMAGE( I, J)
IF( IMAGE( I- 1, J) .GT. MAXGREEN) MAXGREEN= IMAGE( I- 1, J)
IF( IMAGE( I, J- 1) .GT. MAXGREEN) MAXGREEN= IMAGE( I, J- 1)
2 CONTINUE
BLUETOTAL( 0)= DFLOAT( BLUEHIST( 0))
GREENTOTAL( 0)= DFLOAT( GREENHIST( 0))
DO 3 I= 1, MAXPIXEL
BLUETOTAL( I)= BLUETOTAL( I- 1)+ DFLOAT( BLUEHIST( I))
GREENTOTAL( I)= GREENTOTAL( I- 1)+ DFLOAT( GREENHIST( I))
3 CONTINUE
DO 4 I= 1, MAXPIXEL
BLUETOTAL( I)= BLUETOTAL( I)/ DFLOAT( COLUMNS* ROWS/ 4)
GREENTOTAL( I)= GREENTOTAL( I)/ DFLOAT( COLUMNS* ROWS/ 2)
4 CONTINUE
DO 6 J= 1, MAXPIXEL
C FOR EVERY BLUE INTEGRATED TOTAL, FIND THE GREEN VALUE THAT EXCEEDS IT.
IF( BLUEHIST( J) .GT. 0) THEN
I= J
5 I= I+ 1
IF( I .LT. MAXPIXEL .AND.
1 BLUETOTAL( J) .GT. GREENTOTAL( I)) GO TO 5
IF( J .GT. MINBLUE) THEN
GAINCURVE( J)= DFLOAT( I- MINGREEN)/ DFLOAT( J- MINBLUE)
END IF
END IF
6 CONTINUE
IF( MAXBLUE .GT. MINBLUE) THEN
GAIN= DFLOAT( MAXGREEN- MINGREEN)/
1 DFLOAT( MAXBLUE- MINBLUE)
ELSE
GAIN= 1.0D0
END IF
WRITE( *, *) ' BLUE= ', MAXBLUE, MINBLUE
WRITE( *, *) ' GREEN= ', MAXGREEN, MINGREEN
WRITE( *, *) ' GAIN= ', GAIN
CALL QINKEY( BYTES, HANDLE)
DO 10 J= 2, ROWS, 2
DO 10 I= 2, COLUMNS, 2
IMAGE( I, J)= MINGREEN+
1 INT( DFLOAT( IMAGE( I, J)- MINBLUE)* GAINCURVE( IMAGE( I, J)))
IF( I .EQ. J) THEN
WRITE( *, *) 'IMAGE( ', I, ',', J, ')= ', IMAGE( I, J)
END IF
IF( IMAGE( I, J) .GT. Z'4000') THEN
IMAGE( I, J)= Z'4000'
END IF
10 CONTINUE
DO 15 J= 1, ROWS- 1, 2
DO 15 I= 1, COLUMNS- 1, 2
C AVERAGE CELLS GOING ACROSS THE ROWS.
C ODD NUMBERED ROWS ARE RGRGRGRGRGRGRG
C EVEN NUMBERED ROWS ARE GBGBGBGBGBGBGB
C RED PIXELS START THE COLUMN, AND ARE THE FIRST ROW IN EACH 2x2 GRID
C INTERPOLATE ALL OF THE MISSING VALUES FOR EACH IMAGE PLANE.
RED( I, J)= IMAGE( I, J)
RED( I+ 1, J)= ( IMAGE( I, J)+ IMAGE( I+ 2, J))/2
GREEN( I+ 1, J)= IMAGE( I+ 1, J)
BIGVALUE= IMAGE( I- 1, J)+ IMAGE( I+ 1, J)
BIGVALUE= ( BIGVALUE+ IMAGE( I, J- 1)+ IMAGE( I, J+ 1))/ 4
GREEN( I, J)= VALUE( 1)
C USE THE BLUE PIXEL AS A GREEN PIXEL SINCE WE EQUALIZED IT.
GREEN( I+ 1, J+ 1)= IMAGE( I+ 1, J+ 1)
GREEN( I, J+ 1)= IMAGE( I, J+ 1)
15 CONTINUE
C WE ARE DONE WITH GREEN. NOW WE NEED TO FILL IN RED.
DO 20 J= 1, ROWS- 1, 2
DO 20 I= 1, COLUMNS- 1, 2
C COMPUTE RED VALUE THAT IS UNDER GREEN PIXELS IN GBGBGB ROWS.
RED( I, J+ 1)= ( IMAGE( I, J)+ IMAGE( I, J+ 2))/ 2
C AND UNDER BLUE CELLS.
RED( I+ 1, J+ 1)= ( RED( I+ 1, J)+ RED( I+ 1, J+ 2))/2
20 CONTINUE
C THE VALUES ARE 14-BITS PER PLANE. WE WANT TO ADD THEM, THEN SCALE TO
C 16-BITS.
DO 25 J= 1, ROWS
DO 25 I= 1, COLUMNS
BIGVALUE= RED( I, J)
BIGVALUE= BIGVALUE+ GREEN( I, J)
MONOCHROME( I, J)= VALUE( 1)
IMAGE( I, J)= MONOCHROME( I, J)
25 CONTINUE
RETURN
END​

16770781820_9e8a43ec2a_b.jpg
M1015792
by fiftyonepointsix, on Flickr
 
This is the "Image File Directory" parser that turns the color-DNG file into a Monochrome-Linear file.

BLOCK DATA DNGINIT
C+++ BEGIN / CMDNGTAGS/
INTEGER* 4 DNGTAGS
PARAMETER ( DNGTAGS= 49)
INTEGER* 2 IFD0KEEP, TAGCODES
CHARACTER* 64 TAGNAMES
COMMON/ CMDNGTAGS/ TAGNAMES( DNGTAGS),
1 IFD0KEEP( DNGTAGS), TAGCODES( DNGTAGS)
C--- END / CMDNGTAGS
DATA IFD0KEEP/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1,
1 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
1 1/
DATA TAGCODES/
1 Z'00FE', Z'0100', Z'0101', Z'0102', Z'0103',
1 Z'0106', Z'010F', Z'0110', Z'0111', Z'0112',
1 Z'0115', Z'0116', Z'0117', Z'011A', Z'011B',
1 Z'011C', Z'0128', Z'0131', Z'013B', Z'828D',
1 Z'828E', Z'8298', Z'8769', Z'882B', Z'9003',
1 Z'920E', Z'920F', Z'9210', Z'9216', Z'C612',
1 Z'C614', Z'C619', Z'C61A', Z'C61D', Z'C61F',
1 Z'C620', Z'C621', Z'C622', Z'C623', Z'C624',
1 Z'C628', Z'C62B', Z'C62C', Z'C62D', Z'C62F',
1 Z'C632', Z'C635', Z'C65A', Z'C65B'/
DATA TAGNAMES/
1 'GENERAL INDICATOR', 'NUMBER COLUMNS', 'NUMBER OF ROWS',
1 'BITS PER SAMPLE', 'COMPRESSION',
1 'PHOTOMETRIC INTERPRETATION (MOSAIC/LINEAR)', 'MAKE', 'MODEL',
1 'STRIPOFFSET (START OF IMAGE DATA)',
1 'ORIENTATION ( 01= H, 02= V)', 'SAMPLES PER PIXEL',
1 'ROWS PER STRIP', 'STRIP BYTE COUNT', 'X RESOLUTION',
1 'Y RESOLUTION', 'PLANAR CONFIGURATION', 'RESOLUTION UNIT',
1 'SOFTWARE, NAME AND VERSION', 'ARTIST',
1 'CFAREPEATPATTERN', 'CFAPATTERN', 'COPYRIGHT',
1 'POINTER TO EXIF IFD', 'SELF TIME MODE',
1 'DATE_TIME_ORIG', 'X FOCAL PLANE RESOLUTION',
1 'Y FOCAL PLANE RESOLUTION', 'FOCAL PLANE RESOLUTION UNIT',
1 'TIFF STANDARD', 'DNG VERSION', 'UNIQUE CAMERA MODEL',
1 'BLACKLEVELREPEAT', 'BLACK LEVEL', 'WHITE LEVEL',
1 'DEFAULT CROP ORIGIN', 'DEFAULT CROP SIZE',
1 'COLORMATRIX 1', 'COLORMATRIX 2', 'CAMERACALIBRATION',
1 'CAMERACALIBRATION 2', 'AS SHOT NEUTRAL',
1 'BASELINENOISE', 'BASELINESHARPNESS', 'BAYER GREEN SPLIT',
1 'CAMERA SERIAL NUMBER', 'ANTIALIASSTRENGTH',
1 'MAKERNOTESAFETY', 'CALIBRATION ILLUMINANT1',
1 'CALIBRATION ILLUMINANT2'/
END

INTEGER* 4 FUNCTION IFDCONVERT( IFDTABLE, IFDTABLE4, IFDENTRIES)
IMPLICIT NONE
INTEGER* 2 IFDTABLE( 6, IFDENTRIES)
INTEGER* 4 IFDTABLE4( 3, IFDENTRIES)
C+++ BEGIN / CMDNGTAGS/
INTEGER* 4 DNGTAGS
PARAMETER ( DNGTAGS= 49)
INTEGER* 2 IFD0KEEP, TAGCODES
CHARACTER* 64 TAGNAMES
COMMON/ CMDNGTAGS/ TAGNAMES( DNGTAGS),
1 IFD0KEEP( DNGTAGS), TAGCODES( DNGTAGS)
C--- END / CMDNGTAGS
INTEGER* 4 OLDTAGS, NEWTAGS, TAGINDEX, TAGLIST, TAGSEARCH, INDEX
LOGICAL* 4 FOUND
C SEARCH THROUGH ALL OF THE TAGS PASSED IN.
DO 5 OLDTAGS= 1, IFDENTRIES
C IDENTIFY THE TAG FROM THE KNOWN LIST.
TAGSEARCH= 0
10 CONTINUE
TAGSEARCH= TAGSEARCH+ 1
FOUND= TAGCODES( TAGSEARCH) .EQ. IFDTABLE( 1, OLDTAGS)
IF( ( .NOT. FOUND) .AND. TAGSEARCH .LT. DNGTAGS) GO TO 10
IF( FOUND .AND. IFD0KEEP( TAGSEARCH) .NE. 0) THEN
C KEEP THE TAG.
WRITE( *, *) ' KEEPING TAG ', OLDTAGS
NEWTAGS= NEWTAGS+ 1
IF( TAGCODES( TAGSEARCH) .EQ. Z'0106') THEN
C 6 0106: PHOTOMETRIC INTERP: 8023 FOR COLOR; 884C FOR LINEAR
IFDTABLE( 5, OLDTAGS)= Z'884C'
C 17 0128: RESOLUTION UNIT. 02. THE UNIT OF RESOLUTION FOR X AND FOR Y.
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'0128') THEN
IFDTABLE( 5, OLDTAGS)= 1
C 28 9210: FOCAL PLANE RESOLUTION UNIT, 0002. (LOC 0156 SET TO 1?)
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'9210') THEN
IFDTABLE( 5, OLDTAGS)= 1
C 30 C612: DNG VERSION: 4 BYTES 01 04 00 00 (CHANGE LOC 16F TO 01)
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'C612') THEN
IFDTABLE( 5, OLDTAGS)= Z'0101'
C 33 C61A: BLACK LEVEL: 1 WORD, 005C
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'C61A') THEN
C IFDTABLE( 5, OLDTAGS)= 1
C 34 C61D: WHITE LEVEL: 1 WORD, 3FFF
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'C61D') THEN
IFDTABLE( 5, OLDTAGS)= Z'7FFF'
END IF
IFDTABLE( 1, NEWTAGS)= IFDTABLE( 1, OLDTAGS)
IFDTABLE( 2, NEWTAGS)= IFDTABLE( 2, OLDTAGS)
IFDTABLE( 3, NEWTAGS)= IFDTABLE( 3, OLDTAGS)
IFDTABLE( 4, NEWTAGS)= IFDTABLE( 4, OLDTAGS)
IFDTABLE( 5, NEWTAGS)= IFDTABLE( 5, OLDTAGS)
IFDTABLE( 6, NEWTAGS)= IFDTABLE( 6, OLDTAGS)
ELSE
WRITE( *, *) ' ELIMINATE TAG ', OLDTAGS
END IF
5 CONTINUE
DO 20 INDEX= NEWTAGS+ 1, IFDENTRIES
WRITE( *, *) ' ZERO TAG ', INDEX
IFDTABLE( 1, INDEX)= 0
IFDTABLE( 2, INDEX)= 0
IFDTABLE( 3, INDEX)= 0
IFDTABLE( 4, INDEX)= 0
IFDTABLE( 5, INDEX)= 0
IFDTABLE( 6, INDEX)= 0
20 CONTINUE
IFDCONVERT= NEWTAGS
RETURN
END

The Mosaic algorithm shown here is best for the O56, the Yellow Y48 filter- uses a routine that does the demosaic on the three channels as they are more balanced. The O56 is where things got into the two-channel possibility. I will look at a Yellow-3 Y48 and an R60.

But that's enough code written for one day. Takes less time than you m ight think.​
 
Last edited by a moderator:
This is with a Red filter over the 1934 5cm F2 Sonnar.

16786578269_edc0467687_b.jpg
M1015819
by fiftyonepointsix, on Flickr

Full size Jpegs loaded. The color DNG's are batch converted to Monochrome DNG's using the custom raw processor. For the RED filter, the Blue channel is first equalized to green, then they are multiplied *3 and added to Red. The Blue and Green channels are both sensitive in the red region.

16350527724_217af2a6e7_b.jpg
M1015820
by fiftyonepointsix, on Flickr

16946939866_fec59b22e3_b.jpg
M1015827
by fiftyonepointsix, on Flickr

16786689339_dffd43d030_b.jpg
M1015835
by fiftyonepointsix, on Flickr

This is what the original looks like from the camera,

16977120422_9fe93132f5_b.jpg
L1015835
by fiftyonepointsix, on Flickr
 
Last edited by a moderator:
The R60 filter basically makes all of the pixels "Monochrome", just the response of the Blue and Green channel are lower than red. This is where having 14-bit pixels comes in, Blue and Green have been boosted to be equalized with Red. I'm still making two channels, then adding them. But the channels are close. What I do not see in the 100% crops: the jagged edges seen when using a simulated Red filter with Lightroom. The use of the optical Red filter trades-off boosting the response versus interpolating channels. Green is boosted by multiplying by 3, this was a rough estimate looking at the data sheet for spectral response. "Quick and Dirty", might optimize it later. But... I want to try something different... False color, might give us something like Infrared Ektachrome...

C THE VALUES ARE 14-BITS PER PLANE. WE WANT TO ADD THEM, THEN SCALE TO
C 16-BITS.
DO 25 J= 1, ROWS
DO 25 I= 1, COLUMNS
BIGVALUE= RED( I, J)
BIGVALUE= BIGVALUE+ GREEN( I, J)* 3
IF( BIGVALUE .GT. Z'BFFF') BIGVALUE= Z'BFFF'
IMAGE( I, J)= VALUE( 1)
25 CONTINUE
RETURN
END
 
Last edited by a moderator:
Chased down an interpolation error that caused an "solarization" problem, and have a new section to balance the blue/green combined channel with Red.

16375283413_533bfd8ceb_b.jpg
M1015846
by fiftyonepointsix, on Flickr

This should work with the O56 and R60 filters.

16809175939_44d7525a23_b.jpg
M1015861
by fiftyonepointsix, on Flickr

Full-res Jpegs uploaded. Check the sharpness of the first image, and the intensity range of the second.
 
These images are taken with a Red "R60" filter with a cutoff of 600nm.

The Blue and Green channel are picking up a good bit of Infrared, but it is about 1/10th as strong as the Red channel.

This is what the image looks like out of camera, RED dominates the image.

17002960452_0a7e4958a9_b.jpg
L1015853
by fiftyonepointsix, on Flickr

This is what I am doing by boosting the Blue and Green channels to the intensity level of the Red channel.

17003490831_02a70444d5_b.jpg
C1015853
by fiftyonepointsix, on Flickr

All of these are handheld, 4x filter factor for the RED filter.

This is the image converted directly to Monochrome, Linear-DNG.

16818189979_3239f2bfdf_b.jpg
M1015853
by fiftyonepointsix, on Flickr

This should also work with the Visible-Blocking filter, the R72.
 
I will be cleaning up comments in software and posting the key sections, some have expressed an interest in writing their own software.

As I told a neighbor when she asked what I was doing, "With so many people owning digital cameras, you'd think more people would be writing their own software"... I blame Adobe, their spec for DNG is typical...

I found this site very useful:

TIFF tags

For the DNG file generated by Arvid's M8RAW2DNG: the file has one "Image File Directory" that can be parsed for all the information. OR- the easy way, the image file is stored at the end of the file. It is 3968 pixels per row, 2646 rows, two bytes per pixel. 7936 bytes per red-green row, same for blue-green rows. 20,998,656 bytes per image. Read in the file and use those last bytes for the data. If you use "M8RAW2DNG -s -b00" that I use, the file offset is '06A6'X.

The ".RAW" file that comes out of the camera is 3972 pixels per row, 2646 rows. Raw starts at File Offset '06C'x. The extra 4 pixels are for calibration.
 
I changed the thread title.

This is without a filter,

16832305370_0d9f518569_b.jpg
L1015873
by fiftyonepointsix, on Flickr

This is the in-camera JPEG, with a B&W 090 Red, a 5x filter factor.

16833617439_553797fe29_b.jpg
L1015874
by fiftyonepointsix, on Flickr

With this filter, Red is Red, Green is Green+Infrared, and Blue is Infrared.

My Post Processor boosts Blue and Green to the level of RED.

16832062288_eb273d76bf_b.jpg
I1015874_2
by fiftyonepointsix, on Flickr

This is as close to Infrared Ektachrome as I need.
28mm Elmarit, F4, shutter speeds 1/250th ~ 1/500th, all at ISO160.
 
This is the Fortran subroutine that produces the Infrared Ektachrome file from the M8 with a red filter. This version exchanges Red and Blue channels, so you do not need to rotate the Hue in Photoshop.

SUBROUTINE COLORMOSAIC( IMAGE, COLUMNS, ROWS)
IMPLICIT NONE
C IMAGE GENERATED USING EQUALIZATION.
C 1) COMPUTE HISTOGRAM FOR ALL THREE CHANNELS.
C 2) INTEGRATE AREA UNDER HISTOGRAM AND NORMALIZE.
C 3) EQUALIZE BLUE AND GREEN TO RED.
C 4) EXCHANGE BLUE AND RED CHANNELS.
INTEGER* 4 COLUMNS, ROWS
INTEGER* 2 IMAGE( COLUMNS, ROWS)
INTEGER* 4 COLUMN, ROW
C+++ BEGIN DOS I/O FUNCTION CODES.
INTEGER* 4 MAXPIXEL
PARAMETER ( MAXPIXEL= Z'3FFF')
C HISTOGRAM. STRONGEST WILL BE SET THE THE CHANNEL THAT IS THE FIRST TO
C REACH A THRESHOLD
REAL* 8 INTEGRATION_CUTOFF
PARAMETER ( INTEGRATION_CUTOFF= 0.95D0)
INTEGER* 4 BLUECHANNEL, GREENCHANNEL, REDCHANNEL
PARAMETER ( BLUECHANNEL= 1, GREENCHANNEL= 2, REDCHANNEL= 3)
INTEGER* 4 HISTOGRAM( 0: MAXPIXEL, 3)
INTEGER* 4 BLUEHIST( 0: MAXPIXEL), GREENHIST( 0: MAXPIXEL)
INTEGER* 4 REDHIST( 0: MAXPIXEL), CUTOFF( 3), STRONGEST
EQUIVALENCE ( HISTOGRAM( 0, BLUECHANNEL), BLUEHIST)
EQUIVALENCE ( HISTOGRAM( 0, GREENCHANNEL), GREENHIST)
EQUIVALENCE ( HISTOGRAM( 0, REDCHANNEL), REDHIST)
C INTEGRATE THE CURVE.
REAL* 8 IMAGETOTAL( 0: MAXPIXEL, 3)
REAL* 8 BLUETOTAL( 0: MAXPIXEL), GREENTOTAL( 0: MAXPIXEL)
REAL* 8 REDTOTAL( 0: MAXPIXEL)
EQUIVALENCE( IMAGETOTAL( 0, BLUECHANNEL), BLUETOTAL)
EQUIVALENCE( IMAGETOTAL( 0, GREENCHANNEL), GREENTOTAL)
EQUIVALENCE( IMAGETOTAL( 0, REDCHANNEL), REDTOTAL)
C MAP BLUE TOTALS TO GREEN TOTALS, SHOULD EQUALIZE THE HISTOGRAM.
REAL* 8 GAINCURVE( 0: MAXPIXEL)
INTEGER* 2 BYTES, HANDLE, STATUS, LENGTH, EXCHANGE
REAL* 8 SUMGREEN, SUMBLUE, AVERAGE_GREEN, AVERAGE_BLUE, GAIN
INTEGER* 4 PIXELS, MINBLUE, MINGREEN, MAXBLUE, MAXGREEN
INTEGER* 4 MINRED, MAXRED
INTEGER* 4 I, J, K, L
C FORTRAN METHOD OF GETTING LOW-ORDER WORD FROM A 32-BIT VALUE.
INTEGER* 4 BIGVALUE
INTEGER* 2 VALUE( 2)
C EXTERNALS.
INTEGER* 2 MOD
REAL* 8 DFLOAT, DMIN1
EQUIVALENCE ( BIGVALUE, VALUE)
WRITE( *, *) ' COLUMNS= ', COLUMNS, ', ROWS= ', ROWS
C EQUALIZE BLUE AND GREEN CHANNELS TO RED. CAN CHANGE LATER TO USE
C THE STONGEST CHANNEL.
SUMBLUE= 0.0D0
SUMGREEN= 0.0D0
MINBLUE= 32768
MINGREEN= 32768
MINRED= 32768
MAXGREEN= 0
MAXBLUE= 0
MAXRED= 0
PIXELS= COLUMNS* ROWS
C INITIALIZE ARRAYS.
DO 1 I= 0, MAXPIXEL
BLUEHIST( I)= 0
GREENHIST( I)= 0
REDHIST( I)= 0
BLUETOTAL( I)= 0.0D0
GREENTOTAL( I)= 0.0D0
REDTOTAL( I)= 0.0D0
GAINCURVE( I)= 0.0D0
1 CONTINUE
C COMPUTE HISTOGRAMS AND FIND MAX/MIN OF EACH CHANNEL.
DO 2 J= 2, ROWS, 2
DO 2 I= 2, COLUMNS, 2
BLUEHIST( IMAGE( I, J))= BLUEHIST( IMAGE( I, J))+ 1
GREENHIST( IMAGE( I- 1, J))= GREENHIST( IMAGE( I- 1, J))+ 1
GREENHIST( IMAGE( I, J- 1))= GREENHIST( IMAGE( I, J- 1))+ 1
REDHIST( IMAGE( I- 1, J- 1))= REDHIST( IMAGE( I- 1, J- 1))+ 1
IF( IMAGE( I, J) .LT. MINBLUE) MINBLUE= IMAGE( I, J)
IF( IMAGE( I- 1, J- 1) .LT. MINRED) MINRED= IMAGE( I- 1, J- 1)
IF( IMAGE( I- 1, J) .LT. MINGREEN) MINGREEN= IMAGE( I- 1, J)
IF( IMAGE( I, J- 1) .LT. MINGREEN) MINGREEN= IMAGE( I, J- 1)
IF( IMAGE( I, J) .GT. MAXBLUE) MAXBLUE= IMAGE( I, J)
IF( IMAGE( I- 1, J) .GT. MAXGREEN) MAXGREEN= IMAGE( I- 1, J)
IF( IMAGE( I, J- 1) .GT. MAXGREEN) MAXGREEN= IMAGE( I, J- 1)
IF( IMAGE( I- 1, J- 1) .GT. MAXRED) MAXRED= IMAGE( I- 1, J- 1)
IF( IMAGE( I- 1, J- 1) .LT. MINRED) MINRED= IMAGE( I- 1, J- 1)
2 CONTINUE
C NOW INTEGRATE THE AREA UNDER THE HISTOGRAMS FOR EACH COLOR.
BLUETOTAL( 0)= DFLOAT( BLUEHIST( 0))
GREENTOTAL( 0)= DFLOAT( GREENHIST( 0))
REDTOTAL( 0)= DFLOAT( REDHIST( 0))
CUTOFF( REDCHANNEL)= -1
CUTOFF( BLUECHANNEL)= -1
CUTOFF( GREENCHANNEL)= -1
WRITE( *, *) ' BLUE= ', MAXBLUE, MINBLUE
WRITE( *, *) ' GREEN= ', MAXGREEN, MINGREEN
WRITE( *, *) ' RED= ', MAXRED, MINRED
DO 3 I= 1, MAXPIXEL
BLUETOTAL( I)= BLUETOTAL( I- 1)+ DFLOAT( BLUEHIST( I))
GREENTOTAL( I)= GREENTOTAL( I- 1)+ DFLOAT( GREENHIST( I))
REDTOTAL( I)= REDTOTAL( I- 1)+ DFLOAT( REDHIST( I))
3 CONTINUE
C NORMALIZE THE CURVES.
DO 4 I= 1, MAXPIXEL
BLUETOTAL( I)= BLUETOTAL( I)/ DFLOAT( COLUMNS* ROWS/ 4)
REDTOTAL( I)= REDTOTAL( I)/ DFLOAT( COLUMNS* ROWS/ 4)
GREENTOTAL( I)= GREENTOTAL( I)/ DFLOAT( COLUMNS* ROWS/ 2)
C LOOK FOR THE 95%.
IF( BLUETOTAL( I) .GT. INTEGRATION_CUTOFF .AND.
1 CUTOFF( BLUECHANNEL) .EQ. -1) THEN
WRITE( *, *) 'BLUETOTAL( ', I, ')= ', BLUETOTAL( I)
CUTOFF( BLUECHANNEL)= I
END IF
IF( GREENTOTAL( I) .GT. INTEGRATION_CUTOFF .AND.
1 CUTOFF( GREENCHANNEL) .EQ. -1) THEN
WRITE( *, *) 'GREENTOTAL( ', I, ')= ', GREENTOTAL( I)
CUTOFF( GREENCHANNEL)= I
END IF
IF( REDTOTAL( I) .GT. INTEGRATION_CUTOFF .AND.
1 CUTOFF( REDCHANNEL) .EQ. -1) THEN
WRITE( *, *) 'REDTOTAL( ', I, ')= ', REDTOTAL( I)
CUTOFF( REDCHANNEL)= I
END IF
4 CONTINUE
C ADJUST THE BLUE CURVE TO MATCH RED.
DO 20 J= 1, MAXPIXEL
C FOR EVERY BLUE INTEGRATED TOTAL, FIND THE RED VALUE THAT EXCEEDS IT.
IF( BLUEHIST( J) .GT. 0) THEN
I= 1
5 I= I+ 1
IF( I .LT. MAXPIXEL .AND.
1 BLUETOTAL( J) .GT. REDTOTAL( I)) GO TO 5
GAINCURVE( J)= DFLOAT( I)/ DFLOAT( J)
END IF
20 CONTINUE
C SCALE BLUE CHANNEL TO RED RESPONSE CURVE.
K= MIN0( MINRED, MINBLUE)
DO 35 J= 2, ROWS, 2
DO 35 I= 2, COLUMNS, 2
BIGVALUE= IMAGE( I, J)
BIGVALUE= INT( DFLOAT( BIGVALUE)* GAINCURVE( BIGVALUE))
IF( BIGVALUE .GT. Z'03FFF') BIGVALUE= Z'03FFF'
IMAGE( I, J)= VALUE( 1)
35 CONTINUE
C EQUALIZE GREEN TO RED.
DO 41 J= 1, MAXPIXEL
C FOR EVERY GREEN INTEGRATED TOTAL, FIND THE RED VALUE THAT EXCEEDS IT.
I= 1
42 I= I+ 1
IF( I .LT. MAXPIXEL .AND.
1 GREENTOTAL( J) .GT. REDTOTAL( I)) GO TO 42
GAINCURVE( J)= DFLOAT( I)/ DFLOAT( J)
C WRITE( *, *) ' GAIN( ', J, ')= ', GAINCURVE( J)
41 CONTINUE
K= MIN0( MINRED, MINGREEN)
DO 40 J= 1, ROWS- 1, 2
DO 40 I= 1, COLUMNS- 1, 2
C EXCHANGE BLUE AND RED.
EXCHANGE= IMAGE( I, J)
IMAGE( I, J)= IMAGE( I+ 1, J+ 1)
IMAGE( I+ 1, J+ 1)= EXCHANGE
C GREEN PIXEL ON RG ROW
BIGVALUE= IMAGE( I+ 1, J)
BIGVALUE= INT( DFLOAT( BIGVALUE)* GAINCURVE( BIGVALUE))
IF( BIGVALUE .GT. Z'03FFF') BIGVALUE= Z'03FFF'
IMAGE( I+ 1, J)= VALUE( 1)
C GREEN VALUE OF THE GBGBGBGB ROW.
BIGVALUE= IMAGE( I, J+ 1)
BIGVALUE= INT( DFLOAT( BIGVALUE)* GAINCURVE( BIGVALUE))
IF( BIGVALUE .GT. Z'03FFF') BIGVALUE= Z'03FFF'
IMAGE( I, J+ 1)= VALUE( 1)
40 CONTINUE
WRITE( *, *) ' FINISHED COLORMOSAIC'
RETURN
END
 
Last edited by a moderator:
This function parses the IFD and gets the number of columns and rows, and gets the starting point of the image data.

INTEGER* 4 FUNCTION IFDVERS( IFDTABLE, IFDTABLE4, IFDENTRIES)
IMPLICIT NONE
INTEGER* 2 IFDTABLE( 6, IFDENTRIES)
INTEGER* 4 IFDTABLE4( 3, IFDENTRIES)
C+++ BEGIN / CMDNGTAGS/
INTEGER* 4 DNGTAGS
PARAMETER ( DNGTAGS= 50)
INTEGER* 2 IFD0KEEP, TAGCODES
CHARACTER* 64 TAGNAMES
INTEGER* 4 TCOLUMNS, TROWS, TBITS, TIMAGESTART, TSUBIFD
INTEGER* 4 TXRES, TYRES, TCFAREPEAT, TCFAPATTERN
COMMON/ CMDNGTAGS/ TAGNAMES( DNGTAGS), IFD0KEEP( DNGTAGS),
1 TAGCODES( DNGTAGS), TCOLUMNS, TROWS, TBITS, TIMAGESTART,
1 TSUBIFD, TXRES, TYRES, TCFAREPEAT, TCFAPATTERN
C--- END / CMDNGTAGS
INTEGER* 4 OLDTAGS, NEWTAGS, TAGINDEX, TAGLIST, TAGSEARCH, INDEX
INTEGER* 4 TAGLENGTH
INTEGER* 2 I, J
LOGICAL* 4 FOUND
INTEGER* 4 STRLEN
NEWTAGS= 0
C SEARCH THROUGH ALL OF THE TAGS PASSED IN.
DO 5 OLDTAGS= 1, IFDENTRIES
C IDENTIFY THE TAG FROM THE KNOWN LIST.
TAGSEARCH= 0
10 CONTINUE
TAGSEARCH= TAGSEARCH+ 1
FOUND= TAGCODES( TAGSEARCH) .EQ. IFDTABLE( 1, OLDTAGS)
IF( ( .NOT. FOUND) .AND. TAGSEARCH .LT. DNGTAGS) GO TO 10
IF( FOUND .AND. IFD0KEEP( TAGSEARCH) .NE. 0) THEN
TAGLENGTH= STRLEN( TAGNAMES( TAGSEARCH))
C 30 C612: DNG VERSION: 4 BYTES 01 04 00 00 (CHANGE LOC 16F TO 01)
IF( TAGCODES( TAGSEARCH) .EQ. Z'C612') THEN
IFDTABLE( 5, OLDTAGS)= Z'0101'
C 33 C61A: BLACK LEVEL: 1 WORD, 005C
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'C61A') THEN
IFDTABLE( 5, OLDTAGS)= Z'020'
C 34 C61D: WHITE LEVEL: 1 WORD, 3FFF
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'C61D') THEN
CFOR MOSAIC5 IFDTABLE( 5, OLDTAGS)= Z'BFFF'
IFDTABLE( 5, OLDTAGS)= Z'3FFF'
C NUMBER OF COLUMNS.
C Z'0100', Z'0101', Z'0102'
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'0100') THEN
TCOLUMNS= IFDTABLE( 5, OLDTAGS)
WRITE( *, 100) OLDTAGS, TAGCODES( TAGSEARCH),
1 TAGNAMES( TAGSEARCH)( 1: TAGLENGTH), TCOLUMNS
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'0101') THEN
TROWS= IFDTABLE( 5, OLDTAGS)
WRITE( *, 100) OLDTAGS, TAGCODES( TAGSEARCH),
1 TAGNAMES( TAGSEARCH)( 1: TAGLENGTH), TROWS
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'0102') THEN
TBITS= IFDTABLE( 5, OLDTAGS)
WRITE( *, 100) OLDTAGS, TAGCODES( TAGSEARCH),
1 TAGNAMES( TAGSEARCH)( 1: TAGLENGTH), TBITS
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'0111') THEN
TIMAGESTART= IFDTABLE4( 3, OLDTAGS)
WRITE( *, 100) OLDTAGS, TAGCODES( TAGSEARCH),
1 TAGNAMES( TAGSEARCH)( 1: TAGLENGTH), TIMAGESTART
ELSE IF( TAGCODES( TAGSEARCH) .EQ. Z'014A') THEN
TSUBIFD= IFDTABLE4( 3, OLDTAGS)
WRITE( *, 100) OLDTAGS, TAGCODES( TAGSEARCH),
1 TAGNAMES( TAGSEARCH)( 1: TAGLENGTH), TSUBIFD
END IF
100 FORMAT( ' IFD ENTRY ', I3, ': ', Z4.4, ', "', A, '":', Z8)
END IF
5 CONTINUE
C CALL QINKEY( I, J)
RETURN
END
 
This is the code snippet that uses the routines, the DNG file has been read into byte array "BUFFER", BUFFER( 10) is the location of the IFD in the DNG file from m8raw2dng. Function IFDVERS fills in TIMAGESTART, TCOLUMNS, and TROWS.

The M9 and M Monochrom store two images per DNG, the first is the thumbnail and second is main image. IFDVERS must be called twice, the second time after the position of the "SUBIFD" is revealed.

NEWTAGS= IFDVERS( BUFFER( 10), BUFFER( 10), OLDTAGS)
CALL COLORMOSAIC( BUFFER( TIMAGESTART), TCOLUMNS, TROWS)

This is the direct output from the new routine that exchanges Red and Blue. No "Photoshop" except to resize.

17025132765_fa30bd111b_o.jpg
Join to see EXIF info for this image (if available)
I1015874 by fiftyonepointsix, on Flickr
 
Last edited by a moderator:
I took the M8 out today to Gunston Hall, then to the Playground.

I think this is the only camera that can be used like this. I have two full-spectrum cameras, one monochrome, the other color.

The M8 allows Red to be used for Visible-Red, Green to be used for Red+Infrared, and Blue to be used for Infrared only. This is similar to color Infrared film.

FORTRAN Output Files...

28mm Elmarit with the B&W 090 filter.

16826256047_1d2d7e8237_o.jpg
Join to see EXIF info for this image (if available)
I1015897 by fiftyonepointsix, on Flickr


16847445039_0fd5166a72_o.jpg
Join to see EXIF info for this image (if available)
I1015881 by fiftyonepointsix, on Flickr

16826256277_5f46aaffd4_o.jpg
Join to see EXIF info for this image (if available)
I1015880 by fiftyonepointsix, on Flickr
 
This code is strictly for color filters - no other kind of filter? (excuse the dumb question, but that's what it looks like)
 
These routines are for images taken with color filters over the lens, they manipulate the RGB channels, boosting intensity of channels effected by the color filters. The Spectral response of the RGB channels extend much farther out than do traditional color separation filters. The monochrome conversion uses reduced interpolation. The Monochrome conversion method probably works best with Y52 and O56 filters. The second set of routines are used for "Infrared Ektachrome" type effects when a Red filter is used, I will also try with the O56. The latter should cut out Blue light resulting in only the Blue channel being mostly composed of Infrared. The routine simply equalizes the histograms of the three channels and exchanges Blur and Red for False color, as IR Ektachrome did.

The first routines interpolate the image and convert to linear-monochrome. "monomosaic" series of routines. IFDCONVERT convertes the DNG file from mosaic to linear.

The second set of routines are for the Infrared.

The two are very related, I need to clean up the latest Monochrome conversion routine and will repost.

I will also look for a compiler that runs under Win7. Right now: I am using Microway FORTRAN-77, PharLap Extended DOS, and win98 booted into real-mode DOS. I love running DOS programs that take 500MBytes of memory... It's FAST! Lightning Fast! This is why I probably stuck with Film for personal use for all these years. I spent the 1980s writing code for multi-spectral imagers. My DOS computer is much faster than the Floating Point Systems FPS-120b array processor. Which caught on fire running one of my codes. Overloaded the power supplies. My Boss understood, and we had gotten way more than our $130K out of it by then.
 
Last edited:
I've always used the WinXP virtual PC under Windows 7 (have not tried Windows 8). Normally that XP box won't allow XP to 'see' the main computer as drives E, F, G etc., but I found a way to use Terminal Services from that XP window that does allow that.
 
Back
Top