package cz.cuni.lf1.lge.ThunderSTORM.results;
import cz.cuni.lf1.lge.ThunderSTORM.CameraSetupPlugIn;
import static cz.cuni.lf1.lge.ThunderSTORM.util.MathProxy.max;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.Units;
import cz.cuni.lf1.lge.ThunderSTORM.util.ArrayIndexComparator;
import cz.cuni.lf1.lge.ThunderSTORM.util.IValue;
import cz.cuni.lf1.lge.ThunderSTORM.util.Pair;
import ij.IJ;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Vector;
import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
public class GenericTableModel extends AbstractTableModel implements Cloneable {
public static final int COLUMN_NOT_FOUND = -1;
protected Vector<Molecule> rows;
protected MoleculeDescriptor columns;
private int maxId;
public GenericTableModel(GenericTableModel res) {
setModelData(res);
}
protected final void setModelData(GenericTableModel res) {
this.rows = res.rows;
this.columns = res.columns;
this.maxId = res.maxId;
}
// -----------------------------------------------------
public void sortTableByColumn(String colname) {
if(!columnExists(colname)){
return;
}
ArrayIndexComparator cmp = new ArrayIndexComparator(getColumnAsDoubles(colname, Units.UNITLESS));
Integer indices [] = cmp.createIndexArray();
Arrays.sort(indices, cmp);
Molecule [] sorted = new Molecule[rows.size()];
for(int i = 0; i < indices.length; i++) {
sorted[i] = rows.elementAt(indices[i].intValue());
}
rows.clear();
rows.addAll(Arrays.asList(sorted));
}
public void setLabel(int column, String new_name, Units new_units) {
assert(new_units != null);
if (new_name != null) {
columns.setColumnName(column, new_name);
}
columns.units.setElementAt(new_units, column);
columns.labels.setElementAt(columns.getLabel(column, true), column);
fireTableStructureChanged();
}
public void setLabel(String name, String new_name, Units new_units) {
assert(new_units != null);
int column = columns.getParamColumn(name);
if (new_name != null) {
columns.names.setElementAt(new_name, column);
}
columns.units.setElementAt(new_units, column);
columns.getLabel(column, true);
fireTableStructureChanged();
}
public void reset() {
rows.clear();
columns = new MoleculeDescriptor(new String[] { MoleculeDescriptor.LABEL_ID });
maxId = 0;
try {
insertIdColumn();
} catch(Exception ex) {
assert(false) : "This was supposed to never happen due to the `reset` call!";
}
fireTableStructureChanged();
fireTableDataChanged();
}
public Double[] getColumnAsDoubleObjects(String columnName, Units units) {
Double [] column = new Double[rows.size()];
int colidx = columns.getParamIndex(columnName);
if(units == null) {
for(int i = 0; i < column.length; i++) {
column[i] = rows.elementAt(i).getParamAt(colidx);
}
} else {
Units src = columns.units.elementAt(columns.getParamColumn(columnName));
for(int i = 0; i < column.length; i++) {
column[i] = src.convertTo(units, rows.elementAt(i).getParamAt(colidx));
}
}
return column;
}
public double[] getColumnAsDoubles(String columnName, Units units) {
double [] column = new double[rows.size()];
int colidx = columns.getParamIndex(columnName);
if(units == null) {
for(int i = 0; i < column.length; i++) {
column[i] = rows.elementAt(i).getParamAt(colidx);
}
} else {
Units src = columns.units.elementAt(columns.getParamColumn(columnName));
for(int i = 0; i < column.length; i++) {
column[i] = src.convertTo(units, rows.elementAt(i).getParamAt(colidx));
}
}
return column;
}
// -----------------------------------------------------
public GenericTableModel() {
rows = new Vector<Molecule>();
columns = new MoleculeDescriptor(new String[] { MoleculeDescriptor.LABEL_ID });
maxId = 0;
}
public int getNewId() {
return (maxId += 1);
}
@Override
public int getRowCount() {
return rows.size();
}
@Override
public int getColumnCount() {
return columns.getParamsCount();
}
// this is in fact label for column heading, which the TableView asks for this class
@Override
public String getColumnName(int columnIndex) {
return getColumnLabel(columnIndex);
}
public String getColumnLabel(int columnIndex) {
return columns.getLabel(columnIndex, false);
}
// this is ugly, because getColumnName method is used by JTable to recieve
// label of a column, but IJResultsTable needs to distinguish between
// names and labels, i.e., label is "name [units]", not just "name"
public String getColumnRealName(int columnIndex) {
return columns.getParamNameAt(columnIndex);
}
public String getColumnLabel(String columnName) {
return columns.getLabel(columns.getParamColumn(columnName), false);
}
public static Pair<String,Units> parseColumnLabel(String columnLabel) {
return MoleculeDescriptor.parseColumnLabel(columnLabel);
}
@Override
public int findColumn(String columnName) {
if(!columns.hasParam(columnName)) {
return COLUMN_NOT_FOUND;
}
return columns.getParamColumn(columnName);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Double.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return (columnIndex > 0); // column id is not editable!
}
Molecule getRow(int index) {
return rows.elementAt(index);
}
@Override
public Double getValueAt(int rowIndex, int columnIndex) {
return rows.elementAt(rowIndex).values[columns.indices.elementAt(columnIndex)];
}
public Double getValueAt(int rowIndex, String columnName) {
return rows.elementAt(rowIndex).values[columns.getParamIndex(columnName)];
}
public void setColumnUnits(String columnName, Units new_units) {
setColumnUnits(columns.getParamColumn(columnName), new_units);
}
public void setColumnUnits(int columnIndex, Units new_units) {
setColUnitsImpl(columnIndex, new_units);
fireTableStructureChanged();
}
private void setColUnitsImpl(int columnIndex, Units new_units) {
MoleculeDescriptor.Units old_units = getColumnUnits(columnIndex);
if(old_units.equals(new_units)) {
return;
}
for(int row = 0, max = getRowCount(); row < max; row++) {
setValImpl(old_units.convertTo(new_units, getValueAt(row, columnIndex)), row, columnIndex);
}
columns.setColumnUnits(new_units, columnIndex);
}
public Units getColumnUnits(String columnName) {
return columns.units.elementAt(columns.getParamColumn(columnName));
}
public Units getColumnUnits(int columnIndex) {
return columns.units.elementAt(columnIndex);
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
if(!getColumnClass(columnIndex).isInstance(value)) {
throw new ClassCastException("Class of the object does not match the class of the column!");
}
setValImpl((Double)value, rowIndex, columnIndex);
fireTableCellUpdated(rowIndex, columnIndex);
}
private void setValImpl(double value, int rowIndex, int columnIndex) {
rows.elementAt(rowIndex).setParamAt(columns.indices.elementAt(columnIndex), value);
}
// Note that if a molecule already has an id included in `values`,
// a method with `id` must be called!
public synchronized int addRow(double [] values) {
return addRow(new Molecule(columns, values));
}
public synchronized int addRow(Molecule mol) {
if(rows.isEmpty()) { // if the table is empty, use descriptor from the molecule instance
setDescriptor(mol.descriptor);
}
columns.validateMolecule(mol);
mol.descriptor = columns;
rows.add(mol);
if(mol.hasParam(MoleculeDescriptor.LABEL_ID)) {
maxId = (int)max(maxId, mol.getParam(MoleculeDescriptor.LABEL_ID));
}
int last = rows.size() - 1;
fireTableRowsInserted(last, last);
return last;
}
public void setDescriptor(MoleculeDescriptor descriptor) {
columns = descriptor;
for(Molecule row : rows) {
descriptor.validateMolecule(row);
}
fireTableStructureChanged();
}
public MoleculeDescriptor cloneDescriptor() {
return columns.clone();
}
public void insertIdColumn() {
maxId = 0;
insertColumn(0, MoleculeDescriptor.LABEL_ID, Units.UNITLESS, new IValue<Double>(){
@Override
public Double getValue() {
return (double)getNewId();
}
});
}
public void addColumn(String name, Units units, IValue<Double> value) {
insertColumn(getColumnCount(), name, units, value);
}
public void insertColumn(int columnIndex, String name, Units units, IValue<Double> value) {
for(Molecule row : rows) {
row.insertParamAt(columnIndex, name, units, value.getValue());
}
fireTableStructureChanged();
fireTableDataChanged();
}
public void deleteColumn(String name) {
columns.removeParam(name);
fireTableStructureChanged();
}
public void deleteRow(int row) {
rows.removeElementAt(row);
fireTableRowsDeleted(row, row);
}
public Vector<String> getColumnNames() {
return columns.names;
}
public boolean columnExists(int column) {
return ((column >= 0) && (column < getColumnCount()));
}
public boolean columnExists(String columnName) {
return (findColumn(columnName) != COLUMN_NOT_FOUND);
}
public void filterRows(boolean[] keep) {
assert(keep.length == rows.size());
Vector<Molecule> newRows = new Vector<Molecule>();
for(int i = 0; i < keep.length; i++) {
if(keep[i]) {
newRows.add(rows.elementAt(i));
}
}
rows = newRows;
fireTableRowsDeleted(0, keep.length - 1);
}
@Override
public GenericTableModel clone() {
GenericTableModel newModel = new GenericTableModel();
TableModelListener[] listeners = listenerList.getListeners(TableModelListener.class);
newModel.listenerList = new EventListenerList();
for (int i = 0; i < listeners.length; i++) {
newModel.listenerList.add(TableModelListener.class, listeners[i]);
}
newModel.columns = columns.clone();
newModel.rows = new Vector<Molecule>();
for(int i = 0; i < rows.size(); i++) {
newModel.rows.add(rows.elementAt(i).clone(newModel.columns));
}
newModel.maxId = maxId;
return newModel;
}
// ------------------------------------------------------------------------
public void convertAllColumnsToAnalogUnits() {
for(String colName : getColumnNames()) {
Units columnUnits = getColumnUnits(colName);
Units analogUnits = Units.getAnalogUnits(columnUnits);
if(!columnUnits.equals(analogUnits)){
setColUnitsImpl(columns.getParamColumn(colName), analogUnits);
}
fireTableStructureChanged();
}
}
public void convertAllColumnsToDigitalUnits() {
for(String colName : getColumnNames()) {
Units columnUnits = getColumnUnits(colName);
Units digitalUnits = Units.getDigitalUnits(columnUnits);
if(!columnUnits.equals(digitalUnits)){
setColUnitsImpl(columns.getParamColumn(colName), digitalUnits);
}
fireTableStructureChanged();
}
}
public void calculateUncertaintyXY() throws MoleculeDescriptor.Fitting.UncertaintyNotApplicableException {
// Note: even though that the lateral uncertainty can be calculated in pixels,
// we choose to do it in nanometers by default setting
String paramName = MoleculeDescriptor.Fitting.LABEL_UNCERTAINTY_XY;
double paramValue;
Molecule mol;
for(int row = 0, max = getRowCount(); row < max; row++) {
mol = getRow(row);
paramValue = MoleculeDescriptor.Fitting.uncertaintyXY(mol);
if(mol.hasParam(paramName)) {
mol.setParam(paramName, Units.NANOMETER, paramValue);
} else {
mol.addParam(paramName, Units.NANOMETER, paramValue);
}
}
setColumnUnits(paramName, Units.NANOMETER);
fireTableDataChanged();
}
public void calculateUncertaintyZ() throws MoleculeDescriptor.Fitting.UncertaintyNotApplicableException {
String paramName = MoleculeDescriptor.Fitting.LABEL_UNCERTAINTY_Z;
double paramValue;
Molecule mol;
for(int row = 0, max = getRowCount(); row < max; row++) {
mol = getRow(row);
paramValue = MoleculeDescriptor.Fitting.uncertaintyZ(mol);
if(mol.hasParam(paramName)) {
mol.setParam(paramName, Units.NANOMETER, paramValue);
} else {
mol.addParam(paramName, Units.NANOMETER, paramValue);
}
}
setColumnUnits(paramName, Units.NANOMETER);
fireTableDataChanged();
}
}