Skip to content

Commit

Permalink
Merge pull request #7 from mpicbg-csbd/macro
Browse files Browse the repository at this point in the history
Make StarDist (and other problematic IJ2 plugins) accessible from IJ1 Macro
  • Loading branch information
uschmidt83 authored Apr 7, 2020
2 parents 7f0cffe + bd98a4e commit 8e249e5
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 3 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,17 @@
<artifactId>csbdeep</artifactId>
<version>0.3.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
</dependencies>
</project>
267 changes: 267 additions & 0 deletions src/main/java/de/csbdresden/CommandFromMacro.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package de.csbdresden;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.scijava.ItemVisibility;
import org.scijava.Named;
import org.scijava.command.Command;
import org.scijava.command.CommandInfo;
import org.scijava.command.CommandModule;
import org.scijava.command.CommandService;
import org.scijava.log.LogService;
import org.scijava.module.ModuleItem;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.service.Service;
import org.scijava.ui.UIService;

import com.google.gson.Gson;

import de.csbdresden.stardist.StarDist2D;
import ij.ImagePlus;
import ij.WindowManager;
import ij.plugin.frame.Recorder;
import net.imagej.Dataset;
import net.imagej.ImageJ;
import net.imglib2.RandomAccessibleInterval;


@Plugin(type = Command.class, label = "Command From Macro", menuPath = "Plugins > StarDist > Other > Command From Macro")
public class CommandFromMacro implements Command {

private static final List<ItemVisibility> SKIP_VISIBILITY = Arrays.asList(ItemVisibility.MESSAGE, ItemVisibility.INVISIBLE);

@Parameter
private String command;

@Parameter
private String args;

@Parameter
private boolean process;

// ---------

@Parameter
private UIService ui;

@Parameter
private CommandService cmd;

@Parameter
private LogService log;

// ---------

@Override
public void run() {

CommandInfo info = cmd.getCommand(command);
if (info == null) {
for (CommandInfo c: cmd.getCommands()) {
try {
if (command.equals(c.getMenuPath().getLeaf().getName())) {
info = c;
command = info.getClassName();
break;
}
} catch (NullPointerException e) {}
}
if (info == null) {
log.error(String.format("Command \"%s\" not found.", command));
return;
}
}

final Map<String,Object> params = new LinkedHashMap<>();
final List<String> outputs = new ArrayList<>();

Map<?,?> argsMap = new Gson().fromJson("{"+args+"}", Map.class);
// System.out.println(argsMap);

for (Object keyO : argsMap.keySet()) {
final String key = String.valueOf(keyO);
final String value = String.valueOf(argsMap.get(keyO));
ModuleItem<?> item = null;

item = info.getInput(key);
if (item != null) {
Class<?> clazz = item.getType();
if (clazz.isPrimitive())
clazz = ClassUtils.primitiveToWrapper(clazz);
params.put(key, toParameter(value, clazz));
} else {
item = info.getOutput(key);
if (item != null) {
outputs.add(key);
} else {
log.warn(String.format("Ignoring argument \"%s\" since neither an input or output of this command.", key));
}
}
}

try {
final CommandModule result = cmd.run(command, process, params).get();
for (String name : outputs) {
final Object output = result.getOutput(name);
if (output != null) ui.show(output);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}


private Object toParameter(final String value, final Class<?> clazz) {
// some special classes (TODO: incomplete)
if (clazz == String.class)
return value;
if (clazz == File.class)
return new File(value);
// all typical number types and boolean are covered by this
if (clazz.getName().startsWith("java.lang.")) {
try {
return clazz.getDeclaredMethod("valueOf", String.class).invoke(null, value);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
// ImagePlus and typical imagej2 image types (all that implement RAI)
if (clazz == ImagePlus.class || RandomAccessibleInterval.class.isAssignableFrom(clazz)) {
final ImagePlus imp = WindowManager.getImage(value);
if (imp == null)
log.error(String.format("Could not find input image with name/title \"%s\".", value));
return imp;
}
log.error(String.format("Cannot process arguments of class \"%s\".", clazz.getName()));
return null;
}


public static boolean record(final Command command, final CommandService commandService) {
return record(command, commandService, false);
}

public static boolean record(final Command command, final CommandService commandService, final boolean process) {
if (Recorder.getInstance() == null)
return false;
final String recorded = Recorder.getCommand();
final CommandInfo info = commandService.getCommand(command.getClass());
// only proceed if this command is being recorded
final String name = info.getMenuPath().getLeaf().getName();
// System.out.printf("RECORDED: %s, COMMAND: %s\n", recorded, name);
if (recorded==null || !recorded.equals(name))
return false;
// prevent automatic recording
Recorder.setCommand(null);
// record manually
Recorder.recordString(getMacroString(command, commandService, process));
return true;
}


private static String getMacroString(final Command command, final CommandService commandService, final boolean process) {
final Class<?> commandClass = command.getClass();
final CommandInfo info = commandService.getCommand(command.getClass());
final Map<String,String> args = new LinkedHashMap<>();

// add input parameters as arguments
for (final ModuleItem<?> item : info.inputs()) {
final String name = item.getName();
final Class<?> clazz = item.getType();
if (SKIP_VISIBILITY.contains(item.getVisibility()) || // skip items that shouldn't be recorded
Service.class.isAssignableFrom(clazz)) // skip all (injected) services
continue;
try {
final Field field = commandClass.getDeclaredField(name);
if (!field.isAccessible()) field.setAccessible(true);
final Object value = field.get(command);
// skip unassigned items (includes buttons)
if (value == null)
continue;
if (Named.class.isAssignableFrom(clazz)) // ImgPlus and Dataset
args.put(name, ((Named)value).getName());
else if (clazz == ImagePlus.class)
args.put(name, ((ImagePlus)value).getTitle());
else
args.put(name, String.valueOf(value));
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}

// designate assigned outputs to be shown
for (final ModuleItem<?> item : info.outputs()) {
final String name = item.getName();
try {
final Field field = commandClass.getDeclaredField(name);
if (!field.isAccessible()) field.setAccessible(true);
final Object value = field.get(command);
// skip unassigned outputs
if (value == null)
continue;
args.put(name, "");
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}

// convert to json and remove curly braces
String argsStr = new Gson().toJson(args);
argsStr = argsStr.substring(1, argsStr.length()-1);
// to make the macro string look nicer (otherwise have to escape all double quotes):
// replace double quotes around json keys/values with single quotes
// technically not correct json, but can be parsed by Gson
final StringBuilder sb = new StringBuilder(argsStr);
int p = 0;
for (Entry<String, String> arg: args.entrySet()) {
final int k = StringEscapeUtils.escapeJava(arg.getKey()).length();
final int v = StringEscapeUtils.escapeJava(arg.getValue()).length();
sb.replace(p, p+1, "'"); p+=1+k;
sb.replace(p, p+1, "'"); p+=2;
sb.replace(p, p+1, "'"); p+=1+v;
sb.replace(p, p+1, "'"); p+=2;
}
argsStr = sb.toString();

final String execName = commandService.getCommand(CommandFromMacro.class).getMenuPath().getLeaf().getName();
return String.format("run(\"%s\", \"command=[%s], args=[%s], process=[%s]\");\n",
execName, commandClass.getName(), argsStr, String.valueOf(process));
}


public static void main(final String... args) throws Exception {

final ImageJ ij = new ImageJ();
ij.launch(args);

Dataset input = ij.scifio().datasetIO().open(StarDist2D.class.getClassLoader().getResource("yeast_crop.tif").getFile());
ij.ui().show(input);

// Dataset input2 = ij.scifio().datasetIO().open(StarDist2D.class.getClassLoader().getResource("yeast_timelapse.tif").getFile());
// ij.ui().show(input2);

Recorder recorder = new Recorder();
recorder.show();

final Map<String, Object> params = new LinkedHashMap<>();
// params.put("input", input);
ij.command().run(StarDist2D.class, true, params);


// IJ.run("CommandFromMacro", "args=[\"input\":\"yeast_crop.tif\", \"label\":\"\"], process=[false], command=[de.csbdresden.stardist.StarDist2D]");
}


}
9 changes: 8 additions & 1 deletion src/main/java/de/csbdresden/stardist/StarDist2D.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.scijava.widget.ChoiceWidget;
import org.scijava.widget.NumberWidget;

import de.csbdresden.CommandFromMacro;
import ij.IJ;
import ij.ImagePlus;
import net.imagej.Dataset;
Expand All @@ -47,7 +48,7 @@
@Menu(label = MenuConstants.PLUGINS_LABEL, weight = MenuConstants.PLUGINS_WEIGHT, mnemonic = MenuConstants.PLUGINS_MNEMONIC),
@Menu(label = "StarDist"),
@Menu(label = "StarDist 2D", weight = 1)
})
})
public class StarDist2D extends StarDist2DBase implements Command {

@Parameter(label="", visibility=ItemVisibility.MESSAGE, initializer="checkForCSBDeep")
Expand Down Expand Up @@ -317,6 +318,9 @@ public void run() {
final Future<CommandModule> futureNMS = command.run(StarDist2DNMS.class, false, paramsNMS);
label = (Dataset) futureNMS.get().getOutput("label");
}
// call at the end of the run() method
CommandFromMacro.record(this, this.command);

} catch (InterruptedException | ExecutionException | IOException e) {
e.printStackTrace();
} finally {
Expand Down Expand Up @@ -384,6 +388,9 @@ public static void main(final String... args) throws Exception {
// Dataset input = ij.scifio().datasetIO().open(StarDist2D.class.getClassLoader().getResource("yeast_timelapse.tif").getFile());
Dataset input = ij.scifio().datasetIO().open(StarDist2D.class.getClassLoader().getResource("yeast_crop.tif").getFile());
ij.ui().show(input);

// Recorder recorder = new Recorder();
// recorder.show();

final HashMap<String, Object> params = new HashMap<>();
ij.command().run(StarDist2D.class, true, params);
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/de/csbdresden/stardist/StarDist2DBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ protected Dataset labelImageToDataset(String outputType) {
return Utils.raiToDataset(dataset, Opt.LABEL_IMAGE, labelImg, axes);
} else {
return null;
}

}
}
}
4 changes: 4 additions & 0 deletions src/main/java/de/csbdresden/stardist/StarDist2DNMS.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.scijava.widget.ChoiceWidget;
import org.scijava.widget.NumberWidget;

import de.csbdresden.CommandFromMacro;
import ij.IJ;
import ij.ImagePlus;
import net.imagej.Dataset;
Expand Down Expand Up @@ -117,6 +118,9 @@ public void run() {
}

label = labelImageToDataset(outputType);

// call at the end of the run() method
CommandFromMacro.record(this, this.command);
}


Expand Down

0 comments on commit 8e249e5

Please sign in to comment.