Skip to content
  • Home
  • Resources
  • Support
  • IcyServices
  • Contributors
  • Get Involved
  • About
Search
Login
Create Account

0
FAQ
0
Forum
0
Posts
-
Javadoc
Filter by type
Plugin Script Protocol FAQ Forum Topic Article
Filter by category
3-D3D3D coordinatesalignmentbasicsbatch processingCalciumcell trackingCLEMcolocalizationcomptageconfocalconnected componentsconvolutioncorrelationdeconvolutiondeformable registrationdenoisingdetectiondeveloperdisplacementsdistance mapexportezplugfeature detectionfeature matchingfilteringfluorescenceguiheadlessHSV visualisationimage processImageJintensitykymographmaskmavenmeasurementMHTmicroscopymonitoringmorphologymovement detectionmultiple hypothesis trackingnon rigid registrationoperatoroptimizationotsupluginpoint-spread functionprojectionprotocolPSFregistrationreleaseresults managementRipley's K functionroiscriptscriptingsegmentationsequencesmoothingspatial distributionspotspot countstatisticssurfacesyntheticthresholdtoolboxtoolstrack processortrackingtutorialwarpingwavelet transformwaveletswidefieldXLS output
  • Plugin Script Protocol FAQ Forum Topic Article
  • 3-D
  • 3D
  • 3D coordinates
  • alignment
  • basics
  • batch processing
  • Calcium
  • cell tracking
  • CLEM
  • colocalization
  • comptage
  • confocal
  • connected components
  • convolution
  • correlation
  • deconvolution
  • deformable registration
  • denoising
  • detection
  • developer
  • displacements
  • distance map
  • export
  • ezplug
  • feature detection
  • feature matching
  • filtering
  • fluorescence
  • gui
  • headless
  • HSV visualisation
  • image process
  • ImageJ
  • intensity
  • kymograph
  • mask
  • maven
  • measurement
  • MHT
  • microscopy
  • monitoring
  • morphology
  • movement detection
  • multiple hypothesis tracking
  • non rigid registration
  • operator
  • optimization
  • otsu
  • plugin
  • point-spread function
  • projection
  • protocol
  • PSF
  • registration
  • release
  • results management
  • Ripley's K function
  • roi
  • script
  • scripting
  • segmentation
  • sequence
  • smoothing
  • spatial distribution
  • spot
  • spot count
  • statistics
  • surface
  • synthetic
  • threshold
  • toolbox
  • tools
  • track processor
  • tracking
  • tutorial
  • warping
  • wavelet transform
  • wavelets
  • widefield
  • XLS output
Migrate your old Icy plugin to maven
Icy 2.1 – Importants changes for developers

Using image cursors on Icy

This post explains the concept of image cursors, a new way of accessing image data without the struggle of handling image data types, developed by the Icy team.

When developing plugins for Icy, we repeatedly struggle with handling images of different data types (int, short, long, float, double, etc.) The development team of Icy developed a new way of accessing image data without the struggle of handling image data types. This new way of accessing images is called cursors. They act as if they were pointers to the elements on the image but allow you to ignore the data type of the pixels, providing you with an equivalent double value of the pixel instead. This has two advantages: 1) it simplifies the access to single pixel data and 2) it allows to abstract image processing algorithms from the data types. Additionally, cursors were optimized to be compatible with the caching system of Icy so that memory is handled the best possible way!

In general, there are three types of cursors linked to the way Icy handles images:

  1. The most general way to access data is through a SequenceCursor , which provides reading and writing access to all the pixels in the images (X, Y, C, Z, T axes).
  2. Next, the VolumetricImageCursor allows access to 3D image data (X, Y, C, Z axes).
  3. Finally, planar images (X, Y, C axes) can be accessed through IcyBufferedImageCursor .

In the following paragraphs, each one of these classes will be presented with examples to understand them and get the most out of your images. First, we will start by taking a look at the simplest one, the IcyBufferedImageCursor, then we will add depth to the cursor to get a VolumetricImageCursor, and finally we will handle a time series of volumetric images using SequenceCursor.

IcyBufferedImageCursor

This class provides random pixel access to any pixel on a planar image image, acting like a pointer to a position of the image.

Let’s take the following sample code for a plugin that takes the first image of the active sequence and produces a grayscale image:

Iceberg sample image
Iceberg input image
Iceberg sample image converted to gray
Iceberg image converted to gray

 

 

 

 

 

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import icy.image.IcyBufferedImage;
import icy.image.IcyBufferedImageCursor;
import icy.main.Icy;
import icy.plugin.abstract_.PluginActionable;
import icy.sequence.Sequence;
import icy.util.OMEUtil;
 
public class IcyBufferedImageTraversal extends PluginActionable
{
 
