Detailed documentation
Pixel mapping and hot pixels
Hot pixels are bright pixels that show up in images from digital cameras. Most digital cameras produce them but they don't show up unless you use the camera with manual settings and a long capture time. When using a digital camera in Auto mode it recognizes when these pixels are likely to show and will usually apply a noise reduction algorithm to the image data before writing the data to you memory card. This time can be measured in seconds and increases signficantly the time before you can take your next photograph. For this reason many digital camera manufacturers usually provide the configuation option to turn noise reduction off.
If you are like me though, I tend to use my digital camera in manual mode nearly all of the time. I usually forget to turn noise reduction on when working with long capture times (film camera users know this as shutter speed) and I end up with some of my pictures containing a number of hot pixels.
I decided to add the ability to remove these hot pixels mostly for my own benefit, and the reason it had not appeared until the free release was because this feature jumped above other ideas that might have been more compelling if I was still trying to sell iMagine Photo.
Hot pixels are sometimes just known as bad pixels, but bad pixels is a broader term that also includes pixels that stay dark even when they should be bright.
To be able to remove hot pixels from your images you can either apply a generic noise reduction algorithm or you can determine where the hot pixels are and then remove the hot pixels. iMagine Photo allows you to determine where the hot pixels are and allows you to use bilinear interpolation to remove the hot pixels. Each camera has different hot pixels. Each cameras hot pixels stay fairly constant over time. The longer the capture time the more hot pixels you are likely to have in your digital photos. The process described below describes the process that I have set up for myself to remove hot pixels.
I have never really played with the ISO setting of my digitial camera so the following procedure is based around exposure time only and a constant ISO setting.
Firstly I took a number of images using my digitial camera with capture times of (1/15, 1/8, 1/4, 1/2, 1, 2, and 4 seconds) all with the lens cap left on the camera. If you can't leave the lens cap on then find some way to take pictures so that no light can enter the camera.
Once I had taken these photos I rendered the photos and mapped the bad pixels using the following AppleScript.
You can open the following script in your script editor by clicking here. On my G4 1GHz Titanium Powerbook and a 4 mega pixel digital image the get bad pixels line takes about 50 seconds to finish.
on run
set thisFile to choose file with prompt "Select an image file to map hot pixels:"
tell application "iMagine Photo"
--set thisImporter to import graphic file "Kevin:Mac OS 9:Temp pictures:DSCN2059.JPG"
set thisImporter to import graphic thisFile
if the last operation error of thisImporter is not equal to 0 then
return
end if
set {x, y, xDim, yDim} to the natural bounds of thisImporter
set thisDocument to make new window document with properties {dimensions:{xDim, yDim}}
set the drawing destination of thisImporter to thisDocument
draw thisImporter
tell thisDocument
set badPixelRects to get bad pixels using rules {adjacent to:3, different by:5000, color channels:any channel, bounds rectangle:{0, 0, xDim, yDim}}
end tell
close thisDocument
close thisImporter
end tell
badPixelRects
end run
When I run the above script in Script Editor the above script produces the result: {{829, 1269, 832, 1272}} and indicates that I have 4 hot pixels in the contained rectangle from an image taken with a capture time of 1/15 of a second. The four numbers in the brackets represent left edge, top edge, right edge, and bottom edge of the rectangle respectively. However when applying the bilinear interpolation composition element it was necessary to increase the rectangle to cover a larger area because there is some bleeding from the hot pixels into the surrounding pixels. The hot pixels also produce some jpeg artefacts. After a bit of trial and error I found that the rectangle that best removed the hot pixels is: {{827, 1269, 833, 1275}}
I then use the above script on each of the images taken with specified exposure times.
For the image taken with a capture time of 1/8 of a second I get the result: {{829, 1269, 832, 1273}}, and I found that {{827, 1269, 834, 1275}} best removed the effects of the hot pixels.
For the image taken with a capture time of 1/4 of a second I get the result: {{1368, 19, 1371, 22}, {829, 1269, 834, 1275}}, and I found that {{1367, 19, 1372, 23}, {827, 1269, 834, 1275}} best removed the effects of the hot pixels.
For the image taken with a capture time of 1/2 of a second I get the result: {{1367, 19, 1372, 23}, {339, 945, 342, 948}, {826, 1268, 835, 1275}, {1782, 1413, 1785, 1416}}, and I found that {{1367, 19, 1372, 23}, {338, 945, 342, 950}, {826, 1268, 835, 1277}, {1780, 1413, 1788, 1419}} best removed the effects of the hot pixels.
and I continued this way.
1 second: {{1365, 19, 1374, 28}, {657, 167, 662, 173}, {338, 944, 342, 951}, {825, 1268, 836, 1277}, {1780, 1413, 1787, 1419}} removed the effects of the hot pixels.
2 seconds: {{1364, 18, 1374, 27}, {1480, 108, 1485, 113}, {655, 166, 664, 174}, {337, 943, 344, 951}, {341, 950, 342, 951}, {825, 1268, 836, 1277}, {1779, 1412, 1788, 1421}} removed the effects of the hot pixels.
4 seconds: {{1575, 9, 1579, 13}, {1364, 18, 1374, 27}, {1480, 108, 1486, 114}, {655, 166, 664, 174}, {1253, 389, 1257, 392}, {394, 704, 396, 706}, {336, 942, 345, 952}, {825, 1268, 836, 1277}, {1779, 1412, 1789, 1421}, {63, 1620, 66, 1623}}removed the effects of the hot pixels.
The process to here took me about 4 hours. However once at this point and using the following script I can remove the hot pixels by just running the following script, which can be opened in Script Editor by clicking here.
-- There is nothing copyright about this script, the copyright message is only there to demonstrate how to add a copyright message to the exif data.
property createWindowDocument : true -- if false then creates a graphic document which processes image files without displaying them.
property theAuthor : "Kevin Meaney"
property theCopyright : "Copyright Kevin Meaney 2005, Oxford, UK"
property beRecursive : true -- set to false if you dont want to process files in subfolders.
property FifteenthOfSecBadPixelRects : {{827, 1269, 833, 1275}}
property EigthOfSecBadPixelRects : {{827, 1269, 834, 1275}}
property QuarterOfSecBadPixelRects : {{1367, 19, 1372, 23}, {827, 1269, 834, 1275}}
property HalfOfSecBadPixelRects : {{1367, 19, 1372, 23}, {338, 945, 342, 950}, {826, 1268, 835, 1277}, {1780, 1413, 1788, 1419}}
property OneSecondBadPixelRects : {{1365, 19, 1374, 28}, {657, 167, 662, 173}, {338, 944, 342, 951}, {825, 1268, 836, 1277}, {1780, 1413, 1787, 1419}}
property TwoSecondBadPixelRects : {{1364, 18, 1374, 27}, {1480, 108, 1485, 113}, {655, 166, 664, 174}, {337, 943, 344, 951}, {341, 950, 342, 951}, {825, 1268, 836, 1277}, {1779, 1412, 1788, 1421}}
property FourSecondBadPixelRects : {{1575, 9, 1579, 13}, {1364, 18, 1374, 27}, {1480, 108, 1486, 114}, {655, 166, 664, 174}, {1253, 389, 1257, 392}, {394, 704, 396, 706}, {336, 942, 345, 952}, {825, 1268, 836, 1277}, {1779, 1412, 1789, 1421}, {63, 1620, 66, 1623}}
on run
--swap the commented out lines below if you want to process files in a folder instead of just a single file
--set theFolder to choose folder
--MyPrivateOpen({theFolder})
set theFile to choose file
ProcessFile(theFile, theFile) -- by having the input file the same as the output file the contents will be replaced.
end run
on ProcessFile(inFile, outfile)
tell application "iMagine Photo"
set theResult to my OpenGraphicsImporter(inFile)
if validImporter of theResult is false then return
set thisImporter to openedImporter of theResult
set exifData to the exif data of thisImporter
set thisDocument to missing value
-- set theExporter to missing value
set theResult to my RemoveBadPixels(thisImporter, exifData)
if didCreateDocument of theResult is false then
-- If you didn't want to add copyright and author information to the exif data replace the following two lines with the two
-- lines commented out below them.
tell thisImporter to make exporter with properties {export file type:"JPEG", export file location:outfile, export resolution:{72.0, 72.0}}
set theExporter to thisImporter
-- close thisImporter
-- return
else
set thisDocument to theDocument of theResult
set the export file location of thisDocument to outfile
set theExporter to thisDocument
end if
-- The following line demonstrates how to add author and copyright to the exif data.
set exifData to my AddCopyrightAndAuthorToExifData(theCopyright, theAuthor, exifData)
set the export exif data of theExporter to the exifData
-- Since this is kind of a new final version it is best to save at the highest quality setting, to minimize the jpeg aretfacts.
set the export compression quality of theExporter to lossless
export theExporter
if thisDocument is not equal to missing value then
close thisDocument
end if
close thisImporter
end tell
end ProcessFile
on RemoveBadPixels(thisImporter, exifData)
tell application "iMagine Photo"
set exposureTime to my GetExposureTime(exifData)
if exposureTime < 0.01 then -- Exposure time is short - don't need to modify pixels.
return {didCreateDocument:false}
end if
set {x, y, xDim, yDim} to the natural bounds of thisImporter
set thisDocument to my CreateDocument({xDim, yDim})
set the drawing destination of thisImporter to thisDocument
draw thisImporter
set badPixelRects to my GetBadPixelsRects(exposureTime)
tell thisDocument to create composition element with properties {class:bilinearInterpoloate, bounds rectangles:badPixelRects}
return {didCreateDocument:true, theDocument:thisDocument}
end tell
end RemoveBadPixels
on OpenGraphicsImporter(inFile)
tell application "iMagine Photo"
set thisImporter to import graphic inFile
if the last operation error of thisImporter is not equal to 0 then
close thisImporter
return {validImporter:false}
end if
return {validImporter:true, openedImporter:thisImporter}
end tell
end OpenGraphicsImporter
on CreateDocument(theDimensions)
tell application "iMagine Photo"
if createWindowDocument is true then
return make new window document with properties {dimensions:theDimensions}
else
return make new graphic document with properties {dimensions:theDimension}
end if
end tell
end CreateDocument
on GetExposureTime(exifData)
using terms from application "iMagine Photo"
repeat with exifItem in exifData
if the exif type of exifItem is exposure time in seconds then
return exif float of exifItem
end if
end repeat
end using terms from
return 0.0
end GetExposureTime
on GetBadPixelsRects(exposureTime)
if exposureTime < (1.0 / 15.0) then
return FifteenthOfSecBadPixelRects
else if exposureTime < 0.125 then -- 1.0 / 4.0
return EigthOfSecBadPixelRects
else if exposureTime < 0.25 then -- 1.0 / 4.0
return QuarterOfSecBadPixelRects
else if exposureTime < 0.5 then -- 1.0 / 2.0
return HalfOfSecBadPixelRects
else if exposureTime < 1.0 then
return OneSecondBadPixelRects
else if exposureTime < 2.0 then
return TwoSecondBadPixelRects
else
return FourSecondBadPixelRects
end if
end GetBadPixelsRects
on AddCopyrightAndAuthorToExifData(copyrightMessage, authorName, exifData)
using terms from application "iMagine Photo"
set copyrightRecord to {exif type:copyright, exif unicode:copyrightMessage}
copy copyrightRecord to the end of exifData
set authorRecord to {exif type:artist, exif unicode:authorName}
copy authorRecord to the end of exifData
return exifData
end using terms from
end AddCopyrightAndAuthorToExifData
on MyPrivateOpen(DroppedFiles)
repeat with i from 1 to the number of items in DroppedFiles
set sourceFile to item i in DroppedFiles
if folder of (info for sourceFile) is false then
ProcessFile(sourceFile, sourceFile)
end if
end repeat
if beRecursive then
repeat with i from 1 to the number of items in DroppedFiles
set theItem to item i in DroppedFiles
if folder of (info for theItem) is true then
set folderList to list folder theItem without invisibles
set myList to {}
repeat with j from 1 to the number of items in folderList
if the first character of (item j in folderList) is not "." then
set tempFile to (theItem as string) & ¬
(item j in folderList) as alias
set end of myList to tempFile
end if
end repeat
my MyPrivateOpen(myList)
end if
end repeat
end if
end MyPrivateOpen
