focusinspector 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/FI-Test-Chart.pdf +0 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +92 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/focusinspector +5 -0
- data/focusinspector.gemspec +76 -0
- data/lib/focusinspector.rb +20 -0
- data/lib/focusinspector/AppConfig.rb +71 -0
- data/lib/focusinspector/Camera.rb +209 -0
- data/lib/focusinspector/FocusInspector.rb +67 -0
- data/lib/focusinspector/ImageMarker.rb +48 -0
- data/lib/focusinspector/ImageViewer.rb +26 -0
- data/lib/focusinspector/Log.rb +32 -0
- data/lib/focusinspector/SharpnessMeter.rb +245 -0
- data/spec/focusinspector_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +169 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/FI-Test-Chart.pdf
ADDED
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
|
data/Gemfile.lock
ADDED
@@ -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)
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/bin/focusinspector
ADDED
@@ -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
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -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: []
|