    @Override
    public void run()
    {
        // 1. Retrieve sequence and first image
        Sequence colorSeq = Icy.getMainInterface().getActiveSequence();
        IcyBufferedImage colorImg = colorSeq.getFirstImage();
        // 2. Create a blank result image
        IcyBufferedImage grayImg = new IcyBufferedImage(colorImg.getSizeX(), colorImg.getSizeY(), 1,
                colorImg.getDataType_());
 
        // 3. Create cursors for both images
        IcyBufferedImageCursor colorImgCursor = new IcyBufferedImageCursor(colorImg);
        IcyBufferedImageCursor grayImgCursor = new IcyBufferedImageCursor(grayImg);
 
        // 4. Traverse image
        for (int j = 0; j < colorImg.getSizeY(); j++)
        {
            for (int i = 0; i < colorImg.getSizeX(); i++)
            {
                double valueSum = 0d;
                for (int c = 0; c < colorImg.getSizeC(); c++)
                {
                    // 5. get pixel value at channel c using cursor
                    valueSum += colorImgCursor.get(i, j, c);
                }
                // 6. Set pixel value to average of channels
                grayImgCursor.setSafe(i, j, 0, valueSum / colorImg.getSizeC());
            }
        }
        // 7. Finish changes on gray image cursor
        grayImgCursor.commitChanges();
 
        // 8. Insert gray image in result sequence and display it
        Sequence graySeq = new Sequence(OMEUtil.createOMEXMLMetadata(colorSeq.getOMEXMLMetadata()));
        graySeq.addImage(grayImg);
        Icy.getMainInterface().addSequence(graySeq);
    }
 
}

At first sight, this code has nothing outstanding in it, we simply create a new image and we copy the values from one image to the other. However, by using cursors on our code we have completely forgotten about data types. It won’t matter if the image is of type short or float, our cursor is taking care of that and we only deal with double values. Using cursors you have the freedom of traversing the image in any way you need (X->Y->C or C->X->Y or Y->X->C, etc.).

To use a cursor, we create an instance pointing to the target image (Comment number 3). Then we can move around the image and retrieve values from the image using the get method (Comment number 5). Behind the scenes, this method will perform the appropriate conversions to read the original data type and provide a valid double-precision floating-point value. Similarly, we can also set values on the image using cursors by using either set or setSafe methods (Comment number 6). Using the set method will copy the value provided as parameter without taking into account the interval of valid values of the image data type. This means that an value overflow can occur if the passed value is too large. In order to avoid this, the method setSafe can be used to limit values that are higher than the maximum value of the data type of the target image. Once we are done setting all the values on the target cursor, a call to commitChanges (Comment number 7) is made to ensure the data is correctly set on the target image.

It is important to notice that cursors can be used for both reading and writing data on images. We have used the method setSafe in this case to handle possible value overflows on the destination data type (Comment number 6) .

VolumetricImageCursor

Handling data on a planar stack (volumetric image) using cursors is not very different from using planar image cursors, the only difference is a new parameter when accessing pixel data (the depth). The following example shows the same procedure presented on planar images but using volumetric image cursors.

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import icy.image.IcyBufferedImage;
import icy.main.Icy;
import icy.plugin.abstract_.PluginActionable;
import icy.sequence.Sequence;
import icy.sequence.VolumetricImage;
import icy.sequence.VolumetricImageCursor;
import icy.util.OMEUtil;
 
public class VolumetricImageTraversal extends PluginActionable
{
 
    @Override
    public void run()
    {
        // 1. Retrieve sequence and first volume
        Sequence colorSeq = Icy.getMainInterface().getActiveSequence();
        VolumetricImage colorVol = colorSeq.getVolumetricImage(0);
        // 2. Create a blank result volume
        VolumetricImage grayVol = new VolumetricImage();
        for (int k = 0; k < colorVol.getSize(); k++)
        {
            grayVol.setImage(k,
                    new IcyBufferedImage(colorSeq.getSizeX(), colorSeq.getSizeY(), 1, colorSeq.getDataType_()));
        }
 
        // 3. Create cursors for both volumes
        VolumetricImageCursor colorVolCursor = new VolumetricImageCursor(colorVol);
        VolumetricImageCursor grayVolCursor = new VolumetricImageCursor(grayVol);
 
        // 4. Traverse image
        for (int k = 0; k < colorSeq.getSizeZ(); k++) // Z
        {
            for (int j = 0; j < colorSeq.getSizeY(); j++) // Y
            {
                for (int i = 0; i < colorSeq.getSizeX(); i++) // X
                {
                    double valueSum = 0d;
                    for (int c = 0; c < colorSeq.getSizeC(); c++) // C
                    {
                        // 5. get pixel value at channel c using cursor
                        valueSum += colorVolCursor.get(i, j, k, c);
                    }
                    // 6. Set pixel value to average of channels
                    grayVolCursor.setSafe(i, j, k, 0, valueSum / colorSeq.getSizeC());
                }
            }
        }
        // 7. Finish changes on gray volume cursor
        grayVolCursor.commitChanges();
 
        // 8. Insert gray volume in result sequence and display it
        Sequence graySeq = new Sequence(OMEUtil.createOMEXMLMetadata(colorSeq.getOMEXMLMetadata()));
        graySeq.addVolumetricImage(0, grayVol);
        Icy.getMainInterface().addSequence(graySeq);
    }
 
}

