Page 1 of 2

A weird behavior of linkAnnotation

PostPosted: Sun Jul 01, 2018 7:37 pm
by Kouichi_C_Nakamura
I'm just experiencing a weird behavior of OMERO. Maybe I've been overlooking something simple, but I can't figure it out. I've got 11 images with imageIDs 14 to 24.

omero_xlsIHC2MapAnnotation() takes out data in a specified Exel file and turn it into a MapAnnotationI object. The first two operations of linkAnnotation worked as expected; Only the specified images get MapAnnotations. However, the third use of linkAnnotation somehow added all the images in Dataset, and I can't figure out the logic behind it. This has been reproducible in my environment.

Can you guess anything?

Code: Select all
% imageIDs 14:24

k3 = [19:22,24]

k2 = [17:18]

k1 = [14:16]



ma30 = getObjectAnnotations(session, 'map', 'image', 19)

unlinkAnnotations(session,'image',[k1,k2,k3,23],ma30)



ma1 = omero_xlsIHC2MapAnnotation(xlsfile,sheet,[128,130]);

linkAnnotation(session,ma1,'image',k1)



ma2 = omero_xlsIHC2MapAnnotation(xlsfile,sheet,[132,134]);

linkAnnotation(session,ma2,'image',k2)



ma3 = omero_xlsIHC2MapAnnotation(xlsfile,sheet,[137,140]);

linkAnnotation(session,ma3,'image',k3)


Re: A weird behavior of linkeAnnotation

PostPosted: Mon Jul 02, 2018 3:04 am
by jmoore
Good morning, Kouichi.

Is the code for omero_xlsIHC2MapAnnotation available somewhere?

~Josh

Re: A weird behavior of linkeAnnotation

PostPosted: Mon Jul 02, 2018 5:29 am
by Kouichi_C_Nakamura
It's only meaningful for Excel files in a very specific format. Idiosyncratic.

Code: Select all
function ma = omero_xlsIHC2MapAnnotation(xlsfile,sheet,xlRows,varargin)
% omero_xlsIHC2MapAnnotation parse Excel spreadsheet xlsfile that
% contains IHC protocol summary and return a MapAnnotationI object
% (Key-Value Pairs annotation for OMERO) to be linked to an image with
% linkeAnnotation MATLAB function
%
% SYNTAX
% ma = omero_xlsIHC2MapAnnotation(xlsfile,sheet,xlRows)
% ma = omero_xlsIHC2MapAnnotation(xlsfile,sheet,xlRows,iseditable)
%
%
% REQUIREMENTS
%
%   OMERO.matlab toolbox
%   https://docs.openmicroscopy.org/latest/omero/developers/Matlab.html
%
%
% INPUT ARGUMENTS
%
% xlsfile     The file path (name) of an Excel spread sheet including file
%             extension .xlsx
%
%             The Excel file must have the header in row 2 as follows:
%
%             'Experiment ID', 'Animal ID', 'Species of tissue',
%             'Sections', 'Antigen retrieval', 
%             'Antigen', 'Species', 'Supplier', 'Catalog', 
%             'Lot', 'Dilution/Concen?',   
%             'Antigen', 'Species', 'Fluorophore etc', 'Supplier',
%             'Catalog', 'Lot', 'Dilution/Concen?', 
%             'Neg Control', 'Evaluation', 'Notes'
%
% sheet       char row
%             Sheet name for the xlsfile
%
% xlRows      [top bottom]
%             Specifies the range of rows in the spreadsheet.
%             eg. [3 15]
%
% iseditable  false (default) | true | 0 | 1
%             (Optional) If true or 1, MapAnnotation (Key-Value Pairs) will
%             be editable via GUI (OMERO.web or OMERO.insight)
%
%
% OUTPUT ARGUMENTS
% ma          MapAnnotationI objects
%             To link ma to an image in OMERO, identify image ID from OMERO
%             GUI and execute the following command
%
% EXAMPLE
%     
%   client = loadOmero('demo.openmicroscopy.org', 4064);
%   session = client.createSession(username, password);
%
%   ma = omero_xlsIHC2MapAnnotation(xlsfile,sheet,xlRows,iseditable);
%
%   link1 = linkAnnotation(session, ma, 'image', imageID);
%
%   clear
%   unloadOmero
%
% Written by Kouichi C. Nakamura Ph.D.
% MRC Brain Network Dynamics Unit
% University of Oxford
% kouichi.c.nakamura@gmail.com
% 09-Jun-2018 15:20:17
%
% See also
% xlsread, linkAnnotation, omero_str2MapAnnotation


