diff --git a/view-simulation-results/src/main/java/org/vcell/N5/N5ImageHandler.java b/view-simulation-results/src/main/java/org/vcell/N5/N5ImageHandler.java index 82d8bd8..3a9a14e 100644 --- a/view-simulation-results/src/main/java/org/vcell/N5/N5ImageHandler.java +++ b/view-simulation-results/src/main/java/org/vcell/N5/N5ImageHandler.java @@ -1,6 +1,7 @@ package org.vcell.N5; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.scijava.command.Command; @@ -42,6 +43,13 @@ public void run() { setExampleJSONData(); // N5ImageHandler.logService.setLevel(LogService.DEBUG); exportTable.displayExportTable(); + Thread thread = new Thread(() -> { + // For some reason getting a standard client takes three seconds. + // So create one upon initialization, while the user is focused on the GUI + // and by the time they open an Image it's already loaded. + SimResultsLoader.s3ClientBuilder = AmazonS3ClientBuilder.standard(); + }); + thread.start(); } public static Logger getLogger(Class classToLog){ diff --git a/view-simulation-results/src/main/java/org/vcell/N5/SimResultsLoader.java b/view-simulation-results/src/main/java/org/vcell/N5/SimResultsLoader.java index f0804dc..f7d21d0 100644 --- a/view-simulation-results/src/main/java/org/vcell/N5/SimResultsLoader.java +++ b/view-simulation-results/src/main/java/org/vcell/N5/SimResultsLoader.java @@ -9,7 +9,6 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.google.gson.GsonBuilder; import ij.ImagePlus; -import ij.plugin.Duplicator; import net.imglib2.cache.img.CachedCellImg; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.DoubleType; @@ -24,6 +23,7 @@ import org.janelia.saalfeldlab.n5.s3.N5AmazonS3Reader; import org.scijava.log.Logger; import org.vcell.N5.UI.N5ExportTable; +import org.vcell.N5.UI.ImageIntoMemory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -49,10 +49,12 @@ public class SimResultsLoader { private String s3ObjectKey; private URI uri; private String dataSetChosen; - private String userSetFileName = null; + public String userSetFileName = null; private static final String defaultS3Region = "site2-low"; + private N5Reader n5AmazonS3Reader; private static final Logger logger = N5ImageHandler.getLogger(SimResultsLoader.class); + public static AmazonS3ClientBuilder s3ClientBuilder; public SimResultsLoader(){ @@ -70,10 +72,8 @@ public SimResultsLoader(String stringURI, String userSetFileName){ } } - void createS3Client(){ + void createS3ClientAndReader(){ logger.debug("Creating S3 Client with url: " + uri); - AmazonS3ClientBuilder s3ClientBuilder = AmazonS3ClientBuilder.standard(); - if (uri.getHost().equals("minikube.remote") || uri.getHost().equals("minikube.island")){ SSLContext sslContext = null; try { @@ -104,6 +104,8 @@ public boolean verify(String hostname, SSLSession session) { s3ClientBuilder.withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())); s3ClientBuilder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(uri.getScheme() + "://" + uri.getAuthority(), defaultS3Region)); this.s3Client = s3ClientBuilder.build(); + S3KeyValueAccess amazonS3KeyValueAccess = new S3KeyValueAccess(s3Client, bucketName, false); + n5AmazonS3Reader = new N5KeyValueReader(amazonS3KeyValueAccess, s3ObjectKey, new GsonBuilder(), false); } /** @@ -112,9 +114,7 @@ public boolean verify(String hostname, SSLSession session) { don't originate from amazon this is not a format we can possibly mimic, so we have to use path based buckets because it's the fallback style chosen by the N5 libraries if standard format is unavailable. */ - ImagePlus getImgPlusFromN5File() throws IOException { - S3KeyValueAccess amazonS3KeyValueAccess = new S3KeyValueAccess(s3Client, bucketName, false); - N5KeyValueReader n5AmazonS3Reader = new N5KeyValueReader(amazonS3KeyValueAccess, s3ObjectKey, new GsonBuilder(), false); + public ImagePlus getImgPlusFromN5File() throws IOException { // N5AmazonS3Reader n5AmazonS3Reader = new N5AmazonS3Reader(s3Client, bucketName, "/" + s3ObjectKey); long start = System.currentTimeMillis(); @@ -155,23 +155,10 @@ private void setUnits(N5Reader n5Reader, ImagePlus imagePlus){ } } -// private static ImagePlus getSetOfImage(ImagePlus imagePlus){ -// int startZ = 0, startTime = 0, startChannel = 0; -// int endZ = 5, endTime = 2, endChannel = 1; -// ImagePlus newImage = IJ.createHyperStack(imagePlus.getTitle() + " In Memory", imagePlus.getWidth(), -// imagePlus.getHeight(), endChannel, endZ, endTime, imagePlus.getBitDepth()); -// for (startChannel = startChannel; startChannel < endChannel; startChannel++){ -// for (startZ = startZ; startZ < endZ; startZ++){ -// for (startTime = startTime; startTime < endTime; startTime++){ -// int index = imagePlus.getStackIndex(startChannel, startZ, startTime); -// newImage.getImageStack().addSlice("" + startTime + "" + startChannel + "" + startZ, -// imagePlus.getStack().getProcessor(index), index); -// } -// } -// } -// newImage.setDimensions(endChannel, endZ, endTime); -// return newImage; -// } + public ArrayList getN5Dimensions(){ + return n5AmazonS3Reader.getAttribute(dataSetChosen, "dimensions", ArrayList.class); + } + public static void openN5FileDataset(ArrayList filesToOpen, boolean openInMemory){ N5ExportTable.enableCriticalButtons(false); @@ -179,29 +166,30 @@ public static void openN5FileDataset(ArrayList filesToOpen, bo Thread openN5FileDataset = new Thread(() -> { try{ for(SimResultsLoader simResultsLoader: filesToOpen){ - simResultsLoader.createS3Client(); - ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); - if(openInMemory){ - long start = System.currentTimeMillis(); - logger.debug("Loading Virtual N5 File " + simResultsLoader.userSetFileName + " Into Memory"); - - imagePlus = new Duplicator().run(imagePlus); -// imagePlus = SimResultsLoader.getSetOfImage(imagePlus); - long end = System.currentTimeMillis(); - imagePlus.show(); - logger.debug("Loaded Virtual N5 File " + simResultsLoader.userSetFileName + " Into Memory taking: " + ((end - start)/ 1000) + "s"); + simResultsLoader.createS3ClientAndReader(); + ImageIntoMemory imageIntoMemory; + if (openInMemory){ + ArrayList dimensions = simResultsLoader.getN5Dimensions(); + imageIntoMemory = new ImageIntoMemory(dimensions.get(2), dimensions.get(3), dimensions.get(4), simResultsLoader); + imageIntoMemory.displayRangeMenu(); } else{ + ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); imagePlus.show(); } + } - } catch (IOException ex) { + } catch (Exception ex) { + N5ExportTable.exportTableDialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + N5ExportTable.enableCriticalButtons(true); throw new RuntimeException(ex); } finally { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - N5ExportTable.exportTableDialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); - N5ExportTable.enableCriticalButtons(true); + if (!openInMemory) { + N5ExportTable.exportTableDialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + N5ExportTable.enableCriticalButtons(true); + } } }); } diff --git a/view-simulation-results/src/main/java/org/vcell/N5/UI/ImageIntoMemory.java b/view-simulation-results/src/main/java/org/vcell/N5/UI/ImageIntoMemory.java new file mode 100644 index 0000000..3cd4c42 --- /dev/null +++ b/view-simulation-results/src/main/java/org/vcell/N5/UI/ImageIntoMemory.java @@ -0,0 +1,191 @@ +package org.vcell.N5.UI; + +import ij.ImagePlus; +import ij.plugin.Duplicator; +import org.scijava.log.Logger; +import org.vcell.N5.N5ImageHandler; +import org.vcell.N5.SimResultsLoader; + +import javax.swing.*; +import javax.swing.event.EventListenerList; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.io.IOException; + +public class ImageIntoMemory extends EventListenerList implements ActionListener { + public int startC; + public int endC; + public int startT; + public int endT; + public int startZ; + public int endZ; + + private final JTextField channelStartTextField; + private final JTextField channelEndTextField; + private final JTextField timeStartTextField; + private final JTextField timeEndTextField; + private final JTextField zStartTextField; + private final JTextField zEndTextField; + + private final JButton okayButton; + private final JButton cancelButton; + private final JFrame frame; + private final SimResultsLoader simResultsLoader; + + private static final Logger logger = N5ImageHandler.getLogger(ImageIntoMemory.class); + + public ImageIntoMemory(double cDim, double tDim, double zDim, SimResultsLoader simResultsLoader){ + this.simResultsLoader = simResultsLoader; + channelStartTextField = new HintTextField("1"); + channelEndTextField = new HintTextField("" + (int) cDim); + + timeStartTextField = new HintTextField("1"); + timeEndTextField = new HintTextField("" + (int) tDim); + + zStartTextField = new HintTextField("1"); + zEndTextField = new HintTextField("" + (int) zDim); + + // Create the frame + frame = new JFrame("Select " + simResultsLoader.userSetFileName + " Dimensions"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(400, 200); + + // Create a panel to hold the input boxes and buttons + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(4, 4, 10, 10)); // 4 rows: 3 for input, 1 for buttons + + panel.add(new JLabel("Channel Start: ")); + panel.add(channelStartTextField); + panel.add(new JLabel("Channel End: ")); + panel.add(channelEndTextField); + + panel.add(new JLabel("Z-Slice Start: ")); + panel.add(zStartTextField); + panel.add(new JLabel("Z-Slice End: ")); + panel.add(zEndTextField); + + panel.add(new JLabel("Time Start: ")); + panel.add(timeStartTextField); + panel.add(new JLabel("Time End: ")); + panel.add(timeEndTextField); + + + // Create the "Okay" and "Cancel" buttons + panel.add(new JLabel()); + okayButton = new JButton("Okay"); + cancelButton = new JButton("Cancel"); + + // Add action listeners to the buttons + okayButton.addActionListener(this); + + cancelButton.addActionListener(this); + + // Add the buttons to the panel + panel.add(okayButton); + panel.add(cancelButton); + + // Add the panel to the frame + frame.add(panel); + } + + public void displayRangeMenu(){ + // Make the window visible + frame.setVisible(true); + } + + public static void usePopUp(){ + + } + + public static void useExistingParameters(int startC, int endC, int startT, int endT, int startZ, int endZ){ + + } + + public static void main(String[] args) { + ImageIntoMemory inMemoryPopUp = new ImageIntoMemory(1, 2, 3, null); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource().equals(okayButton)){ + frame.dispose(); + + startC = Integer.parseInt(channelStartTextField.getText()) - 1; + endC = Integer.parseInt(channelEndTextField.getText()) - 1; + startT = Integer.parseInt(timeStartTextField.getText()) - 1; + endT = Integer.parseInt(timeEndTextField.getText()) - 1; + startZ = Integer.parseInt(zStartTextField.getText()) - 1; + endZ = Integer.parseInt(zEndTextField.getText()) - 1; + + Thread openInMemory = new Thread(() -> { + try { + ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); + long start = System.currentTimeMillis(); + logger.debug("Loading Virtual N5 File " + simResultsLoader.userSetFileName + " Into Memory"); + imagePlus = new Duplicator().run(imagePlus, startC, endC, startZ, + endZ, startT, endT); + long end = System.currentTimeMillis(); + logger.debug("Loaded Virtual N5 File " + simResultsLoader.userSetFileName + " Into Memory taking: " + ((end - start)/ 1000) + "s"); + imagePlus.show(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } finally { + N5ExportTable.exportTableDialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + N5ExportTable.enableCriticalButtons(true); + } + }); + openInMemory.setName("Open N5 Image in Memory"); + openInMemory.start(); + } + + else if (e.getSource().equals(cancelButton)) { + N5ExportTable.exportTableDialog.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + N5ExportTable.enableCriticalButtons(true); + frame.dispose(); + } + } + + static class HintTextField extends JTextField { + + Font gainFont = new Font("Tahoma", Font.PLAIN, 11); + Font lostFont = new Font("Tahoma", Font.ITALIC, 11); + + public HintTextField(final String hint) { + + setText(hint); + setFont(lostFont); + setForeground(Color.GRAY); + + this.addFocusListener(new FocusAdapter() { + + @Override + public void focusGained(FocusEvent e) { + if (getText().equals(hint)) { + setText(""); + setFont(gainFont); + } else { + setText(getText()); + setFont(gainFont); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (getText().equals(hint)|| getText().isEmpty()) { + setText(hint); + setFont(lostFont); + setForeground(Color.GRAY); + } else { + setText(getText()); + setFont(gainFont); + setForeground(Color.BLACK); + } + } + }); + + } + } +} diff --git a/view-simulation-results/src/main/resources/Help.html b/view-simulation-results/src/main/resources/Help.html index d1bfa4a..cfc7e34 100644 --- a/view-simulation-results/src/main/resources/Help.html +++ b/view-simulation-results/src/main/resources/Help.html @@ -7,33 +7,37 @@

Intro

stored remotely on VCell servers, allowing applications such as ImageJ to directly access the results of these simulations and perform analyses.

-

VCell Spatial Simulation

+

Export Visualization

- Simulation solvers generating 2- or 3-dimensional spatial - domains can be visualized in ImageJ using this plugin. - Spatial dimensions and time directly correspond to fields within an ImageJ - image. The image's channels depict different variables within a simulation. + Virtual Cell has special techniques for spatial simulation results so that they are visually compelling while accurate to their + source data. Those automatic techniques are not implemented within this plugin, so if an export is visually different + from VCell's please go to Image -> Adjust -> Brightness/Contrast, and adjust it to a point where it is similar.

-

N5 and Datasets

-

- Each N5 store is a direct mapping to a VCell simulation, and contains one or more - datasets. Each dataset holds numerical data and metadata corresponding to an Image in ImageJ. - Each N5 store is fully identified through its N5 URL which can be shared and opened by other applications - which support the N5 format. -

-

Accessing Simulation Results

-

For VCell exports generated from your local VCell installation:

+

Dimension Properties

  • -

    Either, Click the recent export button.

    +

    XYZ: The XYZ dimensions directly correspond to the simulations XYZ.

    +
    +
  • +
  • +

    Channel: The different channels represent the variables exported, + and the last channel always represents the geometric domains for your spatial export. +

    +
  • -

    Or, Open the export table and view all past exports with their affiliated metadata.

    +

    Time: Time directly corresponds to the simulations time.

    +
  • +
+

Accessing Simulation Results

+

For VCell exports generated from your local VCell installation:

+
    +
  • +

    Select the appropriate export and click the "Open" button.

From N5 URL (VCell Install Not Required):

    -
  • Click the 'Remote Files' button, paste the N5 URL, - then select dataset from list.
  • +
  • Click the 'Remote Files' button, paste the N5 URL, and click open.
\ No newline at end of file diff --git a/view-simulation-results/src/test/java/org/vcell/N5/N5ImageHandlerTest.java b/view-simulation-results/src/test/java/org/vcell/N5/N5ImageHandlerTest.java index 42e0f4c..1f19578 100644 --- a/view-simulation-results/src/test/java/org/vcell/N5/N5ImageHandlerTest.java +++ b/view-simulation-results/src/test/java/org/vcell/N5/N5ImageHandlerTest.java @@ -1,5 +1,6 @@ package org.vcell.N5; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.google.gson.internal.LinkedTreeMap; import ij.ImagePlus; import ij.io.Opener; @@ -8,6 +9,7 @@ import ij.process.ImageProcessor; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import java.io.File; @@ -44,6 +46,12 @@ private File getTestResourceFiles(String filePath){ } } + @BeforeClass + public static void init(){ + N5ImageHandler.initializeLogService(); + SimResultsLoader.s3ClientBuilder = AmazonS3ClientBuilder.standard(); + } + @Before public void run(){ N5ImageHandler.initializeLogService(); @@ -73,7 +81,7 @@ public void testS3AlphaInstance() throws IOException{ N5DataSetFile[] n5DataSetFiles = N5DataSetFile.alphaTestFiles(); for(N5DataSetFile n5DataSetFile : n5DataSetFiles) { SimResultsLoader simResultsLoader = new SimResultsLoader(n5DataSetFile.uri, ""); - simResultsLoader.createS3Client(); + simResultsLoader.createS3ClientAndReader(); ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); //stats that have been preemptively calculated within VCell @@ -88,7 +96,7 @@ public void testS3AlphaInstanceLoadedIntoMemory() throws IOException { N5DataSetFile[] n5DataSetFiles = N5DataSetFile.alphaTestFiles(); for(N5DataSetFile n5DataSetFile : n5DataSetFiles) { SimResultsLoader simResultsLoader = new SimResultsLoader(n5DataSetFile.uri, ""); - simResultsLoader.createS3Client(); + simResultsLoader.createS3ClientAndReader(); ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); ImagePlus inMemory = new Duplicator().run(imagePlus); for (Object property : imagePlus.getProperties().keySet()){ @@ -109,7 +117,7 @@ public void testUnits() throws IOException { N5DataSetFile[] n5DataSetFiles = N5DataSetFile.alphaTestFiles(); for (N5DataSetFile n5DataSetFile: n5DataSetFiles){ SimResultsLoader simResultsLoader = new SimResultsLoader(n5DataSetFile.uri, ""); - simResultsLoader.createS3Client(); + simResultsLoader.createS3ClientAndReader(); ImagePlus imagePlus = simResultsLoader.getImgPlusFromN5File(); double areaOfPixel = imagePlus.getCalibration().getX(1) * imagePlus.getCalibration().getY(1); double totalArea = areaOfPixel * imagePlus.getWidth() * imagePlus.getHeight();