QconfLoader.java
package com.github.celldynamics.quimp.filesystem;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JOptionPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.BOAState;
import com.github.celldynamics.quimp.BOAState.BOAp;
import com.github.celldynamics.quimp.PropertyReader;
import com.github.celldynamics.quimp.QParams;
import com.github.celldynamics.quimp.QParamsQconf;
import com.github.celldynamics.quimp.QuimpException;
import com.github.celldynamics.quimp.filesystem.converter.FormatConverter;
import com.github.celldynamics.quimp.plugin.bar.QuimP_Bar;
import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.io.FileInfo;
import ij.io.OpenDialog;
/**
* Load QCONF or paQP file and initiate proper instance of {@link QParams} class.
*
* <p>Provide also methods for QCONF verification and loading image file associated with it with
* user
* assistance.
*
* @author p.baniukiewicz
*
*/
public class QconfLoader {
private Path qconfFile; // path to loaded file
/**
* The Constant LOGGER.
*/
static final Logger LOGGER = LoggerFactory.getLogger(QconfLoader.class.getName());
/**
* Stand for bad QCONF file that can not be loaded.
*/
public static final int QCONF_INVALID = 0; // Must be 0
/**
* Stand for bad paQP experiment that has some files missing.
*/
public static final int PAQP_INVALID = QCONF_INVALID; // Must be 0
/**
* Stand for good paQP experiment that has all files.
*/
public static final int PAQP_VALID = 2;
/**
* Stand for missing file in experiment.
*/
public static final int SNQP_MISSING = 4;
/**
* Stand for missing file in experiment.
*/
public static final int STQP_MISSING = 8;
/**
* Stand for missing file in experiment.
*/
public static final int MAP_MISSING = 16;
/**
* Separator for error messages.
*
* @see #validatePaqp(Path)
*/
public static final String SEPARATOR = ";";
/**
* Main object holding loaded configuration file. It can be either traditional QParams or
* QParamsQconf for newer format.
*/
private QParams qp = null;
/**
* Default constructor.
*
* <p>Bring file dialog to load QCONF.
*
* @throws QuimpException when QCONF can not be loaded
*/
public QconfLoader() throws QuimpException {
this(null);
}
/**
* Parametrised constructor. Allow to choose file selector filter.
*
* @param file File *.paQP/QCONF. If <tt>null</tt> user is asked for this file.
* @param fileExt pre-selection extension or <tt>null</tt> to use default selected in QuimP_Bar.
* @throws QuimpException when file can not be loaded
*/
public QconfLoader(File file, String fileExt) throws QuimpException {
loader(file, fileExt);
}
/**
* Parameterised constructor. Assume that active extension for configuration file is set by
* QuimP_Bar.
*
* @param file File *.paQP/QCONF. If <tt>null</tt> user is asked for this file
* @throws QuimpException when file can not be loaded
*/
public QconfLoader(File file) throws QuimpException {
loader(file, null); // use default filter set in QuimP_Bar
}
/**
* File loaded and initialiser for this class.
*
* @param file File *.paQP/QCONF. If <tt>null</tt> user is asked for this file.
* @param fileExt pre-selection extension or null to use default selected in QuimP_Bar.
* @throws QuimpException when file can not be loaded
* @see QuimP_Bar
*/
private void loader(File file, String fileExt) throws QuimpException {
String directory; // directory with paQP
String filename; // file name of paQP
if (file == null) { // no file provided, ask user
FileDialogEx od = new FileDialogEx(IJ.getInstance(), fileExt);
od.setDirectory(OpenDialog.getLastDirectory());
if (od.showOpenDialog() == null) {
IJ.log("Cancelled - exiting...");
return;
}
directory = od.getDirectory();
filename = od.getFile();
} else { // use name provided in constructor
Path path = file.toPath();
// getParent can return null
directory = (path.getParent() == null) ? "." : path.getParent().toString();
if (path.getFileName() == null) {
throw new QuimpException("Can not get file name to load: " + path.toString());
}
filename = path.getFileName().toString();
LOGGER.debug("Use provided file:" + directory + " " + filename);
}
// detect old/new file format
File paramFile = new File(directory, filename); // config file (copy of input)
// TODO #152
if (paramFile.getName().toLowerCase().endsWith(FileExtensions.newConfigFileExt.toLowerCase())) {
qp = new QParamsQconf(paramFile);
} else {
qp = new QParams(paramFile); // initialize general param storage
}
qp.readParams(); // create associated files included in paQP and read params
qconfFile = paramFile.toPath();
}
/**
* Try to load image associated with QCONF or paQP file.
*
* <p>If image has not been found, user is being asked to point relevant file. If file is loaded
* from disk it updates <tt>orgFile</tt> in {@link BOAp}.
*
* <p>If run in testing mode it tries to load an image from folder where QCONF is. Do not display
* UI.
*
* @return Loaded image from QCONF or that pointed by user. <tt>null</tt> if user cancelled or
* image has not been found.
*/
public ImagePlus getImage() {
if (getQp() == null) {
return null;
}
ImagePlus im;
File imagepath = null;
switch (getQp().getParamFormat()) {
case QParams.NEW_QUIMP:
imagepath = ((QParamsQconf) qp).getLoadedDataContainer().getBOAState().boap.getOrgFile();
break;
case QParams.QUIMP_11:
imagepath = qp.getSegImageFile();
break;
default:
throw new IllegalArgumentException("Format not supported");
}
LOGGER.debug("Attempt to open image: " + imagepath.toString());
// try to load from QCONF or paQP
im = IJ.openImage(imagepath.getPath());
if (im == null) { // if failed ask user
// but first check against testing mode
String skipReg = new PropertyReader().readProperty("quimpconfig.properties", "noRegWindow");
if (Boolean.parseBoolean(skipReg) == true) {
Path imName = imagepath.toPath().getFileName();
Path dir = (qconfFile.getParent() == null) ? Paths.get(".") : qconfFile.getParent();
LOGGER.debug("Testing mode, looking for image: " + dir.resolve(imName).toString());
im = IJ.openImage(dir.resolve(imName).toString());
return im; // do not modify paths in boap in testing mode
}
Object[] options = { "Load from disk", "Load from IJ", "Cancel" };
int n = JOptionPane.showOptionDialog(IJ.getInstance(),
"The image " + imagepath.getName()
+ " pointed in loaded configuration file can not be found.\n"
+ "Would you like to load it manually?",
"Warning", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
options, options[2]);
if (n == JOptionPane.YES_OPTION) { // load from disk
LOGGER.trace("Load from disk");
OpenDialog od = new OpenDialog("Open image", OpenDialog.getLastDirectory(), "");
if (od.getFileName() == null) {
return null;
}
im = IJ.openImage(od.getDirectory() + od.getFileName());
}
if (n == JOptionPane.NO_OPTION) { // or open from ij
LOGGER.trace("Load from IJ");
Object[] images = WindowManager.getImageTitles();
images = (images.length == 0) ? new Object[1] : images;
Object message = "Select image";
String s = (String) JOptionPane.showInputDialog(IJ.getInstance(), message,
"Avaiable images", JOptionPane.PLAIN_MESSAGE, null, images, images[0]);
im = WindowManager.getImage(s);
}
// replace old image paths in QCONF to new one
if (im != null) {
Path orgFile;
FileInfo fileinfo = im.getOriginalFileInfo();
if (fileinfo == null) {
orgFile = Paths.get(File.separator, im.getTitle());
} else {
orgFile = Paths.get(fileinfo.directory, fileinfo.fileName);
}
try {
if (isBOAPresent()) {
getBOA().boap.setOrgFile(orgFile.toFile());
}
} catch (QuimpException e) {
throw new Error(); // should never be here, we know there is BOA and we are on new path
}
}
}
LOGGER.debug("Opened image: " + im);
return im;
}
/**
* Validate loaded QCONF file in accordance to modules run on it.
*
* <p>For certain cases this method may not be able to verify if QCONF is valid. This may happen
* if QCONF was obtained from paQP files, where some of experiment files were missing (this should
* no happen).
*
* @return Values:
* <ol>
* <li>0 if QCONF is not loaded properly.
* <li>QParams.QUIMP_11 if it is in old format
* <li>{@link DataContainer#validateDataContainer()} flags otherwise
* </ol>
*
* @see FormatConverter - may return defective QCONF with warnings.
*/
public int validateQconf() {
if (getQp() == null) {
return QconfLoader.QCONF_INVALID;
}
if (getQp().getParamFormat() != QParams.NEW_QUIMP) {
return QParams.QUIMP_11;
}
return ((QParamsQconf) getQp()).getLoadedDataContainer().validateDataContainer();
}
/**
* Perform blind validation of accessible files without reading them.
*
* <p>Check if for each cell in same experiment (identified by provided full name
* /path/name_0.paQP) all other corresponding files exist.
*
* @param firstFile full path to first paQP file in experiment
* @return Map with keys defined in in this class: {@value #PAQP_INVALID}, {@value #PAQP_VALID},
* {@value #SNQP_MISSING}, {@value #STQP_MISSING}, {@value #MAP_MISSING} and String values
* in format problem description; problem description. E.g if two maps are missing both
* are logged in value for {@value #MAP_MISSING}. Empty Map stands for proper experiment
* structure.
*/
public static Map<Integer, String> validatePaqp(Path firstFile) {
HashMap<Integer, String> ret = new HashMap<>();
Path folder = firstFile.getParent();
Path corep = firstFile.getFileName();
if (folder == null || corep == null) {
throw new IllegalArgumentException("Wrong path");
}
String core = corep.toString();
int up = core.lastIndexOf('_');
if (up <= 0) {
ret.put(PAQP_INVALID, "Incorect name."); // wrong name?
return ret;
} else {
core = core.substring(0, up); // remove _0 from name
}
// iterate over paQP
int l = 0;
File file = folder.resolve(core + "_" + l + FileExtensions.configFileExt).toFile();
int hadMap = 0;
while (file.exists()) {
// check snQP
file = folder.resolve(core + "_" + l + FileExtensions.snakeFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(SNQP_MISSING) == null ? "" : ret.get(SNQP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(SNQP_MISSING, prev);
}
// check stQP
file = folder.resolve(core + "_" + l + FileExtensions.statsFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(STQP_MISSING) == null ? "" : ret.get(STQP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(STQP_MISSING, prev);
}
// check maps
// check if there is at least one
if (folder.resolve(core + "_" + l + FileExtensions.convmapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.motmapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.coordmapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.originmapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.xmapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.ymapFileExt).toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.fluomapFileExt.replace('%', '1'))
.toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.fluomapFileExt.replace('%', '2'))
.toFile().exists()
|| folder.resolve(core + "_" + l + FileExtensions.fluomapFileExt.replace('%', '3'))
.toFile().exists()) {
// so we expect all (except flumaps)
hadMap++; // at least one paQP has maps
file = folder.resolve(core + "_" + l + FileExtensions.convmapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
file = folder.resolve(core + "_" + l + FileExtensions.motmapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
file = folder.resolve(core + "_" + l + FileExtensions.coordmapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
file = folder.resolve(core + "_" + l + FileExtensions.originmapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
file = folder.resolve(core + "_" + l + FileExtensions.xmapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
file = folder.resolve(core + "_" + l + FileExtensions.ymapFileExt).toFile();
if (!file.exists()) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat("Missing " + file.getName() + " file");
ret.put(MAP_MISSING, prev);
}
} else {
hadMap--;
}
l++;
file = folder.resolve(core + "_" + l + FileExtensions.configFileExt).toFile();
}
if (l == 0) {
ret.put(PAQP_INVALID, "First file " + file.getName() + " is missing.");
}
// check case if one paQP does not have maps but other has
if (Math.abs(hadMap) != l) {
String prev = ret.get(MAP_MISSING) == null ? "" : ret.get(MAP_MISSING);
prev = prev.concat(SEPARATOR).concat(
"All maps are missing for one or more paQP files whereas avilable for other cases");
ret.put(MAP_MISSING, prev);
}
return ret; // if size 0 - no issues
}
/**
* Just decoder of
* {@link com.github.celldynamics.quimp.filesystem.DataContainer#validateDataContainer()}.
*
* @return true if BOA module was run.
*/
public boolean isBOAPresent() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID || ret == QParams.QUIMP_11) {
return false;
}
if ((ret & DataContainer.BOA_RUN) == DataContainer.BOA_RUN) {
return true;
} else {
return false;
}
}
/**
* Just decoder of
* {@link com.github.celldynamics.quimp.filesystem.DataContainer#validateDataContainer()}.
*
* @return true if ECMM module was run.
*/
public boolean isECMMPresent() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID || ret == QParams.QUIMP_11) {
return false;
}
if ((ret & DataContainer.ECMM_RUN) == DataContainer.ECMM_RUN) {
return true;
} else {
return false;
}
}
/**
* Just decoder of
* {@link com.github.celldynamics.quimp.filesystem.DataContainer#validateDataContainer()}.
*
* @return true if ANA module was run.
*/
public boolean isANAPresent() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID || ret == QParams.QUIMP_11) {
return false;
}
if ((ret & DataContainer.ANA_RUN) == DataContainer.ANA_RUN) {
return true;
} else {
return false;
}
}
/**
* Just decoder of
* {@link com.github.celldynamics.quimp.filesystem.DataContainer#validateDataContainer()}.
*
* @return true if Q module was run.
*/
public boolean isQPresent() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID || ret == QParams.QUIMP_11) {
return false;
}
if ((ret & DataContainer.Q_RUN) == DataContainer.Q_RUN) {
return true;
} else {
return false;
}
}
/**
* Just decoder of
* {@link com.github.celldynamics.quimp.filesystem.DataContainer#validateDataContainer()}.
*
* @return true if stats are present.
*/
public boolean isStatsPresent() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID || ret == QParams.QUIMP_11) {
return false;
}
if ((ret & DataContainer.STATS_AVAIL) == DataContainer.STATS_AVAIL) {
return true;
} else {
return false;
}
}
/**
* Query for BOA object.
*
* @return BOAState object from loaded configuration
* @throws QuimpException when there is no such object in file or old format is used.
*/
public BOAState getBOA() throws QuimpException {
if (isBOAPresent()) {
return ((QParamsQconf) getQp()).getLoadedDataContainer().getBOAState();
} else {
throw new QuimpException("BOA data not found in QCONF file. Run BOA first.");
}
}
/**
* Query for ECMM object.
*
* @return ECMM object from loaded configuration
* @throws QuimpException when there is no such object in file or old format is used.
*/
public OutlinesCollection getEcmm() throws QuimpException {
if (isECMMPresent()) {
return ((QParamsQconf) getQp()).getLoadedDataContainer().getEcmmState();
} else {
throw new QuimpException("ECMM data not found in QCONF file. Run ECMM first.");
}
}
/**
* Query for ANA object.
*
* @return ANA object from loaded configuration
* @throws QuimpException when there is no such object in file or old format is used.
*/
public ANAParamCollection getANA() throws QuimpException {
if (isANAPresent()) {
return ((QParamsQconf) getQp()).getLoadedDataContainer().getANAState();
} else {
throw new QuimpException("ANA data not found in QCONF file. Run ANA first.");
}
}
/**
* Query for Q object.
*
* @return Q object from loaded configuration
* @throws QuimpException when there is no such object in file or old format is used.
*/
public STmap[] getQ() throws QuimpException {
if (isQPresent()) {
return ((QParamsQconf) getQp()).getLoadedDataContainer().getQState();
} else {
throw new QuimpException("Q data not found in QCONF file. Run Q Analysis first.");
}
}
/**
* Query for Stats object.
*
* @return Stats object from loaded configuration
* @throws QuimpException when there is no such object in file or old format is used.
*/
public StatsCollection getStats() throws QuimpException {
if (isStatsPresent()) {
return ((QParamsQconf) getQp()).getLoadedDataContainer().getStats();
} else {
throw new QuimpException("Stats not found in QCONF file. Run BOA Analysis first.");
}
}
/**
* Return QParams object.
*
* @return the qp, can be null if loading dialog was cancelled.
*/
public QParams getQp() {
return qp;
}
/**
* Return type of loaded file or 0 if not loaded yet.
*
* @return {@link QconfLoader#QCONF_INVALID} or {@link QParams#NEW_QUIMP},
* {@link QParams#QUIMP_11}
*/
public int isFileLoaded() {
int ret = validateQconf();
if (ret == QconfLoader.QCONF_INVALID) {
return QconfLoader.QCONF_INVALID;
} else {
return getQp().getParamFormat();
}
}
/**
* Return path to loaded configuration file. A value here does not mean that file has been
* successfully loaded.
*
* @return path to loaded file
* @see QParamsQconf#getParamFile()
*/
public Path getQconfFile() {
return qconfFile;
}
}