p = inputParser;
p.addRequired('xlsfile',@(x) exist(x,'file') > 0);
p.addRequired('sheet',@(x) ischar(x) && isrow(x));
p.addRequired('xlRows',@(x) isreal(x) && isrow(x) && numel(x) ==2);
p.addOptional('iseditable',false,@(x) isscalar(x) && x == 1 || x == 0);
% p.addParameter('C',defaultvalue2,@(x) ismember(lower(x),'on','off'));

p.parse(xlsfile,sheet,xlRows,varargin{:});

iseditable = p.Results.iseditable;

assert(ismatched(xlsfile,'\.xlsx$'),....
    'xlsfile must include the file extension .xlsx')


%% Job

rows = xlRows;

cols = {'A','U'};

header = sprintf('%s%d:%s%d',cols{1},2,cols{2},2);

target = sprintf('%s%d:%s%d',cols{1},rows(1),cols{2},rows(2));

[~,~,headerC] = xlsread(xlsfile,sheet,header);
[~,~,targetC] = xlsread(xlsfile,sheet,target);


li = local_parseXls(headerC,targetC);

eval('import omero.model.MapAnnotationI')

ma = MapAnnotationI(int64(1),true); % 'false' results in Java exception occurred: omero.UnloadedEntityException:
ma.setMapValue(li);

if iseditable
    %NOTE this is required to make it editable from GUI
    eval('import omero.constants.metadata.NSCLIENTMAPANNOTATION')
    ma.setNs(rstring(NSCLIENTMAPANNOTATION.value));
end



end


function li = local_parseXls(headerC,targetC)
%
%
% short description comes here
%
% SYNTAX
% [D] = func1(A)
% [D] = func1(A,B)
% [D] = func1(____,'Param',value)
%
% longer description may come here
%
% INPUT ARGUMENTS
% headerC     cell array
%             Containing the header information
%
% targetC     cell array
%             Containing the acutal data
%
% OUTPUT ARGUMENTS
% li          java.util.ArrayList object containing omero.model.NamedValue
%             objects
%
% Written by Kouichi C. Nakamura Ph.D.
% MRC Brain Network Dynamics Unit
% University of Oxford
% kouichi.c.nakamura@gmail.com
% 09-Jun-2018 14:46:48


% your signature comes here
%
% See also
% doc



import java.util.ArrayList
eval('import omero.model.NamedValue')


li = ArrayList;

tfrows = ~arrayfun(@(y) all(cellfun(@(x) isequal(x,NaN),targetC(y,:))),...
    1:size(targetC,1))';

% get rid of empty rows
targetC = targetC(tfrows,:);


assert(string(headerC{1}) == "Experiment ID",...
    'Header of column 1 does not match for Experiment ID')
assert(all(cellfun(@(x)isnan(x),targetC(2:end,1))),...
    'Experiment ID must only apper in the first row of the target')
experimentID = targetC{1,1};
if isnan(experimentID)
    warning('Experiment ID is blank')
    experimentID = '';
end
li.add(NamedValue('Experiment ID',experimentID));


assert(string(headerC{2}) == "Animal ID",...
    'Header of column 2 does not match for Animal ID')
assert(all(cellfun(@(x)isnan(x),targetC(2:end,2))),...
    'Animal ID must only appear in the first row of the target')
animalID = local_f(targetC{1,2});
li.add(NamedValue('Animal ID',animalID));


assert(string(headerC{3}) == "Species of tissue",...
    'Header of column 3 does not match for Species of tissue')
assert(all(cellfun(@(x)isnan(x),targetC(2:end,3))),...
    'Spiecies of tissue must only appear in the first row of the target')
species_of_tissue = local_f(targetC{1,3});
li.add(NamedValue('Species of tissue',species_of_tissue));


assert(string(headerC{4}) == "Sections",...
    'Header of column 4 does not match for Sections')
assert(all(cellfun(@(x)isnan(x),targetC(2:end,4))),...
    'Specimen must only appear in the first row of the target')
sections = local_f(targetC{1,4});
li.add(NamedValue('Sections',sections));


assert(string(headerC{5}) == "Antigen retrieval",...
    'Header of column 5 does not match for Antigen retrieval')
assert(all(cellfun(@(x)isnan(x),targetC(2:end,5))),...
    'Antigen retrieval must only appear in the first row of the target')
AR = local_f(targetC{1,5});
li.add(NamedValue('Antigen retrieval',AR));

