Commit 63782ff5 authored by Andrey Filippov's avatar Andrey Filippov

initial release

parent f233f5d1
......@@ -12,12 +12,12 @@
<relativePath />
</parent>
<groupId>sc.fiji</groupId>
<artifactId>Process_Pixels</artifactId>
<groupId>com.elphel</groupId>
<artifactId>imagej-elphel</artifactId>
<version>1.0.0</version>
<name>plugins/Process_Pixels.jar</name>
<description>A Maven project implementing an ImageJ 1.x plugin</description>
<name>plugins/ImageJ_Elphel.jar</name>
<description>A Maven project implementing imagej-elphel plugin</description>
<dependencies>
<dependency>
......@@ -25,6 +25,18 @@
<artifactId>ij</artifactId>
<version>${imagej1.version}</version>
</dependency>
<dependency>
<groupId>org.kie.modules</groupId>
<artifactId>
org-apache-commons-configuration-main
</artifactId>
<version>6.1.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.kie.modules</groupId>
<artifactId>org-apache-commons-lang-main</artifactId>
<version>6.1.0.Beta2</version>
</dependency>
</dependencies>
<build>
......@@ -46,8 +58,8 @@
</testResources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<groupId>com.elphel</groupId>
<artifactId>imagej-elphel</artifactId>
<executions>
<execution>
<goals>
......@@ -64,12 +76,12 @@
<developers>
<developer>
<id>dscho</id>
<name>Johannes Schindelin</name>
<email>johannes.schindelin@gmx.de</email>
<url>http://loci.wisc.edu/people/johannes-schindelin</url>
<organization>UW-Madison LOCI</organization>
<organizationUrl>http://loci.wisc.edu/</organizationUrl>
<id>AndreyFilippov</id>
<name>Andrey Filippov</name>
<email>andrey@elphel.com</email>
<url>http://blog.elphel.com/andrey</url>
<organization>Elphel, Inc.</organization>
<organizationUrl>http://www3.elphel.com</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
......@@ -87,10 +99,10 @@
</repositories>
<scm>
<connection>scm:git:git://github.com/imagej/minimal-ij1-plugin</connection>
<developerConnection>scm:git:git@github.com:imagej/minimal-ij1-plugin</developerConnection>
<connection>scm:git:git://github.com/Elphel/imagej-elphel</connection>
<developerConnection>sscm:git:https://github.com/Elphel/imagej-elphel</developerConnection>
<tag>HEAD</tag>
<url>https://github.com/imagej/minimal-ij1-plugin</url>
<url>https://github.com/Elphel/imagej-elpheln</url>
</scm>
</project>
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
import ij.IJ;
import ij.io.OpenDialog;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
public class CalibrationFileManagement {
public static String DEFAULT_DIRECTORY=null;
/*
public String getLatestName(FileFilter filter, String dirPath){
File dFile=new File(dirPath);
if (!dFile.isDirectory()) {
String msg="Could not find directory with saved SFE configuration: "+dirPath;
// IJ.showMessage(msg);
System.out.println("Error: "+msg);
return null;
}
File [] files=dFile.listFiles(filter);
}
*/
/** ======================================================================== */
public static String selectDirectory(boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(false, save,true, title, button, filter,defaultPath); // always open dialog
}
/**
*
* @param smart if true, and defaultPath matches criteria, return it and do not open dialog
* @param save file/directory for writing, new OK
* @param title Dialog title
* @param button Name on the Button
* @param filter Selection filter
* @param defaultPath default path
* @return selected directory name
*/
public static String selectDirectory(boolean smart, boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(smart, save,true, title, button, filter,defaultPath);
}
public static String [] selectDirectories(boolean save, String title, String button, FileFilter filter, String [] defaultPaths) {
return selectDirectoriesOrFiles(save,true, title, button, filter, defaultPaths);
}
public static String selectFile(boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(false, save,false, title, button, filter, defaultPath ); // always open dialog
}
public static String selectFile(boolean smart, boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(smart, save,false, title, button, filter, defaultPath );
}
public static String [] selectFiles(boolean save, String title, String button, FileFilter filter, String [] defaultPaths) {
return selectDirectoriesOrFiles(save,false, title, button, filter, defaultPaths );
}
/** ======================================================================== */
public static String [] selectDirectoriesOrFiles(boolean save,
boolean directory,
String title,
String button,
FileFilter filter,
String [] defaultPaths) {
File dir=null;
String defaultPath=null;
File [] files=null;
int fileNum;
if ((defaultPaths!=null) && (defaultPaths.length>0)) {
File [] tfiles=new File [defaultPaths.length];
int nf=defaultPaths.length;
for (fileNum=0;fileNum<defaultPaths.length; fileNum++) {
tfiles[fileNum]=new File(defaultPaths[fileNum]);
if ((!tfiles[fileNum].exists()) ||(!tfiles[fileNum].isFile())) {
tfiles[fileNum]=null;
nf--;
}
}
files=new File[nf];
nf=0;
for (fileNum=0;fileNum<defaultPaths.length; fileNum++) if (tfiles[fileNum]!=null){
files[nf++]=tfiles[fileNum];
}
}
if ((defaultPaths!=null) && (defaultPaths.length>0) && (!defaultPaths[0].equals(""))) {
defaultPath=defaultPaths[0];
dir = new File(defaultPath);
}
if ((dir==null) || (!dir.exists())) {
if (DEFAULT_DIRECTORY!=null) {
defaultPath = DEFAULT_DIRECTORY;
dir = new File(defaultPath);
}
}
if ((dir==null) || (!dir.exists())) {
defaultPath = OpenDialog.getDefaultDirectory();
if (defaultPath!=null) dir = new File(defaultPath);
}
if ((dir!=null) && (!dir.exists())) dir=null;
if ((dir!=null) && (!dir.isDirectory())){
dir=dir.getParentFile();
}
//getSelectedFiles
JFileChooser fc= new JFileChooser();
fc.setFileSelectionMode(directory?JFileChooser.DIRECTORIES_ONLY:JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(true);
if ((title!=null) && (title.length()>0)) fc.setDialogTitle(title);
if ((button!=null) && (button.length()>0)) fc.setApproveButtonText(button);
if (filter!=null) fc.setFileFilter(filter) ;
if (dir!=null) fc.setCurrentDirectory(dir);
fc.setSelectedFiles(files);
int returnVal = save?(fc.showSaveDialog(IJ.getInstance())):(fc.showOpenDialog(IJ.getInstance()));
if (returnVal!=JFileChooser.APPROVE_OPTION) return null;
DEFAULT_DIRECTORY=fc.getCurrentDirectory().getPath();
files=fc.getSelectedFiles();
if (files.length<1) return null;
String [] filenames=new String[files.length];
// for (int nFile=0;nFile<files.length;nFile++) filenames[nFile]= files[nFile].getName();
for (int nFile=0;nFile<files.length;nFile++) filenames[nFile]= files[nFile].getPath();
return filenames;
}
public static String selectDirectoryOrFile(
boolean smart, // do not open dialog if defaultPath matches filter
boolean save,
boolean directory,
String title,
String button,
FileFilter filter,
String defaultPath) {
File dir=null;
if ((defaultPath!=null) && (defaultPath.length()>0)) {
dir = new File(defaultPath);
}
// If directory is specified, smart=true, save is enabled, but it does not exist - try to create it
if (smart &&
directory &&
(dir!=null) &&
(defaultPath.length()>1) && // skip "/"
save &&
!dir.exists()) dir.mkdirs();
// see if defaultPath matches
if (smart &&
(dir!=null) &&
(defaultPath.length()>1) && // skip "/"
(dir.exists()) &&
(dir.isDirectory() ^ (!directory)) && // directory if requested directory, file if requested file
(dir.isDirectory() || filter.accept(dir))){ // don't care for directory, match filter if file
return defaultPath;
}
if ((dir==null) || (!dir.exists())) {
if (DEFAULT_DIRECTORY!=null) {
defaultPath = DEFAULT_DIRECTORY;
dir = new File(defaultPath);
}
}
if ((dir==null) || (!dir.exists())) {
defaultPath = OpenDialog.getDefaultDirectory();
if (defaultPath!=null) dir = new File(defaultPath);
}
if ((dir!=null) && (!dir.exists())) dir=null;
if ((dir!=null) && (!dir.isDirectory())){
dir=dir.getParentFile();
}
JFileChooser fc= new JFileChooser();
fc.setFileSelectionMode(directory?JFileChooser.DIRECTORIES_ONLY:JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(false);
if ((title!=null) && (title.length()>0)) fc.setDialogTitle(title);
if ((button!=null) && (button.length()>0)) fc.setApproveButtonText(button);
if (filter!=null) fc.setFileFilter(filter) ;
if (dir!=null) fc.setCurrentDirectory(dir);
int returnVal = save?(fc.showSaveDialog(IJ.getInstance())):(fc.showOpenDialog(IJ.getInstance()));
if (returnVal!=JFileChooser.APPROVE_OPTION) return null;
DEFAULT_DIRECTORY=fc.getCurrentDirectory().getPath();
return fc.getSelectedFile().getPath();
}
public static void saveStringToFile (String path,String data){
BufferedWriter writer = null;
try {
writer = new BufferedWriter( new FileWriter( path));
writer.write( data);
} catch ( IOException e) {
String msg = e.getMessage();
if (msg==null || msg.equals("")) msg = ""+e;
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
finally {
try {
if ( writer != null)
writer.close( );
} catch ( IOException e) {
}
}
}
/** ======================================================================== */
static class MultipleExtensionsFileFilter extends FileFilter implements FilenameFilter {
protected String [] patterns; // case insensitive
protected String description="JP4 files";
protected String prefix=""; // case sensitive
public MultipleExtensionsFileFilter (String prefix, String [] patterns,String description) {
this.prefix= prefix;
this.description=description;
this.patterns= patterns.clone();
}
public MultipleExtensionsFileFilter (String [] patterns,String description) {
this.description=description;
this.patterns=patterns.clone();
}
public MultipleExtensionsFileFilter (String [] patterns) {
this.patterns=patterns.clone();
}
public boolean accept (File file) {
int i;
String name=file.getName();
if (file.isDirectory()) return true;
if (!name.startsWith(this.prefix)) return false; // empty prefix OK
for (i=0;i<patterns.length;i++) {
if (name.toLowerCase().endsWith(patterns[i].toLowerCase())) return true;
}
return false;
}
public boolean accept (File dir, String name) { // directory - don't care here, only name
if (!name.startsWith(this.prefix)) return false; // empty prefix OK
for (int i=0;i<patterns.length;i++) {
if (name.toLowerCase().endsWith(patterns[i].toLowerCase())) return true;
}
return false;
}
public String getDescription() {
return description;
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* To the extent possible under law, the Fiji developers have waived
* all copyright and related or neighboring rights to this tutorial code.
*
* See the CC0 1.0 Universal license for details:
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
/**
* ProcessPixels
*
* A template for processing each pixel of either
* GRAY8, GRAY16, GRAY32 or COLOR_RGB images.
*
* @author The Fiji Team
*/
public class Process_Pixels implements PlugInFilter {
protected ImagePlus image;
// image property members
private int width;
private int height;
// plugin parameters
public double value;
public String name;
/**
* @see ij.plugin.filter.PlugInFilter#setup(java.lang.String, ij.ImagePlus)
*/
@Override
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about")) {
showAbout();
return DONE;
}
image = imp;
return DOES_8G | DOES_16 | DOES_32 | DOES_RGB;
}
/**
* @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)
*/
@Override
public void run(ImageProcessor ip) {
// get width and height
width = ip.getWidth();
height = ip.getHeight();
if (showDialog()) {
process(ip);
image.updateAndDraw();
}
}
private boolean showDialog() {
GenericDialog gd = new GenericDialog("Process pixels");
// default value is 0.00, 2 digits right of the decimal point
gd.addNumericField("value", 0.00, 2);
gd.addStringField("name", "John");
gd.showDialog();
if (gd.wasCanceled())
return false;
// get entered values
value = gd.getNextNumber();
name = gd.getNextString();
return true;
}
/**
* Process an image.
*
* Please provide this method even if {@link ij.plugin.filter.PlugInFilter} does require it;
* the method {@link ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)} can only
* handle 2-dimensional data.
*
* If your plugin does not change the pixels in-place, make this method return the results and
* change the {@link #setup(java.lang.String, ij.ImagePlus)} method to return also the
* <i>DOES_NOTHING</i> flag.
*
* @param image the image (possible multi-dimensional)
*/
public void process(ImagePlus image) {
// slice numbers start with 1 for historical reasons
for (int i = 1; i <= image.getStackSize(); i++)
process(image.getStack().getProcessor(i));
}
// Select processing method depending on image type
public void process(ImageProcessor ip) {
int type = image.getType();
if (type == ImagePlus.GRAY8)
process( (byte[]) ip.getPixels() );
else if (type == ImagePlus.GRAY16)
process( (short[]) ip.getPixels() );
else if (type == ImagePlus.GRAY32)
process( (float[]) ip.getPixels() );
else if (type == ImagePlus.COLOR_RGB)
process( (int[]) ip.getPixels() );
else {
throw new RuntimeException("not supported");
}
}
// processing of GRAY8 images
public void process(byte[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (byte)value;
}
}
}
// processing of GRAY16 images
public void process(short[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (short)value;
}
}
}
// processing of GRAY32 images
public void process(float[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (float)value;
}
}
}
// processing of COLOR_RGB images
public void process(int[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (int)value;
}
}
}
public void showAbout() {
IJ.showMessage("ProcessPixels",
"a template for processing each pixel of an image"
);
}
/**
* Main method for debugging.
*
* For debugging, it is convenient to have a method that starts ImageJ, loads an
* image and calls the plugin, e.g. after setting breakpoints.
*
* @param args unused
*/
public static void main(String[] args) {
// set the plugins.dir property to make the plugin appear in the Plugins menu
Class<?> clazz = Process_Pixels.class;
String url = clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class").toString();
String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6);
System.setProperty("plugins.dir", pluginsDir);
// start ImageJ
new ImageJ();
// open the Clown sample
ImagePlus image = IJ.openImage("http://imagej.net/images/clown.jpg");
image.show();
// run the plugin
IJ.runPlugIn(clazz.getName(), "");
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This directory contains several plugins for the popular ImageJ image processing program to work with the images acquired with Elphel cameras. You may unzip them into "plugins" folder of ImageJ to make them accessible through this program.
JP46Reader.tar.gz contains two files: JP46_Reader.java and JP46_Reader_noscale.java. This plugin allows to open images compressed as JP46 - format designed for the raw Bayer mosaic images from the cameras. You may find more details about this format in the LinuxDevices article - http://www.linuxfordevices.com/c/a/Linux-For-Devices-Articles/Elphel-camera-under-the-hood-from-Verilog-to-PHP/ . Plugin makes use of Elphel MakerNote field in Exif header that contains per-channel values of the camera sensor analog gains, black levels and gamma, it takes decompressed 8 bpp images and um-applies gains and gammas resulting in the 32bpp (float) values that are very close to the amount of light that each pixel acquired.
For the best performance you may acquire images with the gamma set to 0.5 (sensor noise matched value) and analog gains set for the white balance (automatic or manual), so pixels of each color will use all the output range of 8 bits (compressed).
Crosstalk_Deconv.java.tar.gz is designed for experimenting with correction of inter-pixel crosstalk. It does not include automatic crosstalk measurement, that values have to be entered to the program in "Configure" menu. It is possible create test images, apply and un-apply crosstalk - all the modern sensors have significant amount of such crosstalk that both degrades resolution and colors in the digital images.
MTF_Bayer.java.tar.gz - plugin for measuring camera resolution. It started from the SE_MTF plugin for ImageJ (http://rsbweb.nih.gov/ij/plugins/se-mtf/index.html), with added functionality focused on measuring Bayer mosaic images. It accepts such images as monochrome and processes multiple edges in the selected rectangular area accumulating results from both black-to-white and white-to-black edges (separately). And as the camera resolution can be different in different directions, the plugin can accept near-horizontal slanted edges also. When the "Calculate MTF" button is pressed it detects (and counts) near-vertical edges that run all the selection height, then does the same in the horizontal direction. After that it selects the direction that has more edges (if any) running all the selection height/width.
With the edges are detected plugin approximates each edge by a quadratic (A*x^2+B*X+C) parabola as straight edges on the target become curves because of the lens distortions. Those edges approximations are calculated either individually for each color component (if "Monochromatic" mode is not selected) or together (useful when the camera had color band-pass filter so all the light that reached pixels was approximately the same wavelength and the chromatic aberrations of the lens can be neglected.
With the approximated edges multiple sensor rows/columns are binned with each bin being 1/4 of the pixel size (similarly to ISO-16067-1), the result of the binning is shown in edge-spread function (ESF) plots and/or tables - separately for each color and edge direction. Green lines on the plot shows green pixels in red rows of Bayer pattern, while cyan color is used for green in blue rows). The derivative of the ESF is line spread function (LSF) that also can be displayed - both in the same scale as the input pixel values or normalized to [0.0..1.0] interval for every color. The result modulation transfer function (MTF) is calculated as the absolute value of the Fourier transform of the LSF.
Some sample files that can be used with the plugins are available here - http://community.elphel.com/files/ImageJ_plugins/sample_files/ The images include shots of nearly-vertical (and nearly-horizontal) black/white stripes, acquired with red, blue, green filters (and no filter at all). These images can be opened with JP46_Reader.java plugin, them processed with MTF_Bayer.java (you need to select rectangular area on the image that includes stripes and press "Calculate MTF" button).
The most recent versions of these programs are available for download from the Git repository - http://elphel.git.sourceforge.net/git/gitweb.cgi?p=elphel/ImageJ-Elphel
\ No newline at end of file
This diff is collapsed.
# Name: Process Pixels
# Author: The Fiji Team
# Name: Elphel Image Processing
# Author: Andrey Filippov
# Version: 1.0.0
# A single .jar file can contain multiple plugins, specified in separate lines.
......@@ -9,4 +9,7 @@
# If something like ("<arg>") is appended to the class name, the setup() method
# will get that as arg parameter; otherwise arg is simply the empty string.
Process, "Process Pixels", Process_Pixels
Plugins, "Aberration Calibration", Aberration_Calibration
Plugins, "Aberration Correction", Aberration_Correction
Plugins, "Eyesis Correction", Eyesis_Correction
Plugins, "JP46 Reader camera", JP46_Reader_camera
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment