Java tutorial
/* * ------------------------------------------------------------------------ * * Copyright (C) 2003 - 2013 * University of Konstanz, Germany and * KNIME GmbH, Konstanz, Germany * Website: http://www.knime.org; Email: contact@knime.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses>. * * Additional permission under GNU GPL version 3 section 7: * * KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs. * Hence, KNIME and ECLIPSE are both independent programs and are not * derived from each other. Should, however, the interpretation of the * GNU GPL Version 3 ("License") under any applicable laws result in * KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants * you the additional permission to use and propagate KNIME together with * ECLIPSE with only the license terms in place for ECLIPSE applying to * ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the * license terms of ECLIPSE themselves allow for the respective use and * propagation of ECLIPSE together with KNIME. * * Additional permission relating to nodes for KNIME that extend the Node * Extension (and in particular that are based on subclasses of NodeModel, * NodeDialog, and NodeView) and that only interoperate with KNIME through * standard APIs ("Nodes"): * Nodes are deemed to be separate and independent programs and to not be * covered works. Notwithstanding anything to the contrary in the * License, the License does not apply to Nodes, you are not required to * license Nodes under the License, and you are granted a license to * prepare and propagate Nodes, in each case even if such Nodes are * propagated with or for interoperation with KNIME. The owner of a Node * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * --------------------------------------------------------------------- * * */ package org.knime.knip.io.nodes.imgreader; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.apache.commons.io.FileUtils; import org.knime.core.data.DataCell; import org.knime.core.data.DataColumnSpec; import org.knime.core.data.DataColumnSpecCreator; import org.knime.core.data.DataRow; import org.knime.core.data.DataTable; import org.knime.core.data.DataTableSpec; import org.knime.core.data.DataType; import org.knime.core.data.RowIterator; import org.knime.core.data.RowKey; import org.knime.core.data.def.DefaultRow; import org.knime.core.data.xml.XMLCell; import org.knime.core.data.xml.XMLCellFactory; import org.knime.core.node.ExecutionContext; import org.knime.core.node.NodeLogger; import org.knime.core.util.FileUtil; import org.knime.core.util.pathresolve.ResolverUtil; import org.knime.knip.base.data.img.ImgPlusCell; import org.knime.knip.base.data.img.ImgPlusCellFactory; import org.knime.knip.base.exceptions.KNIPException; import org.knime.knip.base.node.nodesettings.SettingsModelSubsetSelection; import org.knime.knip.io.ScifioImgSource; import org.knime.knip.io.node.dialog.DialogComponentMultiFileChooser; import io.scif.config.SCIFIOConfig; import loci.formats.FormatException; import net.imagej.ImgPlus; import net.imagej.axis.CalibratedAxis; import net.imagej.axis.TypedAxis; import net.imglib2.img.ImgFactory; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Pair; /** * Implements a <code>DataTable</code> that read image data from files. * * @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a> * @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a> * @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael * Zinsmaier</a> */ public class ReadFileImgTable<T extends NativeType<T> & RealType<T>> implements DataTable { /* Standard node logger */ private static final NodeLogger LOGGER = NodeLogger.getLogger(ReadFileImgTable.class); /* connection timeout if images are read from an url */ private static final int CONNECTION_TIMEOUT = 2000; /* read timeout if images are read from an url */ private static final int READ_TIMEOUT = 10000; private boolean m_completePathRowKey; /* * Indicates weather an error occured in the current run or not */ private boolean m_error = false; /* * Reference to the execution context to set the progress. */ private ExecutionContext m_exec; /* * points to the files to be read */ private Iterable<String> m_fileList; /* * The image factory to read the image files. */ private ScifioImgSource m_imgSource = null; /* Object that keeps scifio-specific settings */ private SCIFIOConfig m_scifioConfig = null; /* * Number of files to be read */ private long m_numberOfFiles; /* * The option whether to add an ome-xml colmn or not */ private final boolean m_omexml; /* * An array encoding the selected image planes in the 3-dimensional space * (Z,C,T). */ private SettingsModelSubsetSelection m_sel; /* * the selected series to be read */ private int m_selectedSeries = -1; private String m_workflowCanonicalPath; /** * Creates an new and empty ImageTable and is useful to get the table * specification without actually knowing the content. * * @param omexml * if true, a omexml column will be appended to the table * */ public ReadFileImgTable(final boolean omexml) { initCanonicalWorkflowPath(); m_omexml = omexml; } /** * Constructor for an ImageTable. * * @param exec * the execution context (for the progress bar) * @param fileList * the files to be opened * @param numberOfFiles * the number of files * @param sel * the subset selection * @param omexml * if true, a ome-xml column will be appended to the table * @param checkFileFormat * checks the file format newly for each single file (might be a * bit slower) * @param completePathRowKey * if true, the complete path will be used as row key, else only * the file name * @param isGroupFiles * if true all files which are referenced from the current file * will also be loaded (e.g. in flex files, files from one * experiment link each other in the header of the file format). * @param selectedSeries * the series to be read, if an image file contains multiple * series, if -1 all series will be read * @param imgFactory * the image factory used to create the individual images * * */ public ReadFileImgTable(final ExecutionContext exec, final Iterable<String> fileList, final long numberOfFiles, final SettingsModelSubsetSelection sel, final boolean omexml, final boolean checkFileFormat, final boolean completePathRowKey, final boolean isGroupFiles, final int selectedSeries, final ImgFactory<T> imgFactory) { initCanonicalWorkflowPath(); m_completePathRowKey = completePathRowKey; m_fileList = fileList; m_numberOfFiles = numberOfFiles; m_sel = sel; m_exec = exec; m_omexml = omexml; m_selectedSeries = selectedSeries; m_scifioConfig = new SCIFIOConfig().groupableSetGroupFiles(isGroupFiles); m_imgSource = new ScifioImgSource(imgFactory, checkFileFormat, m_scifioConfig); } /** * {@inheritDoc} */ @Override public DataTableSpec getDataTableSpec() { final int col = 0; DataColumnSpecCreator creator; final DataColumnSpec[] cspecs = new DataColumnSpec[1 + (m_omexml ? 1 : 0)]; creator = new DataColumnSpecCreator("Image", ImgPlusCell.TYPE); cspecs[col] = creator.createSpec(); if (m_omexml) { creator = new DataColumnSpecCreator("OME-XML Metadata", XMLCell.TYPE); cspecs[cspecs.length - 1] = creator.createSpec(); } return new DataTableSpec(cspecs); } /** * * @return true, if an error occurred while iterating through the filelist * to open the images. */ public boolean hasAnErrorOccured() { return m_error; } /** * @return true if more than one reader class was necessary to open the * files */ public boolean usedDifferentReaders() { return m_imgSource.usedDifferentReaders(); } /** * {@inheritDoc} */ @Override public RowIterator iterator() { if (m_fileList == null) { return new RowIterator() { @Override public boolean hasNext() { return false; } @Override public DataRow next() { return null; } }; } /* Cellfactory to create image cells */ final ImgPlusCellFactory cellFactory = new ImgPlusCellFactory(m_exec); /* * Iterate over the row */ return new RowIterator() { /* id of the current file */ private String currentFile; /* * base row key for the currently read file (possibly with the * series id appended to it later on) */ private String rowKey; /* current image of the series contained in one file */ private int currentSeries = m_selectedSeries == -1 ? 0 : m_selectedSeries; /* iterator over filelist */ private final Iterator<String> fileIterator = m_fileList.iterator(); /* the current iterator position */ private int idx = 0; private int progressCount = 0; /* number of series */ private int seriesCount = 0; { m_error = false; } @Override public boolean hasNext() { if (m_selectedSeries == -1 && (currentSeries + 1) < seriesCount) { return true; } return fileIterator.hasNext(); } @SuppressWarnings("unchecked") @Override public DataRow next() { final Vector<DataCell> row = new Vector<DataCell>(); final DataCell[] result = new DataCell[m_omexml ? 2 : 1]; try { if ((currentSeries + 1) < seriesCount && m_selectedSeries == -1) { // if there still is a series left and ALL series are // selected to be read (m_selectedSeries!=-1) currentSeries++; } else { /* prepare next file name and the according row key */ progressCount++; currentFile = fileIterator.next().trim(); rowKey = currentFile; // replace relative file pathes knime://knime.workflow currentFile = DialogComponentMultiFileChooser.convertToFilePath(currentFile, m_workflowCanonicalPath); // download file and return new file path, if // 'currentFile' is an url String newLoc; if ((newLoc = downloadFileFromURL(currentFile)) != null) { rowKey = currentFile; currentFile = newLoc; } else { // if file wasn't downloaded from a url, re-set // row-key // check whether file exists File f = new File(currentFile); if (!f.exists()) { throw new KNIPException("Image " + rowKey + " doesn't exist!"); } if (!m_completePathRowKey) { rowKey = f.getName(); } } seriesCount = m_imgSource.getSeriesCount(currentFile); currentSeries = m_selectedSeries == -1 ? 0 : m_selectedSeries; idx++; } if (currentSeries >= seriesCount) { LOGGER.warn("Image file only contains " + seriesCount + " series, but series number " + currentSeries + " selected. File skipped!"); } List<CalibratedAxis> calibAxes = m_imgSource.getAxes(currentFile, currentSeries); final Pair<TypedAxis, long[]>[] axisSelectionConstraints = m_sel.createSelectionConstraints( m_imgSource.getDimensions(currentFile, currentSeries), calibAxes.toArray(new CalibratedAxis[calibAxes.size()])); // reads the ome xml metadata if (m_omexml) { result[result.length - 1] = XMLCellFactory .create(m_imgSource.getOMEXMLMetadata(currentFile)); } // One _can_ be sure that if and only if // some dims are removed (as they are of // size 1) an optimized iterable interval // is created final ImgPlus<T> resImgPlus = (ImgPlus<T>) m_imgSource.getImg(currentFile, currentSeries, axisSelectionConstraints); result[0] = cellFactory.createCell(resImgPlus); } catch (final FormatException e) { LOGGER.warn("Format not supported for image " + rowKey + " (" + e.getMessage() + ")"); m_error = true; } catch (final IOException e) { LOGGER.error("An IO problem occured while opening the image " + rowKey + " (" + e.getMessage() + ")"); m_error = true; } catch (final KNIPException e) { LOGGER.warn(e.getLocalizedMessage()); m_error = true; } catch (final Exception e) { LOGGER.error(e); e.printStackTrace(); m_error = true; } for (final DataCell cell : result) { if (cell == null) { row.add(DataType.getMissingCell()); } else { row.add(cell); } } DataCell[] rowvalues = new DataCell[row.size()]; rowvalues = row.toArray(rowvalues); m_exec.setProgress((double) progressCount / m_numberOfFiles); // add series count to row key, if the current file is a series // of images RowKey rk; if (seriesCount > 1) { rk = new RowKey(rowKey + "_" + currentSeries); } else { rk = new RowKey(rowKey); } return new DefaultRow(rk, rowvalues); } }; } /** * Allows on to specify additional settings that can not be passed via the * constructor (e.g. scifio specific settings). They have to be passed * immediately after the instantiation of the {@link ReadFileImgTable} * -object, otherwise they might not be regarded. * * @param key * @param value */ public void set(String key, Object value) { m_scifioConfig.put(key, value); } /* * Determines the absolute workflow path (for workflow-relative files, i.e. * knime://...) */ private void initCanonicalWorkflowPath() { m_workflowCanonicalPath = null; try { m_workflowCanonicalPath = ResolverUtil .resolveURItoLocalFile(new URI(DialogComponentMultiFileChooser.KNIME_WORKFLOW_RELPATH)) .getCanonicalPath(); } catch (URISyntaxException e) { LOGGER.warn("could not resolve the workflow directory as local file"); } catch (IOException e) { LOGGER.warn("could not resolve the workflow directory as local file"); } } /* * Downloads a file specified by an url and saves it to the tmp directory, * returns the respective file path, if its not an url, null will be * returned. If the given url is a 'file-url' it returns the actual absolute * path of the specified file. */ private String downloadFileFromURL(String s) throws KNIPException { // check if the given url really is an url try { URL url = new URL(s); // special handling if its a file url (just return the actual // complete path if (url.getProtocol().equalsIgnoreCase("file")) { return FileUtil.getFileFromURL(url).getAbsolutePath(); } // try to extract the suffix from the file in the url (sometimes // necessary for the scifio readers to identify the right reader) String file = url.getFile(); int idx = file.lastIndexOf("."); String suffix = file.substring(idx, Math.min(idx + 4, file.length() - 1)); // create tmp file and copy data from url File tmpFile = FileUtil.createTempFile(FileUtil.getValidFileName(url.toString(), -1), suffix); FileUtils.copyURLToFile(url, tmpFile, CONNECTION_TIMEOUT, READ_TIMEOUT); return tmpFile.getAbsolutePath(); } catch (MalformedURLException e) { return null; } catch (IOException e) { throw new KNIPException("Can't create temporary file to download image from URL (" + s + ").", e); } } }