diff --git a/pom.xml b/pom.xml index 2f4950f..a06542f 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.micro-manager.acqengj AcqEngJ - 0.27.0 + 0.28.0 jar AcqEngJ Java-based Acquisition engine for Micro-Manager diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index 3066f8f..08a65cc 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -19,6 +19,7 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import mmcorej.org.json.JSONException; import org.micromanager.acqj.api.AcquisitionAPI; import org.micromanager.acqj.main.AcquisitionEvent; import org.micromanager.acqj.api.AcquisitionHook; @@ -534,6 +535,12 @@ private void acquireImages(final AcquisitionEvent event) throws HardwareControlE //add metadata AcqEngMetadata.addImageMetadata(ti.tags, correspondingEvent, currentTime - correspondingEvent.acquisition_.getStartTime_ms(), exposure); + try { + correspondingEvent.acquisition_.addTagsToTaggedImage(ti.tags, + correspondingEvent.getTags()); + } catch (JSONException jse) { + core_.logMessage("Error adding tags to image metadata", false); + } correspondingEvent.acquisition_.addToImageMetadata(ti.tags); correspondingEvent.acquisition_.addToOutput(ti); diff --git a/src/main/java/org/micromanager/acqj/main/AcqEngMetadata.java b/src/main/java/org/micromanager/acqj/main/AcqEngMetadata.java index 52c0655..7254d7d 100644 --- a/src/main/java/org/micromanager/acqj/main/AcqEngMetadata.java +++ b/src/main/java/org/micromanager/acqj/main/AcqEngMetadata.java @@ -77,10 +77,11 @@ public class AcqEngMetadata { public static final String CORE_XYSTAGE = "Core-XYStage"; public static final String CORE_FOCUS = "Core-Focus"; public static final String AXES = "Axes"; - public static final String CHANNEL_AXIS = "channel"; public static final String TIME_AXIS = "time"; public static final String Z_AXIS = "z"; + public static final String TAGS = "tags"; + private static final String ACQUISITION_EVENT = "Event"; diff --git a/src/main/java/org/micromanager/acqj/main/Acquisition.java b/src/main/java/org/micromanager/acqj/main/Acquisition.java index 0fbcfc9..6b44a10 100644 --- a/src/main/java/org/micromanager/acqj/main/Acquisition.java +++ b/src/main/java/org/micromanager/acqj/main/Acquisition.java @@ -16,7 +16,9 @@ // package org.micromanager.acqj.main; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.concurrent.*; import java.util.function.Consumer; @@ -156,11 +158,35 @@ public void addToImageMetadata(JSONObject tags) { } } + /** + * Add provided tags (Key-Value pairs of type String) to the Tagged Image tags. + * These will appear as JSONObjects under the key: + * {@link org.micromanager.acqj.main.AcqEngMetadata#TAGS "Tags"}. + * + * @param tags Tagged Image tags + * @param moreTags User-provided tags as Key-Value pairs + */ + public void addTagsToTaggedImage(JSONObject tags, HashMap moreTags) + throws JSONException { + if (moreTags.isEmpty()) { + return; + } + JSONObject moreTagsObject = new JSONObject(); + for (Map.Entry entry : moreTags.entrySet()) { + try { + moreTagsObject.put(entry.getKey(), entry.getValue()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + tags.put(AcqEngMetadata.TAGS, moreTagsObject); + } + @Override public Future submitEventIterator(Iterator evt) { if (!started_) { start(); - } + } return Engine.getInstance().submitEventIterator(evt); } @@ -283,19 +309,21 @@ public void waitForCompletion() { * 3) Initialize data sink. */ protected void initialize() { - JSONObject summaryMetadata = AcqEngMetadata.makeSummaryMD(this); - addToSummaryMetadata(summaryMetadata); + if (core_ != null) { + JSONObject summaryMetadata = AcqEngMetadata.makeSummaryMD(this); + addToSummaryMetadata(summaryMetadata); - try { - // Make a local in copy in case something else modifies it - summaryMetadata_ = new JSONObject(summaryMetadata.toString()); - } catch (JSONException ex) { - System.err.print("Couldn't copy summaary metadata"); - ex.printStackTrace(); - } - if (dataSink_ != null) { - //It could be null if not using saving and viewing and diverting with custom processor - dataSink_.initialize(this, summaryMetadata); + try { + // Make a local in copy in case something else modifies it + summaryMetadata_ = new JSONObject(summaryMetadata.toString()); + } catch (JSONException ex) { + System.err.print("Couldn't copy summaary metadata"); + ex.printStackTrace(); + } + if (dataSink_ != null) { + //It could be null if not using saving and viewing and diverting with custom processor + dataSink_.initialize(this, summaryMetadata); + } } } diff --git a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java index 9b59336..34d3a5e 100644 --- a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java +++ b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java @@ -21,7 +21,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; import mmcorej.org.json.JSONArray; @@ -64,6 +66,8 @@ enum SpecialFlag { private HashMap stageCoordinates_ = new HashMap(); // Mapping from device names to axis names private HashMap stageDeviceNamesToAxisNames_ = new HashMap(); + // tags to be added to the acquired image + private final HashMap tags_ = new HashMap(); //Option to not acquire an image for SLM events private Boolean acquireImage_ = null; @@ -142,6 +146,7 @@ public AcquisitionEvent copy() { e.acquireImage_ = acquireImage_; e.properties_ = new TreeSet(this.properties_); e.camera_ = camera_; + e.setTags(tags_); return e; } @@ -213,6 +218,14 @@ private static JSONObject eventToJSON(AcquisitionEvent e) { json.put("camera", e.camera_); } + if (e.getTags() != null) { + JSONObject jsonTags = new JSONObject(); + for (Map.Entry entry : e.getTags().entrySet()) { + jsonTags.put(entry.getKey(), entry.getValue()); + } + json.put("tags", jsonTags); + } + //TODO: galvo //TODO: more support for imperative API calls (i.e. SLM set image) //Arbitrary extra properties @@ -223,7 +236,7 @@ private static JSONObject eventToJSON(AcquisitionEvent e) { prop.put(t.prop); prop.put(t.val); props.put(prop); - } + } if (props.length() > 0) { json.put("properties", props); } @@ -318,6 +331,17 @@ private static AcquisitionEvent eventFromJSON(JSONObject json, AcquisitionAPI ac event.camera_ = json.getString("camera"); } + if (json.has("tags")) { + HashMap tags = new HashMap<>(); + JSONObject jsonTags = json.getJSONObject("tags"); + Iterator keys = jsonTags.keys(); + while (keys.hasNext()) { + String key = keys.next(); + tags.put(key, jsonTags.getString(key)); + } + event.setTags(tags); + } + //TODO: galvo, etc (i.e. other aspects of imperative API) @@ -434,9 +458,9 @@ public void setProperty(String device, String property, String value) { } /** - * Set the minimum start time in ms relative to when the acq started + * Set the minimum start time in ms relative to when the acq started. * - * @param l + * @param l Minimum start time in ms. */ public void setMinimumStartTime(Long l) { miniumumStartTime_ms_ = l; @@ -544,7 +568,7 @@ public Double getZPosition() { } /** - * get the minimum start timein system time + * Get the minimum start time in system time. * * @return */ @@ -621,6 +645,18 @@ public void setY(double y) { yPosition_ = y; } + public void setTags(HashMap tags) { + tags_.clear(); + if (tags != null) { + tags_.putAll(tags); + } + } + public HashMap getTags () { + HashMap tags = new HashMap<>(tags_.size()); + tags.putAll(tags_); + return tags; + } + //For debugging @Override diff --git a/tests/org/micromanager/acqj/main/TestAcquisitionEvent.java b/tests/org/micromanager/acqj/main/TestAcquisitionEvent.java new file mode 100644 index 0000000..555db5e --- /dev/null +++ b/tests/org/micromanager/acqj/main/TestAcquisitionEvent.java @@ -0,0 +1,41 @@ +package org.micromanager.acqj.main; + +import java.util.HashMap; +import mmcorej.org.json.JSONObject; +import org.junit.Assert; +import org.junit.Test; +import org.micromanager.acqj.example.BlackHoleDataSink; + +public class TestAcquisitionEvent { + @Test + public void testSetupIsSane() { + Assert.assertTrue("must be true", true); + } + + @Test + public void testEventSerialization() { + BlackHoleDataSink bhds = new BlackHoleDataSink(); + Acquisition acq = new Acquisition(bhds); + AcquisitionEvent event = new AcquisitionEvent(acq); + final int z = 2; + event.setZ(z, z * 0.5); + final int t = 3; + event.setTimeIndex(t); + final long minimumStartTime = 892567265; + event.setMinimumStartTime(minimumStartTime); + HashMap tags = new HashMap<>(); + tags.put("test", "test"); + tags.put("test2", "test2"); + + JSONObject eventSerial = event.toJSON(); + AcquisitionEvent eventCopy = AcquisitionEvent.fromJSON(eventSerial, acq); + + Assert.assertTrue(eventCopy.getZIndex() == z); + Assert.assertEquals(eventCopy.getZPosition(), z * 0.5, 0.0000001); + Assert.assertTrue(eventCopy.getTIndex() == t); + Assert.assertEquals(eventCopy.getMinimumStartTimeAbsolute(), minimumStartTime, 2000); + Assert.assertEquals(eventCopy.getTags().get("test"), event.getTags().get("test")); + Assert.assertEquals(eventCopy.getTags().get("test2"), event.getTags().get("test2")); + } +} +