focusinspector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
Binary file
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", ">= 2.8.0"
10
+ gem "rdoc", ">= 3.9"
11
+ gem "bundler", ">= 1.0.0"
12
+ gem "jeweler", ">= 1.8.4"
13
+ gem "rmagick", ">= 2.13.1"
14
+ gem "mini_exiftool", ">= 1.6.0"
15
+ end
@@ -0,0 +1,33 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ mini_exiftool (1.6.0)
12
+ rake (0.9.2.2)
13
+ rdoc (3.9.4)
14
+ rmagick (2.13.1)
15
+ rspec (2.11.0)
16
+ rspec-core (~> 2.11.0)
17
+ rspec-expectations (~> 2.11.0)
18
+ rspec-mocks (~> 2.11.0)
19
+ rspec-core (2.11.1)
20
+ rspec-expectations (2.11.3)
21
+ diff-lcs (~> 1.1.3)
22
+ rspec-mocks (2.11.2)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (>= 1.0.0)
29
+ jeweler (>= 1.8.4)
30
+ mini_exiftool (>= 1.6.0)
31
+ rdoc (>= 3.9)
32
+ rmagick (>= 2.13.1)
33
+ rspec (>= 2.8.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Chris Schlaeger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,92 @@
1
+ = focusinspector
2
+
3
+ Simple utility for DSLR photographers. It allows you to overlay the
4
+ used focus points over the image. It can also be used to fine tune
5
+ your lens focus.
6
+
7
+ == Installation
8
+
9
+ Before you install this software, make sure you have the following
10
+ dependencies installed:
11
+
12
+ * ImageMagick: You need to install the header files (-devel package) as well. (http://www.imagemagick.org/script/index.php)
13
+ * exiftool: Needed to extract EXIF information from images. (http://owl.phy.queensu.ca/~phil/exiftool/)
14
+ * dcraw: Needed to extract the JPG thumbnail from raw files. (http://www.cybercom.net/~dcoffin/dcraw/)
15
+ * gwenview: This is the default image viewer of the KDE environment. You can use any other software that can show JPG files but you need to use the --viewer option to specify it on the command line.
16
+ * ruby 1.9: Focus Inspector is written in Ruby (http://www.ruby-lang.org/en/)
17
+
18
+ All these package can be found on the commonly used Linux distros.
19
+ You can easily install them with the package manager.
20
+
21
+ Additionally, you need to have the following ruby gem packages installed:
22
+
23
+ * rmagick
24
+ * mini_exiftool
25
+
26
+ These will be automatically installed when you install Focus
27
+ Inspector as a gem package.
28
+
29
+ gem install focusinspector
30
+
31
+ == Usage
32
+
33
+ Focus Inspector has 3 usage modes:
34
+
35
+ === Show focus grid and active focus points
36
+
37
+ Display a JPG or raw file with focus grid overlay. The file needs
38
+ to have proper EXIF information.
39
+
40
+ focusinspector show DSC_1234.NEF
41
+
42
+ If you don't have KDE installed, you need to use the --viewer option
43
+ to specify the JPG viewer to be used.
44
+
45
+ === Measure sharpness of the image of the FI test chart
46
+
47
+ This only works with an photograph of the Focus Inspector test chart.
48
+ The test pattern must be surrounding the active focus point and
49
+ should roughly cover a quarter of the total image.
50
+
51
+ focusinspector measure DSC_1234.NEF
52
+
53
+ The result is a sharpness value in percent. This is not really an
54
+ absolute value, but it can be used to compare sharpness of images
55
+ that were taken under the same lighting conditions with the same test
56
+ chart.
57
+
58
+ === List some focusing relevant EXIF data
59
+
60
+ Just for convenience.
61
+
62
+ focusinspector list DSC_1234.NEF
63
+
64
+ == Supported Cameras
65
+
66
+ Currently, only the following cameras are supported:
67
+
68
+ * Nikon D5100
69
+ * Nikon D300 and D300s
70
+ * Nikon D800 and D800E
71
+
72
+ Adding other cameras is fairly straight forward. The location of all
73
+ focus points needs to be added to lib/focusinspector/Camera.rb.
74
+ Unfortunately, not all EXIF tags are standardized across camera
75
+ vendors. Adding support for other vendors than Nikon might require
76
+ some additional work to find and parse the right EXIF tags.
77
+
78
+ == Contributing to focusinspector
79
+
80
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
81
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
82
+ * Fork the project.
83
+ * Start a feature/bugfix branch.
84
+ * Commit and push until you are happy with your contribution.
85
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
86
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
87
+
88
+ == Copyright and lincense
89
+
90
+ Copyright (c) 2012 Chris Schlaeger. See LICENSE.txt for
91
+ further details.
92
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "focusinspector"
18
+ gem.homepage = "http://github.com/scrapper/focusinspector"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Utility for DSLR photographers}
21
+ gem.description = %Q{Allows you to view active focus points in taken images and to fine tune your lenses}
22
+ gem.email = "chris@linux.com"
23
+ gem.authors = ["Chris Schlaeger"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "focusinspector #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require File.basename(__FILE__)
5
+
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "focusinspector"
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Schlaeger"]
12
+ s.date = "2012-10-13"
13
+ s.description = "Allows you to view active focus points in taken images and to fine tune your lenses"
14
+ s.email = "chris@linux.com"
15
+ s.executables = ["focusinspector"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rspec",
23
+ "FI-Test-Chart.pdf",
24
+ "Gemfile",
25
+ "Gemfile.lock",
26
+ "LICENSE.txt",
27
+ "README.rdoc",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "bin/focusinspector",
31
+ "focusinspector.gemspec",
32
+ "lib/focusinspector.rb",
33
+ "lib/focusinspector/AppConfig.rb",
34
+ "lib/focusinspector/Camera.rb",
35
+ "lib/focusinspector/FocusInspector.rb",
36
+ "lib/focusinspector/ImageMarker.rb",
37
+ "lib/focusinspector/ImageViewer.rb",
38
+ "lib/focusinspector/Log.rb",
39
+ "lib/focusinspector/SharpnessMeter.rb",
40
+ "spec/focusinspector_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = "http://github.com/scrapper/focusinspector"
44
+ s.licenses = ["MIT"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = "1.8.23"
47
+ s.summary = "Utility for DSLR photographers"
48
+
49
+ if s.respond_to? :specification_version then
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
54
+ s.add_development_dependency(%q<rdoc>, [">= 3.9"])
55
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
56
+ s.add_development_dependency(%q<jeweler>, [">= 1.8.4"])
57
+ s.add_development_dependency(%q<rmagick>, [">= 2.13.1"])
58
+ s.add_development_dependency(%q<mini_exiftool>, [">= 1.6.0"])
59
+ else
60
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
61
+ s.add_dependency(%q<rdoc>, [">= 3.9"])
62
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
63
+ s.add_dependency(%q<jeweler>, [">= 1.8.4"])
64
+ s.add_dependency(%q<rmagick>, [">= 2.13.1"])
65
+ s.add_dependency(%q<mini_exiftool>, [">= 1.6.0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
69
+ s.add_dependency(%q<rdoc>, [">= 3.9"])
70
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
71
+ s.add_dependency(%q<jeweler>, [">= 1.8.4"])
72
+ s.add_dependency(%q<rmagick>, [">= 2.13.1"])
73
+ s.add_dependency(%q<mini_exiftool>, [">= 1.6.0"])
74
+ end
75
+ end
76
+
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ require 'rubygems'
12
+ require 'tempfile'
13
+ require 'optparse'
14
+ require 'RMagick'
15
+ require 'mini_exiftool'
16
+
17
+ require 'focusinspector/FocusInspector'
18
+
19
+ FocusInspector.new(ARGV)
20
+
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class AppConfig
12
+
13
+ attr_reader :imageFile, :command, :viewer
14
+
15
+ def initialize(args)
16
+ @imageFile = nil
17
+ @command = nil
18
+ @viewer = 'gwenview'
19
+
20
+ version = IO.read(File.expand_path(File.dirname(__FILE__) +
21
+ "/../../VERSION")).strip
22
+
23
+ opts = OptionParser.new
24
+ opts.banner = <<"EOT"
25
+ Focus Inspector v#{version} (c) Copyright 2012 by Chris Schlaeger
26
+
27
+ Usage: focusinspector [options] <command> <ImageFile>
28
+ EOT
29
+
30
+ opts.on('--viewer <viewer>', 'Image viewer to use') do |v|
31
+ @viewer = v
32
+ end
33
+
34
+ opts.on_tail('-h', '--help', 'Show this message') do
35
+ puts opts
36
+ end
37
+
38
+ opts.separator ""
39
+ opts.separator <<"EOT"
40
+ Supported commands are:
41
+ show : Show image with focus points overlay
42
+ measure : Measure the sharpness of a photo of the test chart
43
+ list : List some focusing information of the image
44
+
45
+ EOT
46
+
47
+ opts.order!
48
+ @command = ARGV[0]
49
+ unless @command
50
+ Log.error('Command is missing')
51
+ puts opts
52
+ end
53
+ unless %w( show measure list ).include?(command)
54
+ Log.error("Unknown command #{@command}")
55
+ puts opts
56
+ end
57
+
58
+ @imageFile = ARGV[1]
59
+ unless @imageFile
60
+ Log.error('Image file name missing.')
61
+ puts opts
62
+ end
63
+
64
+ unless File.exists?(@imageFile)
65
+ Log.error("Cannot find image file #{@imageFile}")
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class Camera
12
+
13
+ def initialize(imageFile)
14
+ @imageFile = imageFile
15
+ @cameras = {
16
+ 'NIKON D300' =>
17
+ begin
18
+ h = {}
19
+ xSkip = 0.0676
20
+ ySkip = 0.095
21
+ # Rows A to E.
22
+ (-2).upto(2) do |row|
23
+ # Rows A and E have only 9 points, the others have 11.
24
+ cols = row.abs == 2 ? 4 : 5
25
+ (-cols).upto(cols) do |col|
26
+ h["#{(?A.ord + row + 2).chr}#{col + cols + 1}"] =
27
+ [ 0.5 + col * xSkip, 0.5 + row * ySkip ]
28
+ end
29
+ end
30
+ h['C6 (Center)'] = h['C6']
31
+ h
32
+ end,
33
+ 'NIKON D5100' =>
34
+ begin
35
+ x = [ 0.225, 0.330, 0.5 ]
36
+ y = [ 0.295, 0.385, 0.5 ]
37
+ x[3] = x[2] + (x[2] - x[1])
38
+ x[4] = x[2] + (x[2] - x[0])
39
+ y[3] = y[2] + (y[2] - y[1])
40
+ y[4] = y[2] + (y[2] - y[0])
41
+ {
42
+ 'Far Left' => [ x[0], y[2] ],
43
+ 'Upper-left' => [ x[1], y[1] ],
44
+ 'Mid-left' => [ x[1], y[2] ],
45
+ 'Lower-left' => [ x[1], y[3] ],
46
+ 'Top' => [ x[2], y[0] ],
47
+ 'Center' => [ x[2], y[2] ],
48
+ 'Bottom' => [ x[2], y[4] ],
49
+ 'Upper-right' => [ x[3], y[1] ],
50
+ 'Mid-right' => [ x[3], y[2] ],
51
+ 'Lower-right' => [ x[3], y[3] ],
52
+ 'Far Right' => [ x[4], y[2] ]
53
+ }
54
+ end,
55
+ 'NIKON D800' =>
56
+ begin
57
+ h = {}
58
+ # Rows A to E.
59
+ (-2).upto(2) do |row|
60
+ # Rows A and E have only 9 points, the others have 11.
61
+ cols = row.abs == 2 ? 4 : 5
62
+ (-cols).upto(cols) do |col|
63
+ # The 3 center columns have a wider x and y spread.
64
+ if col.abs <= 1
65
+ xSkip = 0.053
66
+ xOff = 0.0
67
+ ySkip = 0.075
68
+ else
69
+ xSkip = 0.048
70
+ xOff = 0.011 * (col < 0 ? -1 : 1)
71
+ ySkip = 0.0659
72
+ end
73
+ h["#{(?A.ord + row + 2).chr}#{col + cols + 1}"] =
74
+ [ 0.5 + xOff + col * xSkip, 0.5 + row * ySkip ]
75
+ end
76
+ end
77
+ h['C6 (Center)'] = h['C6']
78
+ h
79
+ end
80
+ }
81
+ # Add some aliases for similar cameras. Just a guess right now.
82
+ @cameras['NIKON D300S'] = @cameras['NIKON D300']
83
+ @cameras['NIKON D800E'] = @cameras['NIKON D800']
84
+
85
+ unless (@exif = MiniExiftool.new(@imageFile))
86
+ Log.error("Image file #{imageFile} has no EXIF information")
87
+ end
88
+ unless (modelName = @exif['Model'])
89
+ Log.error("No camera model name found in image file #{imageFile}")
90
+ end
91
+ unless (@focusPoints = @cameras[modelName])
92
+ Log.error("Camera #{modelName} is not supported.")
93
+ end
94
+ @width = @exif['ImageWidth'].to_i
95
+ @height = @exif['ImageHeight'].to_i
96
+ @cx = @width / 2
97
+ @cy = @height / 2
98
+ end
99
+
100
+ def focusAreaSize
101
+ if (afW = @exif['AFAreaWidth']) && (afH = @exif['AFAreaHeight'])
102
+ [ afW, afH ]
103
+ else
104
+ # We currently use a 27th of the screen width and height. This might have
105
+ # to be made camera specific.
106
+ [ @width / 27, @height / 27 ]
107
+ end
108
+ end
109
+
110
+ def orientation
111
+ @exif['Orientation'] || "Horizontal (normal)"
112
+ end
113
+
114
+ def contrastDetectAF?
115
+ @exif['ContrastDetectAF'] == 'On'
116
+ end
117
+
118
+ def focusPoint
119
+ @exif['Primary_AF_Point']
120
+ end
121
+
122
+ def focusFineTune
123
+ @exif['AFFineTuneAdj']
124
+ end
125
+
126
+ def printDetails
127
+ puts "Focus Mode: #{@exif['FocusMode']}"
128
+ puts "Contrast Detection: #{@exif['ContrastDetectAF']}"
129
+ puts "Phase Detection: #{@exif['PhaseDetectAF']}"
130
+ puts "AF Fine Tune: #{@exif['AFFineTune']}"
131
+ puts "AF Fine Tune Adjustment: #{@exif['AFFineTuneAdj']}"
132
+ puts "Focus Distance: #{@exif['FocusDistance']}"
133
+ puts "Depth Of Field: #{@exif['DOF']}"
134
+ puts "Hyperfocal Distance: #{@exif['HyperfocalDistance']}"
135
+ end
136
+
137
+ def primaryAutoFocusPoint
138
+ if @exif['ContrastDetectAF'] != 'On'
139
+ if (primaryAFP = @exif['Primary_AF_Point']) == "(none)" or primaryAFP.nil?
140
+ Log.error("Image has no primary focus point information")
141
+ end
142
+
143
+ unless (coords = fpCoords(primaryAFP))
144
+ Log.error("Unknown focus point #{primaryAFP}")
145
+ end
146
+ return coords
147
+ else
148
+ unless (x = @exif['AFAreaXPosition'])
149
+ Log.error('Contrast detect focus X position not found')
150
+ end
151
+ x = x.to_i
152
+ unless (y = @exif['AFAreaYPosition'])
153
+ Log.error('Contrast detect focus Y position not found')
154
+ end
155
+ y = y.to_i
156
+
157
+ return [ x, y ]
158
+ end
159
+ end
160
+
161
+ def activeFocusPoints
162
+ return [] if @exif['ContrastDetectAF'] == 'On'
163
+
164
+ if (fps = @exif['AFPointsUsed']) == "(none)" or fps.nil?
165
+ Log.error("Image has no focus point information")
166
+ end
167
+
168
+ allFPs = @focusPoints.keys.join(',')
169
+
170
+ coords = []
171
+ fps.split(',').each do |fp|
172
+ unless (xy = fpCoords(fp))
173
+ Log.error("Unknown focus point #{fp}")
174
+ end
175
+ coords << xy
176
+ end
177
+ coords
178
+ end
179
+
180
+ def inactiveFocusPoints
181
+ return [] if @exif['ContrastDetectAF'] == 'On'
182
+
183
+ if (fps = @exif['AFPointsUsed']) == "(none)" or fps.nil?
184
+ Log.error("Image has no focus point information")
185
+ end
186
+ fps = fps.split(',')
187
+
188
+ allFPs = @focusPoints.keys
189
+
190
+ coords = []
191
+ (allFPs - fps).each do |fp|
192
+ coords << fpCoords(fp)
193
+ end
194
+ coords
195
+ end
196
+
197
+ private
198
+
199
+ def fpCoords(tag)
200
+ x, y = @focusPoints[tag]
201
+ unless x && y
202
+ Log.error("Unknown focus point #{tag} found in image file")
203
+ end
204
+ [ (x * @width).to_i, (y * @height).to_i ]
205
+ end
206
+
207
+ end
208
+
209
+
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ require 'focusinspector/Log'
12
+ require 'focusinspector/AppConfig'
13
+ require 'focusinspector/Camera'
14
+ require 'focusinspector/SharpnessMeter'
15
+ require 'focusinspector/ImageMarker'
16
+ require 'focusinspector/ImageViewer'
17
+
18
+ class FocusInspector
19
+
20
+ def initialize(args)
21
+ @config = AppConfig.new(args)
22
+
23
+ @imageFile = @config.imageFile
24
+ # Conver the raw file to jpg if we have one.
25
+ convertRaw
26
+
27
+ focusInfo = Camera.new(@imageFile)
28
+ x, y = focusInfo.primaryAutoFocusPoint
29
+
30
+ case @config.command
31
+ when 'list'
32
+ focusInfo.printDetails
33
+ when 'measure'
34
+ sm = SharpnessMeter.new(@jpgFile)
35
+ puts "Sharpness: %.2f%% (%s)" % [ (sm.measure(x, y) * 100.0),
36
+ focusInfo.contrastDetectAF? ? 'Contrast Detection AF' :
37
+ "FP: #{focusInfo.focusPoint} AFFT: #{focusInfo.focusFineTune}" ]
38
+ when 'show'
39
+ im = ImageMarker.new(@jpgFile, *focusInfo.focusAreaSize, @config.viewer)
40
+ im.mark(x, y, 'red', true)
41
+
42
+ activeFPxy = focusInfo.activeFocusPoints
43
+ inactiveFPxy = focusInfo.inactiveFocusPoints
44
+
45
+ activeFPxy.each { |xy| im.mark(*xy, 'red') }
46
+ inactiveFPxy.each { |xy| im.mark(*xy, 'grey') }
47
+
48
+ im.show(focusInfo.orientation)
49
+ end
50
+ end
51
+
52
+ def convertRaw
53
+ if @imageFile[-4..-1] == '.NEF' || @imageFile[-4..-1] == '.nef'
54
+ @tmpJPGfile = Tempfile.new('lenstuner-jpg')
55
+ @tmpJPGfile.close
56
+ @jpgFile = @tmpJPGfile.path
57
+ command = "dcraw -c -e #{@imageFile} > #{@jpgFile}"
58
+ unless system(command)
59
+ Log.error("Cannot execute '#{command}'")
60
+ end
61
+ else
62
+ @jpgFile = @imageFile
63
+ end
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class ImageMarker
12
+
13
+ def initialize(imageFile, w, h, viewer)
14
+ begin
15
+ @image = Magick::Image.read(imageFile).first
16
+ # The size of the auto focus area.
17
+ @w = w
18
+ @h = h
19
+ rescue
20
+ Log.error("Cannot open image file #{imageFile}")
21
+ end
22
+ @viewer = viewer
23
+ end
24
+
25
+ def mark(x, y, color, bold = false)
26
+ painter = Magick::Draw.new
27
+ painter.fill_opacity(0.0)
28
+ painter.stroke(color)
29
+ painter.stroke_width = bold ? 13 : 9
30
+ painter.rectangle(x - @w / 2, y - @h / 2, x + @w / 2, y + @h / 2)
31
+ painter.draw(@image)
32
+ end
33
+
34
+ def show(orientation)
35
+ # Since we have created a new image, the orientation of the original got
36
+ # lost. We simply rotate the image to match the original orientation.
37
+ case orientation
38
+ when 'Rotate 90 CW'
39
+ @image.rotate!(90)
40
+ when 'Rotate 270 CW'
41
+ @image.rotate!(270)
42
+ end
43
+
44
+ ImageViewer.new(@image, @viewer)
45
+ end
46
+
47
+ end
48
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class ImageViewer
12
+
13
+ def initialize(image, viewer)
14
+ # Write the marked version of the original image to a temporary file.
15
+ file = Tempfile.new('lenstuner')
16
+ image.write('jpeg:' + file.path)
17
+ file.close
18
+ # Start viewer to display the marked image.
19
+ command = "#{viewer} #{file.path} 2> /dev/null"
20
+ unless system(command)
21
+ Log.error("Cannot execute '#{command}'")
22
+ end
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class Log
12
+
13
+ #def initialize(logLevel)
14
+ @@level = 1
15
+ #end
16
+
17
+ def Log::error(msg)
18
+ $stderr.puts "\nERROR: #{msg}"
19
+ exit 1
20
+ end
21
+
22
+ def Log::warn(msg)
23
+ $stderr.puts "\nWARNING: #{msg}" if @@level > 0
24
+ end
25
+
26
+ def Log::debug(msg)
27
+ puts msg if @@level > 1
28
+ end
29
+
30
+ end
31
+
32
+
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # Focus Inspector - The focus inspection and lens calibration software.
5
+ #
6
+ # Copyright (c) 2012 by Chris Schlaeger <chris@linux.com>
7
+ #
8
+ # This program is Open Source software; you can redistribute it and/or modify
9
+ # it under the terms of MIT license as shipped with this software.
10
+
11
+ class SharpnessMeter
12
+
13
+ def initialize(imageFile)
14
+ @imageFile = imageFile
15
+ @minSquareSize = 100
16
+ end
17
+
18
+ def measure(x, y)
19
+ image = Magick::Image.read(@imageFile).first
20
+
21
+ x, y, sqSize = findBlackSquare(image, x, y)
22
+ crop = cropQuadrants(image, x, y)
23
+ #ImageViewer.new(image)
24
+ colorHistogram = crop.color_histogram
25
+ bwHistogram = convertHistToBW(colorHistogram)
26
+ calcSharpness(bwHistogram, crop)
27
+ end
28
+
29
+ private
30
+
31
+ def color(image, x, y, radius = 2)
32
+ blackCount = 0
33
+ whiteCount = 0
34
+ greyCount = 0
35
+ (-radius).upto(radius) do |xi|
36
+ (-radius).upto(radius) do |yi|
37
+ col = image.pixel_color(x + xi, y + yi)
38
+ val = (col.red + col.green + col.blue) / 3
39
+ if val < 0x6000
40
+ blackCount += 1
41
+ elsif val > 0xA000
42
+ whiteCount += 1
43
+ else
44
+ greyCount += 1
45
+ end
46
+ end
47
+ end
48
+ if blackCount > whiteCount && blackCount > greyCount
49
+ :black
50
+ elsif whiteCount > blackCount && whiteCount > greyCount
51
+ :white
52
+ else
53
+ :grey
54
+ end
55
+ end
56
+
57
+ def showMarkedImage(image, x, y, w, h)
58
+ painter = Magick::Draw.new
59
+ painter.opacity(0)
60
+ painter.stroke('red')
61
+ painter.stroke_width = 9
62
+ painter.rectangle(x, y, x + w, y + h)
63
+ painter.draw(image)
64
+
65
+ ImageViewer.new(image)
66
+ exit
67
+ end
68
+
69
+ def findEdge(image, x, y, dx, dy)
70
+ startCol = color(image, x, y)
71
+ raise 'Starting point must not be grey.' if startCol == :grey
72
+
73
+ while (c = color(image, x, y)) == :grey || c == startCol
74
+ if c == startCol
75
+ sx = x
76
+ sy = y
77
+ end
78
+ x += dx
79
+ y += dy
80
+
81
+ if x < 0 || x >= image.columns || y < 0 || y >= image.rows
82
+ Log.error("Cannot detect the edge of the square (#{dx}, #{dy}).")
83
+ end
84
+ end
85
+ ex = x
86
+ ey = y
87
+
88
+ # The edge is located right in the middle of the grey zone.
89
+ [ sx + (ex - sx) / 2, sy + (ey - sy) / 2 ]
90
+ end
91
+
92
+ def findBlackSquare(image, x, y)
93
+ # Find a pixel inside of a black square by moving along a spiral line.
94
+ stepInc = 11
95
+ steps = stepInc
96
+ stepCount = 0
97
+ dir = :right
98
+ while color(image, x, y) != :black
99
+ case dir
100
+ when :right
101
+ x += steps
102
+ dir = :down unless stepCount % 2 == 0
103
+ when :down
104
+ y += steps
105
+ dir = :left unless stepCount % 2 == 0
106
+ when :left
107
+ x -= steps
108
+ dir = :up unless stepCount % 2 == 0
109
+ when :up
110
+ y -= steps
111
+ dir = :right unless stepCount % 2 == 0
112
+ end
113
+ if stepCount < 3
114
+ stepCount += 1
115
+ else
116
+ stepCount = 0
117
+ steps += stepInc
118
+ end
119
+ Log.debug("x: #{x} y: #{y}")
120
+ if x < 0 || x >= image.columns || y < 0 || y >= image.rows
121
+ Log.error("No black square found.")
122
+ end
123
+ end
124
+ #image.crop!(x - 15, y - 15, 30, 30)
125
+ #ImageViewer.new(image)
126
+ #exit
127
+
128
+ blackSqX0 = findEdge(image, x, y, -1, 0)[0]
129
+ blackSqX1 = findEdge(image, x, y, 1, 0)[0]
130
+ blackSqY0 = findEdge(image, x, y, 0, -1)[1]
131
+ blackSqY1 = findEdge(image, x, y, 0, 1)[1]
132
+
133
+ # Compute the edge length of the square.
134
+ sqWidth = blackSqX1 - blackSqX0 + 1
135
+ sqHeight = blackSqY1 - blackSqY0 + 1
136
+
137
+ Log.debug("Square size is #{sqWidth} x #{sqHeight}")
138
+ if sqWidth < @minSquareSize || sqHeight < @minSquareSize
139
+ Log.error("The size (#{sqWidth} x #{sqHeight}) of the detected " +
140
+ "black square is too small.")
141
+ end
142
+
143
+ #showMarkedImage(image, blackSqX0, blackSqY0, sqWidth, sqHeight)
144
+
145
+ [ blackSqX0, blackSqY0, (sqWidth + sqHeight) / 2 ]
146
+ end
147
+
148
+ def cropQuadrants(image, bx, by)
149
+ probeSize = @minSquareSize
150
+ # Return a probeSize sized rectangle with the top left corner of the
151
+ # black square in the center spot.
152
+ radius = @minSquareSize / 2
153
+ x = bx - radius
154
+ y = by - radius
155
+
156
+ #showMarkedImage(image, x, y, probeSize, probeSize)
157
+ unless color(image, x, y, radius) == :black
158
+ Log.error("Top-left quadrant is not black")
159
+ end
160
+ unless color(image, x, y + @minSquareSize, radius) == :white
161
+ Log.error("Bottom-left quadrant is not white")
162
+ end
163
+ unless color(image, x + @minSquareSize, y + @minSquareSize,
164
+ radius) == :black
165
+ Log.error("Bottom-right quadrant is not black")
166
+ end
167
+ unless color(image, x + @minSquareSize, y, radius) == :white
168
+ Log.error("Top-right quadrant is not white")
169
+ end
170
+
171
+ image.crop(x, y, probeSize, probeSize)
172
+ end
173
+
174
+ def convertHistToBW(cHist)
175
+ bwHist = Hash.new(0)
176
+
177
+ cHist.each do |rgb, val|
178
+ # The color histogram contains the values for 16 bit RGB colors.
179
+ bwHist[(rgb.red + rgb.green + rgb.blue) / 3] += val
180
+ end
181
+
182
+ #bwHist.each { |i, v | Log.debug("Color #{i}: #{v}") }
183
+ bwHist
184
+ end
185
+
186
+ def findHistogramPeak(hist, black)
187
+ peakIndex = nil
188
+ if black
189
+ sIdx = 0
190
+ eIdx = 0x7FFF
191
+ else
192
+ sIdx = 0x8000
193
+ eIdx = 0xFFFF
194
+ end
195
+
196
+ sIdx.upto(eIdx) do |i|
197
+ if peakIndex.nil? || hist[peakIndex] < hist[i]
198
+ peakIndex = i
199
+ end
200
+ end
201
+
202
+ peakIndex
203
+ end
204
+
205
+ def calcSharpness(bwHist, image)
206
+ peakBlackIndex = findHistogramPeak(bwHist, true)
207
+ peakWhiteIndex = findHistogramPeak(bwHist, false)
208
+
209
+ hEdge = 0
210
+ image.columns.times do |c|
211
+ hEdge += edgeSharpness(image, c, 0, 0, 1)
212
+ end
213
+ hEdge /= image.columns
214
+
215
+ vEdge = 0
216
+ image.rows.times do |r|
217
+ vEdge += edgeSharpness(image, 0, r, 1, 0)
218
+ end
219
+ vEdge /= image.rows
220
+
221
+ maxEdge = peakWhiteIndex - peakBlackIndex
222
+
223
+ ((hEdge + vEdge) / 2.0) / maxEdge
224
+ end
225
+
226
+ def edgeSharpness(image, x, y, dx, dy)
227
+ biggestDelta = 0
228
+ while (x >= 0 && x < image.columns && y >= 0 && y < image.rows)
229
+ nx = x + dx
230
+ ny = y + dy
231
+ col = image.pixel_color(x, y)
232
+ val1 = (col.red + col.green + col.blue) / 3
233
+ col = image.pixel_color(nx, ny)
234
+ val2 = (col.red + col.green + col.blue) / 3
235
+ delta = (val1 - val2).abs
236
+ biggestDelta = delta if biggestDelta < delta
237
+ x = nx
238
+ y = ny
239
+ end
240
+
241
+ biggestDelta
242
+ end
243
+
244
+ end
245
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Focusinspector" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'focusinspector'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: focusinspector
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Schlaeger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.8.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.8.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '3.9'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '3.9'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: jeweler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.8.4
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.4
78
+ - !ruby/object:Gem::Dependency
79
+ name: rmagick
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 2.13.1
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 2.13.1
94
+ - !ruby/object:Gem::Dependency
95
+ name: mini_exiftool
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 1.6.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 1.6.0
110
+ description: Allows you to view active focus points in taken images and to fine tune
111
+ your lenses
112
+ email: chris@linux.com
113
+ executables:
114
+ - focusinspector
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - LICENSE.txt
118
+ - README.rdoc
119
+ files:
120
+ - .document
121
+ - .rspec
122
+ - FI-Test-Chart.pdf
123
+ - Gemfile
124
+ - Gemfile.lock
125
+ - LICENSE.txt
126
+ - README.rdoc
127
+ - Rakefile
128
+ - VERSION
129
+ - bin/focusinspector
130
+ - focusinspector.gemspec
131
+ - lib/focusinspector.rb
132
+ - lib/focusinspector/AppConfig.rb
133
+ - lib/focusinspector/Camera.rb
134
+ - lib/focusinspector/FocusInspector.rb
135
+ - lib/focusinspector/ImageMarker.rb
136
+ - lib/focusinspector/ImageViewer.rb
137
+ - lib/focusinspector/Log.rb
138
+ - lib/focusinspector/SharpnessMeter.rb
139
+ - spec/focusinspector_spec.rb
140
+ - spec/spec_helper.rb
141
+ homepage: http://github.com/scrapper/focusinspector
142
+ licenses:
143
+ - MIT
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ segments:
155
+ - 0
156
+ hash: 841599558763575183
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 1.8.23
166
+ signing_key:
167
+ specification_version: 3
168
+ summary: Utility for DSLR photographers
169
+ test_files: []