li.add(NamedValue('*',''));

s = repmat({''},size(targetC,1),1);

% parimary antibody
pab = struct(...
    'Antigen',s,...
    'Species',s,...
    'Supplier',s,...
    'Catalog',s,...
    'Lot',s,...
    'Dilution',s);

% secondary antibody
sab = struct(...
    'Antigen',s,...
    'Species',s,...
    'Fluoropfore',s,...
    'Supplier',s,...
    'Catalog',s,...
    'Lot',s,...
    'Dilution',s);

info = struct(...
    'NegControl',s,...
    'Evaluation',s,...
    'Notes',s);

for ch = 1:size(targetC,1)
   
    row = targetC(ch,:);
   
   
    assert(string(headerC{6}) == "Antigen")
    assert(string(headerC{7}) == "Species")
    assert(string(headerC{8}) == "Supplier")
    assert(string(headerC{9}) == "Catalog")
    assert(string(headerC{10}) == "Lot")
    assert(string(headerC{11}) == "Dilution/Concentration")
   
   
    pab(ch).Antigen  = local_f(row{6});
    pab(ch).Species  = local_f(row{7});
    pab(ch).Supplier = local_f(row{8});
    pab(ch).Catalog  = local_f(row{9});
    pab(ch).Lot      = local_f(row{10});
    pab(ch).Dilution = local_f(row{11});
   
   
   
    li.add(NamedValue(sprintf('ch%d 1ry antigen',ch),...
        pab(ch).Antigen));
    li.add(NamedValue(sprintf('ch%d 1ry host species',ch),...
        pab(ch).Species));
    li.add(NamedValue(sprintf('ch%d 1ry supplier',ch),...
        pab(ch).Supplier));
    li.add(NamedValue(sprintf('ch%d 1ry catalog #',ch),...
        pab(ch).Catalog));
    li.add(NamedValue(sprintf('ch%d 1ry lot #',ch),...
        pab(ch).Lot));
    li.add(NamedValue(sprintf('ch%d 1ry dilution',ch),...
        pab(ch).Dilution));
    li.add(NamedValue('*',''));
   
   
    assert(string(headerC{12}) == "Antigen")
    assert(string(headerC{13}) == "Species")
    assert(string(headerC{14}) == "Fluorophore etc")
    assert(string(headerC{15}) == "Supplier")
    assert(string(headerC{16}) == "Catalog")
    assert(string(headerC{17}) == "Lot")
    assert(string(headerC{18}) == "Dilution/Concentration")
   
    sab(ch).Antigen  = local_f(row{12});
    sab(ch).Species  = local_f(row{13});
    sab(ch).Fluorophore = local_f(row{14});
    sab(ch).Supplier = local_f(row{15});
    sab(ch).Catalog  = local_f(row{16});
    sab(ch).Lot      = local_f(row{17});
    sab(ch).Dilution = local_f(row{18});
   
   
   
    li.add(NamedValue(sprintf('ch%d 2ry antigen',ch),...
        sab(ch).Antigen));
    li.add(NamedValue(sprintf('ch%d 2ry host species',ch),...
        sab(ch).Species));
    li.add(NamedValue(sprintf('ch%d 2ry fluorophore etc',ch),...
        sab(ch).Fluorophore));
    li.add(NamedValue(sprintf('ch%d 2ry supplier',ch),...
        sab(ch).Supplier));
    li.add(NamedValue(sprintf('ch%d 2ry catalog #',ch),...
        sab(ch).Catalog));
    li.add(NamedValue(sprintf('ch%d 2ry lot #',ch),...
        sab(ch).Lot));
    li.add(NamedValue(sprintf('ch%d 2ry dilution',ch),...
        sab(ch).Dilution));
    li.add(NamedValue('*',''));
   
    assert(string(headerC{19}) == "Neg Control")
    assert(string(headerC{20}) == "Evaluation")
    assert(string(headerC{21}) == "Notes")
   
   
    info(ch).NegControl = local_f(row{19});
    info(ch).Evaluation = local_f(row{20});
    info(ch).Notes      = local_f(row{21});
   
   
    li.add(NamedValue(sprintf('ch%d negative control',ch),...
        info(ch).NegControl));
    li.add(NamedValue(sprintf('ch%d evaluation',ch),...
        info(ch).Evaluation));
    li.add(NamedValue(sprintf('ch%d notes',ch),...
        info(ch).Notes));
    li.add(NamedValue('*',''));
   
   
end

end

%--------------------------------------------------------------------------

