Skip to content

Commit

Permalink
Compute distances WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
tischi committed Dec 21, 2023
1 parent 3886102 commit 0b73566
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 23 deletions.
135 changes: 114 additions & 21 deletions src/main/java/org/embl/mobie/lib/table/DistanceComputer.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,135 @@
package org.embl.mobie.lib.table;

import ij.IJ;
import ij.gui.GenericDialog;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.util.Pair;
import net.imglib2.util.ValuePair;
import org.embl.mobie.lib.annotation.Annotation;
import org.embl.mobie.lib.color.ColoringModels;
import org.embl.mobie.lib.color.MobieColoringModel;
import org.embl.mobie.lib.color.NumericAnnotationColoringModel;
import org.embl.mobie.lib.color.lut.LUTs;
import org.embl.mobie.lib.select.SelectionModel;
import org.jetbrains.annotations.NotNull;
import org.scijava.util.ColorRGB;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class DistanceComputer
{
public static < A extends Annotation > void showUI( AnnotationTableModel< A > tableModel, SelectionModel< A > selectionModel )
public static < A extends Annotation > void showUI( AnnotationTableModel< A > tableModel, SelectionModel< A > selectionModel, MobieColoringModel< A > coloringModel )
{
// show dialog
//
final GenericDialog gd = new GenericDialog( "" );
gd.addStringField( "Distance Columns RegEx", ".*" );
gd.addStringField( "Distance Columns RegEx", "anchor_.*" );
gd.addStringField( "Results Column Name", "distance" );
gd.addCheckbox( "Color by Distances", true );
gd.showDialog();
if( gd.wasCanceled() ) return;
final String columnsRegEx = gd.getNextChoice();
final String columnNamesRegEx = gd.getNextString();
final String resultColumnName = gd.getNextString();
boolean colorByDistances = gd.getNextBoolean();

List< String > selectedColumnNames = tableModel.columnNames().stream()
.filter( columnName -> columnName.matches( columnNamesRegEx ) )
.collect( Collectors.toList() );

if ( selectedColumnNames.isEmpty() )
{
IJ.log( "The regular expression " + columnNamesRegEx + " did not match any column names." );
return;
}

// for all selected selectedColumns compute the average or median value
// of all selectedAnnotations and store this in a Map< ColumnName, double >
Set< A > selectedAnnotations = selectionModel.getSelected();

// TODO provide options for other averaging methods, e.g. median
Map< String, Double > columnAverages = averageSelectedAnnotations( selectedColumnNames, selectedAnnotations );

// for all annotations compute the Euclidean distance
// to the above computed average of the selected annotations
tableModel.addNumericColumn( resultColumnName );

long start = System.currentTimeMillis();
computeEuclidanDistances( tableModel, selectedColumnNames, columnAverages, resultColumnName );
String distanceMetric = "Euclidan";
IJ.log( "Computed the " + distanceMetric + " distance of " + selectedColumnNames.size()
+ " features for " + tableModel.annotations().size() + " annotations in " +
( System.currentTimeMillis() - start ) + " ms.");

if ( colorByDistances )
{
NumericAnnotationColoringModel< A > numericModel =
ColoringModels.createNumericModel(
resultColumnName,
LUTs.BLUE_WHITE_RED,
tableModel.getMinMax( resultColumnName ),
true );
coloringModel.setColoringModel( numericModel );
coloringModel.setOpacityNotSelected( 1.0 );
coloringModel.setSelectionColor( new ARGBType( ARGBType.rgba( 255, 255, 0, 255 ) ) );
}

}

@NotNull
private static < A extends Annotation > Map< String, Double > averageSelectedAnnotations( List< String > selectedColumnNames, Set< A > selectedAnnotations )
{
Map<String, Double> columnAverages = new HashMap<>();
for (String column : selectedColumnNames ) {
double sum = selectedAnnotations.stream()
.mapToDouble(annotation -> annotation.getNumber(column).doubleValue())
.sum();
double average = sum / selectedAnnotations.size();
columnAverages.put(column, average);
}
return columnAverages;
}

@NotNull
private static < A extends Annotation > Map< String, Double > medianSelectedAnnotations( List< String > selectedColumnNames, Set< A > selectedAnnotations )
{
Map<String, Double> columnMedians = new HashMap<>();
for (String column : selectedColumnNames) {
List<Double> values = selectedAnnotations.stream()
.map(annotation -> annotation.getNumber(column).doubleValue())
.sorted()
.collect(Collectors.toList());
double median;
int size = values.size();
if (size % 2 == 0) {
median = (values.get(size / 2 - 1) + values.get(size / 2)) / 2.0;
} else {
median = values.get(size / 2);
}
columnMedians.put(column, median);
}
return columnMedians;
}

private static < A extends Annotation > void computeEuclidanDistances( AnnotationTableModel< A > tableModel, List< String > selectedColumnNames, Map< String, Double > columnAverages, String resultColumnName )
{
// Compute Euclidean distances
List< A > annotations = tableModel.annotations();

for ( A annotation : annotations )
{
double sumOfSquares = 0.0;
for ( String column : selectedColumnNames ) {
final double value = annotation.getNumber(column).doubleValue();
final double average = columnAverages.get(column);
sumOfSquares += Math.pow(value - average, 2);
}
final double euclideanDistance = Math.sqrt( sumOfSquares );
annotation.setNumber( resultColumnName, euclideanDistance);
}

// TODO
// tableModel.addNumericColumn( resultColumnName );
//
// selectedColumns = tableModel.columnNames().stream() // TODO: select columns that match columnsRegEx
//
// // for all selected selectedColumns compute the average or median value
// // of all selectedAnnotations
// Set< A > selectedAnnotations = selectionModel.getSelected();
// for ( A annotation : selectedAnnotations )
// {
// annotation.getNumber( column ) // TODO
// }
//
// // for all annotations compute the Euclidean distance
// // to the above computed average of the selected annotations
//
// tableModel.annotations()
// tableModel.annotation( index ).setString( );
}
}
6 changes: 5 additions & 1 deletion src/main/java/org/embl/mobie/lib/table/SwingTableModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,16 @@ public Object getValueAt( int rowIndex, int columnIndex )
@Override
public void setValueAt( Object aValue, int rowIndex, int columnIndex )
{
final String columnName = getColumnName( columnIndex );
final Class< ? > columnClass = getColumnClass( columnIndex );
if ( columnClass.equals( String.class ) )
{
final String columnName = getColumnName( columnIndex );
tableModel.annotations().get( rowIndex ).setString( columnName, aValue.toString() );
}
else if ( columnClass.isAssignableFrom( Number.class ) )
{
tableModel.annotations().get( rowIndex ).setNumber( columnName, ( Double ) aValue );
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/embl/mobie/lib/table/TableView.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ private JMenuItem createComputeDistanceMenuItem()
final JMenuItem menuItem = new JMenuItem( "Compute Distance to Selected Rows..." );
menuItem.addActionListener( e ->
SwingUtilities.invokeLater( ()
-> DistanceComputer.showUI( tableModel, selectionModel ) ) );
-> DistanceComputer.showUI( tableModel, selectionModel, coloringModel ) ) );
return menuItem;
}

Expand Down

0 comments on commit 0b73566

Please sign in to comment.