/* * Copyright (c) 2012 European Synchrotron Radiation Facility, * Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.dawb.fabio; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Vector; import java.util.concurrent.Semaphore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.ImageLoader; import org.embl.cca.utils.imageviewer.HeaderData; import org.embl.cca.utils.imageviewer.TwoDimFloatArrayData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fable.framework.toolbox.ToolBox; //import fable.python.jep.FableJep; import loaders.pilatus.PilatusLoader; /** * The FabioFile class is used to read files using the Python fabio package. It * uses the Jep interpreter to call Python. It can be used in multi-threaded * Java environments. Different thread should supply their own Jep interpreter. * The image data is stored in a cache so that the last N images will be read * from the cache and not from disk. This will be reflected in the time to read * i.e. it will be zero. The cache size can be increased in function of the * memory available. It is currently set to 20 i.e. 20*16 MB. * * @author Andy Gotz + Gaelle Suchet * */ public class FabioFile implements java.lang.Comparable<Object>, IPropertyChangeListener { private Semaphore semaphore = new Semaphore(1); public boolean headerRead = false; // public boolean imageRead = false; private HashMap<String, String> header; private String fullFileName; private String fileName; private Vector<String> vKeysInHeader; // a list of keys as they arrive in // the header; useful in edfViewer // private int width, height; // private float minimum = Float.MAX_VALUE; // private float maximum = Float.MIN_VALUE; // private float sum = 0.f; // private float mean = Float.MIN_VALUE; // private float stddev = Float.MIN_VALUE; final int IMAGE_CACHE_MAX = 10; private int floatImageBufferI = -1; private long timeToReadImage = 0; private String stem; // GS for peaksearch private String fileNumber; // Gs for peaksearch ImageLoader loader; // the loader for the current image file // ImageData[] imageDataArray; // all image data read from the current file Logger logger; private int index; private boolean flag = true; /* * to speed up FabioFile memory management we keep a buffer of the last N * images in memory. This caches the image so that the most recently read * images will be found in the cache and do not have to be read from disk */ private static TwoDimFloatArrayData floatImageBuffer[] = null; private static int floatImageBufferPointer = 0; private static String fileImageBuffer[] = null; private String comparatorKey = "filename"; private int comparatorDir = SWT.DOWN; private int comparedResult; /** * Create a FabioFile object which be able to read Fabio files via the fabio * Python module. * * @param fileName * @throws FabioFileException * @description filename should contains path for the load */ public FabioFile(String _fullFileName) throws FabioFileException { logger = LoggerFactory.getLogger(FabioFile.class); if (floatImageBuffer == null) { // logger.debug("initialise floatImageBuffer"); // floatImageBuffer = new float[IMAGE_CACHE_MAX][]; floatImageBuffer = new TwoDimFloatArrayData[IMAGE_CACHE_MAX]; fileImageBuffer = new String[IMAGE_CACHE_MAX]; for (int i = 0; i < floatImageBuffer.length; i++) { floatImageBuffer[i] = null; fileImageBuffer[i] = new String(); } } // Check if file exists - why ? if (!new File(_fullFileName).exists()) { throw new FabioFileException(this.getClass().getName(), "FabioFile", "File not found: " + _fullFileName); } this.header = new HashMap<String, String>(); vKeysInHeader = new Vector<String>(); fullFileName = _fullFileName; String[] split = fullFileName.split("[\\\\/]"); fileName = fullFileName; if (split.length > 1) { fileName = split[split.length - 1]; } } public synchronized void acquire() { semaphore.acquireUninterruptibly(); } public synchronized void release() { semaphore.release(); } private void importFabioModules(FableJep fj) throws Throwable { try { final boolean isMsg = fj.isRequireErrorMessage(); fj.jepImportModules("sys", isMsg); fj.jepImportModules("numpy", isMsg); fj.jepImportModules("PIL", isMsg); fj.jepImportModules("fabio.openimage", isMsg); } catch (Throwable e) { throw e; } } /** * Set the fullfilename of fabio file if different from header * * @param fullfilename */ public void setFullFileName(String fullfilename) { fullFileName = fullfilename; } public String getFullFileName() { return fullFileName; } public boolean isHeaderRead() { return headerRead; } /* * public boolean isFabioFile(){ boolean ok=true; File f=new * File(fullFileName); * * if(f.exists()){ try { FableJep.getJep().set("filename",fullFileName); // * this is a fairly "heavy" approach to determine if a file is a fabio file * i.e. reading the header * FableJep.getJep().eval("im = fabio.openimage.openheader(filename)"); } * catch (Throwable e) { ok=false; } }else{ ok=false; } return ok; } */ /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { String myString = "{ \\n"; try { synchronized( header ) { if (!isHeaderRead()) loadHeader(); Set<Map.Entry<String, String>> mySet = header.entrySet(); Iterator<Entry<String, String>> it = mySet.iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = (Map.Entry<String, String>) it.next(); myString += entry.getKey() + "=" + entry.getValue() + ";\\n"; } } } catch (FabioFileException e) { } myString += "} \\n"; return myString; } /** * Load only the header and not the image. loadHeader() uses the python * fabio module to read the header. * * @throws FabioFileException * @throws Throwable * * @update 15-01-2008 GS : Check if file exists to avoid jep exception (not * caught !) */ public void loadHeader() throws FabioFileException { synchronized( header ) { if (!isHeaderRead()) try { loadHeaderExternal(); } catch (Throwable e) { throw new FabioFileException(this.getClass().getName(), "loadHeader " + fullFileName, e.getMessage()); } } } /** * Load only the header and not the image. loadHeader() uses the python * fabio module to read the header. Use the jep interpreter provided by the * callee. This is needed for reading headers in multithreaded environments. * * @throws FabioFileException * */ public void loadHeaderExternal() throws FabioFileException { synchronized( header ) { if (isHeaderRead()) return; HeaderData pHeader = null; if( PilatusLoader.supports( fullFileName ) ) { pHeader = PilatusLoader.loadHeader(fullFileName); } else { File f = new File(fullFileName); if (f.exists()) { try { FableJep fableJep = getFableJep(); importFabioModules(fableJep); fableJep.set("filename", fullFileName); fableJep.eval("im = fabio.openimage.openheader(filename)"); fableJep.eval("keys = im.header.keys()"); fableJep.eval("vals = im.header.values()"); fableJep.eval("res = len(keys)"); int n = (Integer) fableJep.getValue("res"); String key = "", val; HashMap<String, String> newHeader = new HashMap<String, String>(); for (int i = 0; i < n; i++) { fableJep.set("i", i); fableJep.eval("res = str(keys[i])"); // have python key = (String) fableJep.getValue("res"); try { // coerce to str fableJep.eval("res = str(vals[i])"); val = (String) fableJep.getValue("res"); } catch (Throwable e) { // if header fails val = "-1"; } newHeader.put(key, val); } pHeader = new HeaderData( newHeader ); } catch (Throwable e) { logger.error(e.getMessage()); throw new FabioFileException(this.getClass().getName(), "loadHeader" + fullFileName, e.getMessage()); } } else { throw new FabioFileException(this.getClass().getName(), "loadHeader", "File not found" + fullFileName); } } pHeader.getKeyAndValuePairs().put("name", fileName); pHeader.getKeyAndValuePairs().put("#", "" + index); vKeysInHeader.addAll( pHeader.getKeyAndValuePairs().keySet() ); header.putAll( pHeader.getKeyAndValuePairs() ); headerRead = true; } } /** * Add new key in header info */ public void addHeaderInfo(String key, String value) { synchronized( header ) { header.put(key, value); } } /** * @description get keys sorted alphabetically * @return Fabio header keys * @throws FabioFileException * @throws Throwable */ public String[] getKeys() throws FabioFileException, Throwable { Set<String> mySet; synchronized( header ) { if (!isHeaderRead()) { loadHeader(); } mySet = header.keySet(); } String[] keys = mySet.toArray(new String[(mySet.size())]); ToolBox.quicksort(keys, 0, keys.length); return keys; } /** * @description use in viewer to get the keys sorted as they are in the edf * header file * @return a vector of EDF Header Keys * @throws FabioFileException * @throws Throwable */ public Vector<String> getKeysAsListedInHeader() throws FabioFileException, Throwable { synchronized( header ) { if (!isHeaderRead()) { loadHeader(); } return vKeysInHeader; } } /** * * @param key * the header key * @return the value of the key header * @throws FabioFileException * @throws Throwable */ public String getValue(String key) throws FabioFileException { synchronized( header ) { if (!isHeaderRead()) { loadHeader(); } if (header.containsKey(key)) { return header.get(key); } else { throw new FabioFileException(this.getClass().getName(), "getValue()", "The key " + key + " has not been found in the header for the file " + fileName); } } } /** * Gaelle : add a sample Vector index for this fabioFile * * @param index * index in sample vector file */ public void addIndex(int index) { this.index = index; } public String getFullFilename() { return fullFileName; } /** * return short file name i.e. without the path * * @return short file name */ public String getFileName() { return fileName; } /** * get time to read image in ms * * @return time to took to read image and make a copy */ public long getTimeToReadImage() { return timeToReadImage; } /** * * 8 janv. 08 * * @author G. Suchet * @return fabio file stem name */ public String getStems() { if (stem == null) { String s = null; String[] splitter = this.fileName.split("\\."); s = fileName; if (splitter != null && splitter.length >= 2) { // check if it is bruker or compressed bruker String ext = splitter[1]; try { Integer.valueOf(ext); int index = fileName.indexOf("."); s = fileName.substring(0, index); } catch (NumberFormatException n) { // this is not a bruker format int j = splitter[0].length() - 1; while (j > 1) { try { Integer.valueOf(splitter[0].substring(j - 1, j)); j--; } catch (NumberFormatException exc) { s = splitter[0].substring(0, j); j = 0; } } } } stem = s; } return stem; } /** * @name getFileNumber * @return file number, even for bruker format //TODO test with bruker files */ public String getFileNumber() { if (fileNumber == null) { String s = null; String[] splitter = this.fileName.split("\\."); s = fileName; if (splitter != null && splitter.length >= 2) { // check if it is bruker or compressed bruker String ext = splitter[1]; try { Integer.valueOf(ext); s = ext; } catch (NumberFormatException n) { // this is not a bruker format int j = splitter[0].length() - 1; while (j > 1) { try { Integer.valueOf(splitter[0].substring(j - 1, j)); j--; } catch (NumberFormatException exc) { s = splitter[0].substring(j, splitter[0].length()); j = 0; } } } } fileNumber = s; } return fileNumber; } /** * get image width * * @return image width * @throws Throwable */ public int getWidth() throws Throwable { synchronized( floatImageBuffer ) { if (!isCached()) { readImage(); } return floatImageBuffer[floatImageBufferI].getWidth(); } } /** * get image height * * @return image height * @throws Throwable */ public int getHeight() throws Throwable { synchronized( floatImageBuffer ) { if (!isCached()) { readImage(); } return floatImageBuffer[floatImageBufferI].getHeight(); } } public int getBytesPerPixel() { return 2; } public boolean isCached() { do { if( floatImageBufferI >= 0 && fileImageBuffer[floatImageBufferI] != null && fileImageBuffer[floatImageBufferI].equalsIgnoreCase(fullFileName) ) break; int i; int iMax = fileImageBuffer.length; for( i = 0; i < iMax; i++ ) if( fileImageBuffer[ i ] != null && fileImageBuffer[ i ].equalsIgnoreCase( fullFileName ) ) break; if( i >= iMax ) i = -1; floatImageBufferI = i; } while( false ); return floatImageBufferI >= 0; } /** * read image into memory * * @throws Throwable */ public void readImage() throws Throwable { readImageAsFloat(); } /** * read image as float into memory * * @throws Throwable */ public void readImageAsFloat() throws Throwable { readImageAsFloatExternal(); } /** * increment float image buffer pointer, wrap around if end of buffer * reached */ private void incrementBufferPointer() { synchronized( floatImageBuffer ) { if (++floatImageBufferPointer >= floatImageBuffer.length) floatImageBufferPointer = 0; } } /** * read image as float into memory * * @throws Throwable */ protected void readImageAsFloatExternal() throws Throwable { synchronized( floatImageBuffer ) { timeToReadImage = 0; if (isCached()) return; TwoDimFloatArrayData pData = null; long before = System.currentTimeMillis(); if( PilatusLoader.supports( fullFileName ) ) { pData = PilatusLoader.loadPilatus(fullFileName); } else { FableJep fableJep = getFableJep(); logger.debug("read file " + fileName); try { importFabioModules(fableJep); fableJep.set("filename", fullFileName); fableJep.eval("im = fabio.openimage.openimage(filename)"); fableJep.eval("res = im.data.astype(numpy.float32).tostring()"); float[] data = fableJep.getValue_floatarray("res"); // now overwrite data to free memory in python fableJep.eval("res = im.dim1"); int width = (Integer) fableJep.getValue("res"); fableJep.eval("res = im.dim2"); // KE: There are not used for anything // long elapsed; // long before_get; int height = (Integer) fableJep.getValue("res"); pData = new TwoDimFloatArrayData( width, height, data ); /* * getting the mean via Python takes too long (approx. 160 ms * for 2048x2048 image) */ /* * if (mean == Float.MIN_VALUE) { before_get = * System.currentTimeMillis(); jep.eval("res = im.getmean()"); * mean = (Float) jep.getValue("res"); elapsed = * System.currentTimeMillis()-before_get; * logger.info("fabio.getmean() took "+elapsed+" ms"); } */ /* * getting the standard deviation via Python takes too long * (approx. 330 ms for 2048x2048 image) */ /* * if (stddev == Float.MIN_VALUE) { before_get = * System.currentTimeMillis(); jep.eval("res = im.getstddev()"); * stddev = (Float) jep.getValue("res"); elapsed = * System.currentTimeMillis()-before_get; * logger.info("fabio.getstddev() took "+elapsed+" ms"); } */ // calculate min and max if not done so already // if (minimum == Float.MAX_VALUE || maximum == Float.MIN_VALUE) { /* * could use fabio to calculate min, max and mean (if we * assume NumPy is faster than Java) but the data type * returned is undetermined and therefore I do not know how * to interpret the result */ /* * jep.eval("res = im.getmin()"); minimum = (float) * (Integer)jep.getValue("res"); * jep.eval("res = im.getmax()"); maximum = (float) * (Integer)jep.getValue("res"); */ // Java version to calculate minimum and maximum (takes only // 60 ms for 2048x2048 image) // before_get = System.currentTimeMillis(); /* sum = 0.f; int iMax = data.length; for (int i = 0; i < iMax; i++) { float f = data[i]; sum += f; if (f < minimum) minimum = f; if (f > maximum) maximum = f; } mean = sum / (float) (iMax); // elapsed = System.currentTimeMillis() - before_get; // logger.info("java get mean,min.max took "+elapsed+" ms"); */ } catch (Throwable j) { throw j; // j.printStackTrace(); } } floatImageBuffer[floatImageBufferPointer] = pData; fileImageBuffer[floatImageBufferPointer] = fullFileName; floatImageBufferI = floatImageBufferPointer; incrementBufferPointer(); timeToReadImage = System.currentTimeMillis() - before; } return; } /** * Return image as floating pint array * * @return image as floating point array * @throws Throwable */ public float[] getImageAsFloat() throws Throwable { synchronized( floatImageBuffer ) { readImageAsFloat(); return floatImageBuffer[floatImageBufferI].getData(); } } /** * Return image as floating pint array * * @return image as floating point array */ /* public float[] getImageAsFloat(FableJep jep) { timeToReadImage = 0; if (!isCached()) try { readImageAsFloat2(jep); fileCached = true; } catch (Throwable e) { // do not print exception - what should be done ? } return floatImageBuffer[floatImageBufferI]; } */ /** * return image as int by converting the float image to int, do not keep the * image in memory * * @return image as integer array * @throws Throwable */ public int[] getImageAsInt() throws Throwable { float[] data = getImageAsFloat(); int[] _imageAsInt = new int[getWidth() * getHeight()]; int iMax = data.length; for (int i = 0; i < iMax; i++) { _imageAsInt[i] = (int) data[i]; } return _imageAsInt; } /** * return minimum value in image * * @return image minimum * @throws Throwable * */ /* public float getMinimum2() throws Throwable { if (!isCached()) { readImage(); } return minimum; } */ /** * return maximum value in image * * @return image maximum * @throws Throwable * */ /* public float getMaximum2() throws Throwable { if (!isCached()) { readImage(); } return maximum; } */ /** * return mean value in image * * @return image mean * @throws Throwable * */ /* public float getMean2() throws Throwable { if (!isCached()) { readImage(); } return mean; } */ /* * What is the flag used for ? We need a description here or it should be * deleted (andy) */ public void setFlag(boolean b) { flag = b; } public boolean getFlag() { return flag; } public int compareTo(final Object other) { try { String valueOther; valueOther = ((FabioFile) other).getValue(comparatorKey); String valueThis = this.getValue(comparatorKey); if (comparatorDir == SWT.UP) { comparedResult = valueOther.compareTo(valueThis); } else { comparedResult = valueThis.compareTo(valueOther); } } catch (FabioFileException e) { logger.error(e.getMessage()); } return comparedResult; } /** * * @param key * the key used to sort * @param other * the fableFile * @return 0 if equal, 1 if keyValue for other is greater than this,-1 if * otherValue<thisvalue, else 99 if an error occured * */ public int compareTo(String key, Object other) { comparatorKey = key; return compareTo(other); } public void propertyChange(PropertyChangeEvent event) { // Listen to its Sample if (event.getProperty().equals("comparator")) { this.comparatorKey = ((String) event.getNewValue()); } else if (event.getProperty().equals("dir")) { this.comparatorDir = ((Integer) event.getNewValue()); } } private FableJep getFableJep() throws Throwable { return FableJep.getFableJep(); } }