function out = local_f(in)
% local_f returns string (char) output for in
%
% out = local_f(in)

if isnan(in)
    out = '';
elseif isnumeric(in)
    out = num2str(in);
else
    out = in;
end

end

Re: A weird behavior of linkeAnnotation

PostPosted: Mon Jul 02, 2018 1:53 pm
by sbesson
Thanks Kuichi,

we will try to reproduce the issue you are seeing using a minimal setup and get back to you.

One question as I see your snippet is using linkAnnotation with multiple parent identifiers, do you have the changes proposed in https://github.com/openmicroscopy/openm ... /pull/5799 locally? Anything else specific to your environment?

Best,
Sebastien

Re: A weird behavior of linkeAnnotation

PostPosted: Mon Jul 02, 2018 2:17 pm
by Kouichi_C_Nakamura
Good question. Yes, indeed, I should have mentioned that I'm using the modified linkAnnotation.

They're quite big (~1 GB each), but if they don't go public, I can share the actual image data files privately.

Re: A weird behavior of linkeAnnotation

PostPosted: Tue Jul 03, 2018 2:54 pm
by sbesson
Hi Kouichi,

understood, we will retest this behavior together with https://github.com/openmicroscopy/openm ... /pull/5799. We might not get back to you until the end of the week or the beginning of the following week as the team is currently working in reduced capacity.

Regarding the original images, I do not think uploading them would be helpful here. We will have a go with some of our sample images of even test images first and see if we can reproduce the issue.

Best,
Sebastien

Re: A weird behavior of linkAnnotation

PostPosted: Fri Jul 13, 2018 11:38 pm
by Kouichi_C_Nakamura
This is a weird behavior. Up to ~20 images, linkAnnotation.m with multiple parentIds works just fine, and then the error happens. All the images that had been already linked with MapAnnotation across datasets and projects ended up having the same MapAnnotation object that was linked last and its ID is 1.


This is the modification of linkAnnotation

Code: Select all
for i = 1:numel(parent)
   
    % Create object annotation link
    context = java.util.HashMap;
    group = parent(i).getDetails().getGroup().getId().getValue();
    context.put('omero.group', java.lang.String(num2str(group)));
   
    link = objectType.annotationLink();
    link.setParent(parent(i))
    link.setChild(annotation);
    link = session.getUpdateService().saveAndReturnObject(link, context);

end


This also ended up in the same problem.

Code: Select all
for i = 1:numel(parentId)

    % Get the parent object
    if isnumeric(parentId(i))
        parent = getObjects(session, parentType, parentId(i));
        assert(~isempty(parent), 'No %s with id %g found', parentType, parentId(i));
    else
        parent = parentId(i);
    end
   
    % Create object annotation link
    context = java.util.HashMap;
    group = parent.getDetails().getGroup().getId().getValue();
    context.put('omero.group', java.lang.String(num2str(group)));
   
    link = objectType.annotationLink();
    link.setParent(parent)
    link.setChild(annotation);
    link = session.getUpdateService().saveAndReturnObject(link, context);

end