In this example, we first initialize the volume with blank planar images and then set pixel values using a volume cursor (Comment number 2). The volume cursors can be created from either a volumetric image (as in the example at Comment 3) or by specifying one timepoint of a given sequence. The volume traversal is then performed first by depth (Z), then by the Y-axis, then by the X-axis and finally by channel (Comment number 4). Note that the only major change besides using volume cursors instead of image cursors is that we are now traversing the Z-axis of the image with the variable k, which is used in both get and set methods of the cursors.

SequenceImageCursor

Finally we reach to the most general cursor, which allows to traverse entire sequences in any desired order. It behaves similar to both IcyBufferedImageCursor and VolumetricImageCursor but handles, in addition to the planar image and the image depth, the time axis. The following example shows the same procedure presented in the previous two codes but it is applied to a sequence.

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import icy.image.IcyBufferedImage;
import icy.main.Icy;
import icy.plugin.abstract_.PluginActionable;
import icy.sequence.Sequence;
import icy.sequence.SequenceCursor;
import icy.util.OMEUtil;
 
public class SequenceTraversal extends PluginActionable
{
 
    @Override
    public void run()
    {
        // 1. Retrieve active sequence
        Sequence colorSeq = Icy.getMainInterface().getActiveSequence();
        // 2. Create a blank result sequence using the same input image metadata
        Sequence graySeq = new Sequence(OMEUtil.createOMEXMLMetadata(colorSeq.getOMEXMLMetadata()));
        for (int l = 0; l < colorSeq.getSizeT(); l++) // T
        {
            for (int k = 0; k < colorSeq.getSizeZ(); k++) // Z
            {
                graySeq.addImage(k,
                        new IcyBufferedImage(colorSeq.getSizeX(), colorSeq.getSizeY(), 1, colorSeq.getDataType_()));
            }
        }
 
        // 3. Create cursors for both volumes
        SequenceCursor colorSeqCursor = new SequenceCursor(colorSeq);
        SequenceCursor graySeqCursor = new SequenceCursor(graySeq);
 
        // 4. Traverse sequence
        for (int l = 0; l < colorSeq.getSizeT(); l++) // T
        {
            for (int k = 0; k < colorSeq.getSizeZ(); k++) // Z
            {
                for (int j = 0; j < colorSeq.getSizeY(); j++) // Y
                {
                    for (int i = 0; i < colorSeq.getSizeX(); i++) // X
                    {
                        double valueSum = 0d;
                        for (int c = 0; c < colorSeq.getSizeC(); c++) // C
                        {
                            // 5. get pixel value at channel c using cursor
                            valueSum += colorSeqCursor.get(i, j, k, l, c);
                        }
                        // 6. Set pixel value to average of channels
                        graySeqCursor.setSafe(i, j, k, l, 0, valueSum / colorSeq.getSizeC());
                    }
                }
            }
        }
        // 7. Finish changes on gray sequence cursor
        graySeqCursor.commitChanges();
 
        // 8. Display result
        Icy.getMainInterface().addSequence(graySeq);
    }
}

We hope the examples presented in this post are helpful for you to understand how cursors work and also that it serves as a reference for future developers in need of data type abstraction when traversing sequences in Icy.

Leave a Review Cancel reply

You must Register or Login to post a review.

Leave a review
Cancel review

Google Group

We’re migrating to a new Forum ⚡️, but if you need to access the old Google Group, please follow this link. All new topics should be opened here from now.

Welcome

Welcome to the Icy community support.

Browse through forum topics, FAQ, articles, ideas, questions and answers between fellow Icy users.

Open a new topic

Forums

  • Announcements
  • Development
  • General Issues
  • Installation
  • Icy Kernel
  • Plugins
  • Protocols
  • Scripts
  • Bug Reports
  • Feature Requests
  • Feedback

Helping Others

Helping others is a great way to contribute, gain badges and help Icy development!

Help solve topics

Latest Articles

  • Migrate your old Icy plugin to maven
  • Using image cursors on Icy
  • Icy 2.1 – Importants changes for developers
  • Icy 2.1 Latest End-User Features
  • Announcement: new Icy 2.1 !
Icy is founded by
Institut Pasteur
France BioImaging
Resources
Plugins Protocols Scripts
Get Involved
Contributors Information Trainings Join Icy
Support
Forum Articles FAQ Ask for help
About
Aknowledgements Bioimage Analysis Contributors Privacy Policy
Credits
Designed by Yhello
contact
Yhello is a digital creation agency based in Paris, created by former scientists passionate about the web.

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.
      This site uses cookies: Find out more.