From c4d5e3788e73fdff1c59d4348c3f890b801a1c6f Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Tue, 27 Feb 2024 20:03:29 -0500 Subject: [PATCH] Built site for gh-pages --- .nojekyll | 2 +- _notebooks/N5-Basics-Tutorial-preview.html | 1033 +++++++++++++++++ _notebooks/N5-Basics-Tutorial.out.ipynb | 582 ++++++++++ index.html | 40 +- listings.json | 1 - .../N5-Basics-Tutorial.html | 2 - posts/2024-02-27-n5-tutorial-basic/index.html | 38 +- .../notebook/N5-Basics-Tutorial-preview.html | 1033 +++++++++++++++++ .../notebook/N5-Basics-Tutorial.html | 946 +++++++++++++++ .../notebook/N5-Basics-Tutorial.out.ipynb | 582 ++++++++++ search.json | 85 +- sitemap.xml | 10 +- 12 files changed, 4247 insertions(+), 107 deletions(-) create mode 100644 _notebooks/N5-Basics-Tutorial-preview.html create mode 100644 _notebooks/N5-Basics-Tutorial.out.ipynb create mode 100644 posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial-preview.html create mode 100644 posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.html create mode 100644 posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.out.ipynb diff --git a/.nojekyll b/.nojekyll index 4d7eaa7..7d71d31 100644 --- a/.nojekyll +++ b/.nojekyll @@ -1 +1 @@ -26f67dd9 \ No newline at end of file +bfda6007 \ No newline at end of file diff --git a/_notebooks/N5-Basics-Tutorial-preview.html b/_notebooks/N5-Basics-Tutorial-preview.html new file mode 100644 index 0000000..ce3d467 --- /dev/null +++ b/_notebooks/N5-Basics-Tutorial-preview.html @@ -0,0 +1,1033 @@ + + + + + + + + + ImgLib2 news and tutorials + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Back to Article +
N5-Basics-Tutorial.ipynb
+ + Download Notebook +
+ +
+
+ +
+ +
+ + +
+ + + +
In [1]:
+
+Code +
%mavenRepo scijava.public https://maven.scijava.org/content/groups/public
+%maven org.scijava:scijava-common:2.97.0
+%maven net.imglib2:imglib2:6.2.0
+%maven org.janelia.saalfeldlab:n5:3.1.2
+%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0
+%maven org.janelia.saalfeldlab:n5-universe:1.3.1
+
+
+
In [2]:
+
+Code +
import java.nio.file.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+
+import com.google.gson.*;
+
+import net.imglib2.*;
+import net.imglib2.img.array.*;
+import net.imglib2.type.numeric.real.*;
+import net.imglib2.view.*;
+import net.imglib2.util.*;
+
+import org.janelia.saalfeldlab.n5.*;
+import org.janelia.saalfeldlab.n5.imglib2.*;
+import org.janelia.saalfeldlab.n5.universe.*;
+
+
+
In [3]:
+
+Code +
public static void pathInfo(Path p) {
+    try {
+        System.out.println(String.format("%s is %d bytes", p, Files.size(p))); 
+    } catch(IOException e ){}
+}
+
+public static void printBlocks(String path) throws IOException {
+
+    try (Stream<Path> stream = Files.walk(Paths.get(path))) {
+        stream.filter(Files::isRegularFile)
+            .filter( p -> p.getFileName().toString().matches("[0-9]"))
+                .forEach( x -> { pathInfo(x); });
+    }
+}
+
+
+
In [4]:
+
+Code +
// N5Factory can make N5Readers and N5Writers
+var factory = new N5Factory();
+
+// trying to open a reader for a container that does not yet exist will throw an error 
+// var n5Reader = factory.openReader("my-container.n5");
+
+// creating a writer creates a container at the given location
+// if it does not already exist
+var n5Writer = factory.openWriter("my-container.n5");
+
+// now we can make a reader
+var n5Reader = factory.openReader("my-container.n5");
+
+// test if the container exists
+n5Reader.exists(""); // true
+
+// "" and "/" both refer to the root of the container
+n5Reader.exists("/"); // true
+
+
+
In [5]:
+
+Code +
factory.openWriter("my-container.h5").getClass();   // N5HDF5Writer
+factory.openWriter("my-container.n5").getClass();   // N5FSWriter
+factory.openWriter("my-container.zarr").getClass(); // N5ZarrWriter
+
+
+
In [6]:
+
+Code +
n5Writer.createGroup("foo");
+n5Writer.createGroup("foo/bar");
+n5Writer.createGroup("lorum/ipsum/dolor/sit/amet");
+
+n5Writer.exists("lorum/ipsum");      // true
+n5Writer.exists("not/a/real/group"); // false
+
+
+
In [7]:
+
+Code +
n5Writer.list("");     // [lorum, foo]
+n5Writer.list("foo");  // [bar]
+
+
+
In [8]:
+
+Code +
Arrays.toString(n5Writer.deepList(""));
+
+
+
[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]
+
+
+
In [9]:
+
+Code +
public static RandomAccessibleInterval<FloatType> demoImage(long... size) {
+
+    final RandomAccessibleInterval<FloatType> img = ArrayImgs.floats(size);
+    float f = 0f;
+    Cursor<FloatType> c = Views.flatIterable(img).cursor();
+    while (c.hasNext())
+        c.next().set(f++);
+
+    return img;
+}
+
+
+
In [10]:
+
+Code +
// the parameters
+var img = demoImage(64,64); // the image to write- size 64 x 64
+var groupPath = "data"; 
+var blockSize = new int[]{32,32};
+var compression = new GzipCompression();
+
+// save the image
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+
+
In [11]:
+
+Code +
var exec = Executors.newFixedThreadPool(4); // with 4 parallel threads
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);
+
+
+
In [12]:
+
+Code +
printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/1/1 is 1762 bytes
+my-container.n5/data/1/0 is 2012 bytes
+my-container.n5/data/0/1 is 1763 bytes
+my-container.n5/data/0/0 is 2020 bytes
+
+
+
In [13]:
+
+Code +
// remove the old data
+n5Writer.remove(groupPath);
+
+// rewrite with a different block size
+var blockSize = new int[]{64,8};
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// how many blocks are there?
+printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/0/1 is 837 bytes
+my-container.n5/data/0/7 is 847 bytes
+my-container.n5/data/0/3 is 839 bytes
+my-container.n5/data/0/6 is 844 bytes
+my-container.n5/data/0/0 is 968 bytes
+my-container.n5/data/0/4 is 846 bytes
+my-container.n5/data/0/2 is 840 bytes
+my-container.n5/data/0/5 is 847 bytes
+
+
+
In [14]:
+
+Code +
// rewrite without compression
+var groupPath = "dataNoCompression"; 
+var blockSize = new int[]{32,32};
+var compression = new RawCompression();
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// what size are the blocks
+printBlocks("my-container.n5/dataNoCompression");
+
+
+
my-container.n5/dataNoCompression/1/1 is 4108 bytes
+my-container.n5/dataNoCompression/1/0 is 4108 bytes
+my-container.n5/dataNoCompression/0/1 is 4108 bytes
+my-container.n5/dataNoCompression/0/0 is 4108 bytes
+
+
+
In [15]:
+
+Code +
var loadedImg = N5Utils.open(n5Writer, groupPath);
+Util.getTypeFromInterval(loadedImg).getClass();      // FloatType
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [64, 64]
+
+
+
In [16]:
+
+Code +
// overwrite our previous data
+var img = ArrayImgs.unsignedBytes(2,2);
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// load the new data, the old data are no longer accessible
+var loadedImg = N5Utils.open(n5Writer, groupPath);
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [2, 2]
+
+
+
In [28]:
+
+Code +
// create a group inside the container (think: "folder")
+var groupName = "put-data-in-me";
+n5Writer.createGroup(groupName);
+
+// attributes have names and values
+// make an attribute called "date" with a String value
+var attributeName = "date";
+n5Writer.setAttribute(groupName, attributeName, "2024-Jan-01");
+
+// Ask the N5 API to make a double array from the data attribute
+// it will try and fail, so an exception will be thrown
+try {
+    var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);
+} catch( N5Exception e ) {
+    System.out.println("Error: could not get attribute as double[]");
+}
+
+// get the value of the "date" attribute as a String
+String date = n5Writer.getAttribute(groupName, attributeName, String.class);
+date
+
+
+
Error: could not get attribute as double[]
+
+
+
2024-Jan-01
+
+
+
In [18]:
+
+Code +
n5Writer.setAttribute(groupName, "a", 42);
+var num = n5Writer.getAttribute(groupName, "a", double.class); // 42.0
+var str = n5Writer.getAttribute(groupName, "a", String.class); // "42"
+
+
+
In [19]:
+
+Code +
class FunWithMetadata {
+    String name;
+    int number;
+    double[] data;
+    
+    public FunWithMetadata(String name, int number, double[] data) {
+        this.name = name;
+        this.number = number;
+        this.data = data;
+    }
+    public String toString(){
+        return String.format( "FunWithMetadata{%s(%d): %s}", 
+            name, number, Arrays.toString(data));
+    }
+};
+
+
+
In [20]:
+
+Code +
var metadata = new FunWithMetadata("Dorothy", 2, new double[]{2.72, 3.14});
+n5Writer.setAttribute(groupName, "metadata", metadata);
+
+var loadedMetadata = n5Writer.getAttribute(groupName, "metadata", 
+    FunWithMetadata.class);
+loadedMetadata
+
+
+
FunWithMetadata{Dorothy(2): [2.72, 3.14]}
+
+
+
In [21]:
+
+Code +
n5Writer.getAttribute(groupName, "/", JsonElement.class);
+
+
+
{"date":"2024-Jan-01","a":42,"metadata":{"name":"Dorothy","number":2,"data":[2.72,3.14]}}
+
+
+
In [22]:
+
+Code +
// set attributes
+n5Writer.setAttribute(groupName, "sender", "Alice");
+n5Writer.setAttribute(groupName, "receiver", "Bob");
+
+// notice that they're set
+n5Writer.getAttribute(groupName, "sender", String.class);   // Alice
+n5Writer.getAttribute(groupName, "receiver", String.class); // Bob
+
+// remove "sender"
+n5Writer.removeAttribute(groupName, "sender");
+
+// remove "receiver" and store result in a variable
+var receiver = n5Writer.removeAttribute(groupName, "receiver", String.class); // Bob
+
+n5Writer.getAttribute(groupName, "sender", String.class);   // null
+n5Writer.getAttribute(groupName, "receiver", String.class); // null
+
+
+
In [23]:
+
+Code +
Arrays.toString(n5Writer.getAttribute("data", "dimensions", long[].class));
+
+
+
[64, 64]
+
+
+ + +
+ + \ No newline at end of file diff --git a/_notebooks/N5-Basics-Tutorial.out.ipynb b/_notebooks/N5-Basics-Tutorial.out.ipynb new file mode 100644 index 0000000..7ccc09e --- /dev/null +++ b/_notebooks/N5-Basics-Tutorial.out.ipynb @@ -0,0 +1,582 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#" + ], + "id": "ede17a47-98bc-4f2d-bedb-a45679a5a1d1" + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%mavenRepo scijava.public https://maven.scijava.org/content/groups/public\n", + "%maven org.scijava:scijava-common:2.97.0\n", + "%maven net.imglib2:imglib2:6.2.0\n", + "%maven org.janelia.saalfeldlab:n5:3.1.2\n", + "%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0\n", + "%maven org.janelia.saalfeldlab:n5-universe:1.3.1" + ], + "id": "ef2f3d2c" + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "import com.google.gson.*;\n", + "\n", + "import net.imglib2.*;\n", + "import net.imglib2.img.array.*;\n", + "import net.imglib2.type.numeric.real.*;\n", + "import net.imglib2.view.*;\n", + "import net.imglib2.util.*;\n", + "\n", + "import org.janelia.saalfeldlab.n5.*;\n", + "import org.janelia.saalfeldlab.n5.imglib2.*;\n", + "import org.janelia.saalfeldlab.n5.universe.*;" + ], + "id": "f70aa4d8" + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "public static void pathInfo(Path p) {\n", + " try {\n", + " System.out.println(String.format(\"%s is %d bytes\", p, Files.size(p))); \n", + " } catch(IOException e ){}\n", + "}\n", + "\n", + "public static void printBlocks(String path) throws IOException {\n", + "\n", + " try (Stream stream = Files.walk(Paths.get(path))) {\n", + " stream.filter(Files::isRegularFile)\n", + " .filter( p -> p.getFileName().toString().matches(\"[0-9]\"))\n", + " .forEach( x -> { pathInfo(x); });\n", + " }\n", + "}" + ], + "id": "b994b3b7" + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [ + "make-reader-writer" + ] + }, + "outputs": [], + "source": [ + "// N5Factory can make N5Readers and N5Writers\n", + "var factory = new N5Factory();\n", + "\n", + "// trying to open a reader for a container that does not yet exist will throw an error \n", + "// var n5Reader = factory.openReader(\"my-container.n5\");\n", + "\n", + "// creating a writer creates a container at the given location\n", + "// if it does not already exist\n", + "var n5Writer = factory.openWriter(\"my-container.n5\");\n", + "\n", + "// now we can make a reader\n", + "var n5Reader = factory.openReader(\"my-container.n5\");\n", + "\n", + "// test if the container exists\n", + "n5Reader.exists(\"\"); // true\n", + "\n", + "// \"\" and \"/\" both refer to the root of the container\n", + "n5Reader.exists(\"/\"); // true" + ], + "id": "89830e81" + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [ + "factory-types" + ] + }, + "outputs": [], + "source": [ + "factory.openWriter(\"my-container.h5\").getClass(); // N5HDF5Writer\n", + "factory.openWriter(\"my-container.n5\").getClass(); // N5FSWriter\n", + "factory.openWriter(\"my-container.zarr\").getClass(); // N5ZarrWriter" + ], + "id": "8a6621e1" + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [ + "make-groups" + ] + }, + "outputs": [], + "source": [ + "n5Writer.createGroup(\"foo\");\n", + "n5Writer.createGroup(\"foo/bar\");\n", + "n5Writer.createGroup(\"lorum/ipsum/dolor/sit/amet\");\n", + "\n", + "n5Writer.exists(\"lorum/ipsum\"); // true\n", + "n5Writer.exists(\"not/a/real/group\"); // false" + ], + "id": "9ee0c14d" + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [ + "list" + ] + }, + "outputs": [], + "source": [ + "n5Writer.list(\"\"); // [lorum, foo]\n", + "n5Writer.list(\"foo\"); // [bar]" + ], + "id": "72496634" + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [ + "deep-list" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]" + ] + } + } + ], + "source": [ + "Arrays.toString(n5Writer.deepList(\"\"));" + ], + "id": "1f363282" + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "public static RandomAccessibleInterval demoImage(long... size) {\n", + "\n", + " final RandomAccessibleInterval img = ArrayImgs.floats(size);\n", + " float f = 0f;\n", + " Cursor c = Views.flatIterable(img).cursor();\n", + " while (c.hasNext())\n", + " c.next().set(f++);\n", + "\n", + " return img;\n", + "}" + ], + "id": "996e162e" + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [ + "n5-imglib2-save" + ] + }, + "outputs": [], + "source": [ + "// the parameters\n", + "var img = demoImage(64,64); // the image to write- size 64 x 64\n", + "var groupPath = \"data\"; \n", + "var blockSize = new int[]{32,32};\n", + "var compression = new GzipCompression();\n", + "\n", + "// save the image\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);" + ], + "id": "28e3835d" + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [ + "n5-imglib2-save-exec" + ] + }, + "outputs": [], + "source": [ + "var exec = Executors.newFixedThreadPool(4); // with 4 parallel threads\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);" + ], + "id": "131e8bd4" + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [ + "four-blocks" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/data/1/1 is 1762 bytes\n", + "my-container.n5/data/1/0 is 2012 bytes\n", + "my-container.n5/data/0/1 is 1763 bytes\n", + "my-container.n5/data/0/0 is 2020 bytes" + ] + } + ], + "source": [ + "printBlocks(\"my-container.n5/data\");" + ], + "id": "79901ca0" + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [ + "eight-blocks" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/data/0/1 is 837 bytes\n", + "my-container.n5/data/0/7 is 847 bytes\n", + "my-container.n5/data/0/3 is 839 bytes\n", + "my-container.n5/data/0/6 is 844 bytes\n", + "my-container.n5/data/0/0 is 968 bytes\n", + "my-container.n5/data/0/4 is 846 bytes\n", + "my-container.n5/data/0/2 is 840 bytes\n", + "my-container.n5/data/0/5 is 847 bytes" + ] + } + ], + "source": [ + "// remove the old data\n", + "n5Writer.remove(groupPath);\n", + "\n", + "// rewrite with a different block size\n", + "var blockSize = new int[]{64,8};\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// how many blocks are there?\n", + "printBlocks(\"my-container.n5/data\");" + ], + "id": "91647d07" + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [ + "no-compression" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/dataNoCompression/1/1 is 4108 bytes\n", + "my-container.n5/dataNoCompression/1/0 is 4108 bytes\n", + "my-container.n5/dataNoCompression/0/1 is 4108 bytes\n", + "my-container.n5/dataNoCompression/0/0 is 4108 bytes" + ] + } + ], + "source": [ + "// rewrite without compression\n", + "var groupPath = \"dataNoCompression\"; \n", + "var blockSize = new int[]{32,32};\n", + "var compression = new RawCompression();\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// what size are the blocks\n", + "printBlocks(\"my-container.n5/dataNoCompression\");" + ], + "id": "9b37d386" + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "n5-imglib2-open" + ] + }, + "outputs": [], + "source": [ + "var loadedImg = N5Utils.open(n5Writer, groupPath);\n", + "Util.getTypeFromInterval(loadedImg).getClass(); // FloatType\n", + "Arrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]" + ], + "id": "de0fadca" + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [ + "n5-imglib2-overwrite" + ] + }, + "outputs": [], + "source": [ + "// overwrite our previous data\n", + "var img = ArrayImgs.unsignedBytes(2,2);\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// load the new data, the old data are no longer accessible\n", + "var loadedImg = N5Utils.open(n5Writer, groupPath);\n", + "Arrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]" + ], + "id": "f861d622" + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "tags": [ + "attributes-1" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Error: could not get attribute as double[]" + ] + }, + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "2024-Jan-01" + ] + } + } + ], + "source": [ + "// create a group inside the container (think: \"folder\")\n", + "var groupName = \"put-data-in-me\";\n", + "n5Writer.createGroup(groupName);\n", + "\n", + "// attributes have names and values\n", + "// make an attribute called \"date\" with a String value\n", + "var attributeName = \"date\";\n", + "n5Writer.setAttribute(groupName, attributeName, \"2024-Jan-01\");\n", + "\n", + "// Ask the N5 API to make a double array from the data attribute\n", + "// it will try and fail, so an exception will be thrown\n", + "try {\n", + " var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);\n", + "} catch( N5Exception e ) {\n", + " System.out.println(\"Error: could not get attribute as double[]\");\n", + "}\n", + "\n", + "// get the value of the \"date\" attribute as a String\n", + "String date = n5Writer.getAttribute(groupName, attributeName, String.class);\n", + "date" + ], + "id": "75f3c15f" + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "tags": [ + "attr-types" + ] + }, + "outputs": [], + "source": [ + "n5Writer.setAttribute(groupName, \"a\", 42);\n", + "var num = n5Writer.getAttribute(groupName, \"a\", double.class); // 42.0\n", + "var str = n5Writer.getAttribute(groupName, \"a\", String.class); // \"42\"" + ], + "id": "bc080bb5" + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "tags": [ + "fun-with-metadata" + ] + }, + "outputs": [], + "source": [ + "class FunWithMetadata {\n", + " String name;\n", + " int number;\n", + " double[] data;\n", + " \n", + " public FunWithMetadata(String name, int number, double[] data) {\n", + " this.name = name;\n", + " this.number = number;\n", + " this.data = data;\n", + " }\n", + " public String toString(){\n", + " return String.format( \"FunWithMetadata{%s(%d): %s}\", \n", + " name, number, Arrays.toString(data));\n", + " }\n", + "};" + ], + "id": "76639ba3" + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "tags": [ + "rich-metadata" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "FunWithMetadata{Dorothy(2): [2.72, 3.14]}" + ] + } + } + ], + "source": [ + "var metadata = new FunWithMetadata(\"Dorothy\", 2, new double[]{2.72, 3.14});\n", + "n5Writer.setAttribute(groupName, \"metadata\", metadata);\n", + "\n", + "var loadedMetadata = n5Writer.getAttribute(groupName, \"metadata\", \n", + " FunWithMetadata.class);\n", + "loadedMetadata" + ], + "id": "03dc0d4d" + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "tags": [ + "all-metadata" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "{\"date\":\"2024-Jan-01\",\"a\":42,\"metadata\":{\"name\":\"Dorothy\",\"number\":2,\"data\":[2.72,3.14]}}" + ] + } + } + ], + "source": [ + "n5Writer.getAttribute(groupName, \"/\", JsonElement.class);" + ], + "id": "1b8133bc" + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "tags": [ + "remove-attrs" + ] + }, + "outputs": [], + "source": [ + "// set attributes\n", + "n5Writer.setAttribute(groupName, \"sender\", \"Alice\");\n", + "n5Writer.setAttribute(groupName, \"receiver\", \"Bob\");\n", + "\n", + "// notice that they're set\n", + "n5Writer.getAttribute(groupName, \"sender\", String.class); // Alice\n", + "n5Writer.getAttribute(groupName, \"receiver\", String.class); // Bob\n", + "\n", + "// remove \"sender\"\n", + "n5Writer.removeAttribute(groupName, \"sender\");\n", + "\n", + "// remove \"receiver\" and store result in a variable\n", + "var receiver = n5Writer.removeAttribute(groupName, \"receiver\", String.class); // Bob\n", + "\n", + "n5Writer.getAttribute(groupName, \"sender\", String.class); // null\n", + "n5Writer.getAttribute(groupName, \"receiver\", String.class); // null" + ], + "id": "5ebc9d0d" + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "tags": [ + "bonus" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "[64, 64]" + ] + } + } + ], + "source": [ + "Arrays.toString(n5Writer.getAttribute(\"data\", \"dimensions\", long[].class));" + ], + "id": "d0fb1e31" + } + ], + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "name": "java", + "display_name": "Java", + "language": "java" + }, + "language_info": { + "name": "Java", + "codemirror_mode": "java", + "file_extension": ".jshell", + "mimetype": "text/x-java-source", + "pygments_lexer": "java", + "version": "19.0.2+7-Ubuntu-0ubuntu322.04" + } + } +} \ No newline at end of file diff --git a/index.html b/index.html index 2130e94..53d6bc1 100644 --- a/index.html +++ b/index.html @@ -189,7 +189,7 @@ +
Categories
All (8)
bigdataviewer (2)
deformation (1)
displacement (1)
fractal (1)
hdf5 (2)
ijava (1)
imglib2 (6)
java (2)
jshell (1)
juliaset (1)
jupyter (3)
kernel (1)
lambda (1)
n5 (2)
notebook (2)
stream-api (1)
transform (1)
tutorial (1)
ui-behaviour (1)
zarr (2)
@@ -202,29 +202,7 @@
Categories
-
-
-

