From c1639eb08d3b4059c0c7cfcf3e6963753ab90dc3 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Sat, 3 Jun 2023 20:57:15 -0700 Subject: [PATCH 1/6] Add tags in events that will be added to metadata. --- .../micromanager/acqj/internal/Engine.java | 1 + .../micromanager/acqj/main/Acquisition.java | 20 ++++++++++++++++- .../acqj/main/AcquisitionEvent.java | 22 ++++++++++++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index 3066f8f..e789d4b 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -534,6 +534,7 @@ private void acquireImages(final AcquisitionEvent event) throws HardwareControlE //add metadata AcqEngMetadata.addImageMetadata(ti.tags, correspondingEvent, currentTime - correspondingEvent.acquisition_.getStartTime_ms(), exposure); + correspondingEvent.acquisition_.addTagsToTaggedImage(ti.tags, correspondingEvent.getTags()); correspondingEvent.acquisition_.addToImageMetadata(ti.tags); correspondingEvent.acquisition_.addToOutput(ti); diff --git a/src/main/java/org/micromanager/acqj/main/Acquisition.java b/src/main/java/org/micromanager/acqj/main/Acquisition.java index 0fbcfc9..c8c0bb1 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,27 @@ public void addToImageMetadata(JSONObject tags) { } } + /** + * Add provided tags (Key-Value pairs of type String) to the Tagged Image tags. + * + * @param tags Tagged Image tags + * @param moreTags User-provided tags as Key-Value pairs + */ + public void addTagsToTaggedImage(JSONObject tags, HashMap moreTags) { + for (Map.Entry entry : moreTags.entrySet()) { + try { + tags.put(entry.getKey(), entry.getValue()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + @Override public Future submitEventIterator(Iterator evt) { if (!started_) { start(); - } + } return Engine.getInstance().submitEventIterator(evt); } diff --git a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java index 9b59336..085cab4 100644 --- a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java +++ b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java @@ -22,8 +22,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.stream.Collectors; import mmcorej.org.json.JSONArray; import mmcorej.org.json.JSONException; import mmcorej.org.json.JSONObject; @@ -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; @@ -434,9 +438,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 +548,7 @@ public Double getZPosition() { } /** - * get the minimum start timein system time + * Get the minimum start time in system time. * * @return */ @@ -621,6 +625,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 From f7000ca2a8ae8208e146c5bf3b8002305b812228 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Sun, 4 Jun 2023 20:14:41 -0700 Subject: [PATCH 2/6] Added JSON serialization / deserialization code for tags. --- .../acqj/main/AcquisitionEvent.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java index 085cab4..34d3a5e 100644 --- a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java +++ b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java @@ -21,11 +21,11 @@ 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 java.util.stream.Collectors; import mmcorej.org.json.JSONArray; import mmcorej.org.json.JSONException; import mmcorej.org.json.JSONObject; @@ -146,6 +146,7 @@ public AcquisitionEvent copy() { e.acquireImage_ = acquireImage_; e.properties_ = new TreeSet(this.properties_); e.camera_ = camera_; + e.setTags(tags_); return e; } @@ -217,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 @@ -227,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); } @@ -322,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) From 93b4ccbf3629eb5b171c309cbe22cc9a2d880002 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Sun, 4 Jun 2023 20:15:14 -0700 Subject: [PATCH 3/6] Allow construction of Acquisition when core is null, to enable test code. --- .../micromanager/acqj/main/Acquisition.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/main/Acquisition.java b/src/main/java/org/micromanager/acqj/main/Acquisition.java index c8c0bb1..d34f415 100644 --- a/src/main/java/org/micromanager/acqj/main/Acquisition.java +++ b/src/main/java/org/micromanager/acqj/main/Acquisition.java @@ -301,19 +301,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); + } } } From 02ed814ac9bf4fbfb996b85863ad7654a3771d75 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Sun, 4 Jun 2023 20:16:11 -0700 Subject: [PATCH 4/6] Adds tests for Event serialization / deserialization. --- .../acqj/main/TestAcquisitionEvent.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/org/micromanager/acqj/main/TestAcquisitionEvent.java 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")); + } +} + From 58664662077c48e32a669e0553a1636030a66ab9 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 6 Jun 2023 11:47:24 -0700 Subject: [PATCH 5/6] Add Tags under key Tags to image metadata. --- .../java/org/micromanager/acqj/internal/Engine.java | 8 +++++++- .../org/micromanager/acqj/main/AcqEngMetadata.java | 3 ++- .../java/org/micromanager/acqj/main/Acquisition.java | 12 ++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index e789d4b..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,7 +535,12 @@ private void acquireImages(final AcquisitionEvent event) throws HardwareControlE //add metadata AcqEngMetadata.addImageMetadata(ti.tags, correspondingEvent, currentTime - correspondingEvent.acquisition_.getStartTime_ms(), exposure); - correspondingEvent.acquisition_.addTagsToTaggedImage(ti.tags, correspondingEvent.getTags()); + 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 d34f415..6b44a10 100644 --- a/src/main/java/org/micromanager/acqj/main/Acquisition.java +++ b/src/main/java/org/micromanager/acqj/main/Acquisition.java @@ -160,18 +160,26 @@ 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) { + public void addTagsToTaggedImage(JSONObject tags, HashMap moreTags) + throws JSONException { + if (moreTags.isEmpty()) { + return; + } + JSONObject moreTagsObject = new JSONObject(); for (Map.Entry entry : moreTags.entrySet()) { try { - tags.put(entry.getKey(), entry.getValue()); + moreTagsObject.put(entry.getKey(), entry.getValue()); } catch (JSONException e) { e.printStackTrace(); } } + tags.put(AcqEngMetadata.TAGS, moreTagsObject); } @Override From bc9cae0930015879f6e802763099d2e26d92e874 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 6 Jun 2023 13:56:23 -0700 Subject: [PATCH 6/6] Bumped version to 0.28.0 since this involves addition to API. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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