Using the original code (https://github.com/openmicroscopy/openm ... tion.m#L46), even the following caused the same problem.

Code: Select all

imgIDs = [363:370];
for i = 1:8
    link1 = linkAnnotation(session,ma1,'image',imgIDs(i))
end



So linkAnnotion.m does not seem working properly. There may be a serious problem here.

I used unlinkAnnotations.m of mine to undo the horrible changes.


Then I thought .... why the ID being 1? It's because when I preparing MapAnnotationI object based on Excel file using omero_xlsIHC2MapAnnotation above, I don't assign new ID. The MapAnnotationI object ma1 probably always has ID of 1. And this can be causing a problem.

I thought it's good enough to just instantiate MapAnnotationI object, so I simply the called constructor. I assumed that the server can assign and overwrite a new ID, but maybe not. Do we need to assign unique ID before linking to an image? How do we know what ID is currently available?

public void setId(Long id)
usually unneeded. Ids are managed by the backend.

http://javadoc.scijava.org/OMERO/ome/mo ... lang.Long-

Code: Select all
ma = MapAnnotationI(int64(1),true);

Re: A weird behavior of linkAnnotation

PostPosted: Sat Jul 14, 2018 8:52 am
by Kouichi_C_Nakamura
I've got an interesting test result to share with you. (MATLAB R2018b prelease)

The test requires a few images with no MapAnnotation linked.

strToMapAnnotation and mapAnnotationToCellstr https://github.com/kouichi-c-nakamura/o ... notation.m

I used the linkAnnotation as in the develop branch, i.e. without for loop to support multiple parentId at the same time. https://github.com/openmicroscopy/openm ... notation.m

Code: Select all
client = loadOmero('xxxxxxx', 4064)
session = client.createSession(username,password)

%% Prepare cell array of characters

str1 = {'bar','1';...
    'hoo','2';...
    'hoge','3';...
    'pyon','4'}

str2 = {'BOO','5';...
    'DON','6';...
    'PERO','7';...
    'PON','8'}

ma(1) = strToMapAnnotation(str1)
ma(1).getId.getValue

ma(2) = strToMapAnnotation(str1)
ma(2).getId.getValue % always 1


isequal(ma(1),ma(2)) % they are not the same object

%% Iterate through images with a new MapAnnotation object with alternating contents

imageIDs = [359:362,363:370,283,284,285,286:289]

for i = 1:length(imageIDs)
   
    keyboard
   
    if rem(i,2)
        ma1 = strToMapAnnotation(str1);
    else
        ma1 = strToMapAnnotation(str2);
    end
           
    linkAnnotation(session,ma1,'image',imageIDs(i));
   
    annt = getObjectAnnotations(session, 'map', 'image', imageIDs(i));
    fprintf('Image ID, %d; Map Annotation ID, %d\n',imageIDs(i), annt.getId.getValue)
    mapAnnotationToCellstr(annt)

end


During the for loop I checked the ID of the newly added MapAnnotation at each cycle of iteration. What I expect from "Ids are managed by the backend.", is that every time I add new MapAnnotation to different images, particularly when I add MapAnnotation with different contents, the ID of the newly added MapAnnotation should be unique and probably ever increasing. The ID of MapAnnotation object before linking is always 1.

This was not the case in my environment. Interestingly, in the second cycle, the MapAnnotation is given ID of 1. At this time, the MapAnnotation assigned to the first image was overwritten as well (confirmed on OMERO.web).

My theory is that there is something wrong about "Ids are managed by the backend." Maybe "the background" does not reliably handle IDs of new Annotations, and gives the ID of 1 sometimes and overwrite existing ones. If I link MapAnnotation with a virgin ID every time, it might work as a workaround.

Code: Select all
Image ID, 359; Map Annotation ID, 1
ans =
    {'bar' }    {'1'}
    {'hoo' }    {'2'}
    {'hoge'}    {'3'}
    {'pyon'}    {'4'}
Image ID, 360; Map Annotation ID, 1
ans =
    {'BOO' }    {'5'}
    {'DON' }    {'6'}
    {'PERO'}    {'7'}
    {'PON' }    {'8'}
Image ID, 361; Map Annotation ID, 1
ans =
    {'bar' }    {'1'}
    {'hoo' }    {'2'}
    {'hoge'}    {'3'}
    {'pyon'}    {'4'}
Image ID, 362; Map Annotation ID, 1
ans =
    {'BOO' }    {'5'}
    {'DON' }    {'6'}
    {'PERO'}    {'7'}
    {'PON' }    {'8'}

Re: A weird behavior of linkAnnotation

PostPosted: Mon Jul 16, 2018 3:22 pm
by dgault
Hi Kouichi,

In this instance if you are creating a new annotation directly with
Code: Select all
new MapAnnotation
then it looks like you will have to make a call to set the ID. The Java doc stating "Ids are managed by the backend" is certainly unclear and something we should better clarify.

In Bio-Formats when we create new Ids they would be of the form "Annotation:index" and the index is calculated fetching the size of the existing annotation list.

David Gault

Re: A weird behavior of linkAnnotation

PostPosted: Mon Jul 16, 2018 4:16 pm
by Kouichi_C_Nakamura
Yes, I think that

Code: Select all
ma = MapAnnotationI(int64(1),true);


is essentially the same as

Code: Select all
new MapAnnotation


I've been trying the workaround I came up with, and so far it's been working without causing problems.

In Bio-Formats when we create new Ids they would be of the form "Annotation:index" and the index is calculated fetching the size of the existing annotation list.


How do I do that with Java API? If I can automate the new ID assignment, then it would be much more practical. At the moment I'm using GUI to find out the newest annotation with the biggest ID and manually assigning next ID by adding 1.

Also, is the ID for MapAnnotation shared with TagAnnotation etc? Or the counting is independent between different types of annotations?

ID will be ever increasing, right? Even when I delete objects, new objects will be assigned a bigger ID, I suppose?