-
 
-

-
- - -
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+Source: N5-Basics-Tutorial.ipynb

The N5 API gives you access to a number of different storage formats: HDF5, Zarr, and N5’s own format. N5Factory’s convenience methods try to infer the storage format from the extension of the path you give it:

@@ -295,7 +295,7 @@

Readers and writersfactory.openWriter("my-container.zarr").getClass(); // N5ZarrWriter

-
+Source: N5-Basics-Tutorial.ipynb

In fact, it is possible to read with N5Writers since every N5Writer is also an N5Reader, so from now on, we’ll just be using the n5Writer.

@@ -326,7 +326,7 @@

Groups

n5Writer.exists("not/a/real/group"); // false
-
+Source: N5-Basics-Tutorial.ipynb

The list method lists groups that are children of the given group:

@@ -336,7 +336,7 @@

Groups

n5Writer.list("foo"); // [bar]
-
+Source: N5-Basics-Tutorial.ipynb

and deepList recursively lists every descendent of the given group:

@@ -348,7 +348,7 @@

Groups

[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]
-
+Source: N5-Basics-Tutorial.ipynb

Notice that these methods only give information about what groups are present and do not provide information about metadata or array data.

@@ -396,7 +396,7 @@

Arrays

N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
- +Source: N5-Basics-Tutorial.ipynb

You can write blocks in parallel by providing an ExecutorService to this variant of N5Utils.save

@@ -406,7 +406,7 @@

Arrays

N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);
- +Source: N5-Basics-Tutorial.ipynb

Reading the array data from the container is also easy with N5Utils.open :

@@ -417,7 +417,7 @@

Arrays

Arrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]
- +Source: N5-Basics-Tutorial.ipynb
@@ -445,7 +445,7 @@

Arrays

Arrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]
-
+Source: N5-Basics-Tutorial.ipynb

Parameter details

@@ -469,7 +469,7 @@

blockSize

my-container.n5/data/0/0 is 2020 bytes - +Source: N5-Basics-Tutorial.ipynb

Quiz: How many blocks would there be if the block size was 64 x 8?

@@ -502,7 +502,7 @@

blockSize

my-container.n5/data/0/5 is 847 bytes - +Source: N5-Basics-Tutorial.ipynb
@@ -541,7 +541,7 @@

compression

my-container.n5/dataNoCompression/0/0 is 4108 bytes
- +Source: N5-Basics-Tutorial.ipynb

Notice that blocks were previously ~1700-2000 bytes and are now ~4100 without compression.

The available compressors at the time of this writing are:

Rich metadata

@@ -653,7 +653,7 @@

Rich metadata

}; - +Source: N5-Basics-Tutorial.ipynb

then make an instance and save it:

@@ -670,7 +670,7 @@

Rich metadata

FunWithMetadata{Dorothy(2): [2.72, 3.14]}
- +Source: N5-Basics-Tutorial.ipynb

To retrieve all the metadata in a group as JSON:

@@ -682,7 +682,7 @@

Rich metadata

{"date":"2024-Jan-01","a":42,"metadata":{"name":"Dorothy","number":2,"data":[2.72,3.14]}}
- +Source: N5-Basics-Tutorial.ipynb

Removing metadata

@@ -709,7 +709,7 @@

Removing metadata

n5Writer.getAttribute(groupName, "receiver", String.class); // null - +Source: N5-Basics-Tutorial.ipynb
diff --git a/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial-preview.html b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial-preview.html new file mode 100644 index 0000000..2be5fb3 --- /dev/null +++ b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial-preview.html @@ -0,0 +1,1033 @@ + + + + + + + + + ImgLib2 news and tutorials + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Back to Article +
N5-Basics-Tutorial.ipynb
+ + Download Notebook +
+ +
+
+ +
+ +
+ + +
+ + + +
In [1]:
+
+Code +
%mavenRepo scijava.public https://maven.scijava.org/content/groups/public
+%maven org.scijava:scijava-common:2.97.0
+%maven net.imglib2:imglib2:6.2.0
+%maven org.janelia.saalfeldlab:n5:3.1.2
+%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0
+%maven org.janelia.saalfeldlab:n5-universe:1.3.1
+
+
+
In [2]:
+
+Code +
import java.nio.file.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+
+import com.google.gson.*;
+
+import net.imglib2.*;
+import net.imglib2.img.array.*;
+import net.imglib2.type.numeric.real.*;
+import net.imglib2.view.*;
+import net.imglib2.util.*;
+
+import org.janelia.saalfeldlab.n5.*;
+import org.janelia.saalfeldlab.n5.imglib2.*;
+import org.janelia.saalfeldlab.n5.universe.*;
+
+
+
In [3]:
+
+Code +
public static void pathInfo(Path p) {
+    try {
+        System.out.println(String.format("%s is %d bytes", p, Files.size(p))); 
+    } catch(IOException e ){}
+}
+
+public static void printBlocks(String path) throws IOException {
+
+    try (Stream<Path> stream = Files.walk(Paths.get(path))) {
+        stream.filter(Files::isRegularFile)
+            .filter( p -> p.getFileName().toString().matches("[0-9]"))
+                .forEach( x -> { pathInfo(x); });
+    }
+}
+
+
+
In [4]:
+
+Code +
// N5Factory can make N5Readers and N5Writers
+var factory = new N5Factory();
+
+// trying to open a reader for a container that does not yet exist will throw an error 
+// var n5Reader = factory.openReader("my-container.n5");
+
+// creating a writer creates a container at the given location
+// if it does not already exist
+var n5Writer = factory.openWriter("my-container.n5");
+
+// now we can make a reader
+var n5Reader = factory.openReader("my-container.n5");
+
+// test if the container exists
+n5Reader.exists(""); // true
+
+// "" and "/" both refer to the root of the container
+n5Reader.exists("/"); // true
+
+
+
In [5]:
+
+Code +
factory.openWriter("my-container.h5").getClass();   // N5HDF5Writer
+factory.openWriter("my-container.n5").getClass();   // N5FSWriter
+factory.openWriter("my-container.zarr").getClass(); // N5ZarrWriter
+
+
+
In [6]:
+
+Code +
n5Writer.createGroup("foo");
+n5Writer.createGroup("foo/bar");
+n5Writer.createGroup("lorum/ipsum/dolor/sit/amet");
+
+n5Writer.exists("lorum/ipsum");      // true
+n5Writer.exists("not/a/real/group"); // false
+
+
+
In [7]:
+
+Code +
n5Writer.list("");     // [lorum, foo]
+n5Writer.list("foo");  // [bar]
+
+
+
In [8]:
+
+Code +
Arrays.toString(n5Writer.deepList(""));
+
+
+
[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]
+
+
+
In [9]:
+
+Code +
public static RandomAccessibleInterval<FloatType> demoImage(long... size) {
+
+    final RandomAccessibleInterval<FloatType> img = ArrayImgs.floats(size);
+    float f = 0f;
+    Cursor<FloatType> c = Views.flatIterable(img).cursor();
+    while (c.hasNext())
+        c.next().set(f++);
+
+    return img;
+}
+
+
+
In [10]:
+
+Code +
// the parameters
+var img = demoImage(64,64); // the image to write- size 64 x 64
+var groupPath = "data"; 
+var blockSize = new int[]{32,32};
+var compression = new GzipCompression();
+
+// save the image
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+
+
In [11]:
+
+Code +
var exec = Executors.newFixedThreadPool(4); // with 4 parallel threads
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);
+
+
+
In [12]:
+
+Code +
printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/1/1 is 1762 bytes
+my-container.n5/data/1/0 is 2012 bytes
+my-container.n5/data/0/1 is 1763 bytes
+my-container.n5/data/0/0 is 2020 bytes
+
+
+
In [13]:
+
+Code +
// remove the old data
+n5Writer.remove(groupPath);
+
+// rewrite with a different block size
+var blockSize = new int[]{64,8};
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// how many blocks are there?
+printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/0/1 is 837 bytes
+my-container.n5/data/0/7 is 847 bytes
+my-container.n5/data/0/3 is 839 bytes
+my-container.n5/data/0/6 is 844 bytes
+my-container.n5/data/0/0 is 968 bytes
+my-container.n5/data/0/4 is 846 bytes
+my-container.n5/data/0/2 is 840 bytes
+my-container.n5/data/0/5 is 847 bytes
+
+
+
In [14]:
+
+Code +
// rewrite without compression
+var groupPath = "dataNoCompression"; 
+var blockSize = new int[]{32,32};
+var compression = new RawCompression();
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// what size are the blocks
+printBlocks("my-container.n5/dataNoCompression");
+
+
+
my-container.n5/dataNoCompression/1/1 is 4108 bytes
+my-container.n5/dataNoCompression/1/0 is 4108 bytes
+my-container.n5/dataNoCompression/0/1 is 4108 bytes
+my-container.n5/dataNoCompression/0/0 is 4108 bytes
+
+
+
In [15]:
+
+Code +
var loadedImg = N5Utils.open(n5Writer, groupPath);
+Util.getTypeFromInterval(loadedImg).getClass();      // FloatType
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [64, 64]
+
+
+
In [16]:
+
+Code +
// overwrite our previous data
+var img = ArrayImgs.unsignedBytes(2,2);
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// load the new data, the old data are no longer accessible
+var loadedImg = N5Utils.open(n5Writer, groupPath);
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [2, 2]
+
+
+
In [28]:
+
+Code +
// create a group inside the container (think: "folder")
+var groupName = "put-data-in-me";
+n5Writer.createGroup(groupName);
+
+// attributes have names and values
+// make an attribute called "date" with a String value
+var attributeName = "date";
+n5Writer.setAttribute(groupName, attributeName, "2024-Jan-01");
+
+// Ask the N5 API to make a double array from the data attribute
+// it will try and fail, so an exception will be thrown
+try {
+    var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);
+} catch( N5Exception e ) {
+    System.out.println("Error: could not get attribute as double[]");
+}
+
+// get the value of the "date" attribute as a String
+String date = n5Writer.getAttribute(groupName, attributeName, String.class);
+date
+
+
+
Error: could not get attribute as double[]
+
+
+
2024-Jan-01
+
+
+
In [18]:
+
+Code +
n5Writer.setAttribute(groupName, "a", 42);
+var num = n5Writer.getAttribute(groupName, "a", double.class); // 42.0
+var str = n5Writer.getAttribute(groupName, "a", String.class); // "42"
+
+
+
In [19]:
+
+Code +
class FunWithMetadata {
+    String name;
+    int number;
+    double[] data;
+    
+    public FunWithMetadata(String name, int number, double[] data) {
+        this.name = name;
+        this.number = number;
+        this.data = data;
+    }
+    public String toString(){
+        return String.format( "FunWithMetadata{%s(%d): %s}", 
+            name, number, Arrays.toString(data));
+    }
+};
+
+
+
In [20]:
+
+Code +
var metadata = new FunWithMetadata("Dorothy", 2, new double[]{2.72, 3.14});
+n5Writer.setAttribute(groupName, "metadata", metadata);
+
+var loadedMetadata = n5Writer.getAttribute(groupName, "metadata", 
+    FunWithMetadata.class);
+loadedMetadata
+
+
+
FunWithMetadata{Dorothy(2): [2.72, 3.14]}
+
+
+
In [21]:
+
+Code +
n5Writer.getAttribute(groupName, "/", JsonElement.class);
+
+
+
{"date":"2024-Jan-01","a":42,"metadata":{"name":"Dorothy","number":2,"data":[2.72,3.14]}}
+
+
+
In [22]:
+
+Code +
// set attributes
+n5Writer.setAttribute(groupName, "sender", "Alice");
+n5Writer.setAttribute(groupName, "receiver", "Bob");
+
+// notice that they're set
+n5Writer.getAttribute(groupName, "sender", String.class);   // Alice
+n5Writer.getAttribute(groupName, "receiver", String.class); // Bob
+
+// remove "sender"
+n5Writer.removeAttribute(groupName, "sender");
+
+// remove "receiver" and store result in a variable
+var receiver = n5Writer.removeAttribute(groupName, "receiver", String.class); // Bob
+
+n5Writer.getAttribute(groupName, "sender", String.class);   // null
+n5Writer.getAttribute(groupName, "receiver", String.class); // null
+
+
+
In [23]:
+
+Code +
Arrays.toString(n5Writer.getAttribute("data", "dimensions", long[].class));
+
+
+
[64, 64]
+
+
+ + +
+ + \ No newline at end of file diff --git a/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.html b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.html new file mode 100644 index 0000000..44b831c --- /dev/null +++ b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.html @@ -0,0 +1,946 @@ + + + + + + + + + +ImgLib2 news and tutorials + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ + + + +
+
+Code +
%mavenRepo scijava.public https://maven.scijava.org/content/groups/public
+%maven org.scijava:scijava-common:2.97.0
+%maven net.imglib2:imglib2:6.2.0
+%maven org.janelia.saalfeldlab:n5:3.1.2
+%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0
+%maven org.janelia.saalfeldlab:n5-universe:1.3.1
+
+
+
+
+Code +
import java.nio.file.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+
+import com.google.gson.*;
+
+import net.imglib2.*;
+import net.imglib2.img.array.*;
+import net.imglib2.type.numeric.real.*;
+import net.imglib2.view.*;
+import net.imglib2.util.*;
+
+import org.janelia.saalfeldlab.n5.*;
+import org.janelia.saalfeldlab.n5.imglib2.*;
+import org.janelia.saalfeldlab.n5.universe.*;
+
+
+
+
+Code +
public static void pathInfo(Path p) {
+    try {
+        System.out.println(String.format("%s is %d bytes", p, Files.size(p))); 
+    } catch(IOException e ){}
+}
+
+public static void printBlocks(String path) throws IOException {
+
+    try (Stream<Path> stream = Files.walk(Paths.get(path))) {
+        stream.filter(Files::isRegularFile)
+            .filter( p -> p.getFileName().toString().matches("[0-9]"))
+                .forEach( x -> { pathInfo(x); });
+    }
+}
+
+
+
+
+Code +
// N5Factory can make N5Readers and N5Writers
+var factory = new N5Factory();
+
+// trying to open a reader for a container that does not yet exist will throw an error 
+// var n5Reader = factory.openReader("my-container.n5");
+
+// creating a writer creates a container at the given location
+// if it does not already exist
+var n5Writer = factory.openWriter("my-container.n5");
+
+// now we can make a reader
+var n5Reader = factory.openReader("my-container.n5");
+
+// test if the container exists
+n5Reader.exists(""); // true
+
+// "" and "/" both refer to the root of the container
+n5Reader.exists("/"); // true
+
+
+
+
+Code +
factory.openWriter("my-container.h5").getClass();   // N5HDF5Writer
+factory.openWriter("my-container.n5").getClass();   // N5FSWriter
+factory.openWriter("my-container.zarr").getClass(); // N5ZarrWriter
+
+
+
+
+Code +
n5Writer.createGroup("foo");
+n5Writer.createGroup("foo/bar");
+n5Writer.createGroup("lorum/ipsum/dolor/sit/amet");
+
+n5Writer.exists("lorum/ipsum");      // true
+n5Writer.exists("not/a/real/group"); // false
+
+
+
+
+Code +
n5Writer.list("");     // [lorum, foo]
+n5Writer.list("foo");  // [bar]
+
+
+
+
+Code +
Arrays.toString(n5Writer.deepList(""));
+
+
+
[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]
+
+
+
+
+Code +
public static RandomAccessibleInterval<FloatType> demoImage(long... size) {
+
+    final RandomAccessibleInterval<FloatType> img = ArrayImgs.floats(size);
+    float f = 0f;
+    Cursor<FloatType> c = Views.flatIterable(img).cursor();
+    while (c.hasNext())
+        c.next().set(f++);
+
+    return img;
+}
+
+
+
+
+Code +
// the parameters
+var img = demoImage(64,64); // the image to write- size 64 x 64
+var groupPath = "data"; 
+var blockSize = new int[]{32,32};
+var compression = new GzipCompression();
+
+// save the image
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+
+
+
+Code +
var exec = Executors.newFixedThreadPool(4); // with 4 parallel threads
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);
+
+
+
+
+Code +
printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/1/1 is 1762 bytes
+my-container.n5/data/1/0 is 2012 bytes
+my-container.n5/data/0/1 is 1763 bytes
+my-container.n5/data/0/0 is 2020 bytes
+
+
+
+
+Code +
// remove the old data
+n5Writer.remove(groupPath);
+
+// rewrite with a different block size
+var blockSize = new int[]{64,8};
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// how many blocks are there?
+printBlocks("my-container.n5/data");
+
+
+
my-container.n5/data/0/1 is 837 bytes
+my-container.n5/data/0/7 is 847 bytes
+my-container.n5/data/0/3 is 839 bytes
+my-container.n5/data/0/6 is 844 bytes
+my-container.n5/data/0/0 is 968 bytes
+my-container.n5/data/0/4 is 846 bytes
+my-container.n5/data/0/2 is 840 bytes
+my-container.n5/data/0/5 is 847 bytes
+
+
+
+
+Code +
// rewrite without compression
+var groupPath = "dataNoCompression"; 
+var blockSize = new int[]{32,32};
+var compression = new RawCompression();
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// what size are the blocks
+printBlocks("my-container.n5/dataNoCompression");
+
+
+
my-container.n5/dataNoCompression/1/1 is 4108 bytes
+my-container.n5/dataNoCompression/1/0 is 4108 bytes
+my-container.n5/dataNoCompression/0/1 is 4108 bytes
+my-container.n5/dataNoCompression/0/0 is 4108 bytes
+
+
+
+
+Code +
var loadedImg = N5Utils.open(n5Writer, groupPath);
+Util.getTypeFromInterval(loadedImg).getClass();      // FloatType
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [64, 64]
+
+
+
+
+Code +
// overwrite our previous data
+var img = ArrayImgs.unsignedBytes(2,2);
+N5Utils.save(img, n5Writer, groupPath, blockSize, compression);
+
+// load the new data, the old data are no longer accessible
+var loadedImg = N5Utils.open(n5Writer, groupPath);
+Arrays.toString(loadedImg.dimensionsAsLongArray());  // [2, 2]
+
+
+
+
+Code +
// create a group inside the container (think: "folder")
+var groupName = "put-data-in-me";
+n5Writer.createGroup(groupName);
+
+// attributes have names and values
+// make an attribute called "date" with a String value
+var attributeName = "date";
+n5Writer.setAttribute(groupName, attributeName, "2024-Jan-01");
+
+// Ask the N5 API to make a double array from the data attribute
+// it will try and fail, so an exception will be thrown
+try {
+    var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);
+} catch( N5Exception e ) {
+    System.out.println("Error: could not get attribute as double[]");
+}
+
+// get the value of the "date" attribute as a String
+String date = n5Writer.getAttribute(groupName, attributeName, String.class);
+date
+
+
+
Error: could not get attribute as double[]
+
+
+
2024-Jan-01
+
+
+
+
+Code +
n5Writer.setAttribute(groupName, "a", 42);
+var num = n5Writer.getAttribute(groupName, "a", double.class); // 42.0
+var str = n5Writer.getAttribute(groupName, "a", String.class); // "42"
+
+
+
+
+Code +
class FunWithMetadata {
+    String name;
+    int number;
+    double[] data;
+    
+    public FunWithMetadata(String name, int number, double[] data) {
+        this.name = name;
+        this.number = number;
+        this.data = data;
+    }
+    public String toString(){
+        return String.format( "FunWithMetadata{%s(%d): %s}", 
+            name, number, Arrays.toString(data));
+    }
+};
+
+
+
+
+Code +
var metadata = new FunWithMetadata("Dorothy", 2, new double[]{2.72, 3.14});
+n5Writer.setAttribute(groupName, "metadata", metadata);
+
+var loadedMetadata = n5Writer.getAttribute(groupName, "metadata", 
+    FunWithMetadata.class);
+loadedMetadata
+
+
+
FunWithMetadata{Dorothy(2): [2.72, 3.14]}
+
+
+
+
+Code +
n5Writer.getAttribute(groupName, "/", JsonElement.class);
+
+
+
{"date":"2024-Jan-01","a":42,"metadata":{"name":"Dorothy","number":2,"data":[2.72,3.14]}}
+
+
+
+
+Code +
// set attributes
+n5Writer.setAttribute(groupName, "sender", "Alice");
+n5Writer.setAttribute(groupName, "receiver", "Bob");
+
+// notice that they're set
+n5Writer.getAttribute(groupName, "sender", String.class);   // Alice
+n5Writer.getAttribute(groupName, "receiver", String.class); // Bob
+
+// remove "sender"
+n5Writer.removeAttribute(groupName, "sender");
+
+// remove "receiver" and store result in a variable
+var receiver = n5Writer.removeAttribute(groupName, "receiver", String.class); // Bob
+
+n5Writer.getAttribute(groupName, "sender", String.class);   // null
+n5Writer.getAttribute(groupName, "receiver", String.class); // null
+
+
+
+
+Code +
Arrays.toString(n5Writer.getAttribute("data", "dimensions", long[].class));
+
+
+
[64, 64]
+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.out.ipynb b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.out.ipynb new file mode 100644 index 0000000..1e43fd9 --- /dev/null +++ b/posts/2024-02-27-n5-tutorial-basic/notebook/N5-Basics-Tutorial.out.ipynb @@ -0,0 +1,582 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#" + ], + "id": "577da703-8eca-4ba2-844a-6846499e9a79" + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%mavenRepo scijava.public https://maven.scijava.org/content/groups/public\n", + "%maven org.scijava:scijava-common:2.97.0\n", + "%maven net.imglib2:imglib2:6.2.0\n", + "%maven org.janelia.saalfeldlab:n5:3.1.2\n", + "%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0\n", + "%maven org.janelia.saalfeldlab:n5-universe:1.3.1" + ], + "id": "ef2f3d2c" + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "import com.google.gson.*;\n", + "\n", + "import net.imglib2.*;\n", + "import net.imglib2.img.array.*;\n", + "import net.imglib2.type.numeric.real.*;\n", + "import net.imglib2.view.*;\n", + "import net.imglib2.util.*;\n", + "\n", + "import org.janelia.saalfeldlab.n5.*;\n", + "import org.janelia.saalfeldlab.n5.imglib2.*;\n", + "import org.janelia.saalfeldlab.n5.universe.*;" + ], + "id": "f70aa4d8" + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "public static void pathInfo(Path p) {\n", + " try {\n", + " System.out.println(String.format(\"%s is %d bytes\", p, Files.size(p))); \n", + " } catch(IOException e ){}\n", + "}\n", + "\n", + "public static void printBlocks(String path) throws IOException {\n", + "\n", + " try (Stream stream = Files.walk(Paths.get(path))) {\n", + " stream.filter(Files::isRegularFile)\n", + " .filter( p -> p.getFileName().toString().matches(\"[0-9]\"))\n", + " .forEach( x -> { pathInfo(x); });\n", + " }\n", + "}" + ], + "id": "b994b3b7" + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [ + "make-reader-writer" + ] + }, + "outputs": [], + "source": [ + "// N5Factory can make N5Readers and N5Writers\n", + "var factory = new N5Factory();\n", + "\n", + "// trying to open a reader for a container that does not yet exist will throw an error \n", + "// var n5Reader = factory.openReader(\"my-container.n5\");\n", + "\n", + "// creating a writer creates a container at the given location\n", + "// if it does not already exist\n", + "var n5Writer = factory.openWriter(\"my-container.n5\");\n", + "\n", + "// now we can make a reader\n", + "var n5Reader = factory.openReader(\"my-container.n5\");\n", + "\n", + "// test if the container exists\n", + "n5Reader.exists(\"\"); // true\n", + "\n", + "// \"\" and \"/\" both refer to the root of the container\n", + "n5Reader.exists(\"/\"); // true" + ], + "id": "89830e81" + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [ + "factory-types" + ] + }, + "outputs": [], + "source": [ + "factory.openWriter(\"my-container.h5\").getClass(); // N5HDF5Writer\n", + "factory.openWriter(\"my-container.n5\").getClass(); // N5FSWriter\n", + "factory.openWriter(\"my-container.zarr\").getClass(); // N5ZarrWriter" + ], + "id": "8a6621e1" + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [ + "make-groups" + ] + }, + "outputs": [], + "source": [ + "n5Writer.createGroup(\"foo\");\n", + "n5Writer.createGroup(\"foo/bar\");\n", + "n5Writer.createGroup(\"lorum/ipsum/dolor/sit/amet\");\n", + "\n", + "n5Writer.exists(\"lorum/ipsum\"); // true\n", + "n5Writer.exists(\"not/a/real/group\"); // false" + ], + "id": "9ee0c14d" + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [ + "list" + ] + }, + "outputs": [], + "source": [ + "n5Writer.list(\"\"); // [lorum, foo]\n", + "n5Writer.list(\"foo\"); // [bar]" + ], + "id": "72496634" + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [ + "deep-list" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]" + ] + } + } + ], + "source": [ + "Arrays.toString(n5Writer.deepList(\"\"));" + ], + "id": "1f363282" + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "public static RandomAccessibleInterval demoImage(long... size) {\n", + "\n", + " final RandomAccessibleInterval img = ArrayImgs.floats(size);\n", + " float f = 0f;\n", + " Cursor c = Views.flatIterable(img).cursor();\n", + " while (c.hasNext())\n", + " c.next().set(f++);\n", + "\n", + " return img;\n", + "}" + ], + "id": "996e162e" + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [ + "n5-imglib2-save" + ] + }, + "outputs": [], + "source": [ + "// the parameters\n", + "var img = demoImage(64,64); // the image to write- size 64 x 64\n", + "var groupPath = \"data\"; \n", + "var blockSize = new int[]{32,32};\n", + "var compression = new GzipCompression();\n", + "\n", + "// save the image\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);" + ], + "id": "28e3835d" + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [ + "n5-imglib2-save-exec" + ] + }, + "outputs": [], + "source": [ + "var exec = Executors.newFixedThreadPool(4); // with 4 parallel threads\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);" + ], + "id": "131e8bd4" + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [ + "four-blocks" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/data/1/1 is 1762 bytes\n", + "my-container.n5/data/1/0 is 2012 bytes\n", + "my-container.n5/data/0/1 is 1763 bytes\n", + "my-container.n5/data/0/0 is 2020 bytes" + ] + } + ], + "source": [ + "printBlocks(\"my-container.n5/data\");" + ], + "id": "79901ca0" + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [ + "eight-blocks" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/data/0/1 is 837 bytes\n", + "my-container.n5/data/0/7 is 847 bytes\n", + "my-container.n5/data/0/3 is 839 bytes\n", + "my-container.n5/data/0/6 is 844 bytes\n", + "my-container.n5/data/0/0 is 968 bytes\n", + "my-container.n5/data/0/4 is 846 bytes\n", + "my-container.n5/data/0/2 is 840 bytes\n", + "my-container.n5/data/0/5 is 847 bytes" + ] + } + ], + "source": [ + "// remove the old data\n", + "n5Writer.remove(groupPath);\n", + "\n", + "// rewrite with a different block size\n", + "var blockSize = new int[]{64,8};\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// how many blocks are there?\n", + "printBlocks(\"my-container.n5/data\");" + ], + "id": "91647d07" + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [ + "no-compression" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "my-container.n5/dataNoCompression/1/1 is 4108 bytes\n", + "my-container.n5/dataNoCompression/1/0 is 4108 bytes\n", + "my-container.n5/dataNoCompression/0/1 is 4108 bytes\n", + "my-container.n5/dataNoCompression/0/0 is 4108 bytes" + ] + } + ], + "source": [ + "// rewrite without compression\n", + "var groupPath = \"dataNoCompression\"; \n", + "var blockSize = new int[]{32,32};\n", + "var compression = new RawCompression();\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// what size are the blocks\n", + "printBlocks(\"my-container.n5/dataNoCompression\");" + ], + "id": "9b37d386" + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "n5-imglib2-open" + ] + }, + "outputs": [], + "source": [ + "var loadedImg = N5Utils.open(n5Writer, groupPath);\n", + "Util.getTypeFromInterval(loadedImg).getClass(); // FloatType\n", + "Arrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]" + ], + "id": "de0fadca" + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [ + "n5-imglib2-overwrite" + ] + }, + "outputs": [], + "source": [ + "// overwrite our previous data\n", + "var img = ArrayImgs.unsignedBytes(2,2);\n", + "N5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n", + "\n", + "// load the new data, the old data are no longer accessible\n", + "var loadedImg = N5Utils.open(n5Writer, groupPath);\n", + "Arrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]" + ], + "id": "f861d622" + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "tags": [ + "attributes-1" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Error: could not get attribute as double[]" + ] + }, + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "2024-Jan-01" + ] + } + } + ], + "source": [ + "// create a group inside the container (think: \"folder\")\n", + "var groupName = \"put-data-in-me\";\n", + "n5Writer.createGroup(groupName);\n", + "\n", + "// attributes have names and values\n", + "// make an attribute called \"date\" with a String value\n", + "var attributeName = \"date\";\n", + "n5Writer.setAttribute(groupName, attributeName, \"2024-Jan-01\");\n", + "\n", + "// Ask the N5 API to make a double array from the data attribute\n", + "// it will try and fail, so an exception will be thrown\n", + "try {\n", + " var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);\n", + "} catch( N5Exception e ) {\n", + " System.out.println(\"Error: could not get attribute as double[]\");\n", + "}\n", + "\n", + "// get the value of the \"date\" attribute as a String\n", + "String date = n5Writer.getAttribute(groupName, attributeName, String.class);\n", + "date" + ], + "id": "75f3c15f" + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "tags": [ + "attr-types" + ] + }, + "outputs": [], + "source": [ + "n5Writer.setAttribute(groupName, \"a\", 42);\n", + "var num = n5Writer.getAttribute(groupName, \"a\", double.class); // 42.0\n", + "var str = n5Writer.getAttribute(groupName, \"a\", String.class); // \"42\"" + ], + "id": "bc080bb5" + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "tags": [ + "fun-with-metadata" + ] + }, + "outputs": [], + "source": [ + "class FunWithMetadata {\n", + " String name;\n", + " int number;\n", + " double[] data;\n", + " \n", + " public FunWithMetadata(String name, int number, double[] data) {\n", + " this.name = name;\n", + " this.number = number;\n", + " this.data = data;\n", + " }\n", + " public String toString(){\n", + " return String.format( \"FunWithMetadata{%s(%d): %s}\", \n", + " name, number, Arrays.toString(data));\n", + " }\n", + "};" + ], + "id": "76639ba3" + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "tags": [ + "rich-metadata" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "FunWithMetadata{Dorothy(2): [2.72, 3.14]}" + ] + } + } + ], + "source": [ + "var metadata = new FunWithMetadata(\"Dorothy\", 2, new double[]{2.72, 3.14});\n", + "n5Writer.setAttribute(groupName, \"metadata\", metadata);\n", + "\n", + "var loadedMetadata = n5Writer.getAttribute(groupName, \"metadata\", \n", + " FunWithMetadata.class);\n", + "loadedMetadata" + ], + "id": "03dc0d4d" + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "tags": [ + "all-metadata" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "{\"date\":\"2024-Jan-01\",\"a\":42,\"metadata\":{\"name\":\"Dorothy\",\"number\":2,\"data\":[2.72,3.14]}}" + ] + } + } + ], + "source": [ + "n5Writer.getAttribute(groupName, \"/\", JsonElement.class);" + ], + "id": "1b8133bc" + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "tags": [ + "remove-attrs" + ] + }, + "outputs": [], + "source": [ + "// set attributes\n", + "n5Writer.setAttribute(groupName, \"sender\", \"Alice\");\n", + "n5Writer.setAttribute(groupName, \"receiver\", \"Bob\");\n", + "\n", + "// notice that they're set\n", + "n5Writer.getAttribute(groupName, \"sender\", String.class); // Alice\n", + "n5Writer.getAttribute(groupName, \"receiver\", String.class); // Bob\n", + "\n", + "// remove \"sender\"\n", + "n5Writer.removeAttribute(groupName, \"sender\");\n", + "\n", + "// remove \"receiver\" and store result in a variable\n", + "var receiver = n5Writer.removeAttribute(groupName, \"receiver\", String.class); // Bob\n", + "\n", + "n5Writer.getAttribute(groupName, \"sender\", String.class); // null\n", + "n5Writer.getAttribute(groupName, \"receiver\", String.class); // null" + ], + "id": "5ebc9d0d" + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "tags": [ + "bonus" + ] + }, + "outputs": [ + { + "output_type": "display_data", + "metadata": {}, + "data": { + "text/plain": [ + "[64, 64]" + ] + } + } + ], + "source": [ + "Arrays.toString(n5Writer.getAttribute(\"data\", \"dimensions\", long[].class));" + ], + "id": "d0fb1e31" + } + ], + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "name": "java", + "display_name": "Java", + "language": "java" + }, + "language_info": { + "name": "Java", + "codemirror_mode": "java", + "file_extension": ".jshell", + "mimetype": "text/x-java-source", + "pygments_lexer": "java", + "version": "19.0.2+7-Ubuntu-0ubuntu322.04" + } + } +} \ No newline at end of file diff --git a/search.json b/search.json index 09b1b4e..c168541 100644 --- a/search.json +++ b/search.json @@ -28,11 +28,46 @@ "text": "In this notebook, we will explore how to store, process and visualize data with ImgLib2 in a notebook.\nFirst let’s add the necessary dependencies. We will use ImageJ to load example images and to generate RenderedImage outputs that we can use to render in the notebook. Then, we will import ImgLib2 and the modules to share data between ImgLib2 and ImageJ and the imglib2-realtransform module that includes various transformations.\n\n\nCode\n%mavenRepo scijava.public https://maven.scijava.org/content/groups/public\n\n%maven net.imglib2:imglib2:6.0.0\n%maven jitk:jitk-tps:3.0.3\n%maven net.imagej:ij:1.53t\n%maven net.imglib2:imglib2-ij:2.0.0-beta-46\n%maven net.imglib2:imglib2-realtransform:3.1.2\n\n\nLet’s open one of ImageJ’s example images and show it in the notebook. This uses Spencer Park’s image renderer:\n\n\nCode\nimport ij.*;\n\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/clown.jpg\");\nimp.getBufferedImage();\n\n\n\n\n\n\n\n\n\nIf we want to work with this image in ImgLib2, we need to provide it as an ImgLib2 interface:\n\n\nCode\nimport net.imglib2.*;\nimport net.imglib2.img.imageplus.*;\n\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/clown.jpg\");\n// for later use without the compiler losing its mind, we must provide type information\n// for the ImagePlus wrapper, so let's not use var here\nRandomAccessibleInterval<?> rai = ImagePlusImgs.from(imp);\nrai;\n\n\nIntImagePlus [320x200]\n\n\nThere is no default renderer for ImgLib2 interfaces available to the notebook kernel, so we see a default String representation of the result (when rendering this cell the first time). So let’s register some simple renderers that use ImgLib2’s ImageJ bridge and Spencer Park’s image renderer to render ImgLib2 data into the notebook. We add a version that renders the first 2D slice of a RandomAccessibleInterval and a second version that renders a default interval 512x512+0+0 of the 2D slice at position 0 in all other dimensions of an infinite RandomAccessible.\n\n\nCode\nimport io.github.spencerpark.jupyter.kernel.display.common.*;\nimport io.github.spencerpark.jupyter.kernel.display.mime.*;\nimport net.imglib2.img.display.imagej.*;\nimport net.imglib2.view.*;\n\ngetKernelInstance().getRenderer().createRegistration(RandomAccessibleInterval.class)\n .preferring(MIMEType.IMAGE_PNG)\n .supporting(MIMEType.IMAGE_JPEG, MIMEType.IMAGE_GIF)\n .register((rai, context) -> Image.renderImage(\n ImageJFunctions.wrap(rai, rai.toString()).getBufferedImage(),\n context));\n\ngetKernelInstance().getRenderer().createRegistration(RandomAccessible.class)\n .preferring(MIMEType.IMAGE_PNG)\n .supporting(MIMEType.IMAGE_JPEG, MIMEType.IMAGE_GIF)\n .register((ra, context) -> Image.renderImage(\n ImageJFunctions.wrap(\n Views.interval(\n ra,\n new FinalInterval(\n Arrays.copyOf(\n new long[]{512, 512},\n ra.numDimensions()))),\n ra.toString()).getBufferedImage(),\n context));\n\n\nNow let’s try the same again:\n\n\nCode\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/clown.jpg\");\n// for later use without the compiler losing its mind, we must provide type information\n// for the ImagePlus wrapper, so let's not use var here\nRandomAccessibleInterval<?> rai = ImagePlusImgs.from(imp);\ndisplay(rai, \"image/gif\");\ndisplay(rai, \"image/jpeg\");\ndisplay(rai, \"image/png\");\n\n\nIntImagePlus [320x200]\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n11d8908f-67c3-47d4-9615-8e3926265adb\n\n\nOk, great! Let’s try the ‘infinite’ version:\n\n\nCode\nvar ra = Views.extendPeriodic(rai);\nra;\n\n\n\n\n\n\n\n\n\nWonderful! We can of course still render a String representation or alternative encodings with the injected display methods of the kernel:\n\n\nCode\ndisplay(rai, \"text/plain\");\ndisplay(ra, \"text/plain\");\ndisplay(rai, \"image/jpeg\");\ndisplay(ra, \"image/gif\");\n\n\nIntImagePlus [320x200]\n\n\nnet.imglib2.view.ExtendedRandomAccessibleInterval@6d692ecb\n\n\n\n\n\n\n\n\n\nnet.imglib2.view.ExtendedRandomAccessibleInterval@6d692ecb\n\n\n3dac1586-b109-4b27-be06-f7a86a64cd2c\n\n\nYou may have noticed that the output of this cell ends with an obscure identifier. We see this, because we did not catch the output of the display method which provides an identifier for the output object that it generates. This identifier can be used to update the contents of this object. We can use this to render simple animations, e.g. to slice through a 3D volume. Let’s try this with a 3D volume from the ImageJ example images:\n\n\nCode\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/flybrain.zip\");\nRandomAccessibleInterval<?> rai = ImagePlusImgs.from(imp);\nvar refSlice = display(Views.hyperSlice(rai, 2, rai.dimension(2) / 2), \"image/jpeg\");\nvar refLabel = display(\"slice \" + rai.dimension(2) / 2);\nfor (int z = 0; z < rai.dimension(2); ++z) {\n var slice = Views.hyperSlice(rai, 2, z);\n updateDisplay(refSlice, slice, \"image/jpeg\");\n updateDisplay(refLabel, \"slice \" + z);\n Thread.sleep(100);\n}\n// for static notebook export\nupdateDisplay(refSlice, Views.hyperSlice(rai, 2, rai.dimension(2) / 2), \"image/jpeg\");\n\n\n\n\n\n\n\n\n\nslice 56\n\n\nOf course, you can only see the animation if you actually run the notebook cell. In a future iteration, we are planning to implement an animated GIF generator for offline animations, but not this time. Let’s see what else we can do with these renderers.\nFirst, let’s apply some transformations to images. Already in the above border extension example as well as in the slicing animation, we have used ImgLib2’s default behavior to apply transformations lazily, i.e. only when a ‘pixel’ is actually queried (e.g. to render it into a RenderedImage raster), the transformations are applied. Transformations can be applied to both coordinates and values. Lets apply some transformations to values:\n\n\nCode\nimport net.imglib2.converter.*;\nimport net.imglib2.type.numeric.*;\n\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/clown.jpg\");\nRandomAccessibleInterval<ARGBType> rai = ImagePlusImgs.from(imp);\ndisplay(Converters.argbChannel(rai, 1));\ndisplay(\"red\");\ndisplay(Converters.argbChannel(rai, 2));\ndisplay(\"green\");\ndisplay(Converters.argbChannel(rai, 3));\ndisplay(\"blue\");\n\ndisplay(\n Converters.<ARGBType, ARGBType>convert2(\n rai,\n (in, out) -> {\n \n final int argb = in.get();\n final double grey = 0.3 * ARGBType.red(argb) + 0.6 * ARGBType.green(argb) + 0.1 * ARGBType.blue(argb);\n out.set(ARGBType.rgba(255 - grey, grey, grey, 255));\n },\n ARGBType::new));\ndisplay(\"grey to red-cyan ramp\");\n\n\n\n\n\n\n\n\n\nred\n\n\n\n\n\n\n\n\n\ngreen\n\n\n\n\n\n\n\n\n\nblue\n\n\n\n\n\n\n\n\n\ngrey to red-cyan ramp\n\n\n5ea04e32-a511-4b25-8e76-b2aaaf5f3fca\n\n\nAnd now some integer coordinate transformations:\n\n\nCode\ndisplay(Views.invertAxis(rai, 0));\ndisplay(\"flip axis 0\");\n\ndisplay(Views.permute(rai, 0, 1));\ndisplay(\"permute axes\");\n\ndisplay(Views.extendMirrorSingle(rai));\ndisplay(\"mirror extension without repeated border pixels\");\n\ndisplay(Views.subsample(Views.shear(Views.extendPeriodic(rai), 0, 1), 3, 1));\ndisplay(\"extend periodically, shear axis 1 into axis 0, subsample by (3, 1)\");\n\n\n\n\n\n\n\n\n\nflip axis 0\n\n\n\n\n\n\n\n\n\npermute axes\n\n\n\n\n\n\n\n\n\nmirror extension without repeated border pixels\n\n\n\n\n\n\n\n\n\nextend periodically, shear axis 1 into axis 0, subsample by (3, 1)\n\n\naaa10b76-8e83-4b78-9239-fa15d4c143eb\n\n\nWhile most trivial integer transformations such as flipping axes work on intervals, you probably noticed that we had to extend the image to infinity in order to shear it, so ImgLib2 can provide values for coordinates outside of the source interval. For real coordinate transformations we will also need to interpolate values at non-integer coordinates. Finally, in order to render the result, we have to read it from a raster. Let’s do this:\n\n\nCode\nimport net.imglib2.interpolation.randomaccess.*;\nimport net.imglib2.realtransform.*;\n\nvar imp = IJ.openImage(\"https://mirror.imagej.net/ij/images/clown.jpg\");\nRandomAccessibleInterval<ARGBType> rai = ImagePlusImgs.from(imp);\nvar ra = Views.extendValue(rai, new ARGBType(0xff00ff00)); // < green background\nvar interpolated = Views.interpolate(ra, new ClampingNLinearInterpolatorFactory<>()); // n-linear interpolation\n/**\n * This would be\n * var interpolated = Views.interpolate(ra, new NLinearInterpolatorFactory<>());\n * if you have no concern about value overflows\n */\nvar affine = new AffineTransform2D();\nvar transformed = Views.interval(RealViews.affine(interpolated, affine), rai); // shortcut for affines\nvar refImage = display(transformed, \"image/jpeg\");\nvar refLabel = display(\"\", \"text/html\");\n\nfinal int steps = 20;\nfor (int i = 0; i < steps; ++i) {\n affine.translate(-rai.dimension(0) / 2, -rai.dimension(1) / 2);\n affine.rotate(Math.PI / 6.0 / steps);\n affine.scale(1.0 + 0.7 / steps);\n affine.translate(rai.dimension(0) / 2, rai.dimension(1) / 2);\n \n updateDisplay(refImage, Views.interval(transformed, rai), \"image/jpeg\");\n updateDisplay(\n refLabel,\n String.format(\"\"\"\n <p>affine transformation matrix:</p>\n <table>\n <tr><td>%.2f</td><td>%.2f</td><td>%.2f</td></tr>\n <tr><td>%.2f</td><td>%.2f</td><td>%.2f</td></tr>\n </table>\"\"\",\n affine.get(0, 0), affine.get(0, 1), affine.get(0, 2),\n affine.get(1, 0), affine.get(1, 1), affine.get(1, 2)), \"text/html\");\n Thread.sleep(100);\n}\n\n\n\n\n\n\n\n\n\naffine transformation matrix:\n\n\n\n\n1.72\n-0.99\n-16.22\n\n\n0.99\n1.72\n-231.50\n\n\n\n\n\nAffine transformation are probably the most well known and simple real coordinate transformations, but there are many more. Let’s try a ThinplateSplineTransform and format text output with markdown:\n\n\nCode\nvar refImage = display(rai, \"image/jpeg\");\nvar refLabel = display(\"\", \"text/markdown\");\n\nint steps = 20;\ndouble stretch = 40;\nfor (int i = 0; i < steps; ++i) {\n final double offset = stretch * i / steps;\n final double[][] p = {\n {0, rai.dimension(0), 0, rai.dimension(0), rai.dimension(0) * 0.25, rai.dimension(0) * 0.75, rai.dimension(0) * 0.25, rai.dimension(0) * 0.75},\n {0, 0, rai.dimension(1), rai.dimension(1), rai.dimension(1) * 0.25, rai.dimension(1) * 0.25, rai.dimension(1) * 0.75, rai.dimension(1) * 0.75}\n };\n final double[][] q = {\n {0, rai.dimension(0), 0, rai.dimension(0),\n rai.dimension(0) * 0.25 + offset , rai.dimension(0) * 0.75 - offset, rai.dimension(0) * 0.25 + offset, rai.dimension(0) * 0.75 - offset},\n {0, 0, rai.dimension(1), rai.dimension(1),\n rai.dimension(1) * 0.25 + offset, rai.dimension(1) * 0.25 + offset, rai.dimension(1) * 0.75 - offset, rai.dimension(1) * 0.75 - offset}\n };\n final var transform = new ThinplateSplineTransform(p, q);\n final var warped = new RealTransformRandomAccessible<>(interpolated, transform);\n String text = \"\"\"\nthinplate spline transformation controls points:\n \n| | p<sub>x</sub> | p<sub>y</sub> | q<sub>x</sub> | q<sub>y</sub> |\n| --- | ---: | ---: | ---: | ---: |\n\"\"\";\n for (int j = 0; j < p[0].length; ++j)\n text += String.format(\"\"\"\n| %d | %.2f | %.2f | %.2f | %.2f |\n\"\"\",\n j, p[0][j], p[1][j], q[0][j], q[1][j]); \n\n updateDisplay(refImage, Views.interval(warped, rai), \"image/jpeg\");\n updateDisplay(refLabel, text, \"text/markdown\");\n\n Thread.sleep(100);\n}\n\n\n\n\n\n\n\n\n\nthinplate spline transformation controls points:\n\n\n\n\npx\npy\nqx\nqy\n\n\n\n\n0\n0.00\n0.00\n0.00\n0.00\n\n\n1\n320.00\n0.00\n320.00\n0.00\n\n\n2\n0.00\n200.00\n0.00\n200.00\n\n\n3\n320.00\n200.00\n320.00\n200.00\n\n\n4\n80.00\n50.00\n118.00\n88.00\n\n\n5\n240.00\n50.00\n202.00\n88.00\n\n\n6\n80.00\n150.00\n118.00\n112.00\n\n\n7\n240.00\n150.00\n202.00\n112.00" }, { - "objectID": "posts/2024-02-27-n5-tutorial-basic/N5-Basics-Tutorial.html", - "href": "posts/2024-02-27-n5-tutorial-basic/N5-Basics-Tutorial.html", - "title": "", + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html", + "title": "N5 API Basics", "section": "", - "text": "CodeShow All CodeHide All CodeView Source\n\n\n\n\nThis notebook is a companion to the N5 Basis Tutorial on the imglib2-blog.\n\n\nCode\n%mavenRepo scijava.public https://maven.scijava.org/content/groups/public\n%maven org.scijava:scijava-common:2.97.0\n%maven net.imglib2:imglib2:6.2.0\n%maven org.janelia.saalfeldlab:n5:3.1.2\n%maven org.janelia.saalfeldlab:n5-imglib2:7.0.0\n%maven org.janelia.saalfeldlab:n5-universe:1.3.1\n\n\n\n\nCode\nimport java.nio.file.*;\nimport java.util.stream.*;\nimport java.util.concurrent.*;\n\nimport com.google.gson.*;\n\nimport net.imglib2.*;\nimport net.imglib2.img.array.*;\nimport net.imglib2.type.numeric.real.*;\nimport net.imglib2.view.*;\nimport net.imglib2.util.*;\n\nimport org.janelia.saalfeldlab.n5.*;\nimport org.janelia.saalfeldlab.n5.imglib2.*;\nimport org.janelia.saalfeldlab.n5.universe.*;\n\n\n\n\nCode\npublic static void pathInfo(Path p) {\n try {\n System.out.println(String.format(\"%s is %d bytes\", p, Files.size(p))); \n } catch(IOException e ){}\n}\n\npublic static void printBlocks(String path) throws IOException {\n\n try (Stream<Path> stream = Files.walk(Paths.get(path))) {\n stream.filter(Files::isRegularFile)\n .filter( p -> p.getFileName().toString().matches(\"[0-9]\"))\n .forEach( x -> { pathInfo(x); });\n }\n}\n\n\n\n\nCode\n// N5Factory can make N5Readers and N5Writers\nvar factory = new N5Factory();\n\n// trying to open a reader for a container that does not yet exist will throw an error \n// var n5Reader = factory.openReader(\"my-container.n5\");\n\n// creating a writer creates a container at the given location\n// if it does not already exist\nvar n5Writer = factory.openWriter(\"my-container.n5\");\n\n// now we can make a reader\nvar n5Reader = factory.openReader(\"my-container.n5\");\n\n// test if the container exists\nn5Reader.exists(\"\"); // true\n\n// \"\" and \"/\" both refer to the root of the container\nn5Reader.exists(\"/\"); // true\n\n\n\n\nCode\nfactory.openWriter(\"my-container.h5\").getClass(); // N5HDF5Writer\nfactory.openWriter(\"my-container.n5\").getClass(); // N5FSWriter\nfactory.openWriter(\"my-container.zarr\").getClass(); // N5ZarrWriter\n\n\n\n\nCode\nn5Writer.createGroup(\"foo\");\nn5Writer.createGroup(\"foo/bar\");\nn5Writer.createGroup(\"lorum/ipsum/dolor/sit/amet\");\n\nn5Writer.exists(\"lorum/ipsum\"); // true\nn5Writer.exists(\"not/a/real/group\"); // false\n\n\n\n\nCode\nn5Writer.list(\"\"); // [lorum, foo]\nn5Writer.list(\"foo\"); // [bar]\n\n\n\n\nCode\nArrays.toString(n5Writer.deepList(\"\"));\n\n\n[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]\n\n\n\n\nCode\npublic static RandomAccessibleInterval<FloatType> demoImage(long... size) {\n\n final RandomAccessibleInterval<FloatType> img = ArrayImgs.floats(size);\n float f = 0f;\n Cursor<FloatType> c = Views.flatIterable(img).cursor();\n while (c.hasNext())\n c.next().set(f++);\n\n return img;\n}\n\n\n\n\nCode\n// the parameters\nvar img = demoImage(64,64); // the image to write- size 64 x 64\nvar groupPath = \"data\"; \nvar blockSize = new int[]{32,32};\nvar compression = new GzipCompression();\n\n// save the image\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n\n\n\nCode\nvar exec = Executors.newFixedThreadPool(4); // with 4 parallel threads\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);\n\n\n\n\nCode\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/1/1 is 1762 bytes\nmy-container.n5/data/1/0 is 2012 bytes\nmy-container.n5/data/0/1 is 1763 bytes\nmy-container.n5/data/0/0 is 2020 bytes\n\n\n\n\nCode\n// remove the old data\nn5Writer.remove(groupPath);\n\n// rewrite with a different block size\nvar blockSize = new int[]{64,8};\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// how many blocks are there?\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/0/1 is 837 bytes\nmy-container.n5/data/0/7 is 847 bytes\nmy-container.n5/data/0/3 is 839 bytes\nmy-container.n5/data/0/6 is 844 bytes\nmy-container.n5/data/0/0 is 968 bytes\nmy-container.n5/data/0/4 is 846 bytes\nmy-container.n5/data/0/2 is 840 bytes\nmy-container.n5/data/0/5 is 847 bytes\n\n\n\n\nCode\n// rewrite without compression\nvar groupPath = \"dataNoCompression\"; \nvar blockSize = new int[]{32,32};\nvar compression = new RawCompression();\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// what size are the blocks\nprintBlocks(\"my-container.n5/dataNoCompression\");\n\n\nmy-container.n5/dataNoCompression/1/1 is 4108 bytes\nmy-container.n5/dataNoCompression/1/0 is 4108 bytes\nmy-container.n5/dataNoCompression/0/1 is 4108 bytes\nmy-container.n5/dataNoCompression/0/0 is 4108 bytes\n\n\n\n\nCode\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nUtil.getTypeFromInterval(loadedImg).getClass(); // FloatType\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]\n\n\n\n\nCode\n// overwrite our previous data\nvar img = ArrayImgs.unsignedBytes(2,2);\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// load the new data, the old data are no longer accessible\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]\n\n\n\n\nCode\n// create a group inside the container (think: \"folder\")\nvar groupName = \"put-data-in-me\";\nn5Writer.createGroup(groupName);\n\n// attributes have names and values\n// make an attribute called \"date\" with a String value\nvar attributeName = \"date\";\nn5Writer.setAttribute(groupName, attributeName, \"2024-Jan-01\");\n\n// Ask the N5 API to make a double array from the data attribute\n// it will try and fail, so an exception will be thrown\ntry {\n var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);\n} catch( N5Exception e ) {\n System.out.println(\"Error: could not get attribute as double[]\");\n}\n\n// get the value of the \"date\" attribute as a String\nString date = n5Writer.getAttribute(groupName, attributeName, String.class);\ndate\n\n\nError: could not get attribute as double[]\n\n\n2024-Jan-01\n\n\n\n\nCode\nn5Writer.setAttribute(groupName, \"a\", 42);\nvar num = n5Writer.getAttribute(groupName, \"a\", double.class); // 42.0\nvar str = n5Writer.getAttribute(groupName, \"a\", String.class); // \"42\"\n\n\n\n\nCode\nclass FunWithMetadata {\n String name;\n int number;\n double[] data;\n \n public FunWithMetadata(String name, int number, double[] data) {\n this.name = name;\n this.number = number;\n this.data = data;\n }\n public String toString(){\n return String.format( \"FunWithMetadata{%s(%d): %s}\", \n name, number, Arrays.toString(data));\n }\n};\n\n\n\n\nCode\nvar metadata = new FunWithMetadata(\"Dorothy\", 2, new double[]{2.72, 3.14});\nn5Writer.setAttribute(groupName, \"metadata\", metadata);\n\nvar loadedMetadata = n5Writer.getAttribute(groupName, \"metadata\", \n FunWithMetadata.class);\nloadedMetadata\n\n\nFunWithMetadata{Dorothy(2): [2.72, 3.14]}\n\n\n\n\nCode\nn5Writer.getAttribute(groupName, \"/\", JsonElement.class);\n\n\n{\"date\":\"2024-Jan-01\",\"a\":42,\"metadata\":{\"name\":\"Dorothy\",\"number\":2,\"data\":[2.72,3.14]}}\n\n\n\n\nCode\n// set attributes\nn5Writer.setAttribute(groupName, \"sender\", \"Alice\");\nn5Writer.setAttribute(groupName, \"receiver\", \"Bob\");\n\n// notice that they're set\nn5Writer.getAttribute(groupName, \"sender\", String.class); // Alice\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // Bob\n\n// remove \"sender\"\nn5Writer.removeAttribute(groupName, \"sender\");\n\n// remove \"receiver\" and store result in a variable\nvar receiver = n5Writer.removeAttribute(groupName, \"receiver\", String.class); // Bob\n\nn5Writer.getAttribute(groupName, \"sender\", String.class); // null\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // null\n\n\n\n\nCode\nArrays.toString(n5Writer.getAttribute(\"data\", \"dimensions\", long[].class));\n\n\n[64, 64]" + "text": "This tutorial for Java developers covers the most basic functionality of the N5 API. We will learn about:" + }, + { + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#readers-and-writers", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html#readers-and-writers", + "title": "N5 API Basics", + "section": "Readers and writers", + "text": "Readers and writers\nN5Readers and N5Writers form the basis of the N5 API and allow you to read and write data, respectively. We generally recommend using an N5Factory to create readers and writers:\n\n\n\nCode\n// N5Factory can make N5Readers and N5Writers\nvar factory = new N5Factory();\n\n// trying to open a reader for a container that does not yet exist will throw an error \n// var n5Reader = factory.openReader(\"my-container.n5\");\n\n// creating a writer creates a container at the given location\n// if it does not already exist\nvar n5Writer = factory.openWriter(\"my-container.n5\");\n\n// now we can make a reader\nvar n5Reader = factory.openReader(\"my-container.n5\");\n\n// test if the container exists\nn5Reader.exists(\"\"); // true\n\n// \"\" and \"/\" both refer to the root of the container\nn5Reader.exists(\"/\"); // true\n\n\nSource: N5-Basics-Tutorial.ipynb\nThe N5 API gives you access to a number of different storage formats: HDF5, Zarr, and N5’s own format. N5Factory’s convenience methods try to infer the storage format from the extension of the path you give it:\n\n\n\nCode\nfactory.openWriter(\"my-container.h5\").getClass(); // N5HDF5Writer\nfactory.openWriter(\"my-container.n5\").getClass(); // N5FSWriter\nfactory.openWriter(\"my-container.zarr\").getClass(); // N5ZarrWriter\n\n\nSource: N5-Basics-Tutorial.ipynb\nIn fact, it is possible to read with N5Writers since every N5Writer is also an N5Reader, so from now on, we’ll just be using the n5Writer.\n\n\n\n\n\n\nTry it!\n\n\n\nWe use the the N5 storage format for the rest of the tutorial, but it will work just as well using either an HDF5 or Zarr writer." + }, + { + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#groups", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html#groups", + "title": "N5 API Basics", + "section": "Groups", + "text": "Groups\nN5 containers form hierarchies of groups - think “nested folders on your file system.” It’s easy to create groups and test if they exist:\n\n\n\nCode\nn5Writer.createGroup(\"foo\");\nn5Writer.createGroup(\"foo/bar\");\nn5Writer.createGroup(\"lorum/ipsum/dolor/sit/amet\");\n\nn5Writer.exists(\"lorum/ipsum\"); // true\nn5Writer.exists(\"not/a/real/group\"); // false\n\n\nSource: N5-Basics-Tutorial.ipynb\nThe list method lists groups that are children of the given group:\n\n\n\nCode\nn5Writer.list(\"\"); // [lorum, foo]\nn5Writer.list(\"foo\"); // [bar]\n\n\nSource: N5-Basics-Tutorial.ipynb\nand deepList recursively lists every descendent of the given group:\n\n\n\nCode\nArrays.toString(n5Writer.deepList(\"\"));\n\n\n[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]\n\n\nSource: N5-Basics-Tutorial.ipynb\nNotice that these methods only give information about what groups are present and do not provide information about metadata or array data.\n\n\n\n\n\n\nNote\n\n\n\nSome storage / access systems (AWS-S3) separate permissions for reading and listing, meaning it may be possible to access data but not list." + }, + { + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#arrays", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html#arrays", + "title": "N5 API Basics", + "section": "Arrays", + "text": "Arrays\nN5 stores arrays in particular groups in the hierarchy. We call groups that contain arrays “datasets” - terminology inherited from HDF5.\n\n\n\n\n\n\nWarning\n\n\n\nDatasets must be terminal (leaf) nodes in the container hierarchy - i.e. a dataset can not contain another group or dataset.\n\n\nWe recommend using code from n5-ij or n5-imglib2 to write arrays. The examples in this post will use the latter.\nThe N5Utils class in n5-imglib2 has many useful methods, but in this post, we’ll cover simple methods for reading and writing. First, N5Utils.save writes an array and required array metadata to the container at a group that you specify. The group will be created if it does not already exist. The parameters will be discussed in more detail below.\n\n\n\nCode\n// the parameters\nvar img = demoImage(64,64); // the image to write- size 64 x 64\nvar groupPath = \"data\"; \nvar blockSize = new int[]{32,32};\nvar compression = new GzipCompression();\n\n// save the image\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n\nSource: N5-Basics-Tutorial.ipynb\nYou can write blocks in parallel by providing an ExecutorService to this variant of N5Utils.save\n\n\n\nCode\nvar exec = Executors.newFixedThreadPool(4); // with 4 parallel threads\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);\n\n\nSource: N5-Basics-Tutorial.ipynb\nReading the array data from the container is also easy with N5Utils.open :\n\n\n\nCode\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nUtil.getTypeFromInterval(loadedImg).getClass(); // FloatType\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]\n\n\nSource: N5-Basics-Tutorial.ipynb\n\n\n\n\n\n\nOverwriting data is possible\n\n\n\nThis save method DOES NOT perform any checks prior to writing data and will overwrite data that exists in the specified location. Be sure to check and take appropriate action if it is possible that data could already at a particular location and container to avoid data loss or corruption.\n\n\nThis example shows that data can be over written:\n\n\n\nCode\n// overwrite our previous data\nvar img = ArrayImgs.unsignedBytes(2,2);\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// load the new data, the old data are no longer accessible\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]\n\n\nSource: N5-Basics-Tutorial.ipynb\n\nParameter details\n\ngroupPath\nis the group inside the container that will store the array. You can store an array at the root of a container by specifying \"\" or \"/\" as the groupPath. In this case, the container will only be able to store one array (see the warning above at the start of the “Arrays” section).\n\n\nblockSize\nis a very important parameter of an array. HDF5, N5, and Zarr all break up the arrays they store into equally sized blocks or “chunks”. The block size parameter specifies the size of these blocks.\nFor the example above, we stored an image of size 64 x 64 using blocks sized 32 x 32. As a result, N5 uses four blocks to store the entire image:\n\n\n\nCode\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/1/1 is 1762 bytes\nmy-container.n5/data/1/0 is 2012 bytes\nmy-container.n5/data/0/1 is 1763 bytes\nmy-container.n5/data/0/0 is 2020 bytes\n\n\nSource: N5-Basics-Tutorial.ipynb\nQuiz: How many blocks would there be if the block size was 64 x 8?\n\n\nClick here to show the answer.\n\nThere would be eight blocks.\nOne block covers the first dimension, but it takes 8 blocks to cover the second dimension (\\(8 \\times 8 = 64\\)). Also demonstrated by the code below:\n\n\n\nCode\n// remove the old data\nn5Writer.remove(groupPath);\n\n// rewrite with a different block size\nvar blockSize = new int[]{64,8};\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// how many blocks are there?\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/0/1 is 837 bytes\nmy-container.n5/data/0/7 is 847 bytes\nmy-container.n5/data/0/3 is 839 bytes\nmy-container.n5/data/0/6 is 844 bytes\nmy-container.n5/data/0/0 is 968 bytes\nmy-container.n5/data/0/4 is 846 bytes\nmy-container.n5/data/0/2 is 840 bytes\nmy-container.n5/data/0/5 is 847 bytes\n\n\nSource: N5-Basics-Tutorial.ipynb\n\n\n\n\n\n\n\nTry it!\n\n\n\nN5 lets you store your image in a single file if you want - just provide a block size that is equal to or larger than the image size.\n\n\n\n\ncompression\nEach block is compressed independently, using the specified compressor. Use RawCompression to store blocks without compression.\n\n\n\nCode\n// rewrite without compression\nvar groupPath = \"dataNoCompression\"; \nvar blockSize = new int[]{32,32};\nvar compression = new RawCompression();\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// what size are the blocks\nprintBlocks(\"my-container.n5/dataNoCompression\");\n\n\nmy-container.n5/dataNoCompression/1/1 is 4108 bytes\nmy-container.n5/dataNoCompression/1/0 is 4108 bytes\nmy-container.n5/dataNoCompression/0/1 is 4108 bytes\nmy-container.n5/dataNoCompression/0/0 is 4108 bytes\n\n\nSource: N5-Basics-Tutorial.ipynb\nNotice that blocks were previously ~1700-2000 bytes and are now ~4100 without compression.\nThe available compressors at the time of this writing are:\n\nBloscCompression\nBzip2Compression\nGzipCompression\nLz4Compression\nRawCompression\nXzCompression\nZstandardCompression" + }, + { + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#metadata", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html#metadata", + "title": "N5 API Basics", + "section": "Metadata", + "text": "Metadata\nBesides array data, N5 can also store rich structured metadata. This tutorial will discuss basic, low-level metadata operations. Advanced operations and metadata standards may be described in a future tutorial.\n\nBasics\nN5Writers have a setAttribute method for writing metadata to the storage backend. It takes three arguments:\n<T> void setAttribute(String groupPath, String attributePath, T attribute)\n\ngroupPath : the group in which to store this metadata\nattributePath : the name of this attribute\nattribute : the metadata attribute to be stored. Can be an arbitrary type (denoted T).\n\n\n\n\n\n\n\nNote\n\n\n\nThere are differences between an attribute “name” and an attribute “path”, but attribute “paths” are an advanced topic and will be covered elsewhere.\n\n\nSimilarly, N5Readers have a getAttribute method:\n<T> T getAttribute(String groupPath, String attributePath, Class<T> clazz)\nThe last argument (Class<T>) lets you specify the type that getAttribute should return. An N5Exception will be thrown if the requested type can not be created from the requested attribute. If an attribute does not exist, null will be returned (see the last example of this section). Consider these examples:\n\n\n\nCode\n// create a group inside the container (think: \"folder\")\nvar groupName = \"put-data-in-me\";\nn5Writer.createGroup(groupName);\n\n// attributes have names and values\n// make an attribute called \"date\" with a String value\nvar attributeName = \"date\";\nn5Writer.setAttribute(groupName, attributeName, \"2024-Jan-01\");\n\n// Ask the N5 API to make a double array from the data attribute\n// it will try and fail, so an exception will be thrown\ntry {\n var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);\n} catch( N5Exception e ) {\n System.out.println(\"Error: could not get attribute as double[]\");\n}\n\n// get the value of the \"date\" attribute as a String\nString date = n5Writer.getAttribute(groupName, attributeName, String.class);\ndate\n\n\nError: could not get attribute as double[]\n\n\n2024-Jan-01\n\n\nSource: N5-Basics-Tutorial.ipynb\nSometimes it is possible to interpret a metadata as multiple different types:\n\n\n\nCode\nn5Writer.setAttribute(groupName, \"a\", 42);\nvar num = n5Writer.getAttribute(groupName, \"a\", double.class); // 42.0\nvar str = n5Writer.getAttribute(groupName, \"a\", String.class); // \"42\"\n\n\nSource: N5-Basics-Tutorial.ipynb\n\n\nRich metadata\nIt possible to save attributes of arbitrary types, enabling you to struture your metadata into classes that are easy to save and load directly. For example, if we define a metadata class FunWithMetadata:\n\n\n\nCode\nclass FunWithMetadata {\n String name;\n int number;\n double[] data;\n \n public FunWithMetadata(String name, int number, double[] data) {\n this.name = name;\n this.number = number;\n this.data = data;\n }\n public String toString(){\n return String.format( \"FunWithMetadata{%s(%d): %s}\", \n name, number, Arrays.toString(data));\n }\n};\n\n\nSource: N5-Basics-Tutorial.ipynb\nthen make an instance and save it:\n\n\n\nCode\nvar metadata = new FunWithMetadata(\"Dorothy\", 2, new double[]{2.72, 3.14});\nn5Writer.setAttribute(groupName, \"metadata\", metadata);\n\nvar loadedMetadata = n5Writer.getAttribute(groupName, \"metadata\", \n FunWithMetadata.class);\nloadedMetadata\n\n\nFunWithMetadata{Dorothy(2): [2.72, 3.14]}\n\n\nSource: N5-Basics-Tutorial.ipynb\nTo retrieve all the metadata in a group as JSON:\n\n\n\nCode\nn5Writer.getAttribute(groupName, \"/\", JsonElement.class);\n\n\n{\"date\":\"2024-Jan-01\",\"a\":42,\"metadata\":{\"name\":\"Dorothy\",\"number\":2,\"data\":[2.72,3.14]}}\n\n\nSource: N5-Basics-Tutorial.ipynb\n\n\nRemoving metadata\nYou can remove attributes by their name as well. To return the element that was removed, just provide the class for that element (this mirrors the remove method for Lists in Java.\n\n\n\nCode\n// set attributes\nn5Writer.setAttribute(groupName, \"sender\", \"Alice\");\nn5Writer.setAttribute(groupName, \"receiver\", \"Bob\");\n\n// notice that they're set\nn5Writer.getAttribute(groupName, \"sender\", String.class); // Alice\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // Bob\n\n// remove \"sender\"\nn5Writer.removeAttribute(groupName, \"sender\");\n\n// remove \"receiver\" and store result in a variable\nvar receiver = n5Writer.removeAttribute(groupName, \"receiver\", String.class); // Bob\n\nn5Writer.getAttribute(groupName, \"sender\", String.class); // null\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // null\n\n\nSource: N5-Basics-Tutorial.ipynb\n\n\n\n\n\n\nTry it!\n\n\n\nThe attributes that describe arrays are also accessible using getAttribute, try running:\nn5Writer.getAttribute(\"data\", \"dimensions\", long[].class);\n\n\n\n\n\n\n\n\nWarning\n\n\n\nThe attributes that N5 uses to read arrays can be set with setAttribute, and modifying them could corrupt your data. Do not manually set these attributes unless you absolutely know what you’re doing!\n\ndimensions\nblockSize\ndataType\ncompression" + }, + { + "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#what-to-try-next", + "href": "posts/2024-02-27-n5-tutorial-basic/index.html#what-to-try-next", + "title": "N5 API Basics", + "section": "What to try next", + "text": "What to try next\n\nHow to work with the N5 API and ImgLib2" }, { "objectID": "posts/2022-10-30-streams/2022-10-30-streams.html", @@ -76,48 +111,6 @@ "section": "Performance", "text": "Performance\nIt’s complicated…\nOne the one hand, there comes considerable performance overhead in replacing simple loops with stream operations. This has nothing to do with ImgLib2, it is just a “feature” of the underlying machinery. This can be observed for example by benchmarking looping over an int[] array:\nint[] values = new int[4_000_000];\n\n@Benchmark\npublic long benchmarkForLoopArray() {\n long count = 0;\n for (int value : values) {\n if (value > 127)\n ++count;\n }\n return count;\n}\n\n@Benchmark\npublic long benchmarkStreamArray() {\n return IntStream.of(values).filter(value -> value > 127).count();\n}\nThe result is\nBenchmark Mode Cnt Score Error Units\nArrayStreamBenchmark.benchmarkForLoopArray avgt 15 2,563 ± 0,026 ms/op\nArrayStreamBenchmark.benchmarkStreamArray avgt 15 11,052 ± 0,022 ms/op\nThat is, the Stream version is > 4 times slower. Equivalent performance overhead often can be observed in ImgLib2, when replacing Cursor based loops with Stream operations.\nOn the other hand, custom Spliterator implementations sometimes benefit more than cursors from tuning to the underlying storage. (Because iteration is “internal” with the spliterator, while the cursor must return control to the caller after every visited element.) For example, consider the following benchmark method (equivalent code for other variations omitted, see github for full details):\n@Benchmark\npublic long benchmarkStream() {\n long sum = Streams.localizable(img)\n .mapToLong(s -> s.get().get()\n + s.getIntPosition(0)\n + s.getIntPosition(1)\n + s.getIntPosition(2)\n ).sum();\n return sum;\n}\nThe result looks like\nBenchmark (imgType) Mode Cnt Score Error Units\nLocalizableSamplerStreamBenchmark.benchmarkCursor ArrayImg avgt 15 10,097 ± 0,046 ms/op\nLocalizableSamplerStreamBenchmark.benchmarkLocalizingCursor ArrayImg avgt 15 3,846 ± 0,020 ms/op\nLocalizableSamplerStreamBenchmark.benchmarkLocalizingStream ArrayImg avgt 15 3,337 ± 0,027 ms/op\nLocalizableSamplerStreamBenchmark.benchmarkLocalizingParallelStream ArrayImg avgt 15 0,962 ± 0,583 ms/op\nThat is, the performance difference between localizing and non-localizing Cursors is much more pronounced than the difference between Cursor loop and Stream. In fact, the Stream version is even faster than the localizingCursor version. On top of that, it is trivial to parallelize.\nFinally, we did not investigate polymorphism effects so far. It is very much possible that this affects performance and we may have to investigate employing LoopBuilders class-copying mechanism to counter these effects.\nIn summary, I think one should not hesitate to use Streams where it makes sense from a readability and ease-of-use perspective. If performance is a critical concern, it is best to benchmark various approaches, because the behaviour is not easy to predict." }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html", - "title": "N5 API Basics", - "section": "", - "text": "This tutorial for Java developers covers the most basic functionality of the N5 API. We will learn about:" - }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#readers-and-writers", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html#readers-and-writers", - "title": "N5 API Basics", - "section": "Readers and writers", - "text": "Readers and writers\nN5Readers and N5Writers form the basis of the N5 API and allow you to read and write data, respectively. We generally recommend using an N5Factory to create readers and writers:\n\n\n\nCode\n// N5Factory can make N5Readers and N5Writers\nvar factory = new N5Factory();\n\n// trying to open a reader for a container that does not yet exist will throw an error \n// var n5Reader = factory.openReader(\"my-container.n5\");\n\n// creating a writer creates a container at the given location\n// if it does not already exist\nvar n5Writer = factory.openWriter(\"my-container.n5\");\n\n// now we can make a reader\nvar n5Reader = factory.openReader(\"my-container.n5\");\n\n// test if the container exists\nn5Reader.exists(\"\"); // true\n\n// \"\" and \"/\" both refer to the root of the container\nn5Reader.exists(\"/\"); // true\n\n\n\nThe N5 API gives you access to a number of different storage formats: HDF5, Zarr, and N5’s own format. N5Factory’s convenience methods try to infer the storage format from the extension of the path you give it:\n\n\n\nCode\nfactory.openWriter(\"my-container.h5\").getClass(); // N5HDF5Writer\nfactory.openWriter(\"my-container.n5\").getClass(); // N5FSWriter\nfactory.openWriter(\"my-container.zarr\").getClass(); // N5ZarrWriter\n\n\n\nIn fact, it is possible to read with N5Writers since every N5Writer is also an N5Reader, so from now on, we’ll just be using the n5Writer.\n\n\n\n\n\n\nTry it!\n\n\n\nWe use the the N5 storage format for the rest of the tutorial, but it will work just as well using either an HDF5 or Zarr writer." - }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#groups", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html#groups", - "title": "N5 API Basics", - "section": "Groups", - "text": "Groups\nN5 containers form hierarchies of groups - think “nested folders on your file system.” It’s easy to create groups and test if they exist:\n\n\n\nCode\nn5Writer.createGroup(\"foo\");\nn5Writer.createGroup(\"foo/bar\");\nn5Writer.createGroup(\"lorum/ipsum/dolor/sit/amet\");\n\nn5Writer.exists(\"lorum/ipsum\"); // true\nn5Writer.exists(\"not/a/real/group\"); // false\n\n\n\nThe list method lists groups that are children of the given group:\n\n\n\nCode\nn5Writer.list(\"\"); // [lorum, foo]\nn5Writer.list(\"foo\"); // [bar]\n\n\n\nand deepList recursively lists every descendent of the given group:\n\n\n\nCode\nArrays.toString(n5Writer.deepList(\"\"));\n\n\n[lorum, lorum/ipsum, lorum/ipsum/dolor, lorum/ipsum/dolor/sit, lorum/ipsum/dolor/sit/amet, foo, foo/bar]\n\n\n\nNotice that these methods only give information about what groups are present and do not provide information about metadata or array data.\n\n\n\n\n\n\nNote\n\n\n\nSome storage / access systems (AWS-S3) separate permissions for reading and listing, meaning it may be possible to access data but not list." - }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#arrays", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html#arrays", - "title": "N5 API Basics", - "section": "Arrays", - "text": "Arrays\nN5 stores arrays in particular groups in the hierarchy. We call groups that contain arrays “datasets” - terminology inherited from HDF5.\n\n\n\n\n\n\nWarning\n\n\n\nDatasets must be terminal (leaf) nodes in the container hierarchy - i.e. a dataset can not contain another group or dataset.\n\n\nWe recommend using code from n5-ij or n5-imglib2 to write arrays. The examples in this post will use the latter.\nThe N5Utils class in n5-imglib2 has many useful methods, but in this post, we’ll cover simple methods for reading and writing. First, N5Utils.save writes an array and required array metadata to the container at a group that you specify. The group will be created if it does not already exist. The parameters will be discussed in more detail below.\n\n\n\nCode\n// the parameters\nvar img = demoImage(64,64); // the image to write- size 64 x 64\nvar groupPath = \"data\"; \nvar blockSize = new int[]{32,32};\nvar compression = new GzipCompression();\n\n// save the image\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n\n\nYou can write blocks in parallel by providing an ExecutorService to this variant of N5Utils.save\n\n\n\nCode\nvar exec = Executors.newFixedThreadPool(4); // with 4 parallel threads\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression, exec);\n\n\n\nReading the array data from the container is also easy with N5Utils.open :\n\n\n\nCode\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nUtil.getTypeFromInterval(loadedImg).getClass(); // FloatType\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [64, 64]\n\n\n\n\n\n\n\n\n\nOverwriting data is possible\n\n\n\nThis save method DOES NOT perform any checks prior to writing data and will overwrite data that exists in the specified location. Be sure to check and take appropriate action if it is possible that data could already at a particular location and container to avoid data loss or corruption.\n\n\nThis example shows that data can be over written:\n\n\n\nCode\n// overwrite our previous data\nvar img = ArrayImgs.unsignedBytes(2,2);\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// load the new data, the old data are no longer accessible\nvar loadedImg = N5Utils.open(n5Writer, groupPath);\nArrays.toString(loadedImg.dimensionsAsLongArray()); // [2, 2]\n\n\n\n\nParameter details\n\ngroupPath\nis the group inside the container that will store the array. You can store an array at the root of a container by specifying \"\" or \"/\" as the groupPath. In this case, the container will only be able to store one array (see the warning above at the start of the “Arrays” section).\n\n\nblockSize\nis a very important parameter of an array. HDF5, N5, and Zarr all break up the arrays they store into equally sized blocks or “chunks”. The block size parameter specifies the size of these blocks.\nFor the example above, we stored an image of size 64 x 64 using blocks sized 32 x 32. As a result, N5 uses four blocks to store the entire image:\n\n\n\nCode\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/1/1 is 1762 bytes\nmy-container.n5/data/1/0 is 2012 bytes\nmy-container.n5/data/0/1 is 1763 bytes\nmy-container.n5/data/0/0 is 2020 bytes\n\n\n\nQuiz: How many blocks would there be if the block size was 64 x 8?\n\n\nClick here to show the answer.\n\nThere would be eight blocks.\nOne block covers the first dimension, but it takes 8 blocks to cover the second dimension (\\(8 \\times 8 = 64\\)). Also demonstrated by the code below:\n\n\n\nCode\n// remove the old data\nn5Writer.remove(groupPath);\n\n// rewrite with a different block size\nvar blockSize = new int[]{64,8};\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// how many blocks are there?\nprintBlocks(\"my-container.n5/data\");\n\n\nmy-container.n5/data/0/1 is 837 bytes\nmy-container.n5/data/0/7 is 847 bytes\nmy-container.n5/data/0/3 is 839 bytes\nmy-container.n5/data/0/6 is 844 bytes\nmy-container.n5/data/0/0 is 968 bytes\nmy-container.n5/data/0/4 is 846 bytes\nmy-container.n5/data/0/2 is 840 bytes\nmy-container.n5/data/0/5 is 847 bytes\n\n\n\n\n\n\n\n\n\n\nTry it!\n\n\n\nN5 lets you store your image in a single file if you want - just provide a block size that is equal to or larger than the image size.\n\n\n\n\ncompression\nEach block is compressed independently, using the specified compressor. Use RawCompression to store blocks without compression.\n\n\n\nCode\n// rewrite without compression\nvar groupPath = \"dataNoCompression\"; \nvar blockSize = new int[]{32,32};\nvar compression = new RawCompression();\nN5Utils.save(img, n5Writer, groupPath, blockSize, compression);\n\n// what size are the blocks\nprintBlocks(\"my-container.n5/dataNoCompression\");\n\n\nmy-container.n5/dataNoCompression/1/1 is 4108 bytes\nmy-container.n5/dataNoCompression/1/0 is 4108 bytes\nmy-container.n5/dataNoCompression/0/1 is 4108 bytes\nmy-container.n5/dataNoCompression/0/0 is 4108 bytes\n\n\n\nNotice that blocks were previously ~1700-2000 bytes and are now ~4100 without compression.\nThe available compressors at the time of this writing are:\n\nBloscCompression\nBzip2Compression\nGzipCompression\nLz4Compression\nRawCompression\nXzCompression\nZstandardCompression" - }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#metadata", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html#metadata", - "title": "N5 API Basics", - "section": "Metadata", - "text": "Metadata\nBesides array data, N5 can also store rich structured metadata. This tutorial will discuss basic, low-level metadata operations. Advanced operations and metadata standards may be described in a future tutorial.\n\nBasics\nN5Writers have a setAttribute method for writing metadata to the storage backend. It takes three arguments:\n<T> void setAttribute(String groupPath, String attributePath, T attribute)\n\ngroupPath : the group in which to store this metadata\nattributePath : the name of this attribute\nattribute : the metadata attribute to be stored. Can be an arbitrary type (denoted T).\n\n\n\n\n\n\n\nNote\n\n\n\nThere are differences between an attribute “name” and an attribute “path”, but attribute “paths” are an advanced topic and will be covered elsewhere.\n\n\nSimilarly, N5Readers have a getAttribute method:\n<T> T getAttribute(String groupPath, String attributePath, Class<T> clazz)\nThe last argument (Class<T>) lets you specify the type that getAttribute should return. An N5Exception will be thrown if the requested type can not be created from the requested attribute. If an attribute does not exist, null will be returned (see the last example of this section). Consider these examples:\n\n\n\nCode\n// create a group inside the container (think: \"folder\")\nvar groupName = \"put-data-in-me\";\nn5Writer.createGroup(groupName);\n\n// attributes have names and values\n// make an attribute called \"date\" with a String value\nvar attributeName = \"date\";\nn5Writer.setAttribute(groupName, attributeName, \"2024-Jan-01\");\n\n// Ask the N5 API to make a double array from the data attribute\n// it will try and fail, so an exception will be thrown\ntry {\n var nothing = n5Writer.getAttribute(groupName, attributeName, double[].class);\n} catch( N5Exception e ) {\n System.out.println(\"Error: could not get attribute as double[]\");\n}\n\n// get the value of the \"date\" attribute as a String\nString date = n5Writer.getAttribute(groupName, attributeName, String.class);\ndate\n\n\nError: could not get attribute as double[]\n\n\n2024-Jan-01\n\n\n\nSometimes it is possible to interpret a metadata as multiple different types:\n\n\n\nCode\nn5Writer.setAttribute(groupName, \"a\", 42);\nvar num = n5Writer.getAttribute(groupName, \"a\", double.class); // 42.0\nvar str = n5Writer.getAttribute(groupName, \"a\", String.class); // \"42\"\n\n\n\n\n\nRich metadata\nIt possible to save attributes of arbitrary types, enabling you to struture your metadata into classes that are easy to save and load directly. For example, if we define a metadata class FunWithMetadata:\n\n\n\nCode\nclass FunWithMetadata {\n String name;\n int number;\n double[] data;\n \n public FunWithMetadata(String name, int number, double[] data) {\n this.name = name;\n this.number = number;\n this.data = data;\n }\n public String toString(){\n return String.format( \"FunWithMetadata{%s(%d): %s}\", \n name, number, Arrays.toString(data));\n }\n};\n\n\n\nthen make an instance and save it:\n\n\n\nCode\nvar metadata = new FunWithMetadata(\"Dorothy\", 2, new double[]{2.72, 3.14});\nn5Writer.setAttribute(groupName, \"metadata\", metadata);\n\nvar loadedMetadata = n5Writer.getAttribute(groupName, \"metadata\", \n FunWithMetadata.class);\nloadedMetadata\n\n\nFunWithMetadata{Dorothy(2): [2.72, 3.14]}\n\n\n\nTo retrieve all the metadata in a group as JSON:\n\n\n\nCode\nn5Writer.getAttribute(groupName, \"/\", JsonElement.class);\n\n\n{\"date\":\"2024-Jan-01\",\"a\":42,\"metadata\":{\"name\":\"Dorothy\",\"number\":2,\"data\":[2.72,3.14]}}\n\n\n\n\n\nRemoving metadata\nYou can remove attributes by their name as well. To return the element that was removed, just provide the class for that element (this mirrors the remove method for Lists in Java.\n\n\n\nCode\n// set attributes\nn5Writer.setAttribute(groupName, \"sender\", \"Alice\");\nn5Writer.setAttribute(groupName, \"receiver\", \"Bob\");\n\n// notice that they're set\nn5Writer.getAttribute(groupName, \"sender\", String.class); // Alice\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // Bob\n\n// remove \"sender\"\nn5Writer.removeAttribute(groupName, \"sender\");\n\n// remove \"receiver\" and store result in a variable\nvar receiver = n5Writer.removeAttribute(groupName, \"receiver\", String.class); // Bob\n\nn5Writer.getAttribute(groupName, \"sender\", String.class); // null\nn5Writer.getAttribute(groupName, \"receiver\", String.class); // null\n\n\n\n\n\n\n\n\n\nTry it!\n\n\n\nThe attributes that describe arrays are also accessible using getAttribute, try running:\nn5Writer.getAttribute(\"data\", \"dimensions\", long[].class);\n\n\n\n\n\n\n\n\nWarning\n\n\n\nThe attributes that N5 uses to read arrays can be set with setAttribute, and modifying them could corrupt your data. Do not manually set these attributes unless you absolutely know what you’re doing!\n\ndimensions\nblockSize\ndataType\ncompression" - }, - { - "objectID": "posts/2024-02-27-n5-tutorial-basic/index.html#what-to-try-next", - "href": "posts/2024-02-27-n5-tutorial-basic/index.html#what-to-try-next", - "title": "N5 API Basics", - "section": "What to try next", - "text": "What to try next\n\nHow to work with the N5 API and ImgLib2" - }, { "objectID": "posts/2022-09-27-n5-imglib2.html", "href": "posts/2022-09-27-n5-imglib2.html", diff --git a/sitemap.xml b/sitemap.xml index 70f26fd..43463dd 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -17,17 +17,13 @@ 2024-02-22T21:30:32.837Z - https://imglib.github.io/imglib2-blog/posts/2024-02-27-n5-tutorial-basic/N5-Basics-Tutorial.html - 2024-02-28T00:34:54.563Z + https://imglib.github.io/imglib2-blog/posts/2024-02-27-n5-tutorial-basic/index.html + 2024-02-28T00:59:24.666Z https://imglib.github.io/imglib2-blog/posts/2022-10-30-streams/2022-10-30-streams.html 2024-02-22T21:30:32.837Z - - https://imglib.github.io/imglib2-blog/posts/2024-02-27-n5-tutorial-basic/index.html - 2024-02-28T00:41:45.817Z - https://imglib.github.io/imglib2-blog/posts/2022-09-27-n5-imglib2.html 2024-02-22T21:30:32.837Z @@ -42,6 +38,6 @@ https://imglib.github.io/imglib2-blog/index.html - 2024-02-22T21:30:32.789Z + 2024-02-28T01:00